wanghongkai 5 lat temu
commit
07a1cb8af9

+ 16 - 0
.editorconfig

@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab

+ 20 - 0
.gitignore

@@ -0,0 +1,20 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/npm-debug.log*
+/yarn-error.log
+/yarn.lock
+/package-lock.json
+
+# production
+/dist
+
+# misc
+.DS_Store
+
+# umi
+/src/.umi
+/src/.umi-production
+/src/.umi-test
+/.env.local

+ 8 - 0
.prettierignore

@@ -0,0 +1,8 @@
+**/*.md
+**/*.svg
+**/*.ejs
+**/*.html
+package.json
+.umi
+.umi-production
+.umi-test

+ 11 - 0
.prettierrc

@@ -0,0 +1,11 @@
+{
+  "singleQuote": true,
+  "trailingComma": "all",
+  "printWidth": 80,
+  "overrides": [
+    {
+      "files": ".prettierrc",
+      "options": { "parser": "json" }
+    }
+  ]
+}

+ 18 - 0
.umirc.ts

@@ -0,0 +1,18 @@
+import { defineConfig } from 'umi';
+
+export default defineConfig({
+  proxy: {
+    '/api': {
+      // 'target': 'http://172.19.240.15:6000/',
+      target: 'http://localhost:8080/',
+      changeOrigin: true,
+    },
+  },
+  nodeModulesTransform: {
+    type: 'none',
+  },
+  // routes: [
+  //   { path: '/', component: '@/pages/index' },
+  //   { path: '/apps', component: '@/pages/apps'},
+  // ],
+});

+ 11 - 0
Dockerfile

@@ -0,0 +1,11 @@
+FROM node:10-alpine as builder  
+WORKDIR /code
+RUN yarn config set registry https://registry.npm.taobao.org/
+ADD package.json /code
+RUN yarn install --production  
+ADD . /code  
+RUN yarn build
+
+FROM nginx:1.19-alpine
+COPY nginx.conf /etc/nginx/conf.d/nginx.conf
+COPY --from=builder /code/dist/ /var/www/html/dist/

+ 15 - 0
README.md

@@ -0,0 +1,15 @@
+# umi project
+
+## Getting Started
+
+Install dependencies,
+
+```bash
+$ yarn
+```
+
+Start the dev server,
+
+```bash
+$ yarn start
+```

+ 0 - 0
mock/.gitkeep


+ 14 - 0
nginx.conf

@@ -0,0 +1,14 @@
+upstream websocket {
+    server environment-13-9.seec.svc.cluster.local:8080;
+}
+server {
+  listen 80 default_server;
+  listen [::]:80 default_server;
+  root /var/www/html/dist;
+  location ~ / {
+    if ($request_uri ~* (api)) {
+        proxy_pass http://environment-13-9.seec.svc.cluster.local:8080;
+    }
+    try_files $uri $uri/ /index.html;
+  }
+}

+ 34 - 0
package.json

@@ -0,0 +1,34 @@
+{
+  "private": true,
+  "scripts": {
+    "start": "umi dev",
+    "build": "umi build",
+    "postinstall": "umi generate tmp",
+    "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
+    "test": "umi-test",
+    "test:coverage": "umi-test --coverage"
+  },
+  "gitHooks": {
+    "pre-commit": "lint-staged"
+  },
+  "lint-staged": {
+    "*.{js,jsx,less,md,json}": [
+      "prettier --write"
+    ],
+    "*.ts?(x)": [
+      "prettier --parser=typescript --write"
+    ]
+  },
+  "dependencies": {
+    "@ant-design/pro-layout": "^5.0.12",
+    "@umijs/hooks": "^1.9.3",
+    "@umijs/preset-react": "1.x",
+    "@umijs/test": "^3.2.24",
+    "lint-staged": "^10.0.7",
+    "prettier": "^1.19.1",
+    "react": "^16.12.0",
+    "react-dom": "^16.12.0",
+    "umi": "^3.2.24",
+    "yorkie": "^2.0.0"
+  }
+}

+ 50 - 0
src/app.ts

