Skip to content

Commit

Permalink
Merge pull request #86 from warjiang/feature/configmap-manage-ui
Browse files Browse the repository at this point in the history
UI for configmap management
  • Loading branch information
karmada-bot authored Aug 19, 2024
2 parents a2850b4 + 8aacca8 commit 922deee
Show file tree
Hide file tree
Showing 14 changed files with 769 additions and 2 deletions.
53 changes: 53 additions & 0 deletions pkg/resource/configmap/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2017 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package configmap

import (
"github.com/karmada-io/dashboard/pkg/common/types"
"github.com/karmada-io/dashboard/pkg/dataselect"
"reflect"
"testing"

v1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestToConfigMapList(t *testing.T) {
cases := []struct {
configMaps []v1.ConfigMap
expected *ConfigMapList
}{
{nil, &ConfigMapList{Items: []ConfigMap{}}},
{
[]v1.ConfigMap{
{Data: map[string]string{"app": "my-name"}, ObjectMeta: metaV1.ObjectMeta{Name: "foo"}},
},
&ConfigMapList{
ListMeta: types.ListMeta{TotalItems: 1},
Items: []ConfigMap{{
TypeMeta: types.TypeMeta{Kind: "configmap"},
ObjectMeta: types.ObjectMeta{Name: "foo"},
}},
},
},
}
for _, c := range cases {
actual := toConfigMapList(c.configMaps, nil, dataselect.NoDataSelect)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("toConfigMapList(%#v) == \n%#v\nexpected \n%#v\n",
c.configMaps, actual, c.expected)
}
}
}
3 changes: 2 additions & 1 deletion ui/apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"react-i18next": "^14.1.0",
"react-router-dom": "^6.22.3",
"tailwind-merge": "^2.2.2",
"yaml": "^2.4.2"
"yaml": "^2.4.2",
"zustand": "^4.5.4"
},
"devDependencies": {
"@karmada/eslint-config-ts-react": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions ui/apps/dashboard/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as useNamespace } from './use-namespace';
export { default as useTagNum } from './use-tag-num';
35 changes: 35 additions & 0 deletions ui/apps/dashboard/src/hooks/use-namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { GetNamespaces } from '@/services/namespace.ts';
import { DataSelectQuery } from '@/services/base.ts';

const useNamespace = (props: { nsFilter?: DataSelectQuery }) => {
const { nsFilter = {} } = props;
const {
data: nsData,
isLoading,
refetch,
} = useQuery({
queryKey: ['GetNamespaces', nsFilter],
queryFn: async () => {
const response = await GetNamespaces(nsFilter);
return response.data || {};
},
});
const nsOptions = useMemo(() => {
if (!nsData?.namespaces) return [];
return nsData.namespaces.map((item) => {
return {
title: item.objectMeta.name,
value: item.objectMeta.name,
};
});
}, [nsData]);
return {
nsOptions,
isNsDataLoading: isLoading,
refetchNsData: refetch,
};
};

export default useNamespace;
21 changes: 21 additions & 0 deletions ui/apps/dashboard/src/hooks/use-tag-num.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useWindowSize } from '@uidotdev/usehooks';

const useTagNum = (
params: {
targetWidth?: number;
defaultTagNum?: number;
} = {},
) => {
const { targetWidth = 1800, defaultTagNum = 1 } = params;
const size = useWindowSize();
if (!size || !size.width)
return {
tagNum: undefined,
};
const tagNum = size.width > targetWidth ? undefined : defaultTagNum;
return {
tagNum,
};
};

export default useTagNum;
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { FC, useEffect, useMemo, useState } from 'react';
import { Form, Modal, Select } from 'antd';
import Editor from '@monaco-editor/react';
import { parse } from 'yaml';
import _ from 'lodash';
import { CreateResource, PutResource } from '@/services/unstructured';
import { ConfigKind, IResponse } from '@/services/base.ts';

export interface NewWorkloadEditorModalProps {
mode: 'create' | 'edit' | 'read';
open: boolean;
kind: ConfigKind;
workloadContent?: string;
onOk: (ret: IResponse<any>) => Promise<void>;
onCancel: () => Promise<void> | void;
}

const NewConfigEditorModal: FC<NewWorkloadEditorModalProps> = (props) => {
const { mode, open, workloadContent = '', onOk, onCancel, kind } = props;
const [content, setContent] = useState<string>(workloadContent);
useEffect(() => {
setContent(workloadContent);
}, [workloadContent]);

function handleEditorChange(value: string | undefined) {
setContent(value || '');
}

const title = useMemo(() => {
switch (mode) {
case 'read':
return '查看配置';
case 'edit':
return '编辑配置';
case 'create':
return '新增配置';
}
}, [mode]);
return (
<Modal
title={title}
open={open}
width={1000}
okText={'确定'}
cancelText={'取消'}
destroyOnClose={true}
onOk={async () => {
try {
const yamlObject = parse(content) as Record<string, string>;
const kind = _.get(yamlObject, 'kind');
const namespace = _.get(yamlObject, 'metadata.namespace');
const name = _.get(yamlObject, 'metadata.name');
if (mode === 'create') {
const ret = await CreateResource({
kind,
name,
namespace,
content: yamlObject,
});
await onOk(ret);
setContent('');
} else if (mode === 'edit') {
const ret = await PutResource({
kind,
name,
namespace,
content: yamlObject,
});
await onOk(ret);
setContent('');
}
} catch (e) {
console.log('e', e);
}
}}
onCancel={async () => {
await onCancel();
setContent('');
}}
>
<Form.Item label={'配置类型'}>
<Select
value={kind}
disabled
options={[
{
label: 'ConfigMap',
value: ConfigKind.ConfigMap,
},
{
label: 'Secret',
value: ConfigKind.Secret,
},
]}
style={{
width: 200,
}}
/>
</Form.Item>

<Editor
height="600px"
defaultLanguage="yaml"
value={content}
theme="vs"
options={{
theme: 'vs',
lineNumbers: 'on',
fontSize: 15,
readOnly: mode === 'read',
minimap: {
enabled: false,
},
}}
onChange={handleEditorChange}
/>
</Modal>
);
};

export default NewConfigEditorModal;
Loading

0 comments on commit 922deee

Please sign in to comment.