This commit is contained in:
xuziqiang
2024-05-15 17:29:42 +08:00
commit d0155dbe3c
7296 changed files with 1832517 additions and 0 deletions

8
nervui-slb/.env Normal file
View File

@@ -0,0 +1,8 @@
# port
VITE_PORT = 5000
# spa-title
VITE_GLOB_APP_TITLE = nervui-slb
# spa shortname
VITE_GLOB_APP_SHORT_NAME = nervui-slb

View File

@@ -0,0 +1,20 @@
# Whether to open mock
VITE_USE_MOCK = true
# public path
VITE_PUBLIC_PATH = /nervui-slb/
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
# Delete console
VITE_DROP_CONSOLE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
# File upload address optional
VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

View File

@@ -0,0 +1,35 @@
# Whether to open mock
VITE_USE_MOCK = true
# public path
VITE_PUBLIC_PATH = /nervui-slb/
# Delete console
VITE_DROP_CONSOLE = true
# Whether to enable gzip or brotli compression
# Optional: gzip | brotli | none
# If you need multiple forms, you can use `,` to separate
VITE_BUILD_COMPRESS = 'none'
# Whether to delete origin files when using compress, default false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
# File upload address optional
# It can be forwarded by nginx or write the actual address directly
VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
# Whether to enable image compression
VITE_USE_IMAGEMIN= true
# use pwa
VITE_USE_PWA = false
# Is it compatible with older browsers
VITE_LEGACY = false

1
nervui-slb/.version Normal file
View File

@@ -0,0 +1 @@
1.0.34

74
nervui-slb/build.sh Normal file
View File

@@ -0,0 +1,74 @@
#!/bin/bash
SOURCE="$0"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
if [ -z $WORKSPACE ];then
echo "WORKSPACE not exists"
else
set DIR=$WORKSPACE
fi
echo "current dir"
echo "$DIR"
cd "$DIR"
projectname=$(basename `pwd`)
npm run slb-build
if [ -d "$DIR/dist" ];then
cd "$DIR/dist"
# copy module.json
cp ../module.json ./
# package
VERSION=$(cat ../.version)
lastdir=../release/
if [ -d ${lastdir} ];then
echo "删除旧release文件夹"
rm -rf ${lastdir}
else
echo "文件夹不存在!"
fi
mkdir -p ${lastdir}
dir=../release/nerv/$projectname/$VERSION
mkdir -p ${dir}
tar -zcvf "${dir}/$projectname-$VERSION.tgz" ./*
templatedir=../release/resources/templates/nerv/$projectname/$VERSION/$projectname
mkdir -p ${templatedir}
cp -r ../resources/templates/* ${templatedir}
cd ../
releasefile=nerv-$projectname-$VERSION.tgz
if [ -f ${releasefile} ];then
echo "删除旧包!"
rm -rf ${releasefile}
fi
tar -zcvf ${releasefile} ./release/* release.yaml
mkdir -p ./release/nervui
cp -r ./release/nerv/* ./release/nervui
if [ -f ${releasefile} ];then
echo "编译成功!"
mv ${releasefile} ./release
else
echo "编译失败!!!"
exit 1
fi
else
echo "编译失败!!!"
exit 1
fi

13
nervui-slb/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon-stack.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>负载均衡</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>

77
nervui-slb/module.json Normal file
View File

@@ -0,0 +1,77 @@
[
{
"catalog": "网络",
"icon": "&#xe702;",
"name": "slb",
"label": "负载均衡",
"menus": [
{
"name": "listener",
"label": "监听器",
"url": "/slb/listener",
"operation": {
"resource": "listener",
"method": "list"
},
"submenus": [
{
"label": "管理",
"notResource": true,
"isOperation": true,
"operation": {
"resource": "manage",
"method": "list"
},
"submenus": [
{
"label": "基本信息",
"operation": {
"method": "list",
"resource": "listenerBasicInfo"
},
"notResource": true,
"submenus":[]
},
{
"label": "后端服务器",
"operation": {
"method": "list",
"resource": "listenerRealServer"
}
}
]
}
]
},
{
"label": "后端服务器组",
"name": "realServerGroup",
"url": "/slb/realServerGroup",
"operation": {
"method": "list",
"resource": "listener"
},
"submenus": [{
"label": "管理",
"notResource": true,
"isOperation": true,
"operation": {
"resource": "manage",
"method": "list"
},
"submenus": [
{
"label": "后端服务器",
"operation": {
"method": "list",
"resource": "realServer"
},
"submenus":[]
}
]
}]
}
]
}
]

16
nervui-slb/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "nerv-lib",
"version": "1.0.27",
"files": [
"dist"
],
"scripts": {
"start": "vite serve --config ./vite.config.ts",
"build": "cross-env NODE_ENV=production vite build",
"dev": "vite",
"lint:eslint-fix": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"serve": "vite preview"
},
"dependencies": {},
"devDependencies": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

12
nervui-slb/release.yaml Normal file
View File

@@ -0,0 +1,12 @@
# 上传release目录信息到nerv-file仓库
release:
- {src: release, dest: /upload/pkg, include: [".*(.tgz)$"]}
- {src: release/resources/templates, dest: /upload/templates}
register:
name: nervui-slb
version: 1.0.34
components:
- type: nervui-slb
resources:
- {type: template, relativePath: /nervui-slb/deploy.json}

18
nervui-slb/src/App.vue Normal file
View File

@@ -0,0 +1,18 @@
<template>
<ns-application />
</template>
<script lang="ts" type="module">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
});
</script>
<style>
#app {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,50 @@
/***
*配置接口 格式 module:Array<resource>
*/
export const apiModule = {
pension: [
'User',
'CurrentUser',
'Gaffer',
// 'Organizational',
// 'Device',
// 'Region',
// 'Owner',
// 'AllList',
// 'VisitorRecord',
// 'Room',
// 'Device',
// 'CommunityVisitor',
// 'AccessControlGroup',
// 'AccessControlLeft',
// 'AccessControlRight',
// 'AccessControlRightIdentity',
// 'Visitor',
// 'DeviceListByDeviceCategory',
// 'Perimeter',
// 'AccessControlRightVisitorIdentity',
// 'BedChoose',
// 'Gateway',
// 'FileUpload',
// 'User',
// 'CurrentUser',
// 'Organizational',
// 'Device',
// 'Region',
// 'Owner',
// 'AllList',
// 'VisitorRecord',
// 'Room',
// 'Device',
// 'CommunityVisitor',
// 'AccessControlGroup',
// 'AccessControlLeft',
// 'AccessControlRight',
// 'AccessControlRightIdentity',
// 'Visitor',
// 'DeviceListByDeviceCategory',
// 'BedChoose',
// 'Perimeter',
// 'OrganizationConfig',
],
};

View File

@@ -0,0 +1,15 @@
import { http } from '/nerv-lib/paas';
enum Api {
USER_LOGIN = '/api/passport/objs/login', //用户登录
USER_INFO = '/api/webui/webui/objs/PassportUserInfo', //获取用户信息
}
export const userLogin = (data: RoomListModel) => http.post(Api.USER_LOGIN, data);
export const userInfo = () => http.get(Api.USER_INFO);
/**
* @description 用户登录
* @property `[fatherRegionUuid]` 父级区域唯一标识
*/
interface RoomListModel {
data: string;
}

View File

@@ -0,0 +1,4 @@
export const appConfig = {
projectType: 'web',
timeout: 10 * 1000,
};

View File

@@ -0,0 +1,2 @@
import { appConfig } from '/@/config/app.config';
export { appConfig };

17
nervui-slb/src/main.ts Normal file
View File