@@ -0,0 +1,50 @@
+import { RequestConfig, history } from 'umi';
+import { useSessionStorageState } from '@umijs/hooks';
+import { message } from 'antd';
+export const request: RequestConfig = {
+  timeout: 10000,
+  dataField: 'result',
+  errorHandler: error => {
+    if (error.data && error.data.msg) {
+      message.error(error.data.msg);
+    }
+    return error.data;
+  },
+  errorConfig: {
+    adaptor: response => {
+      return {
+        ...response,
+        data: response,
+        success: response.code == 0,
+        showType: 2,
+        errorMessage: response.message || response.msg,
+      };
+    },
+  },
+  middlewares: [],
+  requestInterceptors: [
+    (url, options) => {
+      const token = sessionStorage.getItem('token');
+      return {
+        url,
+        options: {
+          ...options,
+          headers: {
+            ...(options.headers || {}),
+            Authorization: token,
+          },
+        },
+      };
+    },
+  ],
+  responseInterceptors: [
+    (response, options) => {
+      if (response.status == 403) {
+        message.error('未登录或登录超时!');
+        sessionStorage.removeItem('token');
+        history.push('/login');
+      }
+      return response;
+    },
+  ],
+};

+ 3 - 0
src/global.css

@@ -0,0 +1,3 @@
+#root {
+  height: 100%;
+}

+ 36 - 0
src/layouts/index.less

@@ -0,0 +1,36 @@
+.layout {
+  background-color: #fff;
+  height: 100%;
+  .header {
+    background: #fff;
+    -webkit-box-shadow: 0 2px 8px #f0f1f2;
+    box-shadow: 0 2px 8px #f0f1f2;
+    .headerContent {
+      display: flex;
+      .logo {
+        cursor: pointer;
+        height: 64px;
+        overflow: hidden;
+        color: rgba(0, 0, 0, 0.85);
+        font-size: 18px;
+        font-family: Avenir, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
+          Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji,
+          Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
+        line-height: 64px;
+        white-space: nowrap;
+        text-decoration: none;
+        font-weight: 500;
+      }
+      .appSelector {
+        flex: 1;
+        margin-left: 16px;
+      }
+    }
+  }
+  .content {
+    margin-top: 10px;
+    padding: 10px 20px 20px 20px;
+    background-color: #fff;
+    height: 100%;
+  }
+}

+ 32 - 0
src/layouts/index.tsx

@@ -0,0 +1,32 @@
+import { IRouteComponentProps, connect, history } from 'umi';
+import React, { PureComponent } from 'react';
+import { Layout, Select } from 'antd';
+import Login from '@/pages/login';
+import styles from './index.less';
+const { Option } = Select;
+const { Header, Content } = Layout;
+@connect(({ app }) => ({ app }))
+export default class GlobalLayout extends PureComponent {
+  toIndex() {
+    history.push('/pipelines');
+  }
+  render() {
+    const { children, location, route, history, match } = this.props;
+    if (location.pathname === '/login') {
+      return <Login></Login>;
+    } else {
+      return (
+        <Layout className={styles.layout}>
+          <Header className={styles.header}>
+            <div className={styles.headerContent}>
+              <div onClick={this.toIndex.bind(this)} className={styles.logo}>
+                Seecoder-Build
+              </div>
+            </div>
+          </Header>
+          <Content className={styles.content}>{children}</Content>
+        </Layout>
+      );
+    }
+  }
+}

+ 55 - 0
src/models/pipeline.js

@@ -0,0 +1,55 @@
+import { getAll, create, update, deletePipeline } from '@/services/pipeline';
+import { message } from 'antd';
+export default {
+  namespace: 'pipeline',
+  state: {
+    pipelines: [],
+  },
+  effects: {
+    *getAll({ payload }, { call, put }) {
+      const response = yield call(getAll);
+      yield put({ type: 'putPipelines', payload: response.result });
+      if (payload && payload.callback) {
+        yield call(payload.callback, response);
+      }
+    },
+    *create({ payload }, { call, put }) {
+      const response = yield call(create, payload);
+      if (response && response.code == 0) {
+        message.success('创建成功!');
+      }
+      yield put({ type: 'getAll' });
+      if (payload && payload.callback) {
+        yield call(payload.callback, response);
+      }
+    },
+    *update({ payload }, { call, put }) {
+      const response = yield call(update, payload);
+      if (response && response.code == 0) {
+        message.success('更新成功!');
+      }
+      yield put({ type: 'getAll' });
+      if (payload && payload.callback) {
+        yield call(payload.callback, response);
+      }
+    },
+    *deletePipeline({ payload }, { call, put }) {
+      const response = yield call(deletePipeline, payload);
+      if (response && response.code == 0) {
+        message.success('删除成功!');
+      }
+      yield put({ type: 'getAll' });
+      if (payload && payload.callback) {
+        yield call(payload.callback, response);
+      }
+    },
+  },
+  reducers: {
+    putPipelines(state, action) {
+      return {
+        ...state,
+        pipelines: action.payload,
+      };
+    },
+  },
+};

