From 8e8bbbf9abbd56d7e219c51d9c158ef874623522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E7=BA=B8=E5=BF=98=E5=BF=A7?= Date: Tue, 24 Mar 2026 21:51:37 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20KMS=20?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E7=BB=93=E6=9E=9C=E8=AF=A6=E6=83=85=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/AppFooter.vue | 46 +++-- app/pages/check/index.vue | 386 ++++++++++++++++++++++++++++++++++- app/pages/index.vue | 6 +- i18n/locales/en.json | 60 +++++- i18n/locales/zh-cn.json | 60 +++++- nuxt.config.ts | 2 +- 6 files changed, 527 insertions(+), 33 deletions(-) diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index 58fe4e41..5b0ba18f 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -1,32 +1,46 @@ diff --git a/app/pages/check/index.vue b/app/pages/check/index.vue index 52abc032..823c6de9 100644 --- a/app/pages/check/index.vue +++ b/app/pages/check/index.vue @@ -26,6 +26,245 @@ const resultInfo = reactive<{ visible: false }) +type KeyValueItem = { + label: string + value: string +} + +type ParsedOutput = { + requestItems: KeyValueItem[] + responseItems: KeyValueItem[] + rpcLines: string[] + errors: string[] + connectionLine: string +} + +const requestParamLabelMap: Record = { + 'Protocol version': 'pages.check.result.request-params.protocol-version', + 'Client is a virtual machine': 'pages.check.result.request-params.client-is-vm', + 'Licensing status': 'pages.check.result.request-params.licensing-status', + 'Remaining time (0 = forever)': 'pages.check.result.request-params.remaining-time', + 'Application ID': 'pages.check.result.request-params.application-id', + 'SKU ID (aka Activation ID)': 'pages.check.result.request-params.sku-id', + 'KMS ID (aka KMS counted ID)': 'pages.check.result.request-params.kms-id', + 'Client machine ID': 'pages.check.result.request-params.client-machine-id', + 'Previous client machine ID': + 'pages.check.result.request-params.previous-client-machine-id', + 'Client request timestamp (UTC)': + 'pages.check.result.request-params.client-request-timestamp-utc', + 'Workstation name': 'pages.check.result.request-params.workstation-name', + 'N count policy (minimum clients)': + 'pages.check.result.request-params.n-count-policy' +} + +const responseParamLabelMap: Record = { + 'Size of KMS Response': 'pages.check.result.response-params.kms-response-size', + 'KMS ePID': 'pages.check.result.response-params.kms-epid', + 'KMS HwId': 'pages.check.result.response-params.kms-hwid', + 'Client machine ID': 'pages.check.result.response-params.client-machine-id', + 'Client request timestamp (UTC)': + 'pages.check.result.response-params.client-request-timestamp-utc', + 'KMS host current active clients': + 'pages.check.result.response-params.kms-host-current-active-clients', + 'Renewal interval policy': + 'pages.check.result.response-params.renewal-interval-policy', + 'Activation interval policy': + 'pages.check.result.response-params.activation-interval-policy' +} + +const parsedOutput = computed(() => { + const message = resultInfo.message + if (!message) { + return { + requestItems: [], + responseItems: [], + rpcLines: [], + errors: [], + connectionLine: '' + } + } + + const lines = message + .replace(/\r/g, '') + .split('\n') + .map(line => line.trimEnd()) + + const requestStart = lines.findIndex(line => + line.includes('Request Parameters') + ) + const responseStart = lines.findIndex(line => + line.includes('Response from KMS server') + ) + const connectionStart = lines.findIndex(line => + line.trimStart().startsWith('Connecting to ') + ) + const rpcStart = lines.findIndex(line => line.includes('Performing RPC bind')) + + const requestEnd = getSectionEnd( + [connectionStart, rpcStart, responseStart], + requestStart, + lines.length + ) + + const requestItems = parseKnownKeyValueBlock( + lines, + requestStart >= 0 ? requestStart + 1 : -1, + requestEnd, + requestParamLabelMap + ) + + const responseItems = parseKnownKeyValueBlock( + lines, + responseStart >= 0 ? responseStart + 1 : -1, + lines.length, + responseParamLabelMap + ) + + const connectionLine = + lines.find(line => line.trimStart().startsWith('Connecting to ')) || '' + + const rpcLines = + rpcStart >= 0 + ? lines + .slice(rpcStart, responseStart > rpcStart ? responseStart : lines.length) + .filter(line => line.trim()) + : [] + + const errorMatcher = /timed out|timeout|failed|error|refused|unreachable/i + const errors = lines + .filter(line => errorMatcher.test(line) && line.trim()) + .map(line => line.trim()) + + if (resultInfo.type === 'error' && !errors.length) { + const fallbackError = [...lines] + .reverse() + .find( + line => + line.trim() && + !line.includes('Request Parameters') && + !line.includes('Response from KMS server') && + !/^=+$/.test(line.trim()) + ) + if (fallbackError) { + errors.push(fallbackError.trim()) + } + } + + return { + requestItems, + responseItems, + rpcLines, + errors, + connectionLine + } +}) + +const statusMeta = computed(() => { + if (resultInfo.type === 'success') { + return { + title: t('pages.check.result.passed-title'), + desc: t('pages.check.result.passed-desc'), + color: 'green' + } + } + + if (resultInfo.type === 'error') { + return { + title: t('pages.check.result.failed-title'), + desc: t('pages.check.result.failed-desc'), + color: 'red' + } + } + + return { + title: t('pages.check.result.default-title'), + desc: t('pages.check.result.default-desc'), + color: 'arcoblue' + } +}) + +const rpcText = computed(() => parsedOutput.value.rpcLines.join('\n')) + +const rawOutputText = computed(() => resultInfo.message.replace(/^\r?\n+/, '')) + +function getRequestLabelI18n(label: string): string { + const i18nKey = requestParamLabelMap[label] + if (!i18nKey) { + return label + } + return t(i18nKey) +} + +function getResponseLabelI18n(label: string): string { + const i18nKey = responseParamLabelMap[label] + if (!i18nKey) { + return label + } + return t(i18nKey) +} + +function parseKeyValueBlock( + lines: string[], + start: number, + end: number +): KeyValueItem[] { + if (start < 0 || start >= end) { + return [] + } + + const result: KeyValueItem[] = [] + for (let i = start; i < end; i++) { + const current = lines[i]?.trim() || '' + if (!current || /^=+$/.test(current)) { + continue + } + + const matched = current.match(/^([^:]+?)\s*:\s*(.+)$/) + if (!matched) { + continue + } + + const rawLabel = matched[1] + const rawValue = matched[2] + if (!rawLabel || !rawValue) { + continue + } + + result.push({ + label: rawLabel.trim(), + value: rawValue.trim() + }) + } + return result +} + +function parseKnownKeyValueBlock( + lines: string[], + start: number, + end: number, + labelMap: Record +): KeyValueItem[] { + const allItems = parseKeyValueBlock(lines, start, end) + return allItems.filter(item => !!labelMap[item.label]) +} + +function getSectionEnd( + candidates: number[], + start: number, + fallbackEnd: number +): number { + if (start < 0) { + return fallbackEnd + } + + const validEnds = candidates.filter(index => index > start) + if (!validEnds.length) { + return fallbackEnd + } + + return Math.min(...validEnds) +} + const handleSubmit = async (data: { values: Record errors: Record | undefined @@ -33,14 +272,17 @@ const handleSubmit = async (data: { if (data.errors === undefined) { resultInfo.loading = true try { - const data = await request()('/api/check', { + const response = await request()('/api/check', { query: formData.value }) - resultInfo.message = data.content - resultInfo.type = data.status ? 'success' : 'error' + resultInfo.message = response.content + resultInfo.type = response.status ? 'success' : 'error' resultInfo.visible = true resultInfo.loading = false } catch (err) { + resultInfo.message = String(err || t('pages.check.result.unknown-request-error')) + resultInfo.type = 'error' + resultInfo.visible = true resultInfo.loading = false } } @@ -86,14 +328,136 @@ const handleSubmit = async (data: { - - {{ resultInfo.message }} - + +
+ +
+
+
+ + + {{ statusMeta.title }} + +
+ {{ statusMeta.desc }} +
+ {{ resultInfo.type.toUpperCase() }} +
+ +
+ {{ + t('pages.check.result.connection-title') + }}: + {{ parsedOutput.connectionLine }} +
+
+ + +
+
+ {{ line }} +
+
+
+ +
+ +
+
+
+ {{ getRequestLabelI18n(item.label) }} +
+
+ {{ item.value }} +
+
+
+ {{ t('pages.check.result.request-empty') }} +
+
+
+ + +
+
+
+ {{ getResponseLabelI18n(item.label) }} +
+
+ {{ item.value }} +
+
+
+ {{ t('pages.check.result.response-empty') }} +
+
+
+
+ + +
{{ rpcText }}
+
+ + + +
{{ rawOutputText }}
+
+
+
diff --git a/app/pages/index.vue b/app/pages/index.vue index a9101bea..022c878a 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -26,9 +26,9 @@ const features = computed(() => [
-
+

@@ -57,7 +57,7 @@ const features = computed(() => [

-