@@ -0,0 +1,17 @@
import { createApp } from 'vue';
import App from '/@/App.vue';
import { paasInit } from '/nerv-lib/paas';
import { apiModule } from '/@/api';
import { appConfig } from '/@/config';
import '/@/theme/theme.scss';
// const modules = import.meta.globEager('/src/router/**/*.ts');
const modules = undefined;
const app = createApp(App);
paasInit({
app,
apiModule,
appConfig,
modules,
});
app.mount('#app');

View File

@@ -0,0 +1,35 @@
echo "=====================================================create====================================================="
#!/usr/bin/env bash
function create() {
if [ -d "$nervui_app_home" ];then
echo "$nervui_app_home exists!"
else
echo "start mkdir $nervui_app_home"
mkdir -p "$nervui_app_home"
fi
pkg_file_name=${pkg_url##*/}
pkg_file_path="$nervui_app_home$pkg_file_name"
echo "start download $pkg_url"
curl -L -o $pkg_file_path $pkg_url
echo "start install $pkg_file_path"
tar -xf $pkg_file_path -C $nervui_app_home
}
if [ "$pkg_url" == "" ]; then
echo {\"error\":\"pkg_url is empty\"}
exit 1
elif [ "$nervui_app_home" == "" ]; then
echo {\"error\":\"nervui_app_home is empty\"}
exit 1
else
create
fi

View File

@@ -0,0 +1 @@
echo "=====================================================delete====================================================="

View File

@@ -0,0 +1 @@
echo "=====================================================setup====================================================="

View File

@@ -0,0 +1 @@
echo "=====================================================start====================================================="

View File

@@ -0,0 +1 @@
echo "=====================================================stop====================================================="

View File

@@ -0,0 +1,30 @@
{
"name":"/nervui/nervui-gateway-api",
"operations": [
{
"name": "Create",
"type": "shell",
"implementor": "create.sh"
},
{
"name": "Delete",
"type": "shell",
"implementor": "delete.sh"
},
{
"name": "Setup",
"type": "shell",
"implementor": "setup.sh"
},
{
"name": "Start",
"type": "shell",
"implementor": "start.sh"
},
{
"name": "Stop",
"type": "shell",
"implementor": "stop.sh"
}
]
}

View File

@@ -0,0 +1,64 @@
{
"name": "/nervui/nervui-gateway-api",
"version": 1,
"inputs": [
{
"name": "server_ip",
"type": "string",
"required":true,
"description":"应用安装IP地址",
"inputType":"ipSelectType"
},
{
"name": "version",
"type": "string",
"required":true,
"description":"软件版本",
"inputType":"versionSelectType"
},
{
"name": "install_dir",
"type": "string",
"required": true,
"defaultValue": "/data",
"inputType": "textInputType",
"description": "安装目录"
}
],
"nodes": [
{
"name": "nervui-gateway-api",
"type": "/nerv/nerv-orchestrator/cluster/Nervui",
"parameters": [
{
"name": "file_repository",
"value": "${nerv_file_repository}"
},
{
"name": "install_dir",
"value": "${install_dir}"
},
{
"name": "pkg_url",
"value": "/api/pkg/nerv/nervui-gateway-api/${version}/nervui-gateway-api-${version}.tgz"
}
],
"dependencies": [
{
"type": "contained",
"target": "host"
}
]
},
{
"name": "host",
"type": "/nerv/nerv-orchestrator/compute/Host",
"parameters": [
{
"name": "address",
"value": "${server_ip}"
}
]
}
]
}

View File

@@ -0,0 +1,9 @@
const RootRoute = {
path: '/root',
name: 'root',
redirect: '/slb',
meta: {
title: 'Root',
},
};
export default RootRoute;

View File

@@ -0,0 +1,385 @@
/** @format */
import { NsViewContent, NsViewDetail, NsViewSideNav } from '/nerv-lib/paas';
const SideNav = () => Promise.resolve(NsViewSideNav);
const Base = () => Promise.resolve(NsViewContent);
const Detail = () => Promise.resolve(NsViewDetail);
const slb = {
path: '/slb',
name: 'slb',
component: SideNav,
redirect: { name: 'listener' },
meta: {
sideMenus: {
title: '负载均衡',
name: 'slb',
menus: [
{
name: 'listener',
label: '监听器',
url: '/slb/listener',
module: 'vm',
},
{
name: 'realServerGroup',
label: '后端服务组',
url: '/slb/realServerGroup',
module: 'vm',
},
],
},
},
children: [
{
path: 'listener',
name: 'listenerModule',
component: Base,
redirect: { name: 'listener' },
children: [
//监听器列表
{
path: 'list',
name: 'listener',
component: () => import('/@/view/listenerManage/list.vue'),
},
//新增监听器
{
path: 'add',
name: 'listenerAdd',
component: () => import('/@/view/listenerManage/add.vue'),
},
//编辑监听器
{
path: 'edit/:ID',
name: 'listenerEdit',
component: () => import('/@/view/listenerManage/edit.vue'),
},
//监听器管理模块
{
path: 'manage/:ID/:rsgID/:pageTitle',
name: 'listenerManage',
component: Base,
meta: {
sideMenus: {
title: '管理',
name: 'listenerManage',
backTo: 'listener',
menus: [
{
name: 'listenerBasicInfo',
label: '基本信息',
url: 'listenerBasicInfo',
module: 'vm',
},
{
name: 'listenerRealServer',
label: '后端服务器',
url: 'listenerRealServer',
module: 'list',
},
],
},
},
children: [
//监听器基本信息
{
path: 'listenerBasicInfo',
name: 'listenerBasicInfo',
component: Detail,
props: {
title: 'pageTitle',
api: '/api/slb/objs/Listener/:ID',
detail: [
{
title: '监听器',
items: [
{
label: '项目',
name: 'projectName',
},
{
label: '名称',
name: 'name',
},
{
label: 'slb集群项目',
name: 'clusterProjectName',
},
{
label: 'slb集群',
name: 'clusterName',
},
{
label: '前端协议',
name: 'frontEndProtocol',
},
{
label: '端口',
name: 'frontEndPort',
},
{
label: '证书域名',
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
name: 'domain',
},
{
label: '安全类型',
name: 'securityType',
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
format: (value: any) => {
switch (value) {
case 'TLS_1_0':
return '安全策略TLS-1-0';
break;
case 'TLS_1_1':
return '安全策略TLS-1-1';
break;
case 'TLS_1_2':
return '安全策略TLS-1-2';
break;
case 'TLS_1_2_Strict':
return '安全策略TLS-1-2-Stric';
break;
default:
return '';
}
},
},
{
label: '双向认证',
name: 'isMutualAuth',
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
format: (value: any) => {
if (value) {
return '是';
} else {
return '否';
}
},
},
{
label: '监听器模式',
name: 'mode',
format: (value: any) => {
if (value === 'master-slave') {
return '主从模式';
} else {
return '负载模式';
}
},
},
{
label: '分配VIP',
name: 'isNeedVIP',
ifShow: (record: any) => {
return record.mode === 'master-slave';
},
format: (value: any) => {
if (value) {
return '是';
} else {
return '否';
}
},
},
{
label: '转发方式',
name: 'trafficDistrStrategy',
format: (value: any) => {
switch (value) {
case 'rr':
return '轮询';
break;
case 'wrr':
return '加权轮询';
break;
case 'lc':
return '最少链接';
break;
case 'wlc':
return '加权最少链接';
break;
default:
return '';
}
},
},
{
label: '描述 ',
name: 'description',
},
],
},
{
title: '后端服务器',
items: [
{
label: '后端服务器组',
name: 'rsgName',
},
{
label: '后端协议',
name: 'backEndProtocol',
},
{
label: '后端端口',
name: 'backEndPort',
},
],
},
{
title: '健康检查',
items: [
{
label: '是否开启',
name: 'isHealthCheck',
format: (value: any) => {
if (value) {
return '是';
} else {
return '否';
}
},
},
{
label: '协议',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthProtocol',
},
{
label: '端口',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthPort',
},
{
label: '检查间隔(s)',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthInterval',
},
{
label: '超时间隔(s)',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthTimeOut',
},
{
label: '健康检查URL',
ifShow: (record: any) => {
return record.isHealthCheck && record.healthProtocol === 'http';
},
name: 'healthUrl',
},
{
label: '最大重复次数',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthFailTimes',
},
],
},
],
},
},
//后端服务器
{
path: 'listenerRealServer',
name: 'listenerRealServer',
component: () => import('/@/view/listenerManage/listenerRealServer.vue'),
},
],
},
],
},
//后端服务器组管理
{
path: 'realServerGroup',
name: 'realServerGroupModule',
component: Base,
children: [
//后端服务器组列表
{
path: 'list',
name: 'realServerGroup',
component: () => import('/@/view/realServerGroup/list.vue'),
},
//新增后端服务器组
{
path: 'add',
name: 'realServerGroupAdd',
component: () => import('/@/view/realServerGroup/add.vue'),
},
//编辑后端服务器组
{
path: 'edit/:ID',
name: 'realServerGroupEdit',
component: () => import('/@/view/realServerGroup/edit.vue'),
},
//后端服务器组 的管理模块
{
path: 'manage/:ID/:projectName/:pageTitle',
name: 'realServerGroupManage',
component: Base,
meta: {
sideMenus: {
title: '管理',
name: 'realServerGroupManage',
backTo: 'realServerGroup',
menus: [
{
name: 'realServer',
label: '后端服务器',
url: 'realServer',
module: 'vm',
},
],
},
},
children: [
{
path: 'realServer',
name: 'realServerModule',
component: Base,
redirect: { name: 'realServer' },
children: [
//后端服务器列表
{
path: 'list',
name: 'realServer',
component: () => import('/@/view/realServerGroup/realServerManage/list.vue'),
},
//新增后端服务器
{
path: 'add',
name: 'realServerAdd',
component: () => import('/@/view/realServerGroup/realServerManage/add.vue'),
},
{
path: 'edit/:editId/:ip',
name: 'realServerEdit',
component: () => import('/@/view/realServerGroup/realServerManage/edit.vue'),
},
],
},
],
},
],
},
],
};
export default slb;

View File

View File

@@ -0,0 +1,2 @@
@import "variable";
@import "global";

View File

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,349 @@
<!-- @format -->
<template>
<div class="ns-detail">
<div v-for="(detailGroup, index) in detail" :key="index">
<a-descriptions :title="detailGroup.title" class="detail_item">
<template v-if="detailGroup.items">
<template v-for="(item, index) in detailGroup.items" :key="index">
<a-descriptions-item
v-if="item.ifShow ? item.ifShow(dataRef) : !item.ifShow"
:label="item.label">
<template v-if="!item.type">
<span>{{
item.format ? item.format(dataRef[item.name]) : dataRef[item.name]
}}</span>
</template>
<template v-else-if="item.type === 'image'">
<a-image
:width="100"
:src="item.value"
fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==" />
</template>
<template v-else-if="item.type === 'table'">
<ns-basic-table
class="ns-detail-table"
:dataSource="dataRef[item.props.listField]"
:columns="item.props.columns"
:pagination="false"
:rowKey="item.props.rowKey" />
</template>
</a-descriptions-item>
</template>
</template>
</a-descriptions>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, unref } from 'vue';
export default defineComponent({
name: 'ApiCusDetail',
props: {
dataSource: {
type: Object,
default: () => {},
},
},
setup(props, context) {
const getDetail = ref('');
const dataRef = unref(props.dataSource);
const detail = [
{
title: '监听器',
items: [
{
label: '项目',
name: 'projectName',
},
{
label: '名称',
name: 'name',
},
{
label: 'slb集群项目',
name: 'clusterProjectName',
},
{
label: 'slb集群',
name: 'clusterName',
},
{
label: '前端协议',
name: 'frontEndProtocol',
},
{
label: '端口',
name: 'frontEndPort',
},
{
label: '证书域名',
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
name: 'domain',
},
{
label: '安全类型',
name: 'securityType',
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
format: (value: any) => {
switch (value) {
case 'TLS_1_0':
return '安全策略TLS-1-0';
break;
case 'TLS_1_1':
return '安全策略TLS-1-1';
break;
case 'TLS_1_2':
return '安全策略TLS-1-2';
break;
case 'TLS_1_2_Strict':
return '安全策略TLS-1-2-Stric';
break;
default:
return '';
}
},
},
{
label: '双向认证',
name: 'isMutualAuth',
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
format: (value: any) => {
if (value) {
return '是';
} else {
return '否';
}
},
},
{
label: '监听器模式',
name: 'mode',
format: (value: any) => {
if (value === 'master-slave') {
return '主从模式';
} else {
return '负载模式';
}
},
},
{
label: '分配VIP',
name: 'isNeedVIP',
ifShow: (record: any) => {
return record.mode === 'master-slave';
},
format: (value: any) => {
if (value) {
return '是';
} else {
return '否';
}
},
},
{
label: '转发方式',
name: 'trafficDistrStrategy',
format: (value: any) => {
switch (value) {
case 'rr':
return '轮询';
break;
case 'wrr':
return '加权轮询';
break;
case 'lc':
return '最少链接';
break;
case 'wlc':
return '加权最少链接';
break;
default:
return '';
}
},
},
{
label: '描述 ',
name: 'description',
},
],
},
{
title: '后端服务器',
items: [
{
label: '后端服务器组',
name: 'rsgName',
},
{
label: '后端协议',
name: 'backEndProtocol',
},
{
label: '后端端口',
name: 'backEndPort',
},
],
},
{
title: '健康检查',
items: [
{
label: '是否开启',
name: 'isHealthCheck',
format: (value: any) => {
if (value) {
return '是';
} else {
return '否';
}
},
},
{
label: '协议',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthProtocol',
},
{
label: '端口',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthPort',
},
{
label: '检查间隔(s)',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthInterval',
},
{
label: '超时间隔(s)',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthTimeOut',
},
{
label: '健康检查URL',
ifShow: (record: any) => {
return record.isHealthCheck && record.healthProtocol === 'http';
},
name: 'healthUrl',
},
{
label: '最大重复次数',
ifShow: (record: any) => {
return record.isHealthCheck;
},
name: 'healthFailTimes',
},
],
},
];
return {
dataRef,
detail,
getDetail,
};
},
});
</script>
<style lang="less" scoped>
// .detail_item {
// border-bottom: 1px solid #c9d8db;
// }
.ant-page-header {
padding: 0 24px;
margin-bottom: 15px;
}
// :deep(.ant-page-header-heading-extra) {
// margin-right: auto !important;
// margin-left: 0;
// }
:deep(.ant-skeleton-paragraph) {
display: inline;
// display: flex;
// flex-wrap: wrap;
}
:deep(.ant-skeleton-paragraph li:nth-child(n)) {
float: left;
display: inline;
margin-right: 130px;
margin-top: 16px;
margin-bottom: 4px;
}
:deep(.ant-skeleton-paragraph li:nth-child(3n + 1)) {
clear: both;
display: inline;
}
:deep(.ant-skeleton-content) {
padding: 0 8px 10px 10px;
}
.ns-detail {
padding: 0 24px 24px 24px;
}
.ns-detail-table {
width: 100%;
}
:deep(.ant-table-wrapper) {
padding: 0;
}
:deep(.ant-tabs-tab) {
padding: 8px 20px 8px 20px;
margin-right: 5px !important;
}
.step {
width: 100%;
min-height: 200px;
margin-bottom: 16px;
border: 1px solid rgba(232, 232, 232, 1);
border-radius: 5px;
.step-header {
height: 50px;
width: 100%;
font-size: 13px;
line-height: 50px;
padding-left: 20px;
background: rgba(242, 242, 242, 1);
}
.content {
width: 100%;
padding: 15px;
.content-item {
width: 100%;
span {
width: 100%;
}
}
}
}
.FCKEditor {
width: 100%;
max-height: 300px;
overflow: auto;
padding: 20px;
border: 1px solid #c9d8db;
}
</style>

View File

@@ -0,0 +1,28 @@
<template>
<div style="margin-bottom: 20px">
<a-steps :current="current">
<a-step title="配置监听器" />
<a-step title="配置后端服务器" />
<a-step title="健康检查" />
<a-step title="配置审核" />
</a-steps>
</div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsStep',
props: {
current: {
type: Number,
default: 0,
},
stepList: {
type: Array,
default: () => [],
},
},
setup() {},
});
</script>

