mirror of https://github.com/certd/certd
perf: 支持网络测试
parent
aee13ad909
commit
2bef608e07
|
@ -1,8 +1,8 @@
|
|||
//转换为import
|
||||
import childProcess from 'child_process';
|
||||
import { safePromise } from './util.promise.js';
|
||||
import { ILogger, logger } from './util.log.js';
|
||||
import iconv from 'iconv-lite';
|
||||
import childProcess from "child_process";
|
||||
import { safePromise } from "./util.promise.js";
|
||||
import { ILogger, logger } from "./util.log.js";
|
||||
import iconv from "iconv-lite";
|
||||
export type ExecOption = {
|
||||
cmd: string | string[];
|
||||
env: any;
|
||||
|
@ -11,12 +11,12 @@ export type ExecOption = {
|
|||
};
|
||||
|
||||
async function exec(opts: ExecOption): Promise<string> {
|
||||
let cmd = '';
|
||||
let cmd = "";
|
||||
const log = opts.logger || logger;
|
||||
if (opts.cmd instanceof Array) {
|
||||
for (const item of opts.cmd) {
|
||||
if (cmd) {
|
||||
cmd += ' && ' + item;
|
||||
cmd += " && " + item;
|
||||
} else {
|
||||
cmd = item;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ async function exec(opts: ExecOption): Promise<string> {
|
|||
log.error(`exec error: ${error}`);
|
||||
reject(error);
|
||||
} else {
|
||||
const res = stdout.toString('utf-8');
|
||||
const res = stdout.toString("utf-8");
|
||||
log.info(`stdout: ${res}`);
|
||||
resolve(res);
|
||||
}
|
||||
|
@ -57,11 +57,11 @@ export type SpawnOption = {
|
|||
};
|
||||
|
||||
function isWindows() {
|
||||
return process.platform === 'win32';
|
||||
return process.platform === "win32";
|
||||
}
|
||||
function convert(buffer: any) {
|
||||
if (isWindows()) {
|
||||
const decoded = iconv.decode(buffer, 'GBK');
|
||||
const decoded = iconv.decode(buffer, "GBK");
|
||||
// 检查是否有有效字符
|
||||
return decoded && decoded.trim().length > 0 ? decoded : buffer.toString();
|
||||
} else {
|
||||
|
@ -74,12 +74,12 @@ function convert(buffer: any) {
|
|||
// }
|
||||
|
||||
async function spawn(opts: SpawnOption): Promise<string> {
|
||||
let cmd = '';
|
||||
let cmd = "";
|
||||
const log = opts.logger || logger;
|
||||
if (opts.cmd instanceof Array) {
|
||||
for (const item of opts.cmd) {
|
||||
if (cmd) {
|
||||
cmd += ' && ' + item;
|
||||
cmd += " && " + item;
|
||||
} else {
|
||||
cmd = item;
|
||||
}
|
||||
|
@ -88,8 +88,8 @@ async function spawn(opts: SpawnOption): Promise<string> {
|
|||
cmd = opts.cmd;
|
||||
}
|
||||
log.info(`执行命令: ${cmd}`);
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
return safePromise((resolve, reject) => {
|
||||
const ls = childProcess.spawn(cmd, {
|
||||
shell: true,
|
||||
|
@ -99,23 +99,23 @@ async function spawn(opts: SpawnOption): Promise<string> {
|
|||
},
|
||||
...opts.options,
|
||||
});
|
||||
ls.stdout.on('data', data => {
|
||||
ls.stdout.on("data", data => {
|
||||
data = convert(data);
|
||||
log.info(`stdout: ${data}`);
|
||||
stdout += data;
|
||||
});
|
||||
|
||||
ls.stderr.on('data', data => {
|
||||
ls.stderr.on("data", data => {
|
||||
data = convert(data);
|
||||
log.warn(`stderr: ${data}`);
|
||||
stderr += data;
|
||||
});
|
||||
ls.on('error', error => {
|
||||
ls.on("error", error => {
|
||||
log.error(`child process error: ${error}`);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
ls.on('close', (code: number) => {
|
||||
ls.on("close", (code: number) => {
|
||||
if (code !== 0) {
|
||||
log.error(`child process exited with code ${code}`);
|
||||
reject(new Error(stderr));
|
||||
|
|
|
@ -175,6 +175,7 @@ export default {
|
|||
suiteSetting: "Suite Settings",
|
||||
orderManager: "Order Management",
|
||||
userSuites: "User Suites",
|
||||
netTest: "Network Test",
|
||||
},
|
||||
certificateRepo: {
|
||||
title: "Certificate Repository",
|
||||
|
|
|
@ -181,6 +181,7 @@ export default {
|
|||
suiteSetting: "套餐设置",
|
||||
orderManager: "订单管理",
|
||||
userSuites: "用户套餐",
|
||||
netTest: "网络测试",
|
||||
},
|
||||
certificateRepo: {
|
||||
title: "证书仓库",
|
||||
|
|
|
@ -249,6 +249,17 @@ export const sysResources = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "certd.sysResources.netTest",
|
||||
name: "NetTest",
|
||||
path: "/sys/nettest",
|
||||
component: "/sys/nettest/index.vue",
|
||||
meta: {
|
||||
icon: "ion:build-outline",
|
||||
auth: true,
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
<template>
|
||||
<div class="domain-test-card">
|
||||
<div class="card-header">
|
||||
<a-form v-if="editing" layout="inline" :model="formData">
|
||||
<a-form-item label="域名">
|
||||
<a-input v-model:value="formData.domain" placeholder="请输入要测试的域名或IP" style="width: 240px" />
|
||||
</a-form-item>
|
||||
<a-form-item label="端口">
|
||||
<a-input-number v-model:value="formData.port" placeholder="请输入端口" :min="1" :max="65535" style="width: 120px" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div v-else class="domain-info">
|
||||
<span>域名: {{ formData.domain }}</span>
|
||||
<span>端口: {{ formData.port }}</span>
|
||||
</div>
|
||||
|
||||
<a-button :disabled="!formData.domain" size="small" type="primary" :loading="loading" @click="runAllTests"> 开始测试 </a-button>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<div class="test-results">
|
||||
<!-- 域名解析结果 -->
|
||||
<test-case ref="domainResolveRef" title="域名解析" :test-method="() => createDomainResolveMethod()" :disabled="!getCurrentDomain()" />
|
||||
|
||||
<!-- Ping测试结果 -->
|
||||
<test-case ref="pingTestRef" title="Ping测试" :test-method="() => createPingTestMethod()" :disabled="!getCurrentDomain()" />
|
||||
|
||||
<!-- Telnet测试结果 -->
|
||||
<test-case ref="telnetTestRef" title="Telnet测试" :port="getCurrentPort()" :test-method="() => createTelnetTestMethod()" :disabled="!getCurrentDomain() || !getCurrentPort()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import { DomainResolve, PingTest, TelnetTest } from "./api";
|
||||
import TestCase from "./TestCase.vue";
|
||||
|
||||
// 组件属性
|
||||
const props = defineProps<{
|
||||
domain?: string;
|
||||
port?: number;
|
||||
autoStart?: boolean;
|
||||
}>();
|
||||
|
||||
const editing = ref(!props.domain);
|
||||
|
||||
// 测试组件的引用
|
||||
const domainResolveRef = ref();
|
||||
const pingTestRef = ref();
|
||||
const telnetTestRef = ref();
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
domain: props.domain || "",
|
||||
port: props.port || 443,
|
||||
});
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 创建域名解析测试方法
|
||||
const createDomainResolveMethod = async () => {
|
||||
const domain = getCurrentDomain();
|
||||
return DomainResolve(domain);
|
||||
};
|
||||
|
||||
// 创建Ping测试方法
|
||||
const createPingTestMethod = async () => {
|
||||
const domain = getCurrentDomain();
|
||||
return PingTest(domain);
|
||||
};
|
||||
|
||||
// 创建Telnet测试方法
|
||||
const createTelnetTestMethod = async () => {
|
||||
const domain = getCurrentDomain();
|
||||
const port = getCurrentPort();
|
||||
|
||||
return TelnetTest(domain, port);
|
||||
};
|
||||
|
||||
// 获取当前使用的域名
|
||||
const getCurrentDomain = () => {
|
||||
return formData.domain;
|
||||
};
|
||||
|
||||
// 获取当前使用的端口
|
||||
const getCurrentPort = () => {
|
||||
return formData.port;
|
||||
};
|
||||
|
||||
// 运行全部测试
|
||||
async function runAllTests() {
|
||||
const domain = getCurrentDomain();
|
||||
|
||||
// 检查是否有域名
|
||||
if (!domain) {
|
||||
message.error("请输入域名");
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
// 通过组件引用调用测试方法
|
||||
try {
|
||||
await Promise.allSettled([domainResolveRef.value?.test(), pingTestRef.value?.test(), telnetTestRef.value?.test()]);
|
||||
|
||||
message.success("所有测试已完成");
|
||||
} catch (error) {
|
||||
message.error("部分测试执行失败,请查看详细结果");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoStart) {
|
||||
runAllTests();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.domain-test-card {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background-color: #fafafa;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.input-form {
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.domain-info {
|
||||
padding: 5.5px 12px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.test-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.test-results {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
/* 调整按钮大小 */
|
||||
.ant-btn {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<a-card title="服务端信息" class="server-info-card">
|
||||
<template #extra>
|
||||
<a-button size="small" :loading="loading" @click="refreshServerInfo">
|
||||
<template #icon>
|
||||
<a-icon type="sync" :spin="loading" />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
</template>
|
||||
<div v-if="loading" class="loading">
|
||||
<a-spin size="small" />
|
||||
<span style="margin-left: 8px">加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="error" class="error">
|
||||
<a-alert message="获取服务器信息失败" :description="error" type="error" show-icon />
|
||||
</div>
|
||||
<div v-else class="server-info-grid">
|
||||
<!-- 本地IP -->
|
||||
<div class="info-item">
|
||||
<div class="info-label">本地IP:</div>
|
||||
<div v-if="serverInfo.localIP" class="info-value">
|
||||
<a-list item-layout="horizontal" :data-source="serverInfo.localIP">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta description="{{ item }}" />
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
<div v-else class="info-empty">暂无信息</div>
|
||||
</div>
|
||||
|
||||
<!-- 外网IP -->
|
||||
<div class="info-item">
|
||||
<div class="info-label">外网IP:</div>
|
||||
<div v-if="serverInfo.publicIP" class="info-value">
|
||||
{{ serverInfo.publicIP }}
|
||||
</div>
|
||||
<div v-else class="info-empty">暂无信息</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS服务器 -->
|
||||
<div class="info-item">
|
||||
<div class="info-label">DNS服务器:</div>
|
||||
<div v-if="serverInfo.dnsServers && serverInfo.dnsServers.length > 0" class="info-value">
|
||||
{{ serverInfo.dnsServers.join(", ") }}
|
||||
</div>
|
||||
<div v-else class="info-empty">暂无信息</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import { GetServerInfo } from "./api";
|
||||
|
||||
// 服务器信息类型
|
||||
interface ServerInfo {
|
||||
localIP?: string[];
|
||||
publicIP?: string;
|
||||
dnsServers?: string[];
|
||||
}
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const serverInfo = ref<ServerInfo>({});
|
||||
|
||||
// 加载服务器信息
|
||||
const loadServerInfo = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
serverInfo.value = await GetServerInfo();
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : String(e);
|
||||
message.error("获取服务器信息失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新服务器信息
|
||||
const refreshServerInfo = () => {
|
||||
loadServerInfo();
|
||||
};
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadServerInfo();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.server-info-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.server-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
.ant-list-item {
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-empty {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,185 @@
|
|||
<template>
|
||||
<div class="test-case" :class="{ loading }">
|
||||
<div class="case-header">
|
||||
<span class="flex items-center">
|
||||
<fs-button size="small" type="text" icon="ion:play-circle" :loading="loading" :disabled="disabled" class="test-button" @click="runTest" />
|
||||
<a-tag color="blue" class="case-title">
|
||||
{{ title }}
|
||||
</a-tag>
|
||||
<span v-if="port" class="port-info">{{ port }}</span>
|
||||
</span>
|
||||
<span v-if="result && isNetTestResult" class="result-status flex-1" :style="{ color: isSuccess ? 'green' : 'red' }">
|
||||
<span>
|
||||
{{ isSuccess ? "✓" : "✗" }}
|
||||
</span>
|
||||
<span class="ml-2">
|
||||
{{ result.message }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="result" class="result-content">
|
||||
<div v-if="error" class="error-message">
|
||||
<span style="color: red">{{ error }}</span>
|
||||
</div>
|
||||
<div v-else-if="isNetTestResult">
|
||||
<div v-if="resultTestLog" class="test-log">
|
||||
<pre>{{ resultTestLog }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="typeof result === 'object'" class="object-result">
|
||||
<pre>{{ JSON.stringify(result, null, 2) }}</pre>
|
||||
</div>
|
||||
<div v-else class="text-result">
|
||||
<pre>{{ result }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-result">
|
||||
<p>暂无结果</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
|
||||
// 组件属性
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
port?: number | string;
|
||||
testMethod: () => Promise<any>;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
// 内部状态
|
||||
const loading = ref(false);
|
||||
const result = ref<any>(null);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
// 运行测试
|
||||
const runTest = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
result.value = null;
|
||||
try {
|
||||
const testResult = await props.testMethod();
|
||||
// 如果结果有 data 属性,则使用 data,否则使用整个结果
|
||||
result.value = testResult.data || testResult;
|
||||
} catch (err: any) {
|
||||
result.value = null;
|
||||
error.value = err.message || "测试失败";
|
||||
message.error(`${props.title} 测试失败: ${error.value}`);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
test: runTest,
|
||||
});
|
||||
|
||||
// 辅助计算属性,用于模板中显示结果
|
||||
const isNetTestResult = computed(() => {
|
||||
return typeof result.value === "object" && result.value !== null && "success" in result.value && "message" in result.value && "testLog" in result.value;
|
||||
});
|
||||
|
||||
const isSuccess = computed(() => {
|
||||
return isNetTestResult.value && result.value.success;
|
||||
});
|
||||
|
||||
const resultMessage = computed(() => {
|
||||
return isNetTestResult.value ? result.value.message : "";
|
||||
});
|
||||
|
||||
const resultTestLog = computed(() => {
|
||||
return isNetTestResult.value ? result.value.testLog : "";
|
||||
});
|
||||
|
||||
const resultError = computed(() => {
|
||||
return isNetTestResult.value ? result.value.error : "";
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.test-case {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.case-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.result-status {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.case-title {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.port-info {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
background-color: #f0f0f0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.test-button {
|
||||
color: #1890ff;
|
||||
font-size: 12px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
.error-message,
|
||||
.object-result,
|
||||
.text-result {
|
||||
background-color: #f8f8f8;
|
||||
padding: 8px 10px;
|
||||
border-radius: 3px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.test-log {
|
||||
background-color: #f8f8f8;
|
||||
padding: 8px 10px;
|
||||
border-radius: 3px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.no-result {
|
||||
padding: 12px 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
import { request } from "/@/api/service";
|
||||
|
||||
export async function DomainResolve(domain: string) {
|
||||
return await request({
|
||||
url: "/sys/nettest/domainResolve",
|
||||
method: "post",
|
||||
data: { domain },
|
||||
});
|
||||
}
|
||||
|
||||
export async function PingTest(domain: string) {
|
||||
return await request({
|
||||
url: "/sys/nettest/ping",
|
||||
method: "post",
|
||||
data: { domain },
|
||||
});
|
||||
}
|
||||
|
||||
export async function TelnetTest(domain: string, port: number) {
|
||||
return await request({
|
||||
url: "/sys/nettest/telnet",
|
||||
method: "post",
|
||||
data: { domain, port },
|
||||
});
|
||||
}
|
||||
|
||||
// 获取服务器信息(包括本地IP、外网IP和DNS服务器)
|
||||
export async function GetServerInfo() {
|
||||
return await request({
|
||||
url: "/sys/nettest/serverInfo",
|
||||
method: "post",
|
||||
});
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<fs-page class="page-sys-nettest">
|
||||
<template #header>
|
||||
<div class="title">网络测试</div>
|
||||
</template>
|
||||
<div class="nettest-container">
|
||||
<!-- 服务端信息 -->
|
||||
<server-info-card />
|
||||
|
||||
<!-- 测试区域 -->
|
||||
<div class="test-areas">
|
||||
<!-- 用户输入域名测试 -->
|
||||
<domain-test-card class="w-50%" />
|
||||
|
||||
<!-- 百度域名测试 (用于对比) -->
|
||||
<domain-test-card class="w-50%" :domain="'baidu.com'" :port="443" :auto-start="true" />
|
||||
</div>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import DomainTestCard from "./DomainTestCard.vue";
|
||||
import ServerInfoCard from "./ServerInfoCard.vue";
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-sys-nettest {
|
||||
.nettest-container {
|
||||
padding: 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.test-areas {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,47 @@
|
|||
import { BaseController } from '@certd/lib-server';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
||||
import { NetTestService } from '../../../modules/sys/nettest/nettest-service.js';
|
||||
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/sys/nettest/')
|
||||
export class SysNetTestController extends BaseController {
|
||||
|
||||
@Inject()
|
||||
netTestService: NetTestService;
|
||||
|
||||
|
||||
@Post('/domainResolve', { summary: 'sys:settings:view' })
|
||||
public async domainResolve(@Body(ALL) body: { domain: string }) {
|
||||
|
||||
const { domain } = body;
|
||||
const result = await this.netTestService.domainResolve(domain);
|
||||
return this.ok(result);
|
||||
}
|
||||
|
||||
// ping
|
||||
@Post('/ping', { summary: 'sys:settings:view' })
|
||||
public async ping(@Body(ALL) body: { domain: string }) {
|
||||
|
||||
const { domain } = body;
|
||||
const result = await this.netTestService.ping(domain);
|
||||
return this.ok(result);
|
||||
}
|
||||
|
||||
// telnet
|
||||
@Post('/telnet', { summary: 'sys:settings:view' })
|
||||
public async telnet(@Body(ALL) body: { domain: string, port: number }) {
|
||||
|
||||
const { domain, port } = body;
|
||||
const result = await this.netTestService.telnet(domain, port);
|
||||
return this.ok(result);
|
||||
}
|
||||
|
||||
// telnet
|
||||
@Post('/serverInfo', { summary: 'sys:settings:view' })
|
||||
public async serverInfo() {
|
||||
|
||||
const result = await this.netTestService.serverInfo();
|
||||
return this.ok(result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { http, utils } from '@certd/basic';
|
||||
|
||||
// 使用@certd/basic包中已有的utils.sp.spawn函数替代自定义的asyncExec
|
||||
// 该函数已经内置了Windows系统编码问题的解决方案
|
||||
|
||||
export type NetTestResult = {
|
||||
success: boolean; //是否成功
|
||||
message: string; //结果
|
||||
testLog: string; //测试日志
|
||||
error?: string; //执行错误信息
|
||||
}
|
||||
|
||||
@Provide('nettestService')
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class NetTestService {
|
||||
/**
|
||||
* 执行Telnet测试
|
||||
* @param domain 域名
|
||||
* @param port 端口
|
||||
* @returns 测试结果
|
||||
*/
|
||||
async telnet(domain: string, port: number): Promise<NetTestResult> {
|
||||
try {
|
||||
let command = '';
|
||||
|
||||
if (this.isWindows()) {
|
||||
// Windows系统使用PowerShell执行测试,避免输入重定向问题
|
||||
// 使用PowerShell的Test-NetConnection命令进行端口测试
|
||||
command = `powershell -Command "& { $result = Test-NetConnection -ComputerName ${domain} -Port ${port} -InformationLevel Quiet; if ($result) { Write-Host '端口连接成功' } else { Write-Host '端口连接失败' } }"`;
|
||||
} else {
|
||||
// Linux系统使用nc命令进行端口测试
|
||||
command = `nc -zv -w 5 ${domain} ${port} 2>&1`;
|
||||
}
|
||||
|
||||
// 使用utils.sp.spawn执行命令,它会自动处理Windows编码问题
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: command,
|
||||
logger: undefined // 可以根据需要传入logger
|
||||
});
|
||||
|
||||
// 判断测试是否成功
|
||||
const success = this.isWindows()
|
||||
? output.includes('端口连接成功')
|
||||
: output.includes('succeeded') || output.includes('open');
|
||||
|
||||
// 处理结果
|
||||
return {
|
||||
success,
|
||||
message: success ? '端口连接测试成功' : '端口连接测试失败',
|
||||
testLog: output,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Telnet测试执行失败',
|
||||
testLog: error instanceof Error ? error.message : String(error),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Ping测试
|
||||
* @param domain 域名
|
||||
* @returns 测试结果
|
||||
*/
|
||||
async ping(domain: string): Promise<NetTestResult> {
|
||||
try {
|
||||
let command = '';
|
||||
|
||||
if (this.isWindows()) {
|
||||
// Windows系统ping命令,发送4个包
|
||||
command = `ping -n 4 ${domain}`;
|
||||
} else {
|
||||
// Linux系统ping命令,发送4个包
|
||||
command = `ping -c 4 ${domain}`;
|
||||
}
|
||||
|
||||
// 使用utils.sp.spawn执行命令
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: command,
|
||||
logger: undefined
|
||||
});
|
||||
|
||||
// 判断测试是否成功
|
||||
const success = this.isWindows()
|
||||
? output.includes('TTL=')
|
||||
: output.includes('0% packet loss');
|
||||
|
||||
return {
|
||||
success,
|
||||
message: success ? 'Ping测试成功' : 'Ping测试失败',
|
||||
testLog: output,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ping测试执行失败',
|
||||
testLog: errorMessage,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private isWindows() {
|
||||
return process.platform === 'win32';
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行域名解析测试
|
||||
* @param domain 域名
|
||||
* @returns 解析结果
|
||||
*/
|
||||
async domainResolve(domain: string): Promise<NetTestResult> {
|
||||
try {
|
||||
let command = '';
|
||||
if (this.isWindows()) {
|
||||
// Windows系统使用nslookup命令
|
||||
command = `nslookup ${domain}`;
|
||||
} else {
|
||||
// Linux系统优先使用dig命令,如果没有则回退到nslookup
|
||||
command = `which dig > /dev/null && dig ${domain} || nslookup ${domain}`;
|
||||
}
|
||||
|
||||
// 使用utils.sp.spawn执行命令
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: command,
|
||||
logger: undefined
|
||||
});
|
||||
|
||||
// 判断测试是否成功
|
||||
const success = output.includes('Address:') || output.includes('IN A') ||
|
||||
(this.isWindows() && output.includes('Name:'));
|
||||
|
||||
return {
|
||||
success,
|
||||
message: success ? '域名解析测试成功' : '域名解析测试失败',
|
||||
testLog: output,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
success: false,
|
||||
message: '域名解析测试执行失败',
|
||||
testLog: errorMessage,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getLocalIP(): Promise<string> {
|
||||
try {
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: 'bash -c "ip a | grep \'inet \' | grep -v \'127.0.0.1\' | awk \'{print $2}\' | cut -d/ -f1"',
|
||||
logger: undefined
|
||||
});
|
||||
return output.trim();
|
||||
} catch (error) {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getPublicIP(): Promise<string> {
|
||||
try {
|
||||
const res = await http.request({
|
||||
url:"https://ipinfo.io/ip",
|
||||
method:"GET",
|
||||
})
|
||||
return res
|
||||
} catch (error) {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getDNSservers(): Promise<string[]> {
|
||||
try {
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: 'cat /etc/resolv.conf | grep nameserver | awk \'{print $2}\'',
|
||||
logger: undefined
|
||||
});
|
||||
return output.trim().split('\n');
|
||||
} catch (error) {
|
||||
return [error instanceof Error ? error.message : String(error)];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取服务器信息(包括本地IP、外网IP和DNS服务器)
|
||||
* @returns 服务器信息
|
||||
*/
|
||||
async serverInfo(): Promise<any> {
|
||||
|
||||
const res = {
|
||||
localIP: '',
|
||||
publicIP: '',
|
||||
dnsServers: [],
|
||||
}
|
||||
|
||||
res.localIP = await this.getLocalIP();
|
||||
res.publicIP = await this.getPublicIP();
|
||||
res.dnsServers = await this.getDNSservers();
|
||||
return res
|
||||
}
|
||||
}
|
|
@ -8,9 +8,6 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
'@certd/ui-server':
|
||||
specifier: link:packages/ui/certd-server
|
||||
version: link:packages/ui/certd-server
|
||||
axios:
|
||||
specifier: ^1.7.7
|
||||
version: 1.9.0(debug@4.4.1)
|
||||
|
@ -49,7 +46,7 @@ importers:
|
|||
packages/core/acme-client:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../basic
|
||||
'@peculiar/x509':
|
||||
specifier: ^1.11.0
|
||||
|
@ -210,10 +207,10 @@ importers:
|
|||
packages/core/pipeline:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../basic
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../pro/plus-core
|
||||
dayjs:
|
||||
specifier: ^1.11.7
|
||||
|
@ -418,7 +415,7 @@ importers:
|
|||
packages/libs/lib-k8s:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/basic
|
||||
'@kubernetes/client-node':
|
||||
specifier: 0.21.0
|
||||
|
@ -458,19 +455,19 @@ importers:
|
|||
packages/libs/lib-server:
|
||||
dependencies:
|
||||
'@certd/acme-client':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/acme-client
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/basic
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-lib':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../plugins/plugin-lib
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../pro/plus-core
|
||||
'@midwayjs/cache':
|
||||
specifier: 3.14.0
|
||||
|
@ -616,16 +613,16 @@ importers:
|
|||
packages/plugins/plugin-cert:
|
||||
dependencies:
|
||||
'@certd/acme-client':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/acme-client
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/basic
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-lib':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../plugin-lib
|
||||
'@google-cloud/publicca':
|
||||
specifier: ^1.3.0
|
||||
|
@ -707,10 +704,10 @@ importers:
|
|||
specifier: ^3.787.0
|
||||
version: 3.810.0(aws-crt@1.26.2)
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/basic
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/pipeline
|
||||
'@kubernetes/client-node':
|
||||
specifier: 0.21.0
|
||||
|
@ -798,19 +795,19 @@ importers:
|
|||
packages/pro/commercial-core:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/basic
|
||||
'@certd/lib-server':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../libs/lib-server
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-plus':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../plugin-plus
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../plus-core
|
||||
'@midwayjs/core':
|
||||
specifier: 3.20.11
|
||||
|
@ -895,19 +892,19 @@ importers:
|
|||
specifier: ^1.0.2
|
||||
version: 1.0.3
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/basic
|
||||
'@certd/lib-k8s':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../libs/lib-k8s
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-cert':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../plugins/plugin-cert
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../plus-core
|
||||
ali-oss:
|
||||
specifier: ^6.21.0
|
||||
|
@ -1010,7 +1007,7 @@ importers:
|
|||
packages/pro/plus-core:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/basic
|
||||
dayjs:
|
||||
specifier: ^1.11.7
|
||||
|
@ -1306,10 +1303,10 @@ importers:
|
|||
version: 0.1.3(zod@3.24.4)
|
||||
devDependencies:
|
||||
'@certd/lib-iframe':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../libs/lib-iframe
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/pipeline
|
||||
'@rollup/plugin-commonjs':
|
||||
specifier: ^25.0.7
|
||||
|
@ -1492,46 +1489,46 @@ importers:
|
|||
specifier: ^3.705.0
|
||||
version: 3.810.0(aws-crt@1.26.2)
|
||||
'@certd/acme-client':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/acme-client
|
||||
'@certd/basic':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/basic
|
||||
'@certd/commercial-core':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../pro/commercial-core
|
||||
'@certd/cv4pve-api-javascript':
|
||||
specifier: ^8.4.2
|
||||
version: 8.4.2
|
||||
'@certd/jdcloud':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../libs/lib-jdcloud
|
||||
'@certd/lib-huawei':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../libs/lib-huawei
|
||||
'@certd/lib-k8s':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../libs/lib-k8s
|
||||
'@certd/lib-server':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../libs/lib-server
|
||||
'@certd/midway-flyway-js':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../libs/midway-flyway-js
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-cert':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../plugins/plugin-cert
|
||||
'@certd/plugin-lib':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../plugins/plugin-lib
|
||||
'@certd/plugin-plus':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../pro/plugin-plus
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.36.24
|
||||
specifier: ^1.37.1
|
||||
version: link:../../pro/plus-core
|
||||
'@huaweicloud/huaweicloud-sdk-cdn':
|
||||
specifier: ^3.1.120
|
||||
|
|
Loading…
Reference in New Issue