Skip to content

Commit

Permalink
feat & fix: Event, Ingress and Secret Overview & Errors in using next…
Browse files Browse the repository at this point in the history
… build (#4374)

* feat: Replaced mobx with zustand

* fix: displayed wrong time and info

* fix: The content of badge overflow

* style: Collected theme and added dumpKubeObject function

* feat: Ingress & Secret resource

* fix: errors in using next build

* feat: Events overview and new UI components
  • Loading branch information
Wishrem committed Dec 2, 2023
1 parent cb35d78 commit f52b174
Show file tree
Hide file tree
Showing 36 changed files with 765 additions and 106 deletions.
@@ -0,0 +1,17 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
username: admin
password: t0p-Secret
Expand Up @@ -35,7 +35,7 @@ export const PieChart = ({ title, data, color }: PieChartProps) => {
<div className="font-medium text-base text-black">{title}</div>
<div className="font-medium text-4xl text-black">{sum(data.map((d) => d.value))}</div>
</div>
);
) as unknown as string;
}
}
},
Expand Down
@@ -0,0 +1,17 @@
interface SectionProps {
children: React.ReactNode;
}

/**
* Renders a section component with the provided children.
*
* @param {SectionProps} children - The children to render within the section.
* @return {JSX.Element} The rendered section component.
*/
export function Section({ children }: SectionProps) {
return (
<section className="w-full bg-white px-10 py-8 flex flex-col flex-wrap gap-3">
{children}
</section>
);
}
22 changes: 22 additions & 0 deletions frontend/plugins/kubepanel/src/components/common/title/title.tsx
@@ -0,0 +1,22 @@
interface PageTitleProps {
children: string;
type: 'primary' | 'secondary' | 'table';
}

const titleClassName: Record<PageTitleProps['type'], string> = {
primary: 'text-2xl font-medium',
secondary: 'text-xl font-medium',
table: 'text-xs font-medium text-gray-500'
};

/**
* Renders a title component with the specified type and children.
*
* @param {PageTitleProps} props - The properties of the title component.
* @param {ReactNode} props.children - The content of the title component.
* @param {string} props.type - The type of the title component.
* @return {JSX.Element} The rendered title component.
*/
export default function Title({ children, type }: PageTitleProps) {
return <span className={titleClassName[type]}>{children}</span>;
}
12 changes: 6 additions & 6 deletions frontend/plugins/kubepanel/src/components/kube/kube-badge.tsx
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';