View File

@@ -0,0 +1,877 @@
<!-- @format -->
<template>
<div>
<page-title title="编辑" />
<a-page-header>
<template #extra>
<ns-button @click="navigateBack">返回</ns-button>
</template>
</a-page-header>
<NsStep
:current="data.current"
style="width: 96%; margin: auto; margin-left: 24px; min-width: 600px" />
<ns-form
v-if="isEdit"
style="margin-top: 30px; margin-left: 24px"
ref="mainRef"
formLayout="修改"
:schemas="formSchema"
:model="data"
v-bind="$attrs" />
<ApiCusDetail v-if="data.current === 3" :dataSource="data" />
<div class="floor_button">
<div class="submit">
<ns-button @click="backStep" v-if="data.current">上一步</ns-button>
<ns-button @click="stepSubmit" :disabled="!isDisable" type="primary">{{
data.current === 3 ? '保存' : '下一步'
}}</ns-button>
</div>
</div>
</div>
</template>
<script lang="ts">
// import { useNavigate } from '/nerv-lib/paas';
import ApiCusDetail from './component/detail.vue';
import { http } from '/nerv-lib/paas';
import { useRouter } from 'vue-router';
import { NsMessage } from '/nerv-lib/paas';
import { defineComponent, reactive, ref, provide, watch, computed } from 'vue';
import NsStep from './component/step.vue';
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
interface listenerInfo {
current: number;
/******************配置监听器 *******************/
ID: number;
name: string;
projectName?: string;
clusterProjectName: string;
clusterName: string;
description: string; //描述
frontEndPort: number; //前端端口
frontEndProtocol: string;
domain?: string; //域名
securityType?: string; //安全策略
mode: string;
isNeedVIP?: boolean; //是否需要vip
trafficDistrStrategy: string; //转发规则 rr/wrr/wlc/lc
isMutualAuth?: boolean; //是否开启双向认证
/******************配置后端服务器****************/
rsgID: number; //后端服务器组ID
backEndProtocol: string; //后端协议
backEndPort: number; //后端端口
/*********************健康检查*******************/
isHealthCheck: boolean; //启用
healthProtocol: string; //协议
healthPort: number; //端口
healthInterval: number; //检查间隔(秒)
healthTimeOut: number; //超时间隔(秒)
healthFailTimes: number; //最大重试次数
healthUrl?: string; //检查url
}
export default defineComponent({
name: 'ListenerEdit',
components: { NsStep, ApiCusDetail },
setup() {
const isEdit = ref(false);
const router = useRouter();
const clusterAddr = ref('');
const projectIDs = ref('');
const initIds = ref('');
// const { navigateBack } = useNavigate();
const navigateBack = () => {
router.back();
};
const mainRef = ref();
const errorItem = ref({});
const backStep = () => {
data.current--;
};
const editId = parseInt(router.currentRoute.value.params.ID);
http.get(`/api/slb/objs/Listener/${editId}`).then((res) => {
/******************配置监听器 *******************/
data.projectName = res.projectName;
data.name = res.name;
data.projectID = res.projectID;
initIds.value = res.projectID;
//projectIDs.value = res.projectID;
data.clusterProjectID = res.clusterProjectID;
clusterAddr.value = res.clusterAddr;
data.clusterProjectName = res.clusterProjectName;
data.description = res.description;
data.clusterName = res.clusterName;
data.frontEndPort = res.frontEndPort;
data.frontEndProtocol = res.frontEndProtocol;
data.securityType = res.securityType;
data.mode = res.mode;
data.trafficDistrStrategy = res.trafficDistrStrategy;
if (res.frontEndProtocol === 'https') {
data.isMutualAuth = res.isMutualAuth;
data.backEndProtocol = res.backEndProtocol;
data.backEndPort = res.backEndPort;
data.domain = res.domain;
}
if (res.mode === 'master-slave') {
data.isNeedVIP = res.isNeedVIP;
}
if (res.frontEndProtocol === 'tcp' || res.frontEndProtocol === 'udp') {
modeList.value = [
{
label: '主从模式',
value: 'master-slave',
},
];
trafficDistrStrategyList.value = [
{
label: '轮询',
value: 'rr',
},
];
}
/******************配置后端服务器****************/
data.rsgID = res.rsgID;
data.rsgName = res.rsgName;
data.backEndProtocol = res.backEndProtocol;
data.backEndPort = res.backEndPort;
/*********************健康检查*******************/
data.isHealthCheck = res.isHealthCheck;
if (res.isHealthCheck && res.isHealthCheck !== undefined) {
data.healthProtocol = res.healthProtocol;
data.healthPort = res.healthPort;
data.healthInterval = res.healthInterval;
data.healthTimeOut = res.healthTimeOut;
data.healthFailTimes = res.healthFailTimes;
if ((res.healthProtocol = 'http')) {
data.healthUrl = res.healthUrl;
}
}
isEdit.value = true;
});
const trafficDistrStrategyList = ref([
{
label: '轮询',
value: 'rr',
},
{
label: '加权轮询',
value: 'wrr',
},
// {
// label: '最少链接',
// value: 'lc',
// },
{
label: '加权最少链接',
value: 'wlc',
},
]);
const modeList = ref([
{
label: '主从模式',
value: 'master-slave',
},
// {
// label: '负载模式',
// value: 'Loadbalance',
// },
]);
const stepSubmit = () => {
const checkField = ref('');
switch (data.current) {
case 0:
checkField.value = 'ListenerCheck';
break;
case 1:
checkField.value = 'RealServerCheck';
break;
case 2:
checkField.value = 'HealthCheck';
break;
}
if (data.current > 2) {
submit();
} else {
http
.post('/api/slb/objs/Listener/PreValidate', data, {
headers: { CheckField: checkField.value },
})
.then(
() => {
errorItem.value = {};
data.current < 3 ? data.current++ : '';
if (data.current === 1) {
projectIDs.value = initIds.value;
console.log(
(formSchema.filter((x) => x.field === 'rsgID')[0].componentProps.immediate =
true),
);
}
},
(error) => {
if (error?.response?.data?.fieldErrors) {
errorItem.value = error?.response?.data?.fieldErrors;
let checkValue = [];
switch (data.current) {
case 0:
checkValue = ['name', 'frontEndPort'];
mainRef.value.triggerSubmit(checkValue);
break;
case 1:
checkValue = ['backEndPort'];
mainRef.value.triggerSubmit(checkValue);
break;
case 2:
checkValue = [''];
break;
}
}
},
);
}
};
function submit() {
mainRef.value
.triggerSubmit()
.then((data) => {
http.put('/api/slb/objs/Listener', data).then((res) => {
NsMessage.success('操作成功', 1, () => {
navigateBack();
});
});
})
.catch((err) => {
console.log(err);
});
}
let data = reactive<listenerInfo>({
current: 0,
ID: editId,
});
const current = ref(0);
const isDisable = computed(() => {
switch (data.current) {
case 0:
let pattern = /^[a-zA-Z][\d\a-zA-Z-]{0,32}$/;
const ischeck =
data.name &&
data.frontEndPort &&
pattern.test(data.name) &&
data.clusterName &&
!(data.frontEndPort < 30000) &&
!(data.frontEndPort > 32767) &&
data.frontEndProtocol &&
data.mode &&
data.trafficDistrStrategy;
if (data.frontEndProtocol === 'https') {
return ischeck && data.securityType && data.domain;
} else {
return ischeck;
}
break;
case 1:
return data.rsgID && data.backEndProtocol && data.backEndPort;
break;
case 2:
if (data.isHealthCheck) {
const ischeck2 =
data.healthProtocol &&
data.healthPort &&
data.healthInterval &&
data.healthTimeOut &&
data.healthFailTimes;
if (data.healthProtocol === 'http') {
return ischeck2 && data.healthUrl;
} else {
return ischeck2;
}
} else {
return true;
}
break;
case 3:
return true;
break;
default:
return false;
}
});
const formSchema = reactive([
/******************************配置监听器******************************* */
{
field: 'ID',
component: 'NsInputText',
show: false,
viewOnly: false,
},
{
label: '项目',
field: 'projectName',
component: 'NsInputText' /* todo 需要根据权限过滤 */,
show: (record: any) => {
return record.current === 0;
},
viewOnly: true,
},
{
field: 'name',
label: '名称',
component: 'NsInputText',
show: (record) => {
return record.current === 0;
},
viewOnly: true,
},
{
label: '集群项目',
field: 'clusterProjectName',
component: 'NsInputText',
show: (record: any) => {
return record.current === 0;
},
viewOnly: true,
},
{
field: 'clusterName',
label: '集群',
component: 'NsInputText',
show: (record: any) => {
return record.current === 0;
},
viewOnly: true,
},
{
field: 'frontEndProtocol',
label: '前端协议',
component: 'NsInputText',
show: (record: any) => {
return record.current === 0;
},
},
{
label: '域名',
field: 'domain',
component: 'NsSelectApi' /* todo 需要根据权限过滤 */,
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
show: (record: any) => {
return record.current === 0 && record.frontEndProtocol === 'https';
},
defaultParams: {
content: clusterAddr,
},
componentProps: {
api: '/api/dns/dns/objs/record/listName',
showSearch: true,
dropdownReload: true,
filterOption: (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
immediate: true,
resultField: 'name',
placeholder: '请选择域名',
},
rules: [
{
required: true,
message: '请选择域名',
},
],
},
{
label: '前端端口',
field: 'frontEndPort',
show: (record: any) => {
return record.current === 0;
},
component: 'NsInputNumber' /* todo 需要根据权限过滤 */,
componentProps: {
min: 1,
},
rules: [
{
required: true,
message: '前端端口必填',
trigger: 'blur',
},
{
trigger: 'change',
validator: async (rule, value) => {
console.log(errorItem);
if (Object.keys(errorItem.value).indexOf('frontEndPort') !== -1) {
const errorInfo = errorItem.value['frontEndPort'];
delete errorItem.value['frontEndPort'];
return Promise.reject(errorInfo);
}
},
},
{
trigger: 'change',
validator: async (rule, value) => {
if (value < 30000 || value > 32767) {
return Promise.reject('前端端口号的范围是30000~32767');
}
},
},
],
},
{
field: 'securityType',
label: '安全类型',
component: 'NsSelect',
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
show: (record: any) => {
return record.current === 0 && record.frontEndProtocol === 'https';
},
componentProps: {
placeholder: '请选择安全类型',
options: [
{
label: '安全策略TLS-1-0',
value: 'TLS_1_0',
},
{
label: '安全策略TLS-1-1',
value: 'TLS_1_1',
},
{
label: '安全策略TLS-1-2',
value: 'TLS_1_2',
},
{
label: '安全策略TLS-1-2-Strict',
value: 'TLS_1_2_Strict',
},
],
},
rules: [
{
required: true,
message: '请选择安全类型',
trigger: 'blur',
},
],
},
{
field: 'isMutualAuth',
label: '双向认证',
class: 'NsSwitch',
component: 'NsSwitch',
ifShow: (record: any) => {
return record.frontEndProtocol === 'https';
},
componentProps: {
checkedChildren: '开',
},
show: (record: any) => {
return record.current === 0 && record.frontEndProtocol === 'https';
},
},
{
field: 'mode',
label: '监听器模式',
component: 'NsSelect',
show: (record: any) => {
return record.current === 0;
},
componentProps: {
placeholder: '请选择',
options: modeList,
},
rules: [
{
required: true,
message: '请选择监听器模式',
trigger: 'blur',
},
],
},
{
field: 'isNeedVIP',
label: '是否绑定VIP',
component: 'NsSwitch',
class: 'NsSwitch',
show: (record: any) => {
return record.current === 0 && record.mode === 'master-slave';
},
componentProps: {
checkedChildren: '开',
},
},
{
field: 'trafficDistrStrategy',
label: '分配策略类型',
component: 'NsSelect',
show: (record: any) => {
return record.current === 0;
},
componentProps: {
placeholder: '请选择分配策略类型',
options: trafficDistrStrategyList,
},
rules: [
{
required: true,
message: '请选择分配策略类型',
trigger: 'blur',
},
],
},
{
field: 'description',
label: '描述',
show: (record: any) => {
return record.current === 0;
},
component: 'NsTextarea',
componentProps: {
showCount: true,
maxlength: 200,
},
rules: [{}],
},
/******************************配置后端服务器******************************* */
{
field: 'rsgID',
label: '后端服务器组',
component: 'NsSelectApi',
addModel: {
rsgName: 'Name',
},
defaultParams: {
projectID: projectIDs,
},
show: (record: any) => {
return record.current === 1;
},
componentProps: {
api: '/api/slb/objs/RealServerGroup/ListWithSuccess',
resultField: 'data',
autoClearValue: true,
showSearch: true,
dropdownReload: true,
labelField: 'Name',
// filterData: (item) => {
// return authorizationService().checkPermission('slb', 'realServerGroup');
// },
immediate: false,
valueField: 'ID',
},
rules: [
{
required: true,
message: '请选择后端服务器组',
trigger: 'blur',
},
],
},
// {
// field: 'rsgName',
// label: '后端服务器组',
// component: 'NsInputText',
// viewOnly: true,
// ifShow: () => {
// return !authorizationService().checkPermission('slb', 'realServerGroup');
// },
// show: (record: any) => {
// return record.current === 1;
// },
// },
{
field: 'backEndProtocol',
label: '后端协议',
component: 'NsSelect',
show: (record: any) => {
return record.current === 1;
},
componentProps: {
disabled: true,
options: [
{
label: 'TCP',
value: 'tcp',
},
{
label: 'UDP',
value: 'udp',
},
{
label: 'HTTP',
value: 'http',
},
// {
// label: 'HTTPS',
// value: 'https',
// },
],
},
rules: [
{
required: true,
message: '请选择后端协议',
trigger: 'blur',
},
],
},
{
label: '后端端口',
field: 'backEndPort',
show: (record: any) => {
return record.current === 1;
},
component: 'NsInputNumber' /* todo 需要根据权限过滤 */,
componentProps: {
onChange: (val) => {
data.healthPort = val;
},
min: 1,
max: 65535,
},
rules: [
{
required: true,
message: '后端端口必填',
trigger: 'blur',
},
{
trigger: 'change',
validator: async (rule, value) => {
if (Object.keys(errorItem.value).indexOf('backEndPort') !== -1) {
const errorInfo = errorItem.value['backEndPort'];
delete errorItem.value['backEndPort'];
return Promise.reject(errorInfo);
}
},
},
],
},
/******************************健康检查******************************* */
{
field: 'isHealthCheck',
label: '启用',
component: 'NsSwitch',
class: 'NsSwitch',
show: (record: any) => {
return record.current === 2;
},
componentProps: {
checkedChildren: '开',
},
},
{
field: 'healthProtocol',
label: '协议',
component: 'NsSelect',
ifShow: (record: any) => {
return record.isHealthCheck;
},
show: (record: any) => {
return record.current === 2 && record.isHealthCheck;
},
componentProps: {
placeholder: '请选择',
options: [
{
label: 'TCP',
value: 'tcp',
},
{
label: 'UDP',
value: 'udp',
},
{
label: 'HTTP',
value: 'http',
},
// {
// label: 'HTTPS',
// value: 'https',
// },
],
},
rules: [
{
required: true,
message: '请选择协议',
trigger: 'blur',
},
],
},
{
label: '端口',
field: 'healthPort',
ifShow: (record: any) => {
return record.isHealthCheck;
},
show: (record: any) => {
return record.current === 2 && record.isHealthCheck;
},
component: 'NsInputNumber',
componentProps: {
min: 1,
max: 65535,
},
rules: [
{
required: true,
message: '端口必填',
trigger: 'blur',
},
],
},
{
label: '检查间隔(秒)',
field: 'healthInterval',
ifShow: (record: any) => {
return record.isHealthCheck;
},
show: (record: any) => {
return record.current === 2 && record.isHealthCheck;
},
component: 'NsInputNumber',
componentProps: {
min: 1,
},
rules: [
{
required: true,
message: '检查间隔必填',
trigger: 'blur',
},
],
},
{
label: '超时间隔(秒)',
field: 'healthTimeOut',
ifShow: (record: any) => {
return record.isHealthCheck;
},
show: (record: any) => {
return record.current === 2 && record.isHealthCheck;
},
component: 'NsInputNumber',
componentProps: {
min: 1,
},
rules: [
{
required: true,
message: '超时间隔必填',
trigger: 'blur',
},
],
},
{
field: 'healthUrl',
label: '健康检查URL',
component: 'NsInput',
componentProps: {
placeholder: '请输入',
},
ifShow: (record: any) => {
return record.isHealthCheck && record.healthProtocol === 'http';
},
show: (record: any) => {
return record.current === 2 && record.isHealthCheck && record.healthProtocol === 'http';
},
rules: [
{
required: true,
message: '健康检查URL必填',
trigger: 'blur',
},
// {
// trigger: 'change',
// validator: async (rule, value) => {
// if (Object.keys(errorItem.value).indexOf('routePath') !== -1) {
// const errorInfo = errorItem.value['routePath'];
// delete errorItem.value['routePath'];
// return Promise.reject(errorInfo);
// }
// },
// },
{
trigger: 'blur',
validator: async (rule, value) => {
if (value) {
if (!/^\/[a-zA-Z\/0-9_\.\-\#\%]{0,127}$/.test(value)) {
return Promise.reject(
'健康检查URL必须以/开头,只能包含字母数字或_.-#%长度限制在1-128字符之间',
);
}
}
},
},
],
},
{
label: '最大重试次数',
field: 'healthFailTimes',
ifShow: (record: any) => {
return record.isHealthCheck;
},
show: (record: any) => {
return record.current === 2 && record.isHealthCheck;
},
component: 'NsInputNumber',
componentProps: {
min: 1,
},
rules: [
{
required: true,
message: '最大重试次数必填',
trigger: 'blur',
},
],
},
]);
return {
authorizationService,
backStep,
navigateBack,
isEdit,
stepSubmit,
isDisable,
mainRef,
submit,
current,
data,
formSchema,
};
},
});
</script>
<style scoped lang="less">
:deep(.NsSwitch) {
.ant-switch {
width: 20px !important;
}
}
:deep(.ant-page-header-heading) {
justify-content: unset !important;
}
.floor_button {
width: 100%;
margin-top: 60px;
margin-bottom: 30px;
display: flex;
.submit {
margin-left: auto !important;
.ant-btn {
width: 150px !important;
margin-right: 50px;
}
}
}
</style>

View File

@@ -0,0 +1,138 @@
<template>
<ns-view-list-table v-bind="tableConfig" rowKey="ID" />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import moment from 'moment';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'ListenerList',
setup() {
const router = useRouter();
const tableConfig = {
api: '/api/slb/objs/Listener',
params: {
order: 'created_at desc',
},
title: '监听器',
refreshTime: 5,
rowSelection: null,
resultField: 'data',
columns: [
{
title: '项目',
dataIndex: 'projectName',
},
{
title: '名称',
dataIndex: 'name',
},
{
title: 'slb集群所属项目',
dataIndex: 'clusterProjectName',
},
{
title: 'slb集群',
dataIndex: 'clusterName',
},
{
title: '访问地址',
dataIndex: 'vip',
},
{
title: '前端协议',
dataIndex: 'frontEndProtocol',
},
{
title: '前端端口',
dataIndex: 'frontEndPort',
},
{
title: '后端服务器组',
dataIndex: 'rsgName',
},
{
title: '状态',
dataIndex: 'status',
transform: {
pipe: 'state',
messageField: 'reason',
},
},
// {
// title: '创建人',
// dataIndex: 'CreatedBy',
// },
{
title: '创建时间',
dataIndex: 'CreatedAt',
sorter: true,
customRender: (text: any) => {
return moment(text.record.CreatedAt).format('YYYY-MM-DD HH:mm:ss');
},
},
],
columnActions: {
title: '操作',
actions: [
{
label: '删除',
name: 'listenerRemove',
confirm: true,
isReload: true,
ifShow: (record: any) => {
return record.status !== 'Creating';
},
api: { method: 'delete', url: '/api/slb/objs/Listener/:ID' },
},
{
label: '编辑',
name: 'listenerEdit',
ifShow: (record: any) => {
return record.status === 'Succeed';
},
route: 'edit/:ID',
},
{
label: '管理',
name: 'listenerManage',
ifShow: (record: any) => {
return record.status === 'Succeed';
},
handle: (record: any) => {
const title = `${record.name}`;
router.push({
name: 'listenerManage',
params: {
ID: record.ID,
rsgID: record.rsgID,
pageTitle: title,
},
});
},
},
],
},
headerActions: [
{
label: '添加监听器',
name: 'listenerAdd',
type: 'primary',
route: 'add',
},
],
formConfig: {},
rowKey: 'ID',
};
return {
moment,
tableConfig,
};
},
methods: {},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,100 @@
<template>
<ns-view-list-table v-bind="tableConfig" rowKey="ID">
<template #healthStatus="{ record }">
<span :style="{ color: dealColor(record.healthStatus) }">{{ record.healthStatus }}</span>
</template>
</ns-view-list-table>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, provide } from 'vue';
import { useRouter } from 'vue-router';
import moment from 'moment';
export default defineComponent({
name: 'ListenerRealServer',
setup() {
const router = useRouter();
const ID = parseInt(router.currentRoute.value.params.rsgID);
const listenerID = parseInt(router.currentRoute.value.params.ID);
const dealColor = (val) => {
switch (val) {
case 'Active':
return '#17be6b';
break;
case 'Abnormal':
return '#ffd591';
break;
case 'Fail':
return 'red';
break;
case 'Unknow':
return '#d9d9d9';
break;
}
};
const tableConfig = {
api: '/api/slb/objs/Listener/RealServer',
params: {
listenerID: listenerID,
rsgID: ID,
order: 'created_at desc',
},
title: router.currentRoute.value.params.pageTitle,
rowSelection: null,
refreshTime: 5,
resultField: 'data',
columns: [
{
title: '名称',
dataIndex: 'name',
},
{
title: '真实服务器IP',
dataIndex: 'ip',
},
{
title: '权重',
dataIndex: 'weight',
},
{
title: '状态',
dataIndex: 'healthStatus',
slots: { customRender: 'healthStatus' },
},
{
title: '描述',
dataIndex: 'description',
sorter: true,
},
{
title: '创建人',
dataIndex: 'CreatedBy',
sorter: true,
},
{
title: '创建时间',
dataIndex: 'CreatedAt',
sorter: true,
customRender: (text: any) => {
return moment(text.record.CreatedAt).format('YYYY-MM-DD HH:mm:ss');
},
},
],
formConfig: {
schemas: [],
params: {},
},
rowKey: 'uuid',
};
return {
dealColor,
moment,
tableConfig,
};
},
methods: {},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,122 @@
<!-- @format -->
<template>
<NsViewAddForm
:schemas="formSchema"
:model="data"
formLayout="修改"
title="新增后端服务器组"
api="/api/slb/objs/RealServerGroup" />
</template>
<script lang="ts">
import { defineComponent, provide, reactive, ref } from 'vue';
import { authorizationService } from '/nerv-lib/paas';
export default defineComponent({
name: 'RealServerGroupAdd',
setup() {
const mainRef = ref();
const data = reactive({});
const formSchema = ref([
{
label: '所属项目',
field: 'projectID',
component: 'NsSelectApi' /* todo 需要根据权限过滤 */,
componentProps: {
api: {
url: '/api/passport/passport/objs/Authorization/CheckAuthorization',
method: 'POST',
},
showSearch: true,
filterOption: (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
filterData: (item) => {
return authorizationService().checkPermission(
'slb',
'realServerGroup',
'add',
item.projectname,
);
},
autoSelectFirst: true,
resultField: 'projects',
labelField: 'projectname',
valueField: 'projectid',
immediate: true,
placeholder: '请选择项目',
},
rules: [
{
type: 'number',
required: true,
message: '项目必填',
trigger: 'blur',
},
],
},
{
field: 'name',
label: '服务器组名称',
component: 'NsInput',
componentProps: {
placeholder: '请输入',
},
rules: [
{
required: true,
message: '服务器组名称必填',
trigger: 'blur',
},
{
pattern: /^[a-z][\d\a-z-]{5,31}$/,
message: '支持小写字母、数字输入和短划线(-)必须以小写字母开头长度6-32字符',
trigger: 'blur',
},
],
},
{
label: '描述',
field: 'description',
component: 'NsTextarea' /* todo 需要根据权限过滤 */,
componentProps: {
showCount: true,
maxlength: 255,
},
},
]);
return {
data,
mainRef,
formSchema,
};
},
beforeCreate() {},
});
</script>
<style lang="less" scoped>
.ant-col {
flex: 0 0 120px !important;
}
.ant-page-header {
padding: 0 24px;
margin-bottom: 15px;
}
:deep(.ant-page-header-heading-extra) {
margin-right: auto !important;
margin-left: 0;
}
.createApp {
position: fixed;
top: 165px;
left: 620px;
}
</style>

View File

@@ -0,0 +1,67 @@
<!-- @format -->
<template>
<NsViewEditForm
:schemas="formSchema"
:model="data"
formLayout="修改"
title="编辑后端服务器组"
:getApi="getApi"
api="/api/slb/objs/RealServerGroup" />
</template>
<script lang="ts">
import { defineComponent, provide, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'RealServerGroupEdit',
setup() {
const mainRef = ref();
const router = useRouter();
const getApi = `/api/slb/objs/RealServerGroup/${router.currentRoute.value.params.ID}`;
const data = reactive({});
const formSchema = ref([
{
field: 'ID',
component: 'NsInputText',
show: false,
viewOnly: false,
},
{
label: '所属项目',
field: 'projectName',
component: 'NsInputText' /* todo 需要根据权限过滤 */,
},
{
field: 'name',
label: '服务器组名称',
component: 'NsInputText',
componentProps: {
placeholder: '请输入',
},
},
{
label: '描述',
field: 'description',
component: 'NsTextarea' /* todo 需要根据权限过滤 */,
componentProps: {
showCount: true,
maxlength: 255,
},
},
]);
return {
data,
mainRef,
getApi,
formSchema,
};
},
beforeCreate() {},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,125 @@
<template>
<ns-view-list-table v-bind="tableConfig" rowKey="ID" />
</template>
<script lang="ts">
import { defineComponent, ref, reactive, provide } from 'vue';
import { useRouter } from 'vue-router';
import moment from 'moment';
export default defineComponent({
name: 'RealServerGroupList',
setup() {
const router = useRouter();
const tableConfig = {
api: '/api/slb/objs/RealServerGroup',
params: {
order: 'created_at desc',
},
title: '后端服务器组管理',
rowSelection: null,
refreshTime: 5,
resultField: 'data',
columns: [
{
title: '项目',
dataIndex: 'projectName',
},
{
title: '名称',
dataIndex: 'name',
},
{
title: '描述',
dataIndex: 'description',
sorter: true,
},
{
title: '状态',
dataIndex: 'status',
transform: {
pipe: 'state',
messageField: 'reason',
},
},
{
title: '创建人',
dataIndex: 'CreatedBy',
sorter: true,
},
{
title: '创建时间',
dataIndex: 'CreatedAt',
sorter: true,
customRender: (text: any) => {
return moment(text.record.CreatedAt).format('YYYY-MM-DD HH:mm:ss');
},
},
],
columnActions: {
title: '操作',
actions: [
{
label: '删除',
name: 'realServerGroupRemove',
confirm: true,
isReload: true,
ifShow: (record: any) => {
return record.status !== 'Creating';
},
dynamicDisabled: (record: any) => {
return record.soldStatus === 1;
},
api: { method: 'delete', url: '/api/slb/objs/RealServerGroup/:ID' },
},
{
label: '编辑',
name: 'realServerGroupEdit',
ifShow: (record: any) => {
return record.status === 'Succeed';
},
route: `/slb/realServerGroup/edit/:ID`,
},
{
label: '管理',
name: 'realServerGroupManage',
ifShow: (record: any) => {
return record.status === 'Succeed';
},
handle: (record) => {
const title = `${record.projectName} > ${record.name}`;
router.push({
name: 'realServerGroupManage',
params: {
ID: record.ID,
pageTitle: title,
projectName: record.projectName,
},
});
},
},
],
},
headerActions: [
{
label: '添加',
name: 'realServerGroupAdd',
type: 'primary',
route: `/slb/realServerGroup/add`,
},
],
formConfig: {
schemas: [],
params: {},
},
rowKey: 'uuid',
};
return {
moment,
tableConfig,
};
},
methods: {},
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,141 @@
<!-- @format -->
<template>
<NsViewAddForm
:schemas="formSchema"
:model="data"
formLayout="修改"
title="添加后端服务器"
api="/api/slb/objs/RealServer" />
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'RealServerAdd',
setup() {
const mainRef = ref();
const router = useRouter();
const data = reactive({});
const params = router.currentRoute.value.params;
let formSchema = ref([
{
field: 'rsgID',
component: 'NsInputText',
show: false,
viewOnly: false,
defaultValue: parseInt(params.ID),
},
{
field: 'name',
label: '服务器名称',
component: 'NsInput',
componentProps: {
placeholder: '请输入',
},
rules: [
{
required: true,
message: '服务器名称必填',
trigger: 'blur',
},
{
pattern: /^[a-z][\d\a-z-]{5,31}$/,
message: '支持小写字母、数字输入和短划线(-)必须以小写字母开头长度6-32字符',
trigger: 'blur',
},
],
},
{
field: 'ip',
label: '真实服务IP',
class: 'NsInputIp',
component: 'NsInputIp',
componentProps: {
placeholder: '请输入',
},
rules: [
{
required: true,
message: '真实服务IP必填',
trigger: 'change',
},
{
trigger: 'change',
validator: async (rule, value) => {
if (value) {
var isTrue = true;
value.split('.').forEach((item) => {
if (item == '') {
isTrue = false;
}
});
if (!isTrue) {
return Promise.reject('请输入正确的IP格式');
}
}
},
},
],
},
{
label: '权重',
field: 'weight',
component: 'NsInputNumber' /* todo 需要根据权限过滤 */,
componentProps: {
min: 0,
max: 65535,
},
rules: [
{
required: true,
type: 'number',
message: '权重必填',
trigger: 'blur',
},
],
},
{
label: '描述',
field: 'description',
component: 'NsTextarea' /* todo 需要根据权限过滤 */,
componentProps: {
showCount: true,
maxlength: 255,
},
},
]);
return {
data,
mainRef,
formSchema,
};
},
});
</script>
<style lang="less" scoped>
.ant-col {
flex: 0 0 120px !important;
}
.ant-page-header {
padding: 0 24px;
margin-bottom: 15px;
}
:deep(.ant-page-header-heading-extra) {
margin-right: auto !important;
margin-left: 0;
}
.createApp {
position: fixed;
top: 165px;
left: 620px;
}
:deep(.NsInputIp .ant-form-item-control-input-content) {
display: flex;
}
</style>

View File

@@ -0,0 +1,115 @@
<!-- @format -->
<template>
<NsViewEditForm
:schemas="formSchema"
:model="data"
formLayout="修改"
title="编辑后端服务器"
:getApi="getApi"
api="/api/slb/objs/RealServer" />
</template>
<script lang="ts">
import { defineComponent, provide, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'RealServerEdit',
setup() {
const mainRef = ref();
const router = useRouter();
const getApi = `/api/slb/objs/RealServer/${router.currentRoute.value.params.editId}`;
const data = reactive({});
const formSchema = ref([
{
field: 'ID',
component: 'NsInputText',
show: false,
viewOnly: false,
defaultValue: parseInt(router.currentRoute.value.params.editId),
},
{
field: 'name',
label: '服务器名称',
component: 'NsInputText',
},
{
field: 'ip',
label: '真实服务IP',
component: 'NsInputIp',
class: 'NsInputIp',
componentProps: {
modelValue: router.currentRoute.value.params.ip,
placeholder: '请输入',
},
rules: [
{
required: true,
message: '真实服务IP必填',
trigger: 'change',
},
{
trigger: 'change',
validator: async (rule, value) => {
if (value) {
var isTrue = true;
value.split('.').forEach((item) => {
if (item == '') {
isTrue = false;
}
});
if (!isTrue) {
return Promise.reject('请输入正确的IP格式');
}
}
},
},
],
},
{
label: '权重',
field: 'weight',
component: 'NsInputNumber' /* todo 需要根据权限过滤 */,
componentProps: {
min: 0,
max: 65535,
},
rules: [
{
required: true,
type: 'number',
message: '权重必填',
trigger: 'blur',
},
],
},
{
label: '描述',
field: 'description',
component: 'NsTextarea' /* todo 需要根据权限过滤 */,
componentProps: {
showCount: true,
maxlength: 255,
},
},
]);
return {
data,
mainRef,
getApi,
formSchema,
};
},
beforeCreate() {},
});
</script>
<style lang="less" scoped>
:deep(.NsInputIp .ant-form-item-control-input-content) {
display: flex;
}
</style>

View File

@@ -0,0 +1,127 @@
<template>
<ns-view-list-table v-bind="tableConfig" rowKey="ID" />
</template>
<script lang="ts">
import { defineComponent, ref, reactive, provide } from 'vue';
import { useRouter } from 'vue-router';
import moment from 'moment';
export default defineComponent({
name: 'RealServerList',
setup() {
const router = useRouter();
const ID = parseInt(router.currentRoute.value.params.ID);
const tableConfig = {
api: '/api/slb/objs/RealServer',
params: {
rsgID: ID,
order: 'created_at desc',
},
title: router.currentRoute.value.params.pageTitle,
rowSelection: null,
refreshTime: 5,
resultField: 'data',
columns: [
{
title: '名称',
dataIndex: 'name',
},
{
title: '真实服务器IP',
dataIndex: 'ip',
},
{
title: '权重',
dataIndex: 'weight',
},
{
title: '状态',
dataIndex: 'status',
transform: {
pipe: 'state',
messageField: 'reason',
},
},
{
title: '描述',
dataIndex: 'description',
sorter: true,
},
{
title: '创建人',
dataIndex: 'CreatedBy',
sorter: true,
},
{
title: '创建时间',
dataIndex: 'CreatedAt',
sorter: true,
customRender: (text: any) => {
return moment(text.record.CreatedAt).format('YYYY-MM-DD HH:mm:ss');
},
},
],
columnActions: {
title: '操作',
actions: [
{
label: '删除',
name: 'realServerRemove',
confirm: true,
isReload: true,
ifShow: (record: any) => {
return record.status !== 'Creating';
},
dynamicDisabled: (record: any) => {
return record.soldStatus === 1;
},
api: { method: 'delete', url: '/api/slb/objs/RealServer/:ID' },
},
{
label: '编辑',
name: 'realServerEdit',
ifShow: (record: any) => {
return record.status === 'Succeed';
},
handle: (record) => {
router.push({
name: 'realServerEdit',
params: {
editId: record.ID,
ip: record.ip,
},
});
},
},
],
},
headerActions: [
{
label: '添加',
name: 'realServerAdd',
type: 'primary',
handle: (record) => {
router.push({
name: 'realServerAdd',
});
},
},
],
formConfig: {
schemas: [],
params: {},
},
rowKey: 'uuid',
};
return {
moment,
tableConfig,
};
},
methods: {},
});
</script>
<style lang="less" scoped></style>

48
nervui-slb/tsconfig.json Normal file
View File

@@ -0,0 +1,48 @@
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "./",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"jsx": "preserve",
"lib": ["esnext", "dom"],
"module": "esnext",
"moduleResolution": "node",
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"strictFunctionTypes": false,
"target": "esnext",
"types": ["vite/client"],
"typeRoots": [
"../node_modules/@types",
"../node_modules/@vue",
"../type"
],
"paths": {
"/@/*": [
"src/*"
],
"/nerv-lib/*": [
"../lib/*"
],
"/nerv-base/*": [
"../lib/paas/*"
]
}
},
"include": [
"src/**/*",
"type/**/*",
"mock/**/*",
"vite.config.ts"
],
"exclude": [
"node_modules",
"dist",
"**/*.js"
]
}

19
nervui-slb/vite.config.ts Normal file
View File

@@ -0,0 +1,19 @@
import configFun from '../build/vite-default.config';
const dirname = __dirname;
const proxy = {
'/api/slb': {
target: 'http://100.86.226.6:30573',
changeOrigin: true,
rewrite: (path: any) => path.replace(/^\/api\/slb/, '/api'),
},
'/api': {
target: 'http://portal.cloud.sh.dingcloud.com:30080',
changeOrigin: true,
},
'/api/dns': {
target: 'http://portal.cloud.sh.dingcloud.com:30080',
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/dns/, '/api/dns'),
},
};
export default configFun({ dirname, serviceMode: 'paas', baseDir: '../', proxy });