Skip to content

Commit

Permalink
style & feat: new UI components & automatically re-watch (#4433)
Browse files Browse the repository at this point in the history
* 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

* feat: kubeconfig parser

* feat: KubePanel v1.0.0

* fix: merge conflict

* fix: Add clear logo and icon

* fix: Fixed Sider

* feat: Auto deploy

* fix: Wrong margin-left in Content due to fixed style Sider

* fix: tailwind css conflict with antd

* style: clean codes

* feat: styled monaco editor

* style: change ui style

* style: make new style for create resource modal

* style: change file location

* style: action button and delete popconfirm

* fix: import error

* style: clean packages

* feat: automatically watch

* feat: table pagination

* feat: resource filter triggered by name

* style: reposition components to fix next build problems

* feat: automatically re-watch
  • Loading branch information
Wishrem committed Jan 10, 2024
1 parent e45e3b7 commit 26e06ad
Show file tree
Hide file tree
Showing 57 changed files with 1,150 additions and 777 deletions.
5 changes: 2 additions & 3 deletions frontend/plugins/kubepanel/package.json
Expand Up @@ -12,16 +12,14 @@
},
"dependencies": {
"@ant-design/charts": "^1.4.2",
"@ant-design/colors": "^7.0.0",
"@ant-design/cssinjs": "^1.17.2",
"@ant-design/icons": "^5.2.6",
"@ant-design/plots": "^1.2.5",
"@chakra-ui/react": "^2.8.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@kubernetes/client-node": "^0.19.0",
"@monaco-editor/react": "^4.6.0",
"antd": "^5.11.0",
"antd": "^5.12.0",
"auto-bind": "^5.0.1",
"axios": "^1.5.1",
"byline": "^5.0.0",
Expand Down Expand Up @@ -64,6 +62,7 @@
"eslint-plugin-xss": "^0.1.12",
"monaco-editor": "^0.44.0",
"postcss": "^8.4.31",
"postcss-nesting": "^12.0.1",
"tailwindcss": "^3.3.4",
"typescript": "^5"
}
Expand Down
425 changes: 240 additions & 185 deletions frontend/plugins/kubepanel/pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions frontend/plugins/kubepanel/postcss.config.js
@@ -1,5 +1,11 @@
module.exports = {
plugins: {
/**
* fix: nesting
* @link https://tailwindcss.com/docs/using-with-preprocessors#nesting
*/
'postcss-import': {},
'tailwindcss/nesting': 'postcss-nesting',
tailwindcss: {},
autoprefixer: {}
}
Expand Down
@@ -0,0 +1,56 @@
import { CloseOutlined, MoreOutlined, RetweetOutlined } from '@ant-design/icons';
import { Button, type MenuProps, Dropdown } from 'antd';
import { useState } from 'react';
import { UpdateEditorModal } from './update-editor-modal';
import { KubeObject } from '@/k8slens/kube-object';
import { DeletePopconfirm } from './delete-popconfirm';

interface Props<K extends KubeObject> {
obj: K;
}

export const ActionButton = <K extends KubeObject = KubeObject>({ obj }: Props<K>) => {
const [openUpdateModal, setOpenUpdateModal] = useState(false);

const items: MenuProps['items'] = [
{
key: 'delete',
label: (
<DeletePopconfirm obj={obj}>
<Button icon={<CloseOutlined />} type="link" size="small" danger>
Delete
</Button>
</DeletePopconfirm>
)
},
{
key: 'update',
label: (
<Button
icon={<RetweetOutlined />}
type="link"
size="small"
onClick={() => setOpenUpdateModal(true)}
>
Update
</Button>
)
}
].filter((item) => item.label);

return (
// wrapper to stop click event propagation
<div onClick={(e) => e.stopPropagation()}>
<Dropdown disabled={items.length === 0} menu={{ items }} arrow trigger={['click']}>
<MoreOutlined className="text-[25px] text-[#667085] hover:text-[#24282C]" />
</Dropdown>
<UpdateEditorModal
key={'update'}
obj={obj}
open={openUpdateModal}
onCancel={() => setOpenUpdateModal(false)}
onOk={() => setOpenUpdateModal(false)}
/>
</div>
);
};
@@ -0,0 +1,143 @@
import { editor as EditorNS } from 'monaco-editor';
import { Button, Flex, Modal, Spin, notification } from 'antd';
import React, { useCallback, useRef, useState } from 'react';
import { createResource, getTemplate } from '@/api/kubernetes';
import { TemplateToggle, TemplateToggleChooseEventHandler } from './toggle';
import { buildErrorResponse } from '@/services/backend/response';
import { KubeObjectKind } from '@/constants/kube-object';
import Title from '@/components/common/title/title';
import StyledEditor from '@/components/common/editor/styled';

interface Props {
open: boolean;
setClose: () => void;
}

const defaultTemplate = 'Please select a template first.';
const noticeKey = 'createResource';

export const CreateResourceModal = ({ open, setClose }: Props) => {
const [disabled, setDisabled] = useState(true);
const [kind, setKind] = useState<KubeObjectKind | undefined>(undefined);
const [loading, setLoading] = useState(false);
const [template, setTemplate] = useState<string>(defaultTemplate);
const [confirmLoading, setConfirmLoading] = useState(false);
const [notifyApi, contextHolder] = notification.useNotification();
const editorRef = useRef<EditorNS.IStandaloneCodeEditor | null>(null);
const onEditorMount = (editor: EditorNS.IStandaloneCodeEditor) => {
editorRef.current = editor;
};

const onChoose = useCallback<TemplateToggleChooseEventHandler>(
(kind) => {
setKind(kind);
setDisabled(false);
const storage = localStorage.getItem(`template-${kind}`);
if (storage) {
setTemplate(storage);
return;
}
setLoading(true);
// fetch template
getTemplate(kind)
.then((res) => {
setTemplate(res.data);
})
.catch((err: any) => {
const errResp = buildErrorResponse(err);
notifyApi.error({
key: noticeKey,
message: errResp.error.reason,
description: errResp.error.message
});
})
.finally(() => {
setLoading(false);
});
},
[notifyApi]
);

const onCreate = useCallback(() => {
setConfirmLoading(true);
const editor = editorRef.current;
if (!editor) {
setConfirmLoading(false);
notifyApi.error({
key: noticeKey,
message: 'Editor Not Found',
description: 'Please reopen the create resource panel, sorry for the inconvenience.'
});
return;
}
if (!kind) {
setConfirmLoading(false);
notifyApi.error({
key: noticeKey,
message: 'Template Not Found',
description: 'Please select a template first, sorry for the inconvenience.'
});
return;
}
const value = editor.getValue();
createResource(kind, value)
.then(() => {
notifyApi.success({
key: noticeKey,
message: 'Success',
description: 'Your resource has been created.'
});
})
.catch((err: any) => {
const errResp = buildErrorResponse(err);
notifyApi.error({
key: noticeKey,
message: errResp.error.reason,
description: errResp.error.message
});
})
.finally(() => {
setConfirmLoading(false);
});
}, [kind, notifyApi]);
return (
<>
{contextHolder}
<Modal
open={open}
width={'90vw'}
onCancel={setClose}
onOk={setClose}
footer={[
<Button key="cancel" onClick={setClose}>
Cancel
</Button>,
<Button
key="create"
type="primary"
loading={confirmLoading}
onClick={onCreate}
disabled={disabled}
>
Create
</Button>
]}
>
<Title type="primary" className="pb-5">
Create Resource
</Title>
<Flex vertical gap="12px">
<TemplateToggle onChoose={onChoose} />
<Spin spinning={loading}>
<StyledEditor
value={template}
language="yaml"
onMount={onEditorMount}
options={{ readOnly: disabled }}
/>
</Spin>
</Flex>
</Modal>
</>
);
};
@@ -0,0 +1,61 @@
import { deleteResource } from '@/api/kubernetes';
import { KubeObject } from '@/k8slens/kube-object';
import { buildErrorResponse } from '@/services/backend/response';
import { WarningOutlined } from '@ant-design/icons';
import { Popconfirm, message } from 'antd';
import { useState } from 'react';

interface Props<K extends KubeObject> {
obj: K;
children: React.ReactNode;
}

export function DeletePopconfirm<K extends KubeObject = KubeObject>({ obj, children }: Props<K>) {
if (!obj) return null;
const [confirmLoading, setConfirmLoading] = useState(false);
const [msgApi, contextHolder] = message.useMessage();
const msgKey = 'deletedMsg';
return (
<>
{contextHolder}
<Popconfirm
title={'Delete Resource'}
description={
<>
Are you sure to delete{' '}
<span className="text-[#0884DD]">
{obj.kind}: {obj.getName()}
</span>
?
</>
}
icon={<WarningOutlined className="text-red-500" />}
onConfirm={() => {
setConfirmLoading(true);
deleteResource(obj.kind, obj.getName())
.then((res) => {
msgApi.success({
content: `Successfully deleted ${res.data.kind} ${res.data.metadata.name}`,
key: msgKey
});
})
.catch((err) => {
const errResp = buildErrorResponse(err);
msgApi.error({
content: `Failed to update ${obj.kind} ${obj.getName()}: ${errResp.error.message}`,
key: msgKey
});
})
.finally(() => {
setConfirmLoading(false);
});
}}
okText="Yes"
okButtonProps={{ loading: confirmLoading }}
cancelText="No"
>
{children}
</Popconfirm>
</>
);
}

0 comments on commit 26e06ad

Please sign in to comment.