1
0
mcy 5 жил өмнө
parent
commit
31ae308760

+ 14 - 7
canal-admin/canal-admin-server/src/main/java/com/alibaba/otter/canal/admin/controller/NodeServerController.java

@@ -21,12 +21,6 @@ public class NodeServerController {
         return BaseModel.getInstance(nodeServerService.findList(nodeServer));
     }
 
-    @DeleteMapping(value = "/nodeServer/{id}")
-    public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
-        nodeServerService.delete(id);
-        return BaseModel.getInstance("success");
-    }
-
     @PostMapping(value = "/nodeServer")
     public BaseModel<String> save(@RequestBody NodeServer nodeServer, @PathVariable String env) {
         nodeServerService.save(nodeServer);
@@ -38,8 +32,21 @@ public class NodeServerController {
         return BaseModel.getInstance(nodeServerService.detail(id));
     }
 
+    @PutMapping(value = "/nodeServer")
+    public BaseModel<String> update(@RequestBody NodeServer nodeServer, @PathVariable String env) {
+        nodeServerService.update(nodeServer);
+        return BaseModel.getInstance("success");
+    }
+
+    @DeleteMapping(value = "/nodeServer/{id}")
+    public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
+        nodeServerService.delete(id);
+        return BaseModel.getInstance("success");
+    }
+
     @GetMapping(value = "/nodeServer/status")
     public BaseModel<Integer> status(@RequestParam String ip, @RequestParam Integer port, @PathVariable String env) {
-        return BaseModel.getInstance(nodeServerService.remoteNodeStatus(ip,port));
+        return BaseModel.getInstance(nodeServerService.remoteNodeStatus(ip, port));
     }
+
 }

+ 4 - 5
canal-admin/canal-admin-server/src/main/java/com/alibaba/otter/canal/admin/service/impl/NodeServerServiceImpl.java

@@ -33,8 +33,6 @@ public class NodeServerServiceImpl implements NodeServerService {
         }
 
         nodeServer.save();
