Skip to content

Commit

Permalink
feat: namespace create support
Browse files Browse the repository at this point in the history
  • Loading branch information
tingfuyeh committed Dec 2, 2021
1 parent aa8fe70 commit ef64449
Show file tree
Hide file tree
Showing 12 changed files with 530 additions and 60 deletions.
12 changes: 6 additions & 6 deletions web/.prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 120,
"trailingComma": "all",
"jsxSingleQuote": true
}
"semi": true,
"singleQuote": false,
"printWidth": 120,
"trailingComma": "all",
"jsxSingleQuote": false
}
56 changes: 14 additions & 42 deletions web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import ServicePage from "@src/polaris/service/Page";
import ServicePageDuck from "@src/polaris/service/PageDuck";
const Service = connectWithDuck(ServicePage, ServicePageDuck);

import NamespacePage from "@src/polaris/namespace/Page";
import NamespacePageDuck from "@src/polaris/namespace/PageDuck";
const Namespace = connectWithDuck(NamespacePage, NamespacePageDuck);

import ServiceDetailPage from "@src/polaris/service/detail/Page";
import ServiceDetailDuck from "@src/polaris/service/detail/PageDuck";
const ServiceDetail = connectWithDuck(ServiceDetailPage, ServiceDetailDuck);
Expand All @@ -24,23 +28,14 @@ import { MenuConfig } from "./menu";
import { connectWithDuck } from "./polaris/common/helpers";
import insertCSS from "./polaris/common/helpers/insertCSS";
import MonitorPage from "@src/polaris/monitor/Page";
import {
CircuitBreakerMonitorDuck,
RouteMonitorDuck,
RatelimitMonitorDuck,
} from "@src/polaris/monitor/PageDuck";
const CircuitBreakerMonitor = connectWithDuck(
MonitorPage,
CircuitBreakerMonitorDuck
);
import { CircuitBreakerMonitorDuck, RouteMonitorDuck, RatelimitMonitorDuck } from "@src/polaris/monitor/PageDuck";
const CircuitBreakerMonitor = connectWithDuck(MonitorPage, CircuitBreakerMonitorDuck);
const RouteMonitor = connectWithDuck(MonitorPage, RouteMonitorDuck);
const RatelimitMonitor = connectWithDuck(MonitorPage, RatelimitMonitorDuck);

export default function root() {
const history = useHistory();
const [selected, setSelected] = React.useState(
history.location.pathname.match(/^\/(\w+)/)?.[1]
);
const [selected, setSelected] = React.useState(history.location.pathname.match(/^\/(\w+)/)?.[1]);
const getMenuItemProps = (id) => ({
selected: selected === id,
onClick: () => {
Expand All @@ -55,11 +50,7 @@ export default function root() {
left={
<>
<NavMenu.Item type="logo" style={{ width: "185px" }}>
<img
src={"/static/img/logo-polaris.png"}
alt="logo"
style={{ height: "27px" }}
/>
<img src={"/static/img/logo-polaris.png"} alt="logo" style={{ height: "27px" }} />
</NavMenu.Item>
<NavMenu.Item></NavMenu.Item>
</>
Expand All @@ -83,49 +74,30 @@ export default function root() {
return null;
}

return (
<Menu.Item
{...menuConfig}
{...getMenuItemProps(item)}
/>
);
return <Menu.Item {...menuConfig} {...getMenuItemProps(item)} />;
})}
</Menu.Group>
);
}
if (typeof MenuConfig[item] !== "object") {
return;
}
return (
<Menu.Item {...MenuConfig[item]} {...getMenuItemProps(item)} />
);
return <Menu.Item {...MenuConfig[item]} {...getMenuItemProps(item)} />;
})}
</Menu>
</Sider>
<Content>
<Switch>
<Route exact path="/" component={Service} />
<Route exact path="/namespace" component={Namespace} />
<Route exact path="/service" component={Service} />
<Route exact path="/service-detail" component={ServiceDetail} />
<Route exact path="/route-create" component={RouteCreate} />
<Route exact path="/ratelimit-create" component={RateLimit} />
<Route
exact
path="/circuitBreaker-create"
component={CircuitBreaker}
/>
<Route
exact
path="/circuitBreaker-monitor"
component={CircuitBreakerMonitor}
/>
<Route exact path="/circuitBreaker-create" component={CircuitBreaker} />
<Route exact path="/circuitBreaker-monitor" component={CircuitBreakerMonitor} />
<Route exact path="/route-monitor" component={RouteMonitor} />