export interface KubeBadgeProps {
interface KubeBadgeProps {
label: React.ReactNode;
disabled?: boolean;
expandable?: boolean;
Expand Down Expand Up @@ -32,14 +32,14 @@ export const KubeBadge = ({
};

const { textColor, backgroundColor } = color ?? {};
const disabledClass = disabled ? 'opacity-50 cursor-not-allowed' : 'opacity-100 cursor-pointer';
const disabledClass = disabled && 'opacity-50 cursor-not-allowed';
const expandedClass = isExpanded ? 'break-words' : 'truncate';
const expandableClass = isExpandable && 'cursor-pointer';
const bgColorClass = backgroundColor ? `bg-${backgroundColor}` : 'bg-[#EFF0F1]';

return (
<div
className={`inline-block py-2 px-2 mr-1 my-1 max-w-full rounded-[4px] ${disabledClass}
${backgroundColor ? `bg-${backgroundColor}` : 'bg-[#EFF0F1]'} ${
isExpanded ? 'break-words' : 'truncate'
} `}
className={`inline-block py-1 px-1.5 mr-1 mb-1 max-w-full rounded-[4px] ${disabledClass} ${expandableClass} ${expandedClass} ${bgColorClass}`}
ref={elem}
onClick={onClick}
>
Expand Down
@@ -1,4 +1,4 @@
import { DrawerItem } from '../../../../pages/kubepanel/components/drawer/drawer-item';
import DrawerItem from '../../../../pages/kubepanel/components/drawer/drawer-item';
import { KubeObjectAge } from '../kube-object-age';
import { LocaleDate } from '../../local-date';
import moment from 'moment-timezone';
Expand Down
18 changes: 18 additions & 0 deletions frontend/plugins/kubepanel/src/constants/kube-api.ts
Expand Up @@ -33,5 +33,23 @@ export const ApiBaseParamsMap: Record<ResourceKey, UrlParams> = {
apiGroup: undefined,
apiVersion: 'v1',
resource: Resources.PersistentVolumeClaims
},
secrets: {
apiPrefix: 'api',
apiGroup: undefined,
apiVersion: 'v1',
resource: Resources.Secrets
},
ingresses: {
apiPrefix: 'apis',
apiGroup: 'networking.k8s.io',
apiVersion: 'v1',
resource: Resources.Ingresses
},
events: {
apiPrefix: 'api',
apiGroup: undefined,
apiVersion: 'v1',
resource: Resources.Events
}
};
18 changes: 15 additions & 3 deletions frontend/plugins/kubepanel/src/constants/kube-object.ts
@@ -1,8 +1,11 @@
import {
ConfigMap,
Deployment,
Ingress,
KubeEvent,
PersistentVolumeClaim,
Pod,
Secret,
StatefulSet
} from '@/k8slens/kube-object';
import { StringKeyOf } from 'type-fest';
Expand All @@ -14,21 +17,30 @@ export enum Resources {
Deployments = 'deployments',
StatefulSets = 'statefulsets',
ConfigMaps = 'configmaps',
PersistentVolumeClaims = 'persistentvolumeclaims'
PersistentVolumeClaims = 'persistentvolumeclaims',
Secrets = 'secrets',
Ingresses = 'ingresses',
Events = 'events'
}

export const KubeObjectConstructorMap: { [key in Resources]: any } = {
[Resources.Pods]: Pod,
[Resources.Deployments]: Deployment,
[Resources.StatefulSets]: StatefulSet,
[Resources.ConfigMaps]: ConfigMap,
[Resources.PersistentVolumeClaims]: PersistentVolumeClaim
[Resources.PersistentVolumeClaims]: PersistentVolumeClaim,
[Resources.Secrets]: Secret,
[Resources.Ingresses]: Ingress,
[Resources.Events]: KubeEvent
};

export const KindMap: { [key in Resources]: any } = {
[Resources.Pods]: 'Pod',
[Resources.Deployments]: 'Deployment',
[Resources.StatefulSets]: 'StatefulSet',
[Resources.ConfigMaps]: 'ConfigMap',
[Resources.PersistentVolumeClaims]: 'PersistentVolumeClaim'
[Resources.PersistentVolumeClaims]: 'PersistentVolumeClaim',
[Resources.Secrets]: 'Secret',
[Resources.Ingresses]: 'Ingress',
[Resources.Events]: 'Event'
};
4 changes: 4 additions & 0 deletions frontend/plugins/kubepanel/src/constants/theme.ts
Expand Up @@ -8,6 +8,10 @@ export const theme: ThemeConfig = {
},
Collapse: {
headerPadding: 0
},
Table: {
headerBorderRadius: 0,
headerBg: '#F6F8F9'
}
}
};
Expand Up @@ -7,7 +7,7 @@ import { editor } from 'monaco-editor';
import { useEffect, useRef, useState } from 'react';

interface Props<K extends KubeObject> {
obj: K;
obj?: K;
open: boolean;
onUpdate: (data: string) => Promise<ApiResp>;
onCancel: () => void;
Expand All @@ -21,6 +21,8 @@ const UpdateEditorModal = <K extends KubeObject = KubeObject>({
onCancel,
onOk
}: Props<K>) => {
if (!obj) return null;

const [clickedUpdate, setClickedUpdate] = useState(false);
const [msgApi, contextHolder] = message.useMessage();
const editorRef = useRef<editor.IStandaloneCodeEditor>();
Expand Down
@@ -1,4 +1,4 @@
import { DrawerItem } from '@/pages/kubepanel/components/drawer/drawer-item';
import DrawerItem from '@/pages/kubepanel/components/drawer/drawer-item';
import { Collapse } from 'antd';

interface Props {
Expand Down
Expand Up @@ -5,24 +5,18 @@ interface DrawerItemProps {
padding?: boolean;
}

export const DrawerItem = ({
name,
value,
hidden = false,
}: DrawerItemProps) => {
const DrawerItem = ({ name, value, hidden = false }: DrawerItemProps) => {
if (hidden) return null;

return (
<div
className={`grid last:border-b-0`}
style={{ gridTemplateColumns: 'minmax(30%, min-content) auto' }}
>
<span className="overflow-hidden text-ellipsis text-[#5A646E] font-semibold">
{name}
</span>
<span className="max-w-full min-w-0 text-[#24282C]">
{value}
</span>
<span className="overflow-hidden text-ellipsis text-[#5A646E] font-semibold">{name}</span>
<span className="max-w-full min-w-0 text-[#24282C]">{value}</span>
</div>
);
};

export default DrawerItem;
Expand Up @@ -9,10 +9,15 @@ interface Props {
const Drawer = ({ title, children, open, onClose }: Props) => {
return (
<AntdDrawer
classNames={{ header: 'bg-[#24282C]' }}
styles={{
header: {
padding: '12px',
backgroundColor: '#24282C'
}
}}
open={open}
closeIcon={<CloseOutlined style={{ color: 'white', fontSize: '32px' }} />}
title={<span className="pl-2 text-white font-medium text-base">{title}</span>}
closeIcon={<CloseOutlined style={{ color: 'white', fontSize: '24px', padding: '4px' }}/>}
title={<span className="text-white font-medium text-base">{title}</span>}
width="550px"
onClose={onClose}
destroyOnClose
Expand Down
@@ -0,0 +1,117 @@
import { Secret } from '@/k8slens/kube-object';
import DrawerPanel from '../../../drawer/drawer-panel';
import { KubeObjectInfoList } from '@/components/kube/object/detail/kube-object-detail-info-list';
import Drawer from '../../../drawer/drawer';
import React, { Suspense } from 'react';
import { Button, Input, Space } from 'antd';
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
import { DetailDrawerProps } from '@/types/detail';
import { entries } from 'lodash';
import { updateResource } from '@/api/update';
import { Resources } from '@/constants/kube-object';
import { dumpKubeObject } from '@/utils/yaml';
import useMessage from 'antd/lib/message/useMessage';

type RevealableInput = {
name: string;
initValue: string;
onChange?: (value: string) => void;
};

const SecretDataInput = ({ name, initValue, onChange }: RevealableInput) => {
const [revealed, setRevealed] = React.useState(false);
const [value, setValue] = React.useState(initValue);
return (
<div>
<div className="mb-1">{name}</div>
<Space>
<Input.TextArea
disabled={!revealed}
autoSize={{ maxRows: 20 }}
styles={{
textarea: {
fontFamily: 'monospace',
lineHeight: 1.2,
width: '478px'
}
}}
onChange={(e) => {
setValue(e.target.value);
onChange?.(e.target.value);
}}
value={revealed ? value : ''}
/>
<Button
icon={revealed ? <EyeInvisibleOutlined /> : <EyeOutlined />}
type="text"
onClick={() => setRevealed(!revealed)}
/>
</Space>
</div>
);
};

const SecretDataForm = ({ secret }: { secret: Secret }) => {
const [isSaving, setIsSaving] = React.useState(false);
const [msgApi, contextHolder] = useMessage({});
const dataMap = React.useRef<Partial<Record<string, string>>>(secret.data).current;

return (
<>
{contextHolder}
{entries(secret.data).map(([name, value]) => (
<SecretDataInput
key={name}
name={name}
initValue={Buffer.from(value || '', 'base64').toString()}
onChange={(value) => {
dataMap[name] = Buffer.from(value, 'ascii').toString('base64');
}}
/>
))}
<Suspense>
<Button
style={{ width: 'max-content' }}
loading={isSaving}
onClick={() => {
setIsSaving(true);
updateResource(
dumpKubeObject<Secret>({
...secret,
data: dataMap
}),
secret.getName(),
Resources.Secrets
)
.then((res) => {
if (res.code >= 300 || res.code < 200) msgApi.error(res.message);
else msgApi.success('Successfully Saved!');
})
.finally(() => {
setIsSaving(false);
});
}}
>
Save
</Button>
</Suspense>
</>
);
};

const SecretDetail = ({ obj, open, onClose }: DetailDrawerProps<Secret>) => {
if (!obj || !(obj instanceof Secret)) return null;

return (
<Drawer open={open} title={`Secret: ${obj.getName()}`} onClose={onClose}>
<DrawerPanel>
<KubeObjectInfoList obj={obj} />
</DrawerPanel>
<DrawerPanel title="Data">
<SecretDataForm secret={obj} />
</DrawerPanel>
</Drawer>
);
};

export default SecretDetail;

0 comments on commit f52b174

Please sign in to comment.