diff --git a/packages/core/package.json b/packages/core/package.json index 14e5b4f..a7bde51 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -25,6 +25,7 @@ }, "devDependencies": { "@types/express": "^4.17.0", + "@types/mongodb": "^3.3.13", "@types/socket.io": "^2.1.2", "nodemon": "^1.19.1", "rimraf": "^2.6.3", @@ -32,7 +33,7 @@ "typescript": "^3.5.2" }, "nodemonConfig": { - "verbose": true, + "verbose": false, "ignore": [ ".git", "public" diff --git a/packages/core/public/editor/index.html b/packages/core/public/editor/index.html new file mode 100644 index 0000000..1746454 --- /dev/null +++ b/packages/core/public/editor/index.html @@ -0,0 +1,30 @@ + + + + + + 编辑器 + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/packages/core/public/editor/index.js b/packages/core/public/editor/index.js new file mode 100644 index 0000000..a4c2d3c --- /dev/null +++ b/packages/core/public/editor/index.js @@ -0,0 +1,51 @@ +(async function() { + var amis = amisRequire('amis/embed'); + amis.embed('#root', { + definitions: { + "editor-page": { + "position": "right", + "resizable": true, + "title": `<% if (data.name) { print('页面 '+data.name+' 详情') } else { print ('新增页面') } %>`, + "size": "lg", + "body": { + "type": "form", + "submitText": '', + "title": "", + "controls": [ + { + "name": "name", + "type": "text", + }, + { + "name": "content", + "type": "editor", + "language": "json", + "editorTheme": "vs-dark", + "height": "800", + "label": false + }, + { + "type": "button", + "label": "保存", + "actionType": "ajax", + "api": { + "url": "/page/manager/${_id}", + "data": { + "name": "${name}", + "content": "${content}" + } + } + } + ] + } + } + }, + type: 'page', + title: '页面管理', + body: { + name: "editor", + type: "service", + schemaApi: "get:/page/manager/editor" + } + }); +})(); diff --git a/packages/core/public/event/index.html b/packages/core/public/event/index.html new file mode 100644 index 0000000..3f9b35d --- /dev/null +++ b/packages/core/public/event/index.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/packages/core/public/js/index.js b/packages/core/public/event/js/index.js similarity index 100% rename from packages/core/public/js/index.js rename to packages/core/public/event/js/index.js diff --git a/packages/core/public/index.html b/packages/core/public/index.html index 3f9b35d..4aafc03 100644 --- a/packages/core/public/index.html +++ b/packages/core/public/index.html @@ -1,28 +1,30 @@ - + + - - - - - - - - + + 四喜服务部署平台 + + + + -
- +
+ + \ No newline at end of file diff --git a/packages/core/public/index.js b/packages/core/public/index.js new file mode 100644 index 0000000..30cf929 --- /dev/null +++ b/packages/core/public/index.js @@ -0,0 +1,8 @@ +(async function() { + var amis = amisRequire('amis/embed'); + let page = window.location.hash.substring(1); + amis.embed('#root', { + type: "service", + schemaApi: "get:/page/manager/" + page + }); +})(); diff --git a/packages/core/resources/docker.http b/packages/core/resources/docker.http new file mode 100644 index 0000000..4730790 --- /dev/null +++ b/packages/core/resources/docker.http @@ -0,0 +1 @@ +POST https://dayu-api.miaowoo.cc/service/swirl_swirl/restart diff --git a/packages/core/src/controller/container.ts b/packages/core/src/controller/docker/container.ts similarity index 100% rename from packages/core/src/controller/container.ts rename to packages/core/src/controller/docker/container.ts diff --git a/packages/core/src/controller/docker/node.ts b/packages/core/src/controller/docker/node.ts new file mode 100644 index 0000000..f1091c1 --- /dev/null +++ b/packages/core/src/controller/docker/node.ts @@ -0,0 +1,21 @@ +import { controller, httpGet, httpPost } from 'inversify-express-utils'; +import * as docker from '@dayu/docker-api' + +@controller('/node') +class NodeController { + @httpGet('/list') + public async list() { + let nodes = await docker.node.list(); + return { + status: 0, + data: nodes.map(n => ({ + id: n.ID, + manager: n.ManagerStatus, + hostname: n.Description.Hostname, + version: n.Description.Engine.EngineVersion, + status: n.Status, + raw: JSON.stringify(n) + })) + } + } +} diff --git a/packages/core/src/controller/docker/service.ts b/packages/core/src/controller/docker/service.ts new file mode 100644 index 0000000..764e3da --- /dev/null +++ b/packages/core/src/controller/docker/service.ts @@ -0,0 +1,75 @@ +import { controller, post, get, requestParam, queryParam } from '@cc-server/binding'; +import * as docker from '@dayu/docker-api' + +@controller('/service') +class ServiceController { + @get('/list') + public async list(@queryParam('page') page: number, @queryParam('perPage') perPage: number, ) { + let services = await docker.service.list(); + return { + status: 0, + msg: '', + data: services.map(s => ({ + "service-name": s.Spec.Name, + image: s.Spec.TaskTemplate.ContainerSpec.Image.split('@')[0], + updated_at: s.UpdatedAt, + mode: s.Spec.Mode, + update_status: s.UpdateStatus + })) + }; + } + + @post('/:id/restart') + public async restart(@requestParam("id") id: string) { + let service = await docker.service.inspect(id); + service.Spec.TaskTemplate.ForceUpdate++ + return { + status: 0, + msg: '重启指令已发送', + data: docker.service.update(id, { version: service.Version.Index }, service.Spec) + } + } + + @post('/:id/rollback') + public async rollback(@requestParam("id") id: string) { + let service = await docker.service.inspect(id); + if (!service.PreviousSpec) { + return { + status: 1, + msg: '服务自启动后从未重启
无法回滚!' + } + } + return { + status: 0, + msg: '回滚指令已发送', + data: docker.service.update(id, { version: service.Version.Index, rollback: 'previous' }, service.Spec) + } + } + + @post('/delete') + public async delete() { + return { + status: 0, + msg: '删除成功!', + } + } + + + @get('/:id') + public async details(@requestParam('id') id: string) { + let service = await docker.service.inspect(id); + return { + status: 0, + data: { + id: service.ID, + name: service.Spec.Name, + env: (service.Spec.TaskTemplate.ContainerSpec.Env ?? []).map(e => { + let args = e.split('='); + return { key: args[0], value: args[1] } + }), + networks: service.Spec.TaskTemplate.Networks ?? [], + raw: JSON.stringify(service) + } + } + } +} diff --git a/packages/core/src/controller/stack.ts b/packages/core/src/controller/docker/stack.ts similarity index 87% rename from packages/core/src/controller/stack.ts rename to packages/core/src/controller/docker/stack.ts index a57f50a..23dc090 100644 --- a/packages/core/src/controller/stack.ts +++ b/packages/core/src/controller/docker/stack.ts @@ -1,11 +1,11 @@ -import { controller, httpGet, httpPost, requestParam } from 'inversify-express-utils'; +import { controller, get, post, requestParam } from '@cc-server/binding'; import * as docker from '@dayu/docker-api' const STACK_LABEL = 'com.docker.stack.namespace'; @controller('/stack') class StackController { - @httpGet('/list') + @get('/list') public async list(): Promise { let stacks: { [key: string]: string[] } = {}; let result = []; @@ -24,10 +24,13 @@ class StackController { stack.push(service.Spec.Name); } } - return result; + return { + status: 0, + data: result + }; } - @httpGet('/:stack') + @get('/:stack') public async details(@requestParam('stack') stack: string) { let filter: any = {} filter[`${STACK_LABEL}=${stack}`] = true diff --git a/packages/core/src/controller/swarm.ts b/packages/core/src/controller/docker/swarm.ts similarity index 100% rename from packages/core/src/controller/swarm.ts rename to packages/core/src/controller/docker/swarm.ts diff --git a/packages/core/src/controller/system.ts b/packages/core/src/controller/docker/system.ts similarity index 100% rename from packages/core/src/controller/system.ts rename to packages/core/src/controller/docker/system.ts diff --git a/packages/core/src/controller/docker/task.ts b/packages/core/src/controller/docker/task.ts new file mode 100644 index 0000000..2dc92dc --- /dev/null +++ b/packages/core/src/controller/docker/task.ts @@ -0,0 +1,21 @@ +import { controller, post, get, requestParam, queryParam } from '@cc-server/binding'; +import * as docker from '@dayu/docker-api' + +@controller('/task') +class TaskController { + @get('/list') + public async list(@queryParam('page') page: number, @queryParam('perPage') perPage: number, ) { + let tasks = await docker.task.list(); + return { + status: 0, + msg: '', + data: tasks.map(s => ({ + id: s.ID, + image: s.Spec.ContainerSpec.Image.split('@')[0], + status: s.Status, + updated_at: s.UpdatedAt, + raw: JSON.stringify(s), + })) + }; + } +} diff --git a/packages/core/src/controller/ext/group.ts b/packages/core/src/controller/ext/group.ts new file mode 100644 index 0000000..0668985 --- /dev/null +++ b/packages/core/src/controller/ext/group.ts @@ -0,0 +1,46 @@ +import { controller, get, post, requestParam } from '@cc-server/binding'; +import * as docker from '@dayu/docker-api' + +const GROUP_LABEL = 'pw.yumc.group.name' + +@controller('/group') +class GroupController { + @get('/list') + public async list(): Promise { + let stacks: { [key: string]: string[] } = {}; + let result = []; + let services = await docker.service.list(); + for (const service of services) { + let stackName = service.Spec.Labels[GROUP_LABEL] + if (stackName) { + let stack = stacks[stackName]; + if (!stack) { + result.push({ + name: stackName, + services: stack = [] + }) + stacks[stackName] = stack; + } + stack.push(service.Spec.Name); + } + } + return result; + } + + @get('/:name') + public async details(@requestParam('name') stack: string) { + let filter: any = {} + filter[`${GROUP_LABEL}=${stack}`] = true + let filterOpt = { + filters: JSON.stringify({ + "label": filter + }) + } + let services = await docker.service.list(filterOpt) + let networks = await docker.network.list(filterOpt) + return { + services: services.map(service => service.Spec.Name), + networks: networks.map(network => network.Name), + } + } +} \ No newline at end of file diff --git a/packages/core/src/controller/node.ts b/packages/core/src/controller/node.ts deleted file mode 100644 index b311506..0000000 --- a/packages/core/src/controller/node.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { controller, httpGet, httpPost } from 'inversify-express-utils'; -import * as docker from '@dayu/docker-api' - -@controller('/node') -class NodeController { - @httpGet('/list') - public async list() { - return await docker.node.list(); - } -} diff --git a/packages/core/src/controller/page/dashboard.ts b/packages/core/src/controller/page/dashboard.ts new file mode 100644 index 0000000..255ffc5 --- /dev/null +++ b/packages/core/src/controller/page/dashboard.ts @@ -0,0 +1,79 @@ +import { controller, get, post } from "@cc-server/binding"; + +@controller('/page') +class PageDashboardController { + @get('') + async page() { + return { + type: 'page', + title: '大禹容器管理', + body: [{ + type: "button", + label: "刷新页面", + level: "dark", + actionType: "reload", + target: "editor" + }, { + type: 'divider' + }, { + "name": "editor", + "type": "service", + "className": "m-t", + "schemaApi": "/page/editor" + }] + } + } + @post('/editor') + async editor() { + return { + status: 0, + msg: '', + data: { + "type": "form", + "initApi": "post:/page/service/list", + "title": "", + "controls": [{ + "name": "api", + "type": "editor", + "language": "json", + "label": "JSON", + }] + } + } + } + @post('/dashboard') + async dashboard() { + return { + status: 0, + msg: '', + data: { + "type": "form", + "api": "https://houtai.baidu.com/api/form/saveForm?waitSeconds=2", + "title": "常规模式", + "mode": "normal", + "controls": [{ + "type": "email", + "name": "email", + "required": true, + "placeholder": "请输入邮箱", + "label": "邮箱" + }, { + "type": + "password", + "name": "password", + "label": "密码", + "required": true, + "placeholder": "请输入密码" + }, { + "type": "checkbox", + "name": "rememberMe", + "label": "记住登录" + }, { + "type": "submit", + "btnClassName": "btn-default", + "label": "登录" + }] + } + } + } +} diff --git a/packages/core/src/controller/page/manager.ts b/packages/core/src/controller/page/manager.ts new file mode 100644 index 0000000..93af844 --- /dev/null +++ b/packages/core/src/controller/page/manager.ts @@ -0,0 +1,58 @@ +import { DBClient } from '@cc-server/db' +import { controller, get, post, httpDelete, requestParam, requestBody, BaseHttpController } from "@cc-server/binding"; +import { inject, named } from '@cc-server/ioc' + +class Page { + _id: string; + name: string; + content: string; +} + +@controller('/page/manager') +class PageManagerController extends BaseHttpController { + @inject(DBClient) + @named('page') + private client: DBClient + + @get('/list') + async list() { + let pages = await this.client.find({}) + return { + status: 0, + data: pages + } + } + + @get('/:name') + async index(@requestParam('name') name: string) { + let page = await this.client.findOne({ name }) + if (!page) { + return { + status: 404, + msg: `未找到 ${name} 的页面配置数据!` + } + } + return { + status: 0, + data: JSON.parse(page.content) + } + } + + @post('/') + async add(@requestBody() page: Page) { + let result = this.client.insertOne(page); + return { status: result ? 0 : 1, msg: result ? '插入 ' + page.name + ' 成功!' : '插入 ' + page.name + ' 失败!' }; + } + + @post('/:id') + async update(@requestParam('id') _id: string, @requestBody() page: Page) { + let result = this.client.updateById(_id, page) + return { status: result ? 0 : 1, msg: result ? '更新 ' + page.name + ' 成功!' : '更新 ' + page.name + ' 失败!' }; + } + + @httpDelete("/:id") + async delete(@requestParam('id') _id: string) { + let result = this.client.deleteById(_id) + return { status: result ? 0 : 1, msg: result ? '删除成功!' : '删除失败!' }; + } +} \ No newline at end of file diff --git a/packages/core/src/controller/page/service.ts b/packages/core/src/controller/page/service.ts new file mode 100644 index 0000000..c956f15 --- /dev/null +++ b/packages/core/src/controller/page/service.ts @@ -0,0 +1,11 @@ +import { controller, get, post } from "@cc-server/binding"; + +@controller('/page/service') +class PageServiceController { + @get('') + index() { + return { + + } + } +} diff --git a/packages/core/src/controller/service.ts b/packages/core/src/controller/service.ts deleted file mode 100644 index 5d902bc..0000000 --- a/packages/core/src/controller/service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { controller, httpGet, requestParam } from 'inversify-express-utils'; -import * as docker from '@dayu/docker-api' - -@controller('/service') -class ServiceController { - @httpGet('/list') - public async list() { - let services = await docker.service.list(); - return services.map(s => ({ - id: s.ID, - name: s.Spec.Name - })); - } - - @httpGet('/:id') - public async details(@requestParam('id') id: string) { - return await docker.service.inspect(id) - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fcc3538..225bd32 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,22 +1,53 @@ import { CcServerBoot } from '@cc-server/core' +import { getContainer } from '@cc-server/ioc' +import { DBClient } from '@cc-server/db' +import { MongoCollection } from '@cc-server/db-mongo'; +import { MongoClient, Db, Logger } from 'mongodb' import * as fs from 'fs' import * as path from 'path' //process.env.DOCKER_HOST = 'https://ndcli.yumc.pw' // process.env.DOCKER_HOST = '/var/run/docker.sock' -process.env.DOCKER_HOST = 'https://dscli.miaowoo.cc' +// process.env.DOCKER_HOST = 'https://dscli.miaowoo.cc' +// process.env.DOCKER_HOST = 'http://172.20.0.90:2378' +// process.env.DOCKER_HOST = 'https://dcli.yumc.pw' +process.env.DOCKER_HOST = 'http://172.16.200.12:8376' +let CC_MONGO_URL = process.env.CC_MONGO_URL +let CC_MONGO_DB = process.env.CC_MONGO_DB +// if (process.env.local) { +console.log("RUN AT LOCAL DOCKER!!!!!") +CC_MONGO_URL = "mongodb://192.168.2.5:27017" +CC_MONGO_DB = "dayu" +// } -let server = new CcServerBoot(); +const container = getContainer() +const server = new CcServerBoot(container); -let modulesDir = path.join(__dirname, 'controller') -let list = fs.readdirSync(modulesDir); - -for (let file of list) { - let moduleDir = path.join(modulesDir, file) - let stat = fs.statSync(moduleDir); - if (stat.isFile() && file.endsWith('.js')) { - require(moduleDir); +function injectDBClient(db: Db, table: string) { + server.container.bind(DBClient).toConstantValue(new MongoCollection(db.collection(table))).whenTargetNamed(table) +} +function requireDir(modulesDir: string) { + let list = fs.readdirSync(modulesDir); + for (let file of list) { + let moduleDir = path.join(modulesDir, file) + let stat = fs.statSync(moduleDir); + if (stat.isDirectory()) { + requireDir(moduleDir) + } else if (stat.isFile() && file.endsWith('.js')) { + require(moduleDir); + } } } -server.static().build().start(81); +requireDir(path.join(__dirname, 'controller')) + +MongoClient.connect(CC_MONGO_URL, { useNewUrlParser: true, connectTimeoutMS: 1000 }, (error, client) => { + if (error) { + console.log(error) + } else { + let db = client.db(CC_MONGO_DB); + // Logger.setLevel('debug'); + injectDBClient(db, "page"); + server.start(81) + } +}) diff --git a/packages/docker-api/src/api/types/service.ts b/packages/docker-api/src/api/types/service.ts index aa72ccf..5e23728 100644 --- a/packages/docker-api/src/api/types/service.ts +++ b/packages/docker-api/src/api/types/service.ts @@ -181,6 +181,13 @@ export declare namespace service { RollbackConfig: RollbackConfig; } + export interface UpdateStatus { + State: string; + StartedAt: string; + CompletedAt: string; + Message: string; + } + export interface Service { ID: string; Version: Version; @@ -189,7 +196,7 @@ export declare namespace service { Spec: Spec; Endpoint: Endpoint; PreviousSpec: PreviousSpec; + UpdateStatus: UpdateStatus; } - } diff --git a/packages/docker-api/src/client/service.ts b/packages/docker-api/src/client/service.ts index fb728ff..a4d4d9d 100644 --- a/packages/docker-api/src/client/service.ts +++ b/packages/docker-api/src/client/service.ts @@ -10,6 +10,9 @@ export namespace service { } export async function inspect(id: string, query: { insertDefaults: boolean } = { insertDefaults: false }) { - return await api.get(`/services/${id}`, query); + return await api.get(`/services/${id}`, query); + } + export async function update(id: string, query: { version: number, registryAuthFrom?: string, rollback?: string }, data: any) { + return await api.post(api.getUri(`/services/${id}/update`, query), data) } } diff --git a/packages/docker-api/src/utils/api.ts b/packages/docker-api/src/utils/api.ts index 700ae4b..ee3870d 100644 --- a/packages/docker-api/src/utils/api.ts +++ b/packages/docker-api/src/utils/api.ts @@ -19,6 +19,13 @@ export async function stream(path: string, data?: objec return await handle("GET", path, { params: data, responseType: "stream" }); } +export function getUri(path: string, data?: object) { + return api.getUri({ + url: path, + params: data + }) +} + async function handle(method: Method, path: string, reqConfig?: AxiosRequestConfig): Promise { let config: AxiosRequestConfig = { method, diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 1bd7017..68ebcac 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -11,7 +11,8 @@ "Other" ], "activationEvents": [ - "*" + "onView:docker-explorer", + "onView:openfaas-explorer" ], "main": "./dist/extension.js", "contributes": { @@ -60,8 +61,8 @@ "vscode:prepublish": "yarn run compile", "compile": "tsc -p ./", "watch": "tsc -watch -p ./", - "postinstall": "node ../../../node_modules/vscode/bin/install", - "test": "yarn run compile && node ../../../node_modules/vscode/bin/test" + "postinstall": "node ../../node_modules/vscode/bin/install", + "test": "yarn run compile && node ../../node_modules/vscode/bin/test" }, "dependencies": { "@dayu/docker-api": "^0.0.1", diff --git a/tsconfig.json b/tsconfig.json index f754065..c3d49b0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "module": "commonjs", "sourceMap": true, "declaration": true, + "declarationMap": true, "noImplicitAny": true, "allowUnreachableCode": true, "experimentalDecorators": true,