+ 17 - 0
src/models/user.js

@@ -0,0 +1,17 @@
+import { login } from '@/services/user';
+
+export default {
+  namespace: 'user',
+  state: {},
+  effects: {
+    *login({ payload }, { call }) {
+      const response = yield call(login, payload);
+      if (response && response.code == 0) {
+        sessionStorage.setItem('token', response.result);
+      }
+      if (payload && payload.callback) {
+        yield call(payload.callback, response);
+      }
+    },
+  },
+};

+ 6 - 0
src/pages/index.tsx

@@ -0,0 +1,6 @@
+import React from 'react';
+import { Redirect } from 'umi';
+
+export default () => {
+  return <Redirect to="/pipelines"></Redirect>;
+};

+ 66 - 0
src/pages/login.js

@@ -0,0 +1,66 @@
+import React, { PureComponent } from 'react';
+import styles from './login.less';
+import { connect, history } from 'umi';
+import { Form, Input, Button } from 'antd';
+
+@connect(({ user }) => ({
+  user,
+}))
+export default class Login extends PureComponent {
+  componentDidMount() {
+    const token = sessionStorage.getItem('token');
+    if (token) {
+      history.push('/pipelines');
+    }
+  }
+  onFinish(values) {
+    this.props.dispatch({
+      type: 'user/login',
+      payload: {
+        ...values,
+        callback: response => {
+          if (response && response.code == 0) {
+            history.push('/pipelines');
+          }
+        },
+      },
+    });
+  }
+  render() {
+    return (
+      <div className={styles.loginContainer}>
+        <h1 className={styles.title}>Seecoder PaaS</h1>
+        <Form
+          name="basic"
+          labelAlign="right"
+          onFinish={this.onFinish.bind(this)}
+        >
+          <Form.Item
+            label="用户名"
+            name="username"
+            labelAlign="right"
+            rules={[{ required: true, message: '请输入用户名' }]}
+          >
+            <Input style={{ width: 180 }} />
+          </Form.Item>
+          <Form.Item
+            label="密码"
+            name="password"
+            rules={[{ required: true, message: '请输入密码' }]}
+          >
+            <Input.Password style={{ width: 180 }} />
+          </Form.Item>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              className={styles.loginButton}
+            >
+              登录
+            </Button>
+          </Form.Item>
+        </Form>
+      </div>
+    );
+  }
+}

+ 12 - 0
src/pages/login.less

@@ -0,0 +1,12 @@
+.loginContainer {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-top: 150px;
+  :global(.ant-form-item-label) {
+    flex: 1;
+  }
+  .loginButton {
+    width: 100%;
+  }
+}

+ 6 - 0
src/pages/pipelines.less

@@ -0,0 +1,6 @@
+.normal {
+}
+
+.title {
+  background: rgb(121, 242, 157);
+}

+ 167 - 0
src/pages/pipelines.tsx