-
-        // 检测节点状态
     }
 
     public NodeServer detail(Long id) {
@@ -43,8 +41,6 @@ public class NodeServerServiceImpl implements NodeServerService {
 
     public void update(NodeServer nodeServer) {
         nodeServer.update("name", "ip", "port", "port2");
-
-        // 检测节点状态
     }
 
     public void delete(Long id) {
@@ -66,10 +62,13 @@ public class NodeServerServiceImpl implements NodeServerService {
         }
         query.order().asc("id");
         List<NodeServer> nodeServers = query.findList();
+        if (nodeServers.isEmpty()) {
+            return nodeServers;
+        }
 
         ExecutorService executorService = Executors.newFixedThreadPool(nodeServers.size());
         List<Future<Boolean>> futures = new ArrayList<>(nodeServers.size());
-        // 取每个节点的状态
+        // get all nodes status
         for (NodeServer ns : nodeServers) {
             futures.add(executorService.submit(() -> {
                 int status = -1;

BIN
canal-admin/canal-admin-ui/public/logo.png


+ 39 - 0
canal-admin/canal-admin-ui/src/api/nodeServer.js

@@ -0,0 +1,39 @@
+import request from '@/utils/request'
+
+export function getNodeServers(params) {
+  return request({
+    url: '/nodeServers',
+    method: 'get',
+    params: params
+  })
+}
+
+export function addNodeServer(data) {
+  return request({
+    url: '/nodeServer',
+    method: 'post',
+    data
+  })
+}
+
+export function nodeServerDetail(id) {
+  return request({
+    url: '/nodeServer/' + id,
+    method: 'get'
+  })
+}
+
+export function updateNodeServer(data) {
+  return request({
+    url: '/nodeServer',
+    method: 'put',
+    data
+  })
+}
+
+export function deleteNodeServer(id) {
+  return request({
+    url: '/nodeServer/' + id,
+    method: 'delete'
+  })
+}

+ 9 - 3
canal-admin/canal-admin-ui/src/router/index.js

@@ -63,16 +63,22 @@ export const constantRoutes = [
     meta: { title: 'Canal Server', icon: 'example' },
     children: [
       {
-        path: 'canalServer',
+        path: 'nodeServers',
+        name: '节点状态',
+        component: () => import('@/views/canalServer/nodeServer'),
+        meta: { title: '节点管理', icon: 'tree' }
+      },
+      {
+        path: 'config',
         name: 'Canal主配置',
         component: () => import('@/views/canalServer/config'),
         meta: { title: 'Canal主配置', icon: 'form' }
       },
       {
         path: 'tree',
-        name: 'Tree',
+        name: '实例管理',
         component: () => import('@/views/tree/index'),
-        meta: { title: 'Tree', icon: 'tree' }
+        meta: { title: '实例管理', icon: 'nested' }
       }
     ]
   },

+ 9 - 0
canal-admin/canal-admin-ui/src/styles/index.scss

@@ -63,3 +63,12 @@ div:focus {
 .app-container {
   padding: 20px;
 }
+
+.filter-container {
+  padding-bottom: 10px;
+  .filter-item {
+    display: inline-block;
+    vertical-align: middle;
+    margin-bottom: 10px;
+  }
+}

+ 2 - 2
canal-admin/canal-admin-ui/src/utils/auth.js

@@ -1,13 +1,13 @@
 import Cookies from 'js-cookie'
 
-const TokenKey = 'vue_admin_template_token'
+const TokenKey = 'canal_admin_token'
 
 export function getToken() {
   return Cookies.get(TokenKey)
 }
 
 export function setToken(token) {
-  return Cookies.set(TokenKey, token)
+  return Cookies.set(TokenKey, token, { maxAge: 0 })
 }
 
 export function removeToken() {

+ 244 - 0
canal-admin/canal-admin-ui/src/views/canalServer/nodeServer.vue

@@ -0,0 +1,244 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <!-- <el-input v-model="listQuery.name" placeholder="节点名称" style="width: 200px;" class="filter-item" />
+      <el-input v-model="listQuery.ip" placeholder="节点IP" style="width: 200px;" class="filter-item" />
+      <el-button class="filter-item" type="primary" icon="el-icon-search" plain @click="fetchData()">查询</el-button> -->
+      <el-button class="filter-item" type="primary" @click="handleCreate()">新建节点</el-button>
+      <el-button class="filter-item" type="info" @click="fetchData()">刷新节点</el-button>
+    </div>
+    <el-table
+      v-loading="listLoading"
+      :data="list"
+      element-loading-text="Loading"
+      border
+      fit
+      highlight-current-row
+    >
+      <el-table-column label="节点名称" min-width="200" align="center">
+        <template slot-scope="scope">
+          {{ scope.row.name }}
+        </template>
+      </el-table-column>
+      <el-table-column label="IP" min-width="200" align="center">
+        <template slot-scope="scope">
+          <span>{{ scope.row.ip }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="端口" min-width="100" align="center">
+        <template slot-scope="scope">
+          {{ scope.row.port }}
+        </template>
+      </el-table-column>
+      <el-table-column label="监控端口" min-width="100" align="center">
+        <template slot-scope="scope">
+          {{ scope.row.port2 }}
+        </template>
+      </el-table-column>
+      <el-table-column class-name="status-col" label="状态" min-width="150" align="center">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.status | statusFilter">{{ scope.row.status | statusLabel }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" prop="created_at" label="操作" min-width="150">
+        <template slot-scope="scope">
+          <el-dropdown trigger="click">
+            <el-button type="primary" size="mini">
+              操作<i class="el-icon-arrow-down el-icon--right" />
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item @click.native="handleUpdate(scope.row)">修改节点</el-dropdown-item>
+              <el-dropdown-item @click.native="handleDelete(scope.row)">删除节点</el-dropdown-item>
+              <el-dropdown-item @click.native="handleResume(scope.row)">启动服务</el-dropdown-item>
+              <el-dropdown-item @click.native="handleDelete(scope.row)">停止服务</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-dialog :visible.sync="dialogFormVisible" title="新建节点信息" width="600px">
+      <el-form ref="dataForm" :rules="rules" :model="nodeModel" label-position="left" label-width="80px" style="width: 350px; margin-left:80px;">
+        <el-form-item label="节点名称" prop="name">
+          <el-input v-model="nodeModel.name" />
+        </el-form-item>
+        <el-form-item label="节点IP" prop="ip">
+          <el-input v-model="nodeModel.ip" />
+        </el-form-item>
+        <el-form-item label="节点端口" prop="port">
+          <el-input v-model="nodeModel.port" placeholder="11113" type="number" />
+        </el-form-item>
+        <el-form-item label="监控端口" prop="port2">
+          <el-input v-model="nodeModel.port2" type="number" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">取消</el-button>
+        <el-button type="primary" @click="createData()">确定</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog :visible.sync="dialogFormVisible" :title="textMap[dialogStatus]" width="600px">
+      <el-form ref="dataForm" :rules="rules" :model="nodeModel" label-position="left" label-width="80px" style="width: 350px; margin-left:80px;">
+        <el-form-item label="节点名称" prop="name">
+          <el-input v-model="nodeModel.name" />
+        </el-form-item>
+        <el-form-item label="节点IP" prop="ip">
+          <el-input v-model="nodeModel.ip" />
+        </el-form-item>
+        <el-form-item label="节点端口" prop="port">
+          <el-input v-model="nodeModel.port" placeholder="11113" type="number" />
+        </el-form-item>
+        <el-form-item label="监控端口" prop="port2">
+          <el-input v-model="nodeModel.port2" type="number" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">取消</el-button>
+        <el-button type="primary" @click="dataOperation()">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { addNodeServer, getNodeServers, updateNodeServer, deleteNodeServer } from '@/api/nodeServer'
+
+export default {
+  filters: {
+    statusFilter(status) {
+      const statusMap = {
+        '1': 'success',
+        '0': 'gray',
+        '-1': 'danger'
+      }
+      return statusMap[status]
+    },
+    statusLabel(status) {
+      const statusMap = {
+        '1': '启动',
+        '0': '停止',
+        '-1': '断开'
+      }
+      return statusMap[status]
+    }
+  },
+  data() {
+    return {
+      list: null,
+      listLoading: true,
+      listQuery: {
+        name: '',
+        ip: ''
+      },
+      dialogFormVisible: false,
+      textMap: {
+        create: '新建节点信息',
+        update: '修改节点信息'
+      },
+      nodeModel: {
+        id: undefined,
+        name: null,
+        ip: null,
+        port: 11113,
+        port2: null
+      },
+      rules: {
+        name: [{ required: true, message: '节点名称不能为空', trigger: 'change' }],
+        ip: [{ required: true, message: '节点IP不能为空', trigger: 'change' }],
+        port: [{ required: true, message: '节点端口不能为空', trigger: 'change' }]
+      }
+    }
+  },
+  // { min: 2, max: 5, message: '长度在 2 到 5 个字符', trigger: 'change' }
+  created() {
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      this.listLoading = true
+      getNodeServers(this.listQuery).then(res => {
+        this.list = res.data
+        this.listLoading = false
+      })
+    },
+    resetModel() {
+      this.nodeModel = {
+        id: undefined,
+        name: null,
+        ip: null,
+        port: null,
+        port2: null
+      }
+    },
+    handleCreate() {
+      this.resetModel()
+      this.dialogStatus = 'create'
+      this.dialogFormVisible = true
+      this.$nextTick(() => {
+        this.$refs['dataForm'].clearValidate()
+      })
+    },
+    dataOperation() {
+      this.$refs['dataForm'].validate((valid) => {
+        if (valid) {
+          if (this.dialogStatus === 'create') {
+            addNodeServer(this.nodeModel).then(res => {
+              this.operationRes(res)
+            })
+          }
+          if (this.dialogStatus === 'update') {
+            updateNodeServer(this.nodeModel).then(res => {
+              this.operationRes(res)
+            })
+          }
+        }
+      })
+    },
+    operationRes(res) {
+      if (res.data === 'success') {
+        this.fetchData()
+        this.dialogFormVisible = false
+        this.$message({
+          message: this.textMap[this.dialogStatus] + '成功',
+          type: 'success'
+        })
+      } else {
+        this.$message({
+          message: this.textMap[this.dialogStatus] + '失败',
+          type: 'error'
+        })
+      }
+    },
+    handleUpdate(row) {
+      this.resetModel()
+      this.nodeModel = Object.assign({}, row)
+      this.dialogStatus = 'update'
+      this.dialogFormVisible = true
+      this.$nextTick(() => {
+        this.$refs['dataForm'].clearValidate()
+      })
+    },
+    handleDelete(row) {
+      this.$confirm('删除节点信息并不会导致节点服务停止', '确定删除节点信息', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        deleteNodeServer(row.id).then((res) => {
+          if (res.data === 'success') {
+            this.fetchData()
+            this.$message({
+              message: '删除点信息成功',
+              type: 'success'
+            })
+          } else {
+            this.$message({
+              message: '删除点信息失败',
+              type: 'error'
+            })
+          }
+        })
+      })
+    }
+  }
+}
+</script>