@yuku123/z-ops-frontend-component 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/z-ops-component.es.js +758 -0
- package/dist/z-ops-component.umd.js +23 -0
- package/package.json +56 -0
- package/src/api.js +113 -0
- package/src/components/ChannelPage/index.jsx +80 -0
- package/src/components/DeliveryPage/index.jsx +78 -0
- package/src/components/DeploymentPage/index.jsx +79 -0
- package/src/components/DomainPage/index.jsx +18 -0
- package/src/components/EcsPage/index.jsx +18 -0
- package/src/components/RdOpsPage/index.jsx +39 -0
- package/src/components/RdsPage/index.jsx +18 -0
- package/src/components/ResourceOssPage/index.jsx +36 -0
- package/src/components/SprintPage/index.jsx +18 -0
- package/src/components/TaskPage/index.jsx +42 -0
- package/src/index.js +37 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
(function(d,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("react"),require("antd"),require("@ant-design/icons"),require("axios")):typeof define=="function"&&define.amd?define(["exports","react","antd","@ant-design/icons","axios"],i):(d=typeof globalThis<"u"?globalThis:d||self,i(d.ZOpsFrontendComponent={},d.React,d.antd,d.icons,d.axios))})(this,(function(d,i,t,b,oe){"use strict";var V={exports:{}},N={};/**
|
|
2
|
+
* @license React
|
|
3
|
+
* react-jsx-runtime.production.js
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/var X;function ce(){if(X)return N;X=1;var l=Symbol.for("react.transitional.element"),p=Symbol.for("react.fragment");function E(j,m,c){var h=null;if(c!==void 0&&(h=""+c),m.key!==void 0&&(h=""+m.key),"key"in m){c={};for(var g in m)g!=="key"&&(c[g]=m[g])}else c=m;return m=c.ref,{$$typeof:l,type:j,key:h,ref:m!==void 0?m:null,props:c}}return N.Fragment=p,N.jsx=E,N.jsxs=E,N}var L={};/**
|
|
10
|
+
* @license React
|
|
11
|
+
* react-jsx-runtime.development.js
|
|
12
|
+
*
|
|
13
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14
|
+
*
|
|
15
|
+
* This source code is licensed under the MIT license found in the
|
|
16
|
+
* LICENSE file in the root directory of this source tree.
|
|
17
|
+
*/var Z;function de(){return Z||(Z=1,process.env.NODE_ENV!=="production"&&(function(){function l(s){if(s==null)return null;if(typeof s=="function")return s.$$typeof===Re?null:s.displayName||s.name||null;if(typeof s=="string")return s;switch(s){case x:return"Fragment";case u:return"Profiler";case A:return"StrictMode";case v:return"Suspense";case _:return"SuspenseList";case Te:return"Activity"}if(typeof s=="object")switch(typeof s.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),s.$$typeof){case P:return"Portal";case W:return s.displayName||"Context";case O:return(s._context.displayName||"Context")+".Consumer";case M:var n=s.render;return s=s.displayName,s||(s=n.displayName||n.name||"",s=s!==""?"ForwardRef("+s+")":"ForwardRef"),s;case ke:return n=s.displayName||null,n!==null?n:l(s.type)||"Memo";case Y:n=s._payload,s=s._init;try{return l(s(n))}catch{}}return null}function p(s){return""+s}function E(s){try{p(s);var n=!1}catch{n=!0}if(n){n=console;var y=n.error,S=typeof Symbol=="function"&&Symbol.toStringTag&&s[Symbol.toStringTag]||s.constructor.name||"Object";return y.call(n,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",S),p(s)}}function j(s){if(s===x)return"<>";if(typeof s=="object"&&s!==null&&s.$$typeof===Y)return"<...>";try{var n=l(s);return n?"<"+n+">":"<...>"}catch{return"<...>"}}function m(){var s=G.A;return s===null?null:s.getOwner()}function c(){return Error("react-stack-top-frame")}function h(s){if(se.call(s,"key")){var n=Object.getOwnPropertyDescriptor(s,"key").get;if(n&&n.isReactWarning)return!1}return s.key!==void 0}function g(s,n){function y(){le||(le=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",n))}y.isReactWarning=!0,Object.defineProperty(s,"key",{get:y,configurable:!0})}function f(){var s=l(this.type);return re[s]||(re[s]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),s=this.props.ref,s!==void 0?s:null}function k(s,n,y,S,U,K){var w=y.ref;return s={$$typeof:R,type:s,key:n,props:y,_owner:S},(w!==void 0?w:null)!==null?Object.defineProperty(s,"ref",{enumerable:!1,get:f}):Object.defineProperty(s,"ref",{enumerable:!1,value:null}),s._store={},Object.defineProperty(s._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(s,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(s,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:U}),Object.defineProperty(s,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:K}),Object.freeze&&(Object.freeze(s.props),Object.freeze(s)),s}function o(s,n,y,S,U,K){var w=n.children;if(w!==void 0)if(S)if(Ae(w)){for(S=0;S<w.length;S++)I(w[S]);Object.freeze&&Object.freeze(w)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else I(w);if(se.call(n,"key")){w=l(s);var B=Object.keys(n).filter(function(Oe){return Oe!=="key"});S=0<B.length?"{key: someKey, "+B.join(": ..., ")+": ...}":"{key: someKey}",ie[w+S]||(B=0<B.length?"{"+B.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
18
|
+
let props = %s;
|
|
19
|
+
<%s {...props} />
|
|
20
|
+
React keys must be passed directly to JSX without using spread:
|
|
21
|
+
let props = %s;
|
|
22
|
+
<%s key={someKey} {...props} />`,S,w,B,w),ie[w+S]=!0)}if(w=null,y!==void 0&&(E(y),w=""+y),h(n)&&(E(n.key),w=""+n.key),"key"in n){y={};for(var H in n)H!=="key"&&(y[H]=n[H])}else y=n;return w&&g(y,typeof s=="function"?s.displayName||s.name||"Unknown":s),k(s,w,y,m(),U,K)}function I(s){C(s)?s._store&&(s._store.validated=1):typeof s=="object"&&s!==null&&s.$$typeof===Y&&(s._payload.status==="fulfilled"?C(s._payload.value)&&s._payload.value._store&&(s._payload.value._store.validated=1):s._store&&(s._store.validated=1))}function C(s){return typeof s=="object"&&s!==null&&s.$$typeof===R}var T=i,R=Symbol.for("react.transitional.element"),P=Symbol.for("react.portal"),x=Symbol.for("react.fragment"),A=Symbol.for("react.strict_mode"),u=Symbol.for("react.profiler"),O=Symbol.for("react.consumer"),W=Symbol.for("react.context"),M=Symbol.for("react.forward_ref"),v=Symbol.for("react.suspense"),_=Symbol.for("react.suspense_list"),ke=Symbol.for("react.memo"),Y=Symbol.for("react.lazy"),Te=Symbol.for("react.activity"),Re=Symbol.for("react.client.reference"),G=T.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,se=Object.prototype.hasOwnProperty,Ae=Array.isArray,J=console.createTask?console.createTask:function(){return null};T={react_stack_bottom_frame:function(s){return s()}};var le,re={},ae=T.react_stack_bottom_frame.bind(T,c)(),ne=J(j(c)),ie={};L.Fragment=x,L.jsx=function(s,n,y){var S=1e4>G.recentlyCreatedOwnerStacks++;return o(s,n,y,!1,S?Error("react-stack-top-frame"):ae,S?J(j(s)):ne)},L.jsxs=function(s,n,y){var S=1e4>G.recentlyCreatedOwnerStacks++;return o(s,n,y,!0,S?Error("react-stack-top-frame"):ae,S?J(j(s)):ne)}})()),L}var Q;function ue(){return Q||(Q=1,process.env.NODE_ENV==="production"?V.exports=ce():V.exports=de()),V.exports}var e=ue();let q="/api";function pe(l){q=l||"/api"}function r(){return oe.create({baseURL:q,timeout:1e4})}const a=l=>{const p=l.data;return p&&typeof p=="object"&&"code"in p&&"data"in p?p.code!==0&&p.code!==200?Promise.reject(new Error(p.msg||p.message||"请求失败")):p.data:p},D={list:()=>r().get("/ops/deployment/list").then(a),get:l=>r().get(`/ops/deployment/${l}`).then(a),create:l=>r().post("/ops/deployment",l).then(a),update:l=>r().put("/ops/deployment",l).then(a),delete:l=>r().delete(`/ops/deployment/${l}`).then(a),deploy:l=>r().post(`/ops/deployment/deploy/${l}`).then(a),log:l=>r().get(`/ops/deployment/log/${l}`).then(a)},F={list:()=>r().get("/ops/target/list").then(a),get:l=>r().get(`/ops/target/${l}`).then(a),create:l=>r().post("/ops/target",l).then(a),update:l=>r().put("/ops/target",l).then(a),delete:l=>r().delete(`/ops/target/${l}`).then(a)},z={list:()=>r().get("/ops/environment/list").then(a),get:l=>r().get(`/ops/environment/${l}`).then(a),create:l=>r().post("/ops/environment",l).then(a),update:l=>r().put("/ops/environment",l).then(a),delete:l=>r().delete(`/ops/environment/${l}`).then(a)},$={list:l=>r().get("/ops/record/list",{params:l?{unitId:l}:{}}).then(a),get:l=>r().get(`/ops/record/${l}`).then(a)},me={list:l=>r().get("/ops/webhook/list",{params:l?{unitId:l}:{}}).then(a),create:l=>r().post("/ops/webhook",l).then(a),update:l=>r().put("/ops/webhook",l).then(a),delete:l=>r().delete(`/ops/webhook/${l}`).then(a),url:l=>r().get(`/ops/webhook/url/${l}`).then(a)},he={page:l=>r().post("/image/page",l).then(a),list:()=>r().get("/image/list").then(a),get:l=>r().get("/image/get",{params:{id:l}}).then(a),add:l=>r().post("/image",l).then(a),update:l=>r().post("/image/update",l).then(a),delete:l=>r().post("/image/delete",null,{params:{id:l}}).then(a),tags:l=>r().get("/image/tags",{params:{imageId:l}}).then(a),addTag:l=>r().post("/image/tag",l).then(a),deleteTag:l=>r().post("/image/tag/delete",null,{params:{id:l}}).then(a)},ge={list:l=>r().get("/ops/image-tag/list",{params:{imageId:l}}).then(a),create:l=>r().post("/ops/image-tag",l).then(a),delete:l=>r().delete(`/ops/image-tag/${l}`).then(a)},ee={page:l=>r().post("/image-build/page",l).then(a),list:()=>r().get("/image-build/list").then(a),get:l=>r().get("/image-build/get",{params:{id:l}}).then(a),add:l=>r().post("/image-build",l).then(a)},te={list:()=>r().get("/v1/bucket").then(a)},xe=()=>{const[l,p]=i.useState([]),[E,j]=i.useState(!1),[m,c]=i.useState(!1),[h,g]=i.useState(null),[f]=t.Form.useForm(),k=async()=>{j(!0);try{p(await F.list()||[])}catch{t.message.error("加载失败")}finally{j(!1)}};i.useEffect(()=>{k()},[]);const o=()=>{g(null),f.resetFields(),f.setFieldsValue({type:"k8s",status:"ENABLE",port:22}),c(!0)},I=x=>{g(x),f.setFieldsValue(x),c(!0)},C=x=>{t.Modal.confirm({title:"确认删除",content:`删除 ${x.name}?`,onOk:async()=>{await F.delete(x.id),t.message.success("删除成功"),k()}})},T=async()=>{try{const x=await f.validateFields();h?await F.update({...x,id:h.id}):await F.create(x),t.message.success(h?"更新成功":"创建成功"),c(!1),k()}catch{}},R={k8s:{text:"K8s",color:"blue"},docker:{text:"Docker",color:"cyan"},vm:{text:"VM",color:"orange"},serverless:{text:"Serverless",color:"purple"}},P=[{title:"名称",dataIndex:"name",width:150},{title:"类型",dataIndex:"type",width:110,render:x=>{const A=R[x]||{text:x,color:"default"};return e.jsx(t.Tag,{color:A.color,children:A.text})}},{title:"主机",dataIndex:"host",ellipsis:!0},{title:"端口",dataIndex:"port",width:70},{title:"命名空间",dataIndex:"namespace",width:120},{title:"状态",dataIndex:"status",width:80,render:x=>e.jsx(t.Tag,{color:x==="ENABLE"?"green":"red",children:x==="ENABLE"?"启用":"禁用"})},{title:"操作",width:160,render:(x,A)=>e.jsxs(t.Space,{size:"small",children:[e.jsx(t.Button,{size:"small",type:"text",icon:e.jsx(b.EditOutlined,{}),onClick:()=>I(A),children:"编辑"}),e.jsx(t.Button,{size:"small",type:"text",danger:!0,icon:e.jsx(b.DeleteOutlined,{}),onClick:()=>C(A),children:"删除"})]})}];return e.jsxs("div",{style:{padding:24},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:24},children:[e.jsxs("div",{children:[e.jsx("h2",{style:{margin:0,fontSize:20,fontWeight:600},children:"渠道管理"}),e.jsx("span",{style:{color:"#8a8f98",fontSize:14},children:"管理部署目标 (Kubernetes / Docker / VM)"})]}),e.jsx(t.Button,{type:"primary",icon:e.jsx(b.PlusOutlined,{}),onClick:o,children:"新建渠道"})]}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:16},children:e.jsx(t.Table,{columns:P,dataSource:l,loading:E,rowKey:"id",pagination:{pageSize:10},locale:{emptyText:"暂无部署目标"}})}),e.jsx(t.Drawer,{title:h?"编辑渠道":"新建渠道",placement:"right",width:520,open:m,onClose:()=>c(!1),children:e.jsxs(t.Form,{form:f,layout:"vertical",onFinish:T,children:[e.jsx(t.Form.Item,{name:"name",label:"名称",rules:[{required:!0}],children:e.jsx(t.Input,{placeholder:"如: prod-k8s-cluster"})}),e.jsx(t.Form.Item,{name:"type",label:"类型",rules:[{required:!0}],children:e.jsxs(t.Select,{children:[e.jsx(t.Select.Option,{value:"k8s",children:"Kubernetes"}),e.jsx(t.Select.Option,{value:"docker",children:"Docker"}),e.jsx(t.Select.Option,{value:"vm",children:"虚拟机"}),e.jsx(t.Select.Option,{value:"serverless",children:"Serverless"})]})}),e.jsxs(t.Space,{style:{display:"flex"},size:"middle",children:[e.jsx(t.Form.Item,{name:"host",label:"主机地址",rules:[{required:!0}],style:{flex:1},children:e.jsx(t.Input,{placeholder:"192.168.1.100"})}),e.jsx(t.Form.Item,{name:"port",label:"端口",children:e.jsx(t.InputNumber,{min:1,max:65535})})]}),e.jsxs(t.Space,{style:{display:"flex"},size:"middle",children:[e.jsx(t.Form.Item,{name:"username",label:"用户名",style:{flex:1},children:e.jsx(t.Input,{placeholder:"root"})}),e.jsx(t.Form.Item,{name:"namespace",label:"命名空间",style:{flex:1},children:e.jsx(t.Input,{placeholder:"default"})})]}),e.jsx(t.Form.Item,{name:"authPath",label:"密钥路径",children:e.jsx(t.Input,{placeholder:"~/.ssh/id_rsa"})}),e.jsx(t.Form.Item,{name:"description",label:"描述",children:e.jsx(t.Input.TextArea,{rows:2})}),e.jsx(t.Form.Item,{name:"status",label:"状态",initialValue:"ENABLE",children:e.jsxs(t.Select,{children:[e.jsx(t.Select.Option,{value:"ENABLE",children:"启用"}),e.jsx(t.Select.Option,{value:"DISABLE",children:"禁用"})]})}),e.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:12,paddingTop:24,borderTop:"1px solid rgba(255,255,255,0.06)"},children:[e.jsx(t.Button,{onClick:()=>c(!1),children:"取消"}),e.jsx(t.Button,{type:"primary",htmlType:"submit",children:"保存"})]})]})})]})},fe=()=>{const[l,p]=i.useState([]),[E,j]=i.useState([]),[m,c]=i.useState(!1),[h,g]=i.useState(!1),[f,k]=i.useState(null),[o]=t.Form.useForm(),I=async()=>{c(!0);try{const[u,O]=await Promise.all([z.list(),F.list()]);p(u||[]),j(O||[])}catch{t.message.error("加载失败")}finally{c(!1)}};i.useEffect(()=>{I()},[]);const C=()=>{k(null),o.resetFields(),o.setFieldsValue({status:"ENABLE",sort:0,autoDeploy:0}),g(!0)},T=u=>{k(u),o.setFieldsValue(u),g(!0)},R=u=>{t.Modal.confirm({title:"确认删除",content:`删除环境 ${u.name}?`,onOk:async()=>{await z.delete(u.id),t.message.success("删除成功"),I()}})},P=async()=>{try{const u=await o.validateFields(),O={...u,autoDeploy:u.autoDeploy?1:0};f?await z.update({...O,id:f.id}):await z.create(O),t.message.success(f?"更新成功":"创建成功"),g(!1),I()}catch{}},x=Object.fromEntries(E.map(u=>[u.id,u.name])),A=[{title:"名称",dataIndex:"name",width:120},{title:"编码",dataIndex:"code",width:100},{title:"关联渠道",dataIndex:"targetId",width:140,render:u=>x[u]||"-"},{title:"自动部署",dataIndex:"autoDeploy",width:90,render:u=>u?e.jsx(t.Tag,{color:"green",children:"是"}):e.jsx(t.Tag,{children:"否"})},{title:"描述",dataIndex:"description",ellipsis:!0},{title:"状态",dataIndex:"status",width:80,render:u=>e.jsx(t.Tag,{color:u==="ENABLE"?"green":"red",children:u==="ENABLE"?"启用":"禁用"})},{title:"操作",width:160,render:(u,O)=>e.jsxs(t.Space,{size:"small",children:[e.jsx(t.Button,{size:"small",type:"text",icon:e.jsx(b.EditOutlined,{}),onClick:()=>T(O),children:"编辑"}),e.jsx(t.Button,{size:"small",type:"text",danger:!0,icon:e.jsx(b.DeleteOutlined,{}),onClick:()=>R(O),children:"删除"})]})}];return e.jsxs("div",{style:{padding:24},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:24},children:[e.jsxs("div",{children:[e.jsx("h2",{style:{margin:0,fontSize:20,fontWeight:600},children:"环境管理"}),e.jsx("span",{style:{color:"#8a8f98",fontSize:14},children:"管理部署环境 (开发 / 测试 / 预发 / 生产)"})]}),e.jsx(t.Button,{type:"primary",icon:e.jsx(b.PlusOutlined,{}),onClick:C,children:"新建环境"})]}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:16},children:e.jsx(t.Table,{columns:A,dataSource:l,loading:m,rowKey:"id",pagination:{pageSize:10},locale:{emptyText:"暂无环境"}})}),e.jsx(t.Drawer,{title:f?"编辑环境":"新建环境",placement:"right",width:520,open:h,onClose:()=>g(!1),children:e.jsxs(t.Form,{form:o,layout:"vertical",onFinish:P,children:[e.jsxs(t.Space,{style:{display:"flex"},size:"middle",children:[e.jsx(t.Form.Item,{name:"name",label:"名称",rules:[{required:!0}],style:{flex:1},children:e.jsx(t.Input,{placeholder:"如: 生产环境"})}),e.jsx(t.Form.Item,{name:"code",label:"编码",rules:[{required:!0}],style:{flex:1},children:e.jsx(t.Input,{placeholder:"如: prod"})})]}),e.jsx(t.Form.Item,{name:"targetId",label:"关联部署渠道",children:e.jsx(t.Select,{allowClear:!0,placeholder:"选择部署目标",children:E.map(u=>e.jsx(t.Select.Option,{value:u.id,children:u.name},u.id))})}),e.jsxs(t.Space,{style:{display:"flex"},size:"middle",children:[e.jsx(t.Form.Item,{name:"sort",label:"排序",style:{flex:1},children:e.jsx(t.InputNumber,{min:0})}),e.jsx(t.Form.Item,{name:"autoDeploy",label:"自动部署",valuePropName:"checked",style:{flex:1},children:e.jsx(t.Switch,{})})]}),e.jsx(t.Form.Item,{name:"description",label:"描述",children:e.jsx(t.Input.TextArea,{rows:2})}),e.jsx(t.Form.Item,{name:"config",label:"配置 (JSON)",children:e.jsx(t.Input.TextArea,{rows:3})}),e.jsx(t.Form.Item,{name:"status",label:"状态",initialValue:"ENABLE",children:e.jsxs(t.Select,{children:[e.jsx(t.Select.Option,{value:"ENABLE",children:"启用"}),e.jsx(t.Select.Option,{value:"DISABLE",children:"禁用"})]})}),e.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:12,paddingTop:24,borderTop:"1px solid rgba(255,255,255,0.06)"},children:[e.jsx(t.Button,{onClick:()=>g(!1),children:"取消"}),e.jsx(t.Button,{type:"primary",htmlType:"submit",children:"保存"})]})]})})]})},ye=()=>{const[l,p]=i.useState([]),[E,j]=i.useState(!1),[m,c]=i.useState(!1),[h,g]=i.useState(!1),[f,k]=i.useState(null),[o,I]=i.useState(null),[C,T]=i.useState(""),[R]=t.Form.useForm(),P=async()=>{j(!0);try{p(await D.list()||[])}catch{t.message.error("加载失败")}finally{j(!1)}};i.useEffect(()=>{P()},[]);const x=()=>{k(null),R.resetFields(),R.setFieldsValue({status:"ENABLE",gitBranch:"main"}),c(!0)},A=v=>{k(v),R.setFieldsValue(v),c(!0)},u=v=>{t.Modal.confirm({title:"确认删除",content:`删除 ${v.name}?`,onOk:async()=>{await D.delete(v.id),t.message.success("删除成功"),P()}})},O=async v=>{I(v.id),g(!0),T(`正在部署...
|
|
23
|
+
`);try{const _=await D.deploy(v.id);T(_||"部署完成")}catch(_){T("部署失败: "+(_.message||"未知错误"))}finally{I(null)}},W=async()=>{try{const v=await R.validateFields();f?await D.update({...v,id:f.id}):await D.create(v),t.message.success(f?"更新成功":"创建成功"),c(!1),P()}catch{}},M=[{title:"名称",dataIndex:"name",width:150},{title:"Git 仓库",dataIndex:"gitRepo",ellipsis:!0},{title:"分支",dataIndex:"gitBranch",width:100},{title:"工作目录",dataIndex:"workDir",ellipsis:!0},{title:"状态",dataIndex:"status",width:80,render:v=>e.jsx(t.Tag,{color:v==="ENABLE"?"green":"red",children:v==="ENABLE"?"启用":"禁用"})},{title:"操作",width:240,render:(v,_)=>e.jsxs(t.Space,{size:"small",children:[e.jsx(t.Button,{size:"small",type:"text",icon:e.jsx(b.PlayCircleOutlined,{spin:o===_.id}),onClick:()=>O(_),disabled:o!==null,children:o===_.id?"部署中":"部署"}),e.jsx(t.Button,{size:"small",type:"text",icon:e.jsx(b.EditOutlined,{}),onClick:()=>A(_),children:"编辑"}),e.jsx(t.Button,{size:"small",type:"text",danger:!0,icon:e.jsx(b.DeleteOutlined,{}),onClick:()=>u(_),children:"删除"})]})}];return e.jsxs("div",{style:{padding:24},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:24},children:[e.jsxs("div",{children:[e.jsx("h2",{style:{margin:0,fontSize:20,fontWeight:600},children:"部署单元"}),e.jsx("span",{style:{color:"#8a8f98",fontSize:14},children:"管理 Git 仓库部署配置"})]}),e.jsx(t.Button,{type:"primary",icon:e.jsx(b.PlusOutlined,{}),onClick:x,children:"新建部署单元"})]}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:16},children:e.jsx(t.Table,{columns:M,dataSource:l,loading:E,rowKey:"id",pagination:{pageSize:10},locale:{emptyText:"暂无部署单元"}})}),e.jsx(t.Drawer,{title:f?"编辑部署单元":"新建部署单元",placement:"right",width:520,open:m,onClose:()=>c(!1),children:e.jsxs(t.Form,{form:R,layout:"vertical",onFinish:W,children:[e.jsx(t.Form.Item,{name:"name",label:"名称",rules:[{required:!0}],children:e.jsx(t.Input,{placeholder:"如: z-agent-backend"})}),e.jsx(t.Form.Item,{name:"description",label:"描述",children:e.jsx(t.Input.TextArea,{rows:2})}),e.jsx(t.Form.Item,{name:"status",label:"状态",initialValue:"ENABLE",children:e.jsxs(t.Select,{children:[e.jsx(t.Select.Option,{value:"ENABLE",children:"启用"}),e.jsx(t.Select.Option,{value:"DISABLE",children:"禁用"})]})}),e.jsx(t.Form.Item,{name:"gitRepo",label:"Git 仓库地址",rules:[{required:!0}],children:e.jsx(t.Input,{placeholder:"https://github.com/username/repo.git"})}),e.jsx(t.Form.Item,{name:"gitBranch",label:"分支",initialValue:"main",children:e.jsx(t.Input,{placeholder:"main"})}),e.jsx(t.Form.Item,{name:"workDir",label:"本地工作目录",rules:[{required:!0}],children:e.jsx(t.Input,{placeholder:"/deploy/my-app"})}),e.jsx(t.Form.Item,{name:"buildCommand",label:"构建命令",children:e.jsx(t.Input.TextArea,{rows:2,placeholder:"mvn clean package -DskipTests"})}),e.jsx(t.Form.Item,{name:"deployCommand",label:"部署命令",children:e.jsx(t.Input.TextArea,{rows:2})}),e.jsx(t.Form.Item,{name:"healthCheckUrl",label:"健康检查 URL",children:e.jsx(t.Input,{placeholder:"http://localhost:8080/actuator/health"})}),e.jsx(t.Form.Item,{name:"envVars",label:"环境变量 (JSON)",children:e.jsx(t.Input.TextArea,{rows:3})}),e.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:12,paddingTop:24,borderTop:"1px solid rgba(255,255,255,0.06)"},children:[e.jsx(t.Button,{onClick:()=>c(!1),children:"取消"}),e.jsx(t.Button,{type:"primary",htmlType:"submit",children:"保存"})]})]})}),e.jsx(t.Drawer,{title:e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(b.SyncOutlined,{spin:o!==null}),e.jsx("span",{children:o?"部署中":"部署完成"})]}),placement:"right",width:600,open:h,onClose:()=>g(!1),children:e.jsx("pre",{style:{background:"#0a0a0f",color:"#d0d6e0",padding:16,borderRadius:6,fontFamily:"monospace",fontSize:12,lineHeight:1.6,maxHeight:500,overflow:"auto",whiteSpace:"pre-wrap"},children:C})})]})},je=()=>{const[l,p]=i.useState([]),[E,j]=i.useState([]),[m,c]=i.useState(null),[h,g]=i.useState(!1);i.useEffect(()=>{(async()=>{try{const o=await D.list()||[];j(o),o.length&&c(o[0].id)}catch{t.message.error("加载部署单元失败")}})()},[]),i.useEffect(()=>{if(!m){p([]);return}(async()=>{g(!0);try{p(await $.list(m)||[])}catch{t.message.error("加载记录失败")}finally{g(!1)}})()},[m]);const f={SUCCESS:{text:"成功",color:"green"},FAILED:{text:"失败",color:"red"},RUNNING:{text:"运行中",color:"processing"},PENDING:{text:"等待中",color:"default"}},k=[{title:"ID",dataIndex:"id",width:60},{title:"分支",dataIndex:"branch",width:120},{title:"Commit",dataIndex:"commitId",width:100,ellipsis:!0,render:o=>o?e.jsx("code",{children:o.substring(0,8)}):"-"},{title:"部署人",dataIndex:"deployer",width:100},{title:"触发方式",dataIndex:"triggerType",width:100,render:o=>({MANUAL:"手动",WEBHOOK:"Webhook",SCHEDULE:"定时"})[o]||o||"-"},{title:"开始时间",dataIndex:"startedAt",width:170},{title:"结束时间",dataIndex:"finishedAt",width:170},{title:"状态",dataIndex:"status",width:90,render:o=>{const I=f[o]||{text:o,color:"default"};return e.jsx(t.Tag,{color:I.color,children:I.text})}},{title:"结果",dataIndex:"result",ellipsis:!0}];return e.jsxs("div",{style:{padding:24},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:24},children:[e.jsxs("div",{children:[e.jsx("h2",{style:{margin:0,fontSize:20,fontWeight:600},children:"部署记录"}),e.jsx("span",{style:{color:"#8a8f98",fontSize:14},children:"查看历史部署记录"})]}),e.jsxs(t.Space,{children:[e.jsx("span",{style:{color:"#8a8f98"},children:"部署单元:"}),e.jsx(t.Select,{value:m,onChange:c,style:{width:240},children:E.map(o=>e.jsx(t.Select.Option,{value:o.id,children:o.name},o.id))})]})]}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:16},children:e.jsx(t.Table,{columns:k,dataSource:l,loading:h,rowKey:"id",pagination:{pageSize:10},locale:{emptyText:"暂无部署记录"}})})]})},be=()=>{const[l,p]=i.useState([]),[E,j]=i.useState(!1),m=async()=>{j(!0);try{p(await te.list()||[])}catch{t.message.error("加载 Bucket 列表失败")}finally{j(!1)}};i.useEffect(()=>{m()},[]);const c=[{title:"Bucket 名称",dataIndex:"name",copyable:!0},{title:"创建时间",dataIndex:"createdAt",width:180},{title:"存储类型",dataIndex:"storageClass",width:100,render:h=>h||"标准"},{title:"区域",dataIndex:"region",width:100},{title:"对象数",dataIndex:"objectCount",width:100,render:h=>h??"-"},{title:"用量",dataIndex:"sizeGb",width:100,render:h=>h!=null?`${h} GB`:"-"}];return e.jsxs("div",{style:{padding:24},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:24},children:[e.jsxs("div",{children:[e.jsxs("h2",{style:{margin:0,fontSize:20,fontWeight:600},children:[e.jsx(b.FolderOpenOutlined,{style:{marginRight:8}}),"对象存储"]}),e.jsx("span",{style:{color:"#8a8f98",fontSize:14},children:"OSS Bucket 与文件目录"})]}),e.jsx(t.Button,{icon:e.jsx(b.ReloadOutlined,{}),onClick:m,children:"刷新"})]}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:16},children:e.jsx(t.Table,{columns:c,dataSource:l,loading:E,rowKey:"name",pagination:{pageSize:10},locale:{emptyText:"暂无 Bucket"}})})]})},Se=()=>e.jsxs("div",{style:{padding:24},children:[e.jsxs("h2",{style:{margin:"0 0 8px",fontSize:20,fontWeight:600},children:[e.jsx(b.GlobalOutlined,{style:{marginRight:8}}),"域名管理"]}),e.jsx("p",{style:{color:"#8a8f98",marginBottom:24},children:"管理已绑定的域名与 SSL 证书"}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:48},children:e.jsx(t.Empty,{description:"后端 API 开发中,敬请期待"})})]}),we=()=>e.jsxs("div",{style:{padding:24},children:[e.jsxs("h2",{style:{margin:"0 0 8px",fontSize:20,fontWeight:600},children:[e.jsx(b.DesktopOutlined,{style:{marginRight:8}}),"ECS 管理"]}),e.jsx("p",{style:{color:"#8a8f98",marginBottom:24},children:"查看与管理云服务器实例"}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:48},children:e.jsx(t.Empty,{description:"后端 API 开发中,敬请期待"})})]}),Ee=()=>e.jsxs("div",{style:{padding:24},children:[e.jsxs("h2",{style:{margin:"0 0 8px",fontSize:20,fontWeight:600},children:[e.jsx(b.DatabaseOutlined,{style:{marginRight:8}}),"RDS 管理"]}),e.jsx("p",{style:{color:"#8a8f98",marginBottom:24},children:"查看云数据库实例与慢查询监控"}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:48},children:e.jsx(t.Empty,{description:"后端 API 开发中,敬请期待"})})]}),ve=()=>{const[l,p]=i.useState([]),[E,j]=i.useState(!1),m=async()=>{j(!0);try{p(await ee.list()||[])}catch{t.message.error("加载构建记录失败")}finally{j(!1)}};i.useEffect(()=>{m()},[]);const c={SUCCESS:{text:"成功",color:"green"},FAILED:{text:"失败",color:"red"},BUILDING:{text:"构建中",color:"processing"}},h=[{title:"ID",dataIndex:"id",width:60},{title:"镜像名",dataIndex:"imageName",width:160},{title:"应用名",dataIndex:"appName",width:120},{title:"分支",dataIndex:"branch",width:120},{title:"环境",dataIndex:"env",width:80},{title:"状态",dataIndex:"status",width:90,render:g=>{const f=c[g]||{text:g,color:"default"};return e.jsx(t.Tag,{color:f.color,children:f.text})}},{title:"镜像版本",dataIndex:"imageTag",width:140,ellipsis:!0},{title:"构建时间",dataIndex:"createdAt",width:170}];return e.jsxs("div",{style:{padding:24},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:24},children:[e.jsxs("div",{children:[e.jsxs("h2",{style:{margin:0,fontSize:20,fontWeight:600},children:[e.jsx(b.ToolOutlined,{style:{marginRight:8}}),"镜像构建"]}),e.jsx("span",{style:{color:"#8a8f98",fontSize:14},children:"镜像构建记录"})]}),e.jsx(t.Button,{icon:e.jsx(b.ReloadOutlined,{}),onClick:m,children:"刷新"})]}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:16},children:e.jsx(t.Table,{columns:h,dataSource:l,loading:E,rowKey:"id",pagination:{pageSize:10},locale:{emptyText:"暂无构建记录"}})})]})},Ie=()=>e.jsxs("div",{style:{padding:24},children:[e.jsxs("h2",{style:{margin:"0 0 8px",fontSize:20,fontWeight:600},children:[e.jsx(b.CalendarOutlined,{style:{marginRight:8}}),"迭代管理"]}),e.jsx("p",{style:{color:"#8a8f98",marginBottom:24},children:"管理研发迭代与里程碑"}),e.jsx("div",{style:{background:"rgba(255,255,255,0.02)",border:"1px solid rgba(255,255,255,0.06)",borderRadius:8,padding:48},children:e.jsx(t.Empty,{description:"后端 API 开发中,敬请期待"})})]});d.ChannelPage=xe,d.DeliveryPage=fe,d.DeploymentPage=ye,d.DomainPage=Se,d.EcsPage=we,d.RdOpsPage=ve,d.RdsPage=Ee,d.ResourceOssPage=be,d.SprintPage=Ie,d.TaskPage=je,d.configureApiBaseURL=pe,d.deploymentUnitApi=D,d.environmentApi=z,d.imageApi=he,d.imageBuildApi=ee,d.imageTagApi=ge,d.ossBucketApi=te,d.recordApi=$,d.targetApi=F,d.webhookApi=me,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yuku123/z-ops-frontend-component",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "z-ops 共享前端组件 - 运维中心/资源管理/研发协同页面组件",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react",
|
|
8
|
+
"antd",
|
|
9
|
+
"ops",
|
|
10
|
+
"deployment",
|
|
11
|
+
"z-opc"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "zifang",
|
|
16
|
+
"email": "1340947819@qq.com"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/yuku123/z-opc-frontend/tree/main/packages/z-ops-frontend-component#readme",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/yuku123/z-opc-frontend.git",
|
|
22
|
+
"directory": "packages/z-ops-frontend-component"
|
|
23
|
+
},
|
|
24
|
+
"main": "./dist/z-ops-component.umd.js",
|
|
25
|
+
"module": "./dist/z-ops-component.es.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "vite",
|
|
34
|
+
"build": "vite build",
|
|
35
|
+
"preview": "vite preview"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"registry": "https://registry.npmjs.org/"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": ">=18",
|
|
43
|
+
"react-dom": ">=18",
|
|
44
|
+
"antd": ">=5",
|
|
45
|
+
"@ant-design/icons": ">=5"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
49
|
+
"vite": "^6.2.0",
|
|
50
|
+
"react": "^19.0.0",
|
|
51
|
+
"react-dom": "^19.0.0",
|
|
52
|
+
"antd": "^6.0.0",
|
|
53
|
+
"@ant-design/icons": "^6.0.0",
|
|
54
|
+
"axios": "^1.7.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* z-ops 后端 API 客户端
|
|
3
|
+
*
|
|
4
|
+
* 后端端点:
|
|
5
|
+
* - DeploymentUnitController (/api/ops/deployment)
|
|
6
|
+
* - DeploymentTargetController(/api/ops/target)
|
|
7
|
+
* - EnvironmentController (/api/ops/environment)
|
|
8
|
+
* - DeploymentRecordController(/api/ops/record)
|
|
9
|
+
* - WebhookController (/api/ops/webhook)
|
|
10
|
+
* - ImageController (/api/image)
|
|
11
|
+
* - ImageTagController (/api/ops/image-tag)
|
|
12
|
+
* - ImageBuildController (/api/image-build)
|
|
13
|
+
*
|
|
14
|
+
* 后端返回 { code, data, msg } → 解包后返回 data
|
|
15
|
+
*/
|
|
16
|
+
import axios from 'axios'
|
|
17
|
+
|
|
18
|
+
let _baseURL = '/api'
|
|
19
|
+
|
|
20
|
+
export function configureApiBaseURL(baseURL) {
|
|
21
|
+
_baseURL = baseURL || '/api'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function req() {
|
|
25
|
+
return axios.create({baseURL: _baseURL, timeout: 10000})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 解包 { code, data, msg } */
|
|
29
|
+
const unwrap = (r) => {
|
|
30
|
+
const d = r.data
|
|
31
|
+
if (d && typeof d === 'object' && 'code' in d && 'data' in d) {
|
|
32
|
+
if (d.code !== 0 && d.code !== 200) return Promise.reject(new Error(d.msg || d.message || '请求失败'))
|
|
33
|
+
return d.data
|
|
34
|
+
}
|
|
35
|
+
return d
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============ 部署单元 ============
|
|
39
|
+
export const deploymentUnitApi = {
|
|
40
|
+
list: () => req().get('/ops/deployment/list').then(unwrap),
|
|
41
|
+
get: (id) => req().get(`/ops/deployment/${id}`).then(unwrap),
|
|
42
|
+
create: (data) => req().post('/ops/deployment', data).then(unwrap),
|
|
43
|
+
update: (data) => req().put('/ops/deployment', data).then(unwrap),
|
|
44
|
+
delete: (id) => req().delete(`/ops/deployment/${id}`).then(unwrap),
|
|
45
|
+
deploy: (id) => req().post(`/ops/deployment/deploy/${id}`).then(unwrap),
|
|
46
|
+
log: (id) => req().get(`/ops/deployment/log/${id}`).then(unwrap),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ============ 部署目标 (渠道) ============
|
|
50
|
+
export const targetApi = {
|
|
51
|
+
list: () => req().get('/ops/target/list').then(unwrap),
|
|
52
|
+
get: (id) => req().get(`/ops/target/${id}`).then(unwrap),
|
|
53
|
+
create: (data) => req().post('/ops/target', data).then(unwrap),
|
|
54
|
+
update: (data) => req().put('/ops/target', data).then(unwrap),
|
|
55
|
+
delete: (id) => req().delete(`/ops/target/${id}`).then(unwrap),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============ 环境管理 ============
|
|
59
|
+
export const environmentApi = {
|
|
60
|
+
list: () => req().get('/ops/environment/list').then(unwrap),
|
|
61
|
+
get: (id) => req().get(`/ops/environment/${id}`).then(unwrap),
|
|
62
|
+
create: (data) => req().post('/ops/environment', data).then(unwrap),
|
|
63
|
+
update: (data) => req().put('/ops/environment', data).then(unwrap),
|
|
64
|
+
delete: (id) => req().delete(`/ops/environment/${id}`).then(unwrap),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============ 部署记录 ============
|
|
68
|
+
export const recordApi = {
|
|
69
|
+
list: (unitId) => req().get('/ops/record/list', {params: unitId ? {unitId} : {}}).then(unwrap),
|
|
70
|
+
get: (id) => req().get(`/ops/record/${id}`).then(unwrap),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ============ Webhook ============
|
|
74
|
+
export const webhookApi = {
|
|
75
|
+
list: (unitId) => req().get('/ops/webhook/list', {params: unitId ? {unitId} : {}}).then(unwrap),
|
|
76
|
+
create: (data) => req().post('/ops/webhook', data).then(unwrap),
|
|
77
|
+
update: (data) => req().put('/ops/webhook', data).then(unwrap),
|
|
78
|
+
delete: (id) => req().delete(`/ops/webhook/${id}`).then(unwrap),
|
|
79
|
+
url: (id) => req().get(`/ops/webhook/url/${id}`).then(unwrap),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============ 镜像管理 ============
|
|
83
|
+
export const imageApi = {
|
|
84
|
+
page: (params) => req().post('/image/page', params).then(unwrap),
|
|
85
|
+
list: () => req().get('/image/list').then(unwrap),
|
|
86
|
+
get: (id) => req().get('/image/get', {params: {id}}).then(unwrap),
|
|
87
|
+
add: (data) => req().post('/image', data).then(unwrap),
|
|
88
|
+
update: (data) => req().post('/image/update', data).then(unwrap),
|
|
89
|
+
delete: (id) => req().post('/image/delete', null, {params: {id}}).then(unwrap),
|
|
90
|
+
tags: (imageId) => req().get('/image/tags', {params: {imageId}}).then(unwrap),
|
|
91
|
+
addTag: (data) => req().post('/image/tag', data).then(unwrap),
|
|
92
|
+
deleteTag: (id) => req().post('/image/tag/delete', null, {params: {id}}).then(unwrap),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============ 镜像版本 (ImageTagController) ============
|
|
96
|
+
export const imageTagApi = {
|
|
97
|
+
list: (imageId) => req().get('/ops/image-tag/list', {params: {imageId}}).then(unwrap),
|
|
98
|
+
create: (data) => req().post('/ops/image-tag', data).then(unwrap),
|
|
99
|
+
delete: (id) => req().delete(`/ops/image-tag/${id}`).then(unwrap),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============ 镜像构建 ============
|
|
103
|
+
export const imageBuildApi = {
|
|
104
|
+
page: (params) => req().post('/image-build/page', params).then(unwrap),
|
|
105
|
+
list: () => req().get('/image-build/list').then(unwrap),
|
|
106
|
+
get: (id) => req().get('/image-build/get', {params: {id}}).then(unwrap),
|
|
107
|
+
add: (data) => req().post('/image-build', data).then(unwrap),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============ OSS (z-oss) ============
|
|
111
|
+
export const ossBucketApi = {
|
|
112
|
+
list: () => req().get('/v1/bucket').then(unwrap),
|
|
113
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 渠道管理 (DeploymentTarget CRUD)
|
|
3
|
+
*/
|
|
4
|
+
import {useEffect, useState} from 'react'
|
|
5
|
+
import {Button, Drawer, Form, Input, InputNumber, message, Modal, Select, Space, Table, Tag} from 'antd'
|
|
6
|
+
import {DeleteOutlined, EditOutlined, PlusOutlined} from '@ant-design/icons'
|
|
7
|
+
import {targetApi} from '../../api'
|
|
8
|
+
|
|
9
|
+
const ChannelPage = () => {
|
|
10
|
+
const [data, setData] = useState([])
|
|
11
|
+
const [loading, setLoading] = useState(false)
|
|
12
|
+
const [drawerVisible, setDrawerVisible] = useState(false)
|
|
13
|
+
const [editing, setEditing] = useState(null)
|
|
14
|
+
const [form] = Form.useForm()
|
|
15
|
+
|
|
16
|
+
const fetchData = async () => {
|
|
17
|
+
setLoading(true)
|
|
18
|
+
try { setData(await targetApi.list() || []) } catch { message.error('加载失败') }
|
|
19
|
+
finally { setLoading(false) }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
useEffect(() => { fetchData() }, [])
|
|
23
|
+
|
|
24
|
+
const handleAdd = () => { setEditing(null); form.resetFields(); form.setFieldsValue({type: 'k8s', status: 'ENABLE', port: 22}); setDrawerVisible(true) }
|
|
25
|
+
const handleEdit = (r) => { setEditing(r); form.setFieldsValue(r); setDrawerVisible(true) }
|
|
26
|
+
const handleDelete = (r) => { Modal.confirm({title: '确认删除', content: `删除 ${r.name}?`, onOk: async () => { await targetApi.delete(r.id); message.success('删除成功'); fetchData() }}) }
|
|
27
|
+
const handleSubmit = async () => {
|
|
28
|
+
try {
|
|
29
|
+
const v = await form.validateFields()
|
|
30
|
+
editing ? await targetApi.update({...v, id: editing.id}) : await targetApi.create(v)
|
|
31
|
+
message.success(editing ? '更新成功' : '创建成功'); setDrawerVisible(false); fetchData()
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const typeMap = {k8s: {text: 'K8s', color: 'blue'}, docker: {text: 'Docker', color: 'cyan'}, vm: {text: 'VM', color: 'orange'}, serverless: {text: 'Serverless', color: 'purple'}}
|
|
36
|
+
const columns = [
|
|
37
|
+
{title: '名称', dataIndex: 'name', width: 150},
|
|
38
|
+
{title: '类型', dataIndex: 'type', width: 110, render: v => { const t = typeMap[v] || {text: v, color: 'default'}; return <Tag color={t.color}>{t.text}</Tag> }},
|
|
39
|
+
{title: '主机', dataIndex: 'host', ellipsis: true},
|
|
40
|
+
{title: '端口', dataIndex: 'port', width: 70},
|
|
41
|
+
{title: '命名空间', dataIndex: 'namespace', width: 120},
|
|
42
|
+
{title: '状态', dataIndex: 'status', width: 80, render: v => <Tag color={v === 'ENABLE' ? 'green' : 'red'}>{v === 'ENABLE' ? '启用' : '禁用'}</Tag>},
|
|
43
|
+
{title: '操作', width: 160, render: (_, r) => (<Space size="small"><Button size="small" type="text" icon={<EditOutlined/>} onClick={() => handleEdit(r)}>编辑</Button><Button size="small" type="text" danger icon={<DeleteOutlined/>} onClick={() => handleDelete(r)}>删除</Button></Space>)}
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div style={{padding: 24}}>
|
|
48
|
+
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24}}>
|
|
49
|
+
<div><h2 style={{margin: 0, fontSize: 20, fontWeight: 600}}>渠道管理</h2><span style={{color: '#8a8f98', fontSize: 14}}>管理部署目标 (Kubernetes / Docker / VM)</span></div>
|
|
50
|
+
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd}>新建渠道</Button>
|
|
51
|
+
</div>
|
|
52
|
+
<div style={{background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 8, padding: 16}}>
|
|
53
|
+
<Table columns={columns} dataSource={data} loading={loading} rowKey="id" pagination={{pageSize: 10}} locale={{emptyText: '暂无部署目标'}}/>
|
|
54
|
+
</div>
|
|
55
|
+
<Drawer title={editing ? '编辑渠道' : '新建渠道'} placement="right" width={520} open={drawerVisible} onClose={() => setDrawerVisible(false)}>
|
|
56
|
+
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
|
57
|
+
<Form.Item name="name" label="名称" rules={[{required: true}]}><Input placeholder="如: prod-k8s-cluster"/></Form.Item>
|
|
58
|
+
<Form.Item name="type" label="类型" rules={[{required: true}]}>
|
|
59
|
+
<Select><Select.Option value="k8s">Kubernetes</Select.Option><Select.Option value="docker">Docker</Select.Option><Select.Option value="vm">虚拟机</Select.Option><Select.Option value="serverless">Serverless</Select.Option></Select>
|
|
60
|
+
</Form.Item>
|
|
61
|
+
<Space style={{display: 'flex'}} size="middle">
|
|
62
|
+
<Form.Item name="host" label="主机地址" rules={[{required: true}]} style={{flex: 1}}><Input placeholder="192.168.1.100"/></Form.Item>
|
|
63
|
+
<Form.Item name="port" label="端口"><InputNumber min={1} max={65535}/></Form.Item>
|
|
64
|
+
</Space>
|
|
65
|
+
<Space style={{display: 'flex'}} size="middle">
|
|
66
|
+
<Form.Item name="username" label="用户名" style={{flex: 1}}><Input placeholder="root"/></Form.Item>
|
|
67
|
+
<Form.Item name="namespace" label="命名空间" style={{flex: 1}}><Input placeholder="default"/></Form.Item>
|
|
68
|
+
</Space>
|
|
69
|
+
<Form.Item name="authPath" label="密钥路径"><Input placeholder="~/.ssh/id_rsa"/></Form.Item>
|
|
70
|
+
<Form.Item name="description" label="描述"><Input.TextArea rows={2}/></Form.Item>
|
|
71
|
+
<Form.Item name="status" label="状态" initialValue="ENABLE"><Select><Select.Option value="ENABLE">启用</Select.Option><Select.Option value="DISABLE">禁用</Select.Option></Select></Form.Item>
|
|
72
|
+
<div style={{display: 'flex', justifyContent: 'flex-end', gap: 12, paddingTop: 24, borderTop: '1px solid rgba(255,255,255,0.06)'}}>
|
|
73
|
+
<Button onClick={() => setDrawerVisible(false)}>取消</Button><Button type="primary" htmlType="submit">保存</Button>
|
|
74
|
+
</div>
|
|
75
|
+
</Form>
|
|
76
|
+
</Drawer>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
export default ChannelPage
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 环境管理 (Environment CRUD)
|
|
3
|
+
*/
|
|
4
|
+
import {useEffect, useState} from 'react'
|
|
5
|
+
import {Button, Drawer, Form, Input, InputNumber, message, Modal, Select, Space, Switch, Table, Tag} from 'antd'
|
|
6
|
+
import {DeleteOutlined, EditOutlined, PlusOutlined} from '@ant-design/icons'
|
|
7
|
+
import {environmentApi, targetApi} from '../../api'
|
|
8
|
+
|
|
9
|
+
const DeliveryPage = () => {
|
|
10
|
+
const [data, setData] = useState([])
|
|
11
|
+
const [targets, setTargets] = useState([])
|
|
12
|
+
const [loading, setLoading] = useState(false)
|
|
13
|
+
const [drawerVisible, setDrawerVisible] = useState(false)
|
|
14
|
+
const [editing, setEditing] = useState(null)
|
|
15
|
+
const [form] = Form.useForm()
|
|
16
|
+
|
|
17
|
+
const fetchData = async () => {
|
|
18
|
+
setLoading(true)
|
|
19
|
+
try { const [envRes, tRes] = await Promise.all([environmentApi.list(), targetApi.list()]); setData(envRes || []); setTargets(tRes || []) }
|
|
20
|
+
catch { message.error('加载失败') } finally { setLoading(false) }
|
|
21
|
+
}
|
|
22
|
+
useEffect(() => { fetchData() }, [])
|
|
23
|
+
|
|
24
|
+
const handleAdd = () => { setEditing(null); form.resetFields(); form.setFieldsValue({status: 'ENABLE', sort: 0, autoDeploy: 0}); setDrawerVisible(true) }
|
|
25
|
+
const handleEdit = (r) => { setEditing(r); form.setFieldsValue(r); setDrawerVisible(true) }
|
|
26
|
+
const handleDelete = (r) => { Modal.confirm({title: '确认删除', content: `删除环境 ${r.name}?`, onOk: async () => { await environmentApi.delete(r.id); message.success('删除成功'); fetchData() }}) }
|
|
27
|
+
const handleSubmit = async () => {
|
|
28
|
+
try {
|
|
29
|
+
const v = await form.validateFields()
|
|
30
|
+
const payload = {...v, autoDeploy: v.autoDeploy ? 1 : 0}
|
|
31
|
+
editing ? await environmentApi.update({...payload, id: editing.id}) : await environmentApi.create(payload)
|
|
32
|
+
message.success(editing ? '更新成功' : '创建成功'); setDrawerVisible(false); fetchData()
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const targetMap = Object.fromEntries(targets.map(t => [t.id, t.name]))
|
|
37
|
+
const columns = [
|
|
38
|
+
{title: '名称', dataIndex: 'name', width: 120},
|
|
39
|
+
{title: '编码', dataIndex: 'code', width: 100},
|
|
40
|
+
{title: '关联渠道', dataIndex: 'targetId', width: 140, render: v => targetMap[v] || '-'},
|
|
41
|
+
{title: '自动部署', dataIndex: 'autoDeploy', width: 90, render: v => v ? <Tag color="green">是</Tag> : <Tag>否</Tag>},
|
|
42
|
+
{title: '描述', dataIndex: 'description', ellipsis: true},
|
|
43
|
+
{title: '状态', dataIndex: 'status', width: 80, render: v => <Tag color={v === 'ENABLE' ? 'green' : 'red'}>{v === 'ENABLE' ? '启用' : '禁用'}</Tag>},
|
|
44
|
+
{title: '操作', width: 160, render: (_, r) => (<Space size="small"><Button size="small" type="text" icon={<EditOutlined/>} onClick={() => handleEdit(r)}>编辑</Button><Button size="small" type="text" danger icon={<DeleteOutlined/>} onClick={() => handleDelete(r)}>删除</Button></Space>)}
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div style={{padding: 24}}>
|
|
49
|
+
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24}}>
|
|
50
|
+
<div><h2 style={{margin: 0, fontSize: 20, fontWeight: 600}}>环境管理</h2><span style={{color: '#8a8f98', fontSize: 14}}>管理部署环境 (开发 / 测试 / 预发 / 生产)</span></div>
|
|
51
|
+
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd}>新建环境</Button>
|
|
52
|
+
</div>
|
|
53
|
+
<div style={{background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 8, padding: 16}}>
|
|
54
|
+
<Table columns={columns} dataSource={data} loading={loading} rowKey="id" pagination={{pageSize: 10}} locale={{emptyText: '暂无环境'}}/>
|
|
55
|
+
</div>
|
|
56
|
+
<Drawer title={editing ? '编辑环境' : '新建环境'} placement="right" width={520} open={drawerVisible} onClose={() => setDrawerVisible(false)}>
|
|
57
|
+
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
|
58
|
+
<Space style={{display: 'flex'}} size="middle">
|
|
59
|
+
<Form.Item name="name" label="名称" rules={[{required: true}]} style={{flex: 1}}><Input placeholder="如: 生产环境"/></Form.Item>
|
|
60
|
+
<Form.Item name="code" label="编码" rules={[{required: true}]} style={{flex: 1}}><Input placeholder="如: prod"/></Form.Item>
|
|
61
|
+
</Space>
|
|
62
|
+
<Form.Item name="targetId" label="关联部署渠道"><Select allowClear placeholder="选择部署目标">{targets.map(t => <Select.Option key={t.id} value={t.id}>{t.name}</Select.Option>)}</Select></Form.Item>
|
|
63
|
+
<Space style={{display: 'flex'}} size="middle">
|
|
64
|
+
<Form.Item name="sort" label="排序" style={{flex: 1}}><InputNumber min={0}/></Form.Item>
|
|
65
|
+
<Form.Item name="autoDeploy" label="自动部署" valuePropName="checked" style={{flex: 1}}><Switch/></Form.Item>
|
|
66
|
+
</Space>
|
|
67
|
+
<Form.Item name="description" label="描述"><Input.TextArea rows={2}/></Form.Item>
|
|
68
|
+
<Form.Item name="config" label="配置 (JSON)"><Input.TextArea rows={3}/></Form.Item>
|
|
69
|
+
<Form.Item name="status" label="状态" initialValue="ENABLE"><Select><Select.Option value="ENABLE">启用</Select.Option><Select.Option value="DISABLE">禁用</Select.Option></Select></Form.Item>
|
|
70
|
+
<div style={{display: 'flex', justifyContent: 'flex-end', gap: 12, paddingTop: 24, borderTop: '1px solid rgba(255,255,255,0.06)'}}>
|
|
71
|
+
<Button onClick={() => setDrawerVisible(false)}>取消</Button><Button type="primary" htmlType="submit">保存</Button>
|
|
72
|
+
</div>
|
|
73
|
+
</Form>
|
|
74
|
+
</Drawer>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
export default DeliveryPage
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 部署单元 (DeploymentUnit CRUD + deploy)
|
|
3
|
+
*/
|
|
4
|
+
import {useEffect, useState} from 'react'
|
|
5
|
+
import {Button, Drawer, Form, Input, message, Modal, Select, Space, Table, Tag} from 'antd'
|
|
6
|
+
import {DeleteOutlined, EditOutlined, PlayCircleOutlined, PlusOutlined, SyncOutlined} from '@ant-design/icons'
|
|
7
|
+
import {deploymentUnitApi} from '../../api'
|
|
8
|
+
|
|
9
|
+
const DeploymentPage = () => {
|
|
10
|
+
const [data, setData] = useState([])
|
|
11
|
+
const [loading, setLoading] = useState(false)
|
|
12
|
+
const [drawerVisible, setDrawerVisible] = useState(false)
|
|
13
|
+
const [logVisible, setLogVisible] = useState(false)
|
|
14
|
+
const [editing, setEditing] = useState(null)
|
|
15
|
+
const [deploying, setDeploying] = useState(null)
|
|
16
|
+
const [deployLog, setDeployLog] = useState('')
|
|
17
|
+
const [form] = Form.useForm()
|
|
18
|
+
|
|
19
|
+
const fetchData = async () => { setLoading(true); try { setData(await deploymentUnitApi.list() || []) } catch { message.error('加载失败') } finally { setLoading(false) } }
|
|
20
|
+
useEffect(() => { fetchData() }, [])
|
|
21
|
+
|
|
22
|
+
const handleAdd = () => { setEditing(null); form.resetFields(); form.setFieldsValue({status: 'ENABLE', gitBranch: 'main'}); setDrawerVisible(true) }
|
|
23
|
+
const handleEdit = (r) => { setEditing(r); form.setFieldsValue(r); setDrawerVisible(true) }
|
|
24
|
+
const handleDelete = (r) => { Modal.confirm({title: '确认删除', content: `删除 ${r.name}?`, onOk: async () => { await deploymentUnitApi.delete(r.id); message.success('删除成功'); fetchData() }}) }
|
|
25
|
+
const handleDeploy = async (r) => { setDeploying(r.id); setLogVisible(true); setDeployLog('正在部署...\n'); try { const res = await deploymentUnitApi.deploy(r.id); setDeployLog(res || '部署完成') } catch (e) { setDeployLog('部署失败: ' + (e.message || '未知错误')) } finally { setDeploying(null) } }
|
|
26
|
+
const handleSubmit = async () => {
|
|
27
|
+
try {
|
|
28
|
+
const v = await form.validateFields()
|
|
29
|
+
editing ? await deploymentUnitApi.update({...v, id: editing.id}) : await deploymentUnitApi.create(v)
|
|
30
|
+
message.success(editing ? '更新成功' : '创建成功'); setDrawerVisible(false); fetchData()
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const columns = [
|
|
35
|
+
{title: '名称', dataIndex: 'name', width: 150},
|
|
36
|
+
{title: 'Git 仓库', dataIndex: 'gitRepo', ellipsis: true},
|
|
37
|
+
{title: '分支', dataIndex: 'gitBranch', width: 100},
|
|
38
|
+
{title: '工作目录', dataIndex: 'workDir', ellipsis: true},
|
|
39
|
+
{title: '状态', dataIndex: 'status', width: 80, render: v => <Tag color={v === 'ENABLE' ? 'green' : 'red'}>{v === 'ENABLE' ? '启用' : '禁用'}</Tag>},
|
|
40
|
+
{title: '操作', width: 240, render: (_, r) => (<Space size="small">
|
|
41
|
+
<Button size="small" type="text" icon={<PlayCircleOutlined spin={deploying === r.id}/>} onClick={() => handleDeploy(r)} disabled={deploying !== null}>{deploying === r.id ? '部署中' : '部署'}</Button>
|
|
42
|
+
<Button size="small" type="text" icon={<EditOutlined/>} onClick={() => handleEdit(r)}>编辑</Button>
|
|
43
|
+
<Button size="small" type="text" danger icon={<DeleteOutlined/>} onClick={() => handleDelete(r)}>删除</Button>
|
|
44
|
+
</Space>)}
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div style={{padding: 24}}>
|
|
49
|
+
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24}}>
|
|
50
|
+
<div><h2 style={{margin: 0, fontSize: 20, fontWeight: 600}}>部署单元</h2><span style={{color: '#8a8f98', fontSize: 14}}>管理 Git 仓库部署配置</span></div>
|
|
51
|
+
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd}>新建部署单元</Button>
|
|
52
|
+
</div>
|
|
53
|
+
<div style={{background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 8, padding: 16}}>
|
|
54
|
+
<Table columns={columns} dataSource={data} loading={loading} rowKey="id" pagination={{pageSize: 10}} locale={{emptyText: '暂无部署单元'}}/>
|
|
55
|
+
</div>
|
|
56
|
+
<Drawer title={editing ? '编辑部署单元' : '新建部署单元'} placement="right" width={520} open={drawerVisible} onClose={() => setDrawerVisible(false)}>
|
|
57
|
+
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
|
58
|
+
<Form.Item name="name" label="名称" rules={[{required: true}]}><Input placeholder="如: z-agent-backend"/></Form.Item>
|
|
59
|
+
<Form.Item name="description" label="描述"><Input.TextArea rows={2}/></Form.Item>
|
|
60
|
+
<Form.Item name="status" label="状态" initialValue="ENABLE"><Select><Select.Option value="ENABLE">启用</Select.Option><Select.Option value="DISABLE">禁用</Select.Option></Select></Form.Item>
|
|
61
|
+
<Form.Item name="gitRepo" label="Git 仓库地址" rules={[{required: true}]}><Input placeholder="https://github.com/username/repo.git"/></Form.Item>
|
|
62
|
+
<Form.Item name="gitBranch" label="分支" initialValue="main"><Input placeholder="main"/></Form.Item>
|
|
63
|
+
<Form.Item name="workDir" label="本地工作目录" rules={[{required: true}]}><Input placeholder="/deploy/my-app"/></Form.Item>
|
|
64
|
+
<Form.Item name="buildCommand" label="构建命令"><Input.TextArea rows={2} placeholder="mvn clean package -DskipTests"/></Form.Item>
|
|
65
|
+
<Form.Item name="deployCommand" label="部署命令"><Input.TextArea rows={2}/></Form.Item>
|
|
66
|
+
<Form.Item name="healthCheckUrl" label="健康检查 URL"><Input placeholder="http://localhost:8080/actuator/health"/></Form.Item>
|
|
67
|
+
<Form.Item name="envVars" label="环境变量 (JSON)"><Input.TextArea rows={3}/></Form.Item>
|
|
68
|
+
<div style={{display: 'flex', justifyContent: 'flex-end', gap: 12, paddingTop: 24, borderTop: '1px solid rgba(255,255,255,0.06)'}}>
|
|
69
|
+
<Button onClick={() => setDrawerVisible(false)}>取消</Button><Button type="primary" htmlType="submit">保存</Button>
|
|
70
|
+
</div>
|
|
71
|
+
</Form>
|
|
72
|
+
</Drawer>
|
|
73
|
+
<Drawer title={<div style={{display: 'flex', alignItems: 'center', gap: 8}}><SyncOutlined spin={deploying !== null}/><span>{deploying ? '部署中' : '部署完成'}</span></div>} placement="right" width={600} open={logVisible} onClose={() => setLogVisible(false)}>
|
|
74
|
+
<pre style={{background: '#0a0a0f', color: '#d0d6e0', padding: 16, borderRadius: 6, fontFamily: 'monospace', fontSize: 12, lineHeight: 1.6, maxHeight: 500, overflow: 'auto', whiteSpace: 'pre-wrap'}}>{deployLog}</pre>
|
|
75
|
+
</Drawer>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
export default DeploymentPage
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 域名管理 (占位页面,后端 API 待开发)
|
|
3
|
+
*/
|
|
4
|
+
import {Empty} from 'antd'
|
|
5
|
+
import {GlobalOutlined} from '@ant-design/icons'
|
|
6
|
+
|
|
7
|
+
const DomainPage = () => (
|
|
8
|
+
<div style={{padding: 24}}>
|
|
9
|
+
<h2 style={{margin: '0 0 8px', fontSize: 20, fontWeight: 600}}>
|
|
10
|
+
<GlobalOutlined style={{marginRight: 8}}/>域名管理
|
|
11
|
+
</h2>
|
|
12
|
+
<p style={{color: '#8a8f98', marginBottom: 24}}>管理已绑定的域名与 SSL 证书</p>
|
|
13
|
+
<div style={{background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 8, padding: 48}}>
|
|
14
|
+
<Empty description="后端 API 开发中,敬请期待"/>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
export default DomainPage
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECS 管理 (占位页面,后端 API 待开发)
|
|
3
|
+
*/
|
|
4
|
+
import {Empty} from 'antd'
|
|
5
|
+
import {DesktopOutlined} from '@ant-design/icons'
|
|
6
|
+
|
|
7
|
+
const EcsPage = () => (
|
|
8
|
+
<div style={{padding: 24}}>
|
|
9
|
+
<h2 style={{margin: '0 0 8px', fontSize: 20, fontWeight: 600}}>
|
|
10
|
+
<DesktopOutlined style={{marginRight: 8}}/>ECS 管理
|
|
11
|
+
</h2>
|
|
12
|
+
<p style={{color: '#8a8f98', marginBottom: 24}}>查看与管理云服务器实例</p>
|
|
13
|
+
<div style={{background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 8, padding: 48}}>
|
|
14
|
+
<Empty description="后端 API 开发中,敬请期待"/>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
export default EcsPage
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 镜像构建记录
|
|
3
|
+
*/
|
|
4
|
+
import {useEffect, useState} from 'react'
|
|
5
|
+
import {Button, message, Table, Tag} from 'antd'
|
|
6
|
+
import {ToolOutlined, ReloadOutlined} from '@ant-design/icons'
|
|
7
|
+
import {imageBuildApi} from '../../api'
|
|
8
|
+
|
|
9
|
+
const RdOpsPage = () => {
|
|
10
|
+
const [data, setData] = useState([])
|
|
11
|
+
const [loading, setLoading] = useState(false)
|
|
12
|
+
const fetchData = async () => { setLoading(true); try { setData(await imageBuildApi.list() || []) } catch { message.error('加载构建记录失败') } finally { setLoading(false) } }
|
|
13
|
+
useEffect(() => { fetchData() }, [])
|
|
14
|
+
|
|
15
|
+
const statusMap = {SUCCESS: {text: '成功', color: 'green'}, FAILED: {text: '失败', color: 'red'}, BUILDING: {text: '构建中', color: 'processing'}}
|
|
16
|
+
const columns = [
|
|
17
|
+
{title: 'ID', dataIndex: 'id', width: 60},
|
|
18
|
+
{title: '镜像名', dataIndex: 'imageName', width: 160},
|
|
19
|
+
{title: '应用名', dataIndex: 'appName', width: 120},
|
|
20
|
+
{title: '分支', dataIndex: 'branch', width: 120},
|
|
21
|
+
{title: '环境', dataIndex: 'env', width: 80},
|
|
22
|
+
{title: '状态', dataIndex: 'status', width: 90, render: v => { const s = statusMap[v] || {text: v, color: 'default'}; return <Tag color={s.color}>{s.text}</Tag> }},
|
|
23
|
+
{title: '镜像版本', dataIndex: 'imageTag', width: 140, ellipsis: true},
|
|
24
|
+
{title: '构建时间', dataIndex: 'createdAt', width: 170},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div style={{padding: 24}}>
|
|
29
|
+
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24}}>
|
|
30
|
+
<div><h2 style={{margin: 0, fontSize: 20, fontWeight: 600}}><ToolOutlined style={{marginRight: 8}}/>镜像构建</h2><span style={{color: '#8a8f98', fontSize: 14}}>镜像构建记录</span></div>
|
|
31
|
+
<Button icon={<ReloadOutlined/>} onClick={fetchData}>刷新</Button>
|
|
32
|
+
</div>
|
|
33
|
+
<div style={{background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 8, padding: 16}}>
|
|
34
|
+
<Table columns={columns} dataSource={data} loading={loading} rowKey="id" pagination={{pageSize: 10}} locale={{emptyText: '暂无构建记录'}}/>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
export default RdOpsPage
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RDS 管理 (占位页面,后端 API 待开发)
|
|
3
|
+
*/
|
|
4
|
+
import {Empty} from 'antd'
|
|
5
|
+
import {DatabaseOutlined} from '@ant-design/icons'
|
|
6
|
+
|
|
7
|
+
const RdsPage = () => (
|
|
8
|
+
<div style={{padding: 24}}>
|
|
9
|
+
<h2 style={{margin: '0 0 8px', fontSize: 20, fontWeight: 600}}>
|
|
10
|
+
<DatabaseOutlined style={{marginRight: 8}}/>RDS 管理
|
|
11
|
+
</h2>
|
|
12
|
+
<p style={{color: '#8a8f98', marginBottom: 24}}>查看云数据库实例与慢查询监控</p>
|
|
13
|
+
<div style={{background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 8, padding: 48}}>
|
|
14
|
+
<Empty description="后端 API 开发中,敬请期待"/>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
export default RdsPage
|