<Route
exact
path="/ratelimit-monitor"
component={RatelimitMonitor}
/>
<Route exact path="/ratelimit-monitor" component={RatelimitMonitor} />
</Switch>
</Content>
</Body>
Expand Down
1 change: 1 addition & 0 deletions web/src/assets/img/namespace.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions web/src/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export const MenuConfig = {
title: "服务列表",
icon: ["/static/img/service.svg", "/static/img/service.svg"],
},
namespace: {
title: "命名空间",
icon: ["/static/img/namespace.svg", "/static/img/namespace.svg"],
},
},
observability: {
isGroup: true,
Expand All @@ -17,10 +21,7 @@ export const MenuConfig = {
},
"circuitBreaker-monitor": {
title: "熔断监控",
icon: [
"/static/img/circuit-monitor.svg",
"/static/img/circuit-monitor.svg",
],
icon: ["/static/img/circuit-monitor.svg", "/static/img/circuit-monitor.svg"],
},
"ratelimit-monitor": {
title: "限流监控",
Expand Down
63 changes: 63 additions & 0 deletions web/src/polaris/namespace/Page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from "react";
import { DuckCmpProps, memorize } from "saga-duck";
import NamespaceDuck from "./PageDuck";
import getColumns from "./getColumns";
import insertCSS from "../common/helpers/insertCSS";
import { Justify, H3, Table, Button, SearchBox, Card } from "tea-component";
import GridPageGrid from "../common/duckComponents/GridPageGrid";
import GridPagePagination from "../common/duckComponents/GridPagePagination";
import BasicLayout from "../common/components/BaseLayout";

insertCSS(
"service",
`
.justify-search{
margin-right:20px
}
.justify-button{
vertical-align: bottom
}
`,
);
const getHandlers = memorize(({ creators }: NamespaceDuck, dispatch) => ({
inputKeyword: (keyword) => dispatch(creators.inputKeyword(keyword)),
search: (keyword) => dispatch(creators.search(keyword)),
clearKeyword: () => dispatch(creators.inputKeyword("")),
reload: () => dispatch(creators.reload()),
create: () => dispatch(creators.create()),
}));
export default function ServicePage(props: DuckCmpProps<NamespaceDuck>) {
const { duck, store, dispatch } = props;
const { selectors } = duck;
const columns = React.useMemo(() => getColumns(props), []);
const handlers = getHandlers(props);
return (
<BasicLayout title={"命名空间"} store={store} selectors={duck.selectors} header={<></>}>
<Table.ActionPanel>
<Justify
left={
<Button type={"primary"} onClick={handlers.create}>
{"新建"}
</Button>
}
right={
<>
<SearchBox
value={selectors.pendingKeyword(store)}
placeholder={"请输入命名空间名称"}
onSearch={handlers.search}
onChange={handlers.inputKeyword}
onClear={handlers.clearKeyword}
/>
<Button type={"icon"} icon={"refresh"} onClick={handlers.reload}></Button>
</>
}
/>
</Table.ActionPanel>
<Card>
<GridPageGrid duck={duck} dispatch={dispatch} store={store} columns={columns} />
<GridPagePagination duck={duck} dispatch={dispatch} store={store} />
</Card>
</BasicLayout>
);
}
157 changes: 157 additions & 0 deletions web/src/polaris/namespace/PageDuck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { createToPayload, reduceFromPayload } from "saga-duck";
import { takeLatest } from "redux-saga-catch";
import Create from "./operation/Create";
import CreateDuck from "./operation/CreateDuck";
import { put, select } from "redux-saga/effects";
import GridPageDuck, { Filter as BaseFilter } from "../common/ducks/GridPage";
import { ComposedId } from "../service/detail/types";
import { resolvePromise } from "saga-duck/build/helper";
import { showDialog } from "../common/helpers/showDialog";
import { Modal, notification } from "tea-component";
import { Namespace } from "../service/types";
import { deleteNamespace, describeComplicatedNamespaces } from "./model";

export interface NamespaceItem extends Namespace {
id: string;
}
export default class ServicePageDuck extends GridPageDuck {
Filter: BaseFilter;
Item: NamespaceItem;
get baseUrl() {
return "";
}
get quickTypes() {
enum Types {
EDIT,
REMOVE,
CREATE,
LOAD,
SET_COMPOSE_ID,
}
return {
...super.quickTypes,
...Types,
};
}
get initialFetch() {
return true;
}
get recordKey() {
return "id";
}
get watchTypes() {
return [...super.watchTypes, this.types.SEARCH, this.types.SET_COMPOSE_ID];
}
get params() {
return [...super.params];
}
get quickDucks() {
return {
...super.quickDucks,
};
}
get reducers() {
const { types } = this;
return {
...super.reducers,
composedId: reduceFromPayload(types.SET_COMPOSE_ID, {} as ComposedId),
};
}
get creators() {
const { types } = this;
return {
...super.creators,
edit: createToPayload<NamespaceItem>(types.EDIT),
remove: createToPayload<NamespaceItem>(types.REMOVE),
create: createToPayload<void>(types.CREATE),
load: (composedId, data) => ({
type: types.LOAD,
payload: { composedId, data },
}),
};
}
get rawSelectors() {
type State = this["State"];
return {
...super.rawSelectors,
filter: (state: State) => ({
page: state.page,
count: state.count,
keyword: state.keyword,
}),
};
}

*saga() {
const { types, creators, selectors } = this;
yield* super.saga();
yield takeLatest(types.LOAD, function* (action) {
const { composedId } = action.payload;
yield put({ type: types.SET_COMPOSE_ID, payload: composedId });
});
yield takeLatest(types.CREATE, function* () {
const res = yield* resolvePromise(
new Promise((resolve) => {
showDialog(Create, CreateDuck, function* (duck: CreateDuck) {
try {
resolve(yield* duck.execute({}, { isModify: false }));
} finally {
resolve(false);
}
});
}),
);
if (res) {
yield put(creators.reload());
}
});
yield takeLatest(types.EDIT, function* (action) {
const data = action.payload;
const res = yield* resolvePromise(
new Promise((resolve) => {
showDialog(Create, CreateDuck, function* (duck: CreateDuck) {
try {
resolve(yield* duck.execute(data, { isModify: true }));
} finally {
resolve(false);
}
});
}),
);
if (res) {
yield put(creators.reload());
}
});
yield takeLatest(types.REMOVE, function* (action) {
const { name } = action.payload;

const confirm = yield Modal.confirm({
message: `确认删除命名空间`,
description: "删除后,无法恢复",
okText: "删除",
});
if (confirm) {
const res = yield deleteNamespace([{ name, token: "polaris@12345678" }]);
if (res) notification.success({ description: "删除成功" });
yield put(creators.reload());
}
});
}

async getData(filters: this["Filter"]) {
const { page, count, keyword } = filters;
const result = await describeComplicatedNamespaces({
limit: count,
offset: (page - 1) * count,
name: keyword || undefined,
});
return {
totalCount: result.amount,
list:
result.namespaces?.map((item) => ({
...item,
id: item.name,
})) || [],
};
}
}
Loading

0 comments on commit ef64449

Please sign in to comment.