@@ -0,0 +1,167 @@
+import React, { PureComponent } from 'react';
+import styles from './pipelines.less';
+import { connect } from 'umi';
+import { Form, Input, Button, Modal, Table, Popconfirm } from 'antd';
+
+@connect(({ pipeline }) => ({
+  pipeline,
+}))
+export default class Pipelines extends PureComponent {
+  formRef = React.createRef();
+  state = {
+    showModal: false,
+    currentPipeline: null,
+  };
+  componentDidMount() {
+    this.props.dispatch({
+      type: 'pipeline/getAll',
+    });
+  }
+  showPipelineModal(currentPipeline) {
+    if (currentPipeline) {
+      this.formRef.current.setFieldsValue({
+        ...currentPipeline,
+      });
+    } else {
+      this.formRef.current.resetFields();
+    }
+    this.setState({
+      showModal: true,
+      currentPipeline: currentPipeline ? currentPipeline : null,
+    });
+  }
+  hidePipelineModal() {
+    this.setState({
+      showModal: false,
+      currentPipeline: null,
+    });
+  }
+  onFinish(values) {
+    this.formRef.current.validateFields().then(values => {
+      this.props.dispatch({
+        type: this.state.currentPipeline?.id
+          ? 'pipeline/update'
+          : 'pipeline/create',
+        payload: {
+          ...values,
+          id: this.state.currentPipeline?.id,
+          callback: response => {
+            if (response && response.code == 0) {
+              this.setState({
+                showModal: false,
+                currentPipeline: null,
+              });
+            }
+          },
+        },
+      });
+    });
+  }
+  deletePipeline(record) {
+    this.props.dispatch({
+      type: 'pipeline/deletePipeline',
+      payload: {
+        id: record.id,
+      },
+    });
+  }
+  render() {
+    const columns = [
+      {
+        title: '名称',
+        dataIndex: 'name',
+      },
+      {
+        title: '描述',
+        dataIndex: ['description'],
+      },
+      {
+        title: '创建时间',
+        dataIndex: ['createdAt'],
+      },
+      {
+        title: '更新时间',
+        dataIndex: ['modifiedAt'],
+      },
+      {
+        title: '操作',
+        render: (text, record, index) => {
+          return (
+            <div key={`action-${record.name}`}>
+              <a
+                key={`action-update-${record.name}`}
+                style={{ marginRight: 5 }}
+                onClick={() => this.showPipelineModal(record)}
+              >
+                更新
+              </a>
+              <Popconfirm
+                title="确认要删除吗?"
+                onConfirm={() => this.deletePipeline(record)}
+                okText="确定"
+                cancelText="取消"
+              >
+                <a
+                  key={`action-delete-${record.name}`}
+                  style={{ marginRight: 5 }}
+                >
+                  删除
+                </a>
+              </Popconfirm>
+            </div>
+          );
+        },
+      },
+    ];
+    return (
+      <div className={styles.loginContainer}>
+        <Button type="primary" onClick={() => this.showPipelineModal(null)}>
+          创建Pipeline
+        </Button>
+        <Table
+          dataSource={this.props.pipeline.pipelines || []}
+          columns={columns}
+        ></Table>
+        <Modal
+          visible={this.state.showModal}
+          forceRender
+          onCancel={this.hidePipelineModal.bind(this)}
+          title={'新建/更新Pipeline'}
+          okText="确认"
+          cancelText="取消"
+          onOk={this.onFinish.bind(this)}
+          width="80%"
+        >
+          <Form
+            ref={this.formRef}
+            labelCol={{ span: 4 }}
+            wrapperCol={{ span: 20 }}
+            name="basic"
+            labelAlign="right"
+            onFinish={this.onFinish.bind(this)}
+          >
+            <Form.Item
+              ref={this.formRef}
+              label="名称"
+              name="name"
+              labelAlign="right"
+              rules={[{ required: true, message: '请输入名称' }]}
+            >
+              <Input />
+            </Form.Item>
+            <Form.Item label="描述" name="description">
+              <Input />
+            </Form.Item>
+            <Form.Item
+              label="脚本"
+              name="script"
+              rules={[{ required: true, message: '请输入脚本' }]}
+            >
+              <Input.TextArea />
+            </Form.Item>
+          </Form>
+        </Modal>
+      </div>
+    );
+  }
+}

+ 24 - 0
src/services/pipeline.js

@@ -0,0 +1,24 @@
+import { request } from 'umi';
+
+export async function getAll(params) {
+  return request('/api/pipeline', {
+    method: 'get',
+  });
+}
+export async function create(params) {
+  return request('/api/pipeline', {
+    method: 'post',
+    data: params,
+  });
+}
+export async function update(params) {
+  return request('/api/pipeline/' + params.id, {
+    method: 'put',
+    data: params,
+  });
+}
+export async function deletePipeline(params) {
+  return request('/api/pipeline/' + params.id, {
+    method: 'delete',
+  });
+}

+ 8 - 0
src/services/user.js

@@ -0,0 +1,8 @@
+import { request } from 'umi';
+
+export async function login(params) {
+  return request('/api/auth', {
+    method: 'post',
+    data: params,
+  });
+}

+ 25 - 0
tsconfig.json

@@ -0,0 +1,25 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "moduleResolution": "node",
+    "importHelpers": true,
+    "jsx": "react",
+    "esModuleInterop": true,
+    "sourceMap": true,
+    "baseUrl": "./",
+    "strict": true,
+    "paths": {
+      "@/*": ["src/*"],
+      "@@/*": ["src/.umi/*"]
+    },
+    "allowSyntheticDefaultImports": true
+  },
+  "include": [
+    "mock/**/*",
+    "src/**/*",
+    "config/**/*",
+    ".umirc.ts",
+    "typings.d.ts"
+  ]
+}

+ 10 - 0
typings.d.ts

@@ -0,0 +1,10 @@
+declare module '*.css';
+declare module '*.less';
+declare module '*.png';
+declare module '*.svg' {
+  export function ReactComponent(
+    props: React.SVGProps<SVGSVGElement>,
+  ): React.ReactElement;
+  const url: string;
+  export default url;
+}