@liujitcn/kratos-admin-system 0.0.27 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package/declarations/src/api/system/runtime_log.d.ts +13 -0
- package/dist/package/declarations/src/api/system/runtime_log_sse.d.ts +8 -0
- package/dist/package/declarations/src/locales/generated.d.ts +116 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/ops_monitoring.d.ts +4 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/project_document.d.ts +4 -6
- package/dist/package/declarations/src/rpc/system/admin/v1/runtime_log.d.ts +96 -0
- package/dist/package/declarations/src/views/tool/runtime-log/index.vue.d.ts +3 -0
- package/dist/package/src/api/system/runtime_log.ts +80 -0
- package/dist/package/src/api/system/runtime_log_sse.ts +40 -0
- package/dist/package/src/locales/en-US.json +31 -2
- package/dist/package/src/locales/ja-JP.json +31 -2
- package/dist/package/src/locales/zh-CN.json +31 -2
- package/dist/package/src/locales/zh-TW.json +31 -2
- package/dist/package/src/rpc/system/admin/v1/ops_monitoring.ts +4 -0
- package/dist/package/src/rpc/system/admin/v1/project_document.ts +4 -7
- package/dist/package/src/rpc/system/admin/v1/runtime_log.ts +113 -0
- package/dist/package/src/views/tool/api-doc/index.vue +14 -6
- package/dist/package/src/views/tool/code-gen/code-preview/index.vue +17 -0
- package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +96 -14
- package/dist/package/src/views/tool/code-gen/table/index.vue +32 -0
- package/dist/package/src/views/tool/ops-monitoring/index.vue +130 -24
- package/dist/package/src/views/tool/project-doc/index.vue +9 -4
- package/dist/package/src/views/tool/runtime-log/index.vue +883 -0
- package/package.json +2 -2
|
@@ -28,6 +28,8 @@ export interface ProjectDocumentListItem {
|
|
|
28
28
|
id: string;
|
|
29
29
|
/** 项目内相对路径 */
|
|
30
30
|
path: string;
|
|
31
|
+
/** 当前语言下的文档显示文件名 */
|
|
32
|
+
name: string;
|
|
31
33
|
/** Markdown文件更新时间,RFC3339格式 */
|
|
32
34
|
updated_at: string;
|
|
33
35
|
}
|
|
@@ -66,19 +68,14 @@ export interface ProjectDocument {
|
|
|
66
68
|
project_name: string;
|
|
67
69
|
/** 项目内相对路径 */
|
|
68
70
|
path: string;
|
|
71
|
+
/** 当前语言下的文档显示文件名 */
|
|
72
|
+
name: string;
|
|
69
73
|
/** Markdown文档内容 */
|
|
70
74
|
content: string;
|
|
71
|
-
/** 按语言标识索引的Markdown翻译内容 */
|
|
72
|
-
locale: Map<string, string>;
|
|
73
75
|
/** Markdown文件更新时间,RFC3339格式 */
|
|
74
76
|
updated_at: string;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
export interface ProjectDocument_LocaleEntry {
|
|
78
|
-
key: string;
|
|
79
|
-
value: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
79
|
/** 项目文档服务 */
|
|
83
80
|
export interface ProjectDocumentService {
|
|
84
81
|
/** 查询项目文档树 */
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
2
|
+
// versions:
|
|
3
|
+
// protoc-gen-ts_proto v2.11.8
|
|
4
|
+
// protoc unknown
|
|
5
|
+
// source: system/admin/v1/runtime_log.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
|
|
9
|
+
/** 历史日志文件列表查询条件。 */
|
|
10
|
+
export interface ListRuntimeLogFilesRequest {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 历史日志文件列表响应。 */
|
|
14
|
+
export interface ListRuntimeLogFilesResponse {
|
|
15
|
+
/** 历史日志文件列表 */
|
|
16
|
+
files: RuntimeLogFile[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 历史日志文件信息。 */
|
|
20
|
+
export interface RuntimeLogFile {
|
|
21
|
+
/** 服务端签发的日志文件标识 */
|
|
22
|
+
file_id: string;
|
|
23
|
+
/** 日志文件名称 */
|
|
24
|
+
name: string;
|
|
25
|
+
/** 日志文件大小,单位字节 */
|
|
26
|
+
size_bytes: number;
|
|
27
|
+
/** 最后修改时间,RFC3339格式 */
|
|
28
|
+
modified_at: string;
|
|
29
|
+
/** 是否为Gzip压缩文件 */
|
|
30
|
+
is_compressed: boolean;
|
|
31
|
+
/** 是否为当前活动日志文件 */
|
|
32
|
+
is_active: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 历史日志文件读取条件。 */
|
|
36
|
+
export interface ReadRuntimeLogFileRequest {
|
|
37
|
+
/** 服务端签发的日志文件标识 */
|
|
38
|
+
file_id: string;
|
|
39
|
+
/** 向更早日志翻页的不透明游标,留空时从文件尾部读取 */
|
|
40
|
+
cursor: string;
|
|
41
|
+
/** 单次读取条数,0使用默认值,最大500 */
|
|
42
|
+
limit: number;
|
|
43
|
+
/** 日志内容搜索关键字 */
|
|
44
|
+
keyword: string;
|
|
45
|
+
/** 日志级别筛选列表 */
|
|
46
|
+
levels: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 历史日志文件读取响应。 */
|
|
50
|
+
export interface ReadRuntimeLogFileResponse {
|
|
51
|
+
/** 按时间正序排列的日志条目 */
|
|
52
|
+
entries: RuntimeLogEntry[];
|
|
53
|
+
/** 继续读取更早日志的不透明游标 */
|
|
54
|
+
next_cursor: string;
|
|
55
|
+
/** 是否还有更早日志 */
|
|
56
|
+
has_more: boolean;
|
|
57
|
+
/** 读取期间日志文件是否已滚动或截断 */
|
|
58
|
+
file_changed: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 实时或历史日志条目。 */
|
|
62
|
+
export interface RuntimeLogEntry {
|
|
63
|
+
/** 当前进程内的日志序号,历史文件日志为空 */
|
|
64
|
+
sequence: string;
|
|
65
|
+
/** 日志时间 */
|
|
66
|
+
timestamp: string;
|
|
67
|
+
/** 日志级别 */
|
|
68
|
+
level: string;
|
|
69
|
+
/** 日志来源 */
|
|
70
|
+
source: string;
|
|
71
|
+
/** 日志消息 */
|
|
72
|
+
message: string;
|
|
73
|
+
/** 可直接展示的纯文本日志行 */
|
|
74
|
+
line: string;
|
|
75
|
+
/** 日志行是否因长度限制被截断 */
|
|
76
|
+
is_truncated: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 实时控制台开启条件。 */
|
|
80
|
+
export interface OpenRuntimeConsoleRequest {
|
|
81
|
+
/** 首次返回的最近日志条数,0使用默认值,最大500 */
|
|
82
|
+
backlog_limit: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 实时控制台开启响应。 */
|
|
86
|
+
export interface OpenRuntimeConsoleResponse {
|
|
87
|
+
/** SSE业务流标识 */
|
|
88
|
+
stream: string;
|
|
89
|
+
/** 当前用户专属SSE频道标识 */
|
|
90
|
+
channel_id: string;
|
|
91
|
+
/** 频道过期时间,RFC3339格式 */
|
|
92
|
+
expires_at: string;
|
|
93
|
+
/** 当前服务实例标识 */
|
|
94
|
+
instance_id: string;
|
|
95
|
+
/** 当前进程最新日志序号 */
|
|
96
|
+
latest_sequence: string;
|
|
97
|
+
/** 当前进程最近日志 */
|
|
98
|
+
entries: RuntimeLogEntry[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 实时控制台丢失消息提示。 */
|
|
102
|
+
export interface RuntimeLogGap {
|
|
103
|
+
/** 本次丢弃的日志条数 */
|
|
104
|
+
dropped_count: number;
|
|
105
|
+
/** 发生丢弃时的最新日志序号 */
|
|
106
|
+
latest_sequence: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 历史日志文件下载条件。 */
|
|
110
|
+
export interface DownloadRuntimeLogFileRequest {
|
|
111
|
+
/** 服务端签发的日志文件标识 */
|
|
112
|
+
file_id: string;
|
|
113
|
+
}
|
|
@@ -27,15 +27,16 @@
|
|
|
27
27
|
<script setup lang="ts">
|
|
28
28
|
import SwaggerUIBundle from "swagger-ui-dist/swagger-ui-bundle.js";
|
|
29
29
|
import "swagger-ui-dist/swagger-ui.css";
|
|
30
|
+
import { DEFAULT_LOCALE, t, useLocaleStore } from "@liujitcn/kratos-admin-core";
|
|
30
31
|
import { getLocaleRequestHeaders, getRequestAccessToken } from "@liujitcn/kratos-admin-core/request";
|
|
31
32
|
import { defBaseApiService } from "@liujitcn/kratos-admin-system/api/system/base_api";
|
|
32
33
|
import type { OpenApiServiceOption } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_api";
|
|
33
|
-
import { t } from "@liujitcn/kratos-admin-core";
|
|
34
34
|
|
|
35
35
|
const swaggerRootRef = ref<HTMLElement>();
|
|
36
36
|
const loading = ref(true);
|
|
37
37
|
const documents = ref<OpenApiServiceOption[]>([]);
|
|
38
38
|
const selectedDocumentKey = ref("");
|
|
39
|
+
const { locale } = useLocaleStore();
|
|
39
40
|
|
|
40
41
|
/** 初始化携带当前登录令牌的 Swagger UI。 */
|
|
41
42
|
function initializeSwaggerUI(documentKey: string) {
|
|
@@ -44,9 +45,10 @@ function initializeSwaggerUI(documentKey: string) {
|
|
|
44
45
|
|
|
45
46
|
loading.value = true;
|
|
46
47
|
swaggerRoot.replaceChildren();
|
|
48
|
+
const localePath = locale.value === DEFAULT_LOCALE ? "" : `/${encodeURIComponent(locale.value)}`;
|
|
47
49
|
SwaggerUIBundle({
|
|
48
50
|
domNode: swaggerRoot,
|
|
49
|
-
url: `/api/docs/openapi/${encodeURIComponent(documentKey)}`,
|
|
51
|
+
url: `/api/docs/openapi/${encodeURIComponent(documentKey)}${localePath}`,
|
|
50
52
|
deepLinking: true,
|
|
51
53
|
displayOperationId: true,
|
|
52
54
|
displayRequestDuration: true,
|
|
@@ -84,16 +86,18 @@ function handleDocumentChange(documentKey: string) {
|
|
|
84
86
|
|
|
85
87
|
/** 加载可访问的 OpenAPI 文档并初始化默认文档。 */
|
|
86
88
|
async function loadOpenAPIDocuments() {
|
|
89
|
+
loading.value = true;
|
|
87
90
|
try {
|
|
88
91
|
const response = await defBaseApiService.OptionOpenApiService({});
|
|
89
92
|
documents.value = response.list;
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
93
|
+
const selectedDocument = response.list.find(document => document.key === selectedDocumentKey.value) ?? response.list[0];
|
|
94
|
+
if (!selectedDocument) {
|
|
95
|
+
selectedDocumentKey.value = "";
|
|
92
96
|
loading.value = false;
|
|
93
97
|
return;
|
|
94
98
|
}
|
|
95
|
-
selectedDocumentKey.value =
|
|
96
|
-
initializeSwaggerUI(
|
|
99
|
+
selectedDocumentKey.value = selectedDocument.key;
|
|
100
|
+
initializeSwaggerUI(selectedDocument.key);
|
|
97
101
|
} catch {
|
|
98
102
|
loading.value = false;
|
|
99
103
|
}
|
|
@@ -103,6 +107,10 @@ onMounted(() => {
|
|
|
103
107
|
loadOpenAPIDocuments();
|
|
104
108
|
});
|
|
105
109
|
|
|
110
|
+
watch(locale, () => {
|
|
111
|
+
loadOpenAPIDocuments();
|
|
112
|
+
});
|
|
113
|
+
|
|
106
114
|
onBeforeUnmount(() => {
|
|
107
115
|
swaggerRootRef.value?.replaceChildren();
|
|
108
116
|
});
|
|
@@ -134,6 +134,23 @@ async function handleGenerate() {
|
|
|
134
134
|
ElMessage.warning(t("system.code.gen.table.message.disabled", { name: table.value.name }));
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
|
+
if (missingTranslations.value.length) {
|
|
138
|
+
try {
|
|
139
|
+
await ElMessageBox.alert(
|
|
140
|
+
t("system.code.gen.preview.message.missing_translations", {
|
|
141
|
+
items: missingTranslations.value.join(t("system.code.gen.preview.value.list_separator"))
|
|
142
|
+
}),
|
|
143
|
+
t("common.title.warning"),
|
|
144
|
+
{
|
|
145
|
+
confirmButtonText: t("common.action.close"),
|
|
146
|
+
type: "warning"
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
} catch {
|
|
150
|
+
// 关闭提示框与点击关闭按钮语义一致。
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
137
154
|
try {
|
|
138
155
|
await ElMessageBox.confirm(
|
|
139
156
|
t("system.code.gen.table.dialog.generate_one", { name: table.value.name }),
|
|
@@ -17,26 +17,57 @@
|
|
|
17
17
|
{{ t("system.code.gen.i18n.status.missing") }}
|
|
18
18
|
</el-tag>
|
|
19
19
|
</div>
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
<div class="code-gen-locale-editor__control">
|
|
21
|
+
<el-input
|
|
22
|
+
:model-value="localeValue(locale.value).comment"
|
|
23
|
+
:placeholder="t('system.code.gen.i18n.placeholder.comment', { source: sourceComment || '-' })"
|
|
24
|
+
:disabled="isTranslating(locale.value, 'comment')"
|
|
25
|
+
maxlength="255"
|
|
26
|
+
@update:model-value="value => updateLocale(locale.value, 'comment', value)"
|
|
27
|
+
/>
|
|
28
|
+
<el-button
|
|
29
|
+
class="code-gen-locale-editor__translate"
|
|
30
|
+
link
|
|
31
|
+
type="primary"
|
|
32
|
+
size="small"
|
|
33
|
+
:icon="Promotion"
|
|
34
|
+
:loading="isTranslating(locale.value, 'comment')"
|
|
35
|
+
:disabled="!canTranslate(locale.value, 'comment', sourceComment)"
|
|
36
|
+
@click="handleTranslate(locale.value, 'comment', sourceComment, locale.label)"
|
|
37
|
+
>
|
|
38
|
+
{{ t("system.base.translation.action.translate") }}
|
|
39
|
+
</el-button>
|
|
40
|
+
</div>
|
|
41
|
+
<div v-if="showLeftTreeComment" class="code-gen-locale-editor__control">
|
|
42
|
+
<el-input
|
|
43
|
+
:model-value="localeValue(locale.value).left_tree_comment"
|
|
44
|
+
:placeholder="t('system.code.gen.i18n.placeholder.left_tree_comment', { source: sourceLeftTreeComment || '-' })"
|
|
45
|
+
:disabled="isTranslating(locale.value, 'left_tree_comment')"
|
|
46
|
+
maxlength="255"
|
|
47
|
+
@update:model-value="value => updateLocale(locale.value, 'left_tree_comment', value)"
|
|
48
|
+
/>
|
|
49
|
+
<el-button
|
|
50
|
+
class="code-gen-locale-editor__translate"
|
|
51
|
+
link
|
|
52
|
+
type="primary"
|
|
53
|
+
size="small"
|
|
54
|
+
:icon="Promotion"
|
|
55
|
+
:loading="isTranslating(locale.value, 'left_tree_comment')"
|
|
56
|
+
:disabled="!canTranslate(locale.value, 'left_tree_comment', sourceLeftTreeComment)"
|
|
57
|
+
@click="handleTranslate(locale.value, 'left_tree_comment', sourceLeftTreeComment, locale.label)"
|
|
58
|
+
>
|
|
59
|
+
{{ t("system.base.translation.action.translate") }}
|
|
60
|
+
</el-button>
|
|
61
|
+
</div>
|
|
33
62
|
</div>
|
|
34
63
|
</div>
|
|
35
64
|
</template>
|
|
36
65
|
|
|
37
66
|
<script setup lang="ts">
|
|
38
|
-
import { computed } from "vue";
|
|
67
|
+
import { computed, ref } from "vue";
|
|
68
|
+
import { Promotion } from "@element-plus/icons-vue";
|
|
39
69
|
import { t } from "@liujitcn/kratos-admin-core";
|
|
70
|
+
import { defBaseI18nService } from "@liujitcn/kratos-admin-system/api/system/base_i18n";
|
|
40
71
|
import { loadEnabledBaseLanguages, useEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/base_language";
|
|
41
72
|
import type { CodeGenLocaleConfig } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
|
|
42
73
|
|
|
@@ -63,6 +94,7 @@ const emit = defineEmits<{
|
|
|
63
94
|
}>();
|
|
64
95
|
|
|
65
96
|
const { languages } = useEnabledBaseLanguages();
|
|
97
|
+
const translatingField = ref("");
|
|
66
98
|
void loadEnabledBaseLanguages();
|
|
67
99
|
|
|
68
100
|
const editableLocales = computed(() =>
|
|
@@ -92,6 +124,33 @@ function updateLocale(locale: string, field: keyof CodeGenLocaleConfig, value: s
|
|
|
92
124
|
next.set(locale, { ...localeValue(locale), [field]: value });
|
|
93
125
|
emit("update:modelValue", next);
|
|
94
126
|
}
|
|
127
|
+
|
|
128
|
+
/** 判断指定语言字段是否正在翻译。 */
|
|
129
|
+
function isTranslating(locale: string, field: keyof CodeGenLocaleConfig) {
|
|
130
|
+
return translatingField.value === `${locale}:${field}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 判断指定语言字段当前是否允许自动翻译。 */
|
|
134
|
+
function canTranslate(locale: string, field: keyof CodeGenLocaleConfig, source: string) {
|
|
135
|
+
return Boolean(source) && !localeValue(locale)[field] && !translatingField.value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 翻译指定语言的单个代码生成描述。 */
|
|
139
|
+
async function handleTranslate(locale: string, field: keyof CodeGenLocaleConfig, source: string, language: string) {
|
|
140
|
+
if (!canTranslate(locale, field, source)) return;
|
|
141
|
+
translatingField.value = `${locale}:${field}`;
|
|
142
|
+
try {
|
|
143
|
+
const response = await defBaseI18nService.DraftBaseTranslation({ source, locale });
|
|
144
|
+
const translation = response.translations.find(item => item.locale === locale)?.translation;
|
|
145
|
+
if (!translation) throw new Error("translation not found");
|
|
146
|
+
updateLocale(locale, field, translation);
|
|
147
|
+
ElMessage.success(t("system.base.translation.message.translate_success", { language }));
|
|
148
|
+
} catch {
|
|
149
|
+
ElMessage.error(t("system.base.translation.message.translate_failed", { language }));
|
|
150
|
+
} finally {
|
|
151
|
+
translatingField.value = "";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
95
154
|
</script>
|
|
96
155
|
|
|
97
156
|
<style scoped lang="scss">
|
|
@@ -115,4 +174,27 @@ function updateLocale(locale: string, field: keyof CodeGenLocaleConfig, value: s
|
|
|
115
174
|
font-weight: 600;
|
|
116
175
|
color: var(--el-text-color-primary);
|
|
117
176
|
}
|
|
177
|
+
|
|
178
|
+
.code-gen-locale-editor__control {
|
|
179
|
+
position: relative;
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
width: 100%;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.code-gen-locale-editor__control :deep(.el-input) {
|
|
186
|
+
flex: 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.code-gen-locale-editor__control :deep(.el-input__suffix) {
|
|
190
|
+
padding-right: 78px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.code-gen-locale-editor__translate {
|
|
194
|
+
position: absolute;
|
|
195
|
+
z-index: 1;
|
|
196
|
+
top: 50%;
|
|
197
|
+
right: 8px;
|
|
198
|
+
transform: translateY(-50%);
|
|
199
|
+
}
|
|
118
200
|
</style>
|
|
@@ -588,6 +588,38 @@ async function handleGenerate(selected: CodeGenGenerateTarget) {
|
|
|
588
588
|
ElMessage.warning(t("system.code.gen.table.message.disabled", { name: disabledTable.name }));
|
|
589
589
|
return;
|
|
590
590
|
}
|
|
591
|
+
generating.value = true;
|
|
592
|
+
let missingTranslations: string[] = [];
|
|
593
|
+
try {
|
|
594
|
+
const previewEntries = await Promise.all(
|
|
595
|
+
tables.map(async table => ({
|
|
596
|
+
table,
|
|
597
|
+
preview: await defCodeGenService.PreviewCodeGen({ table_id: table.id, output_paths: undefined })
|
|
598
|
+
}))
|
|
599
|
+
);
|
|
600
|
+
missingTranslations = previewEntries.flatMap(({ table, preview }) =>
|
|
601
|
+
(preview.missing_translations ?? []).map(item => (tables.length === 1 ? item : `${table.name}: ${item}`))
|
|
602
|
+
);
|
|
603
|
+
} finally {
|
|
604
|
+
generating.value = false;
|
|
605
|
+
}
|
|
606
|
+
if (missingTranslations.length) {
|
|
607
|
+
try {
|
|
608
|
+
await ElMessageBox.alert(
|
|
609
|
+
t("system.code.gen.preview.message.missing_translations", {
|
|
610
|
+
items: missingTranslations.join(t("system.code.gen.preview.value.list_separator"))
|
|
611
|
+
}),
|
|
612
|
+
t("common.title.warning"),
|
|
613
|
+
{
|
|
614
|
+
confirmButtonText: t("common.action.close"),
|
|
615
|
+
type: "warning"
|
|
616
|
+
}
|
|
617
|
+
);
|
|
618
|
+
} catch {
|
|
619
|
+
// 关闭提示框与点击关闭按钮语义一致。
|
|
620
|
+
}
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
591
623
|
const message =
|
|
592
624
|
tables.length === 1
|
|
593
625
|
? t("system.code.gen.table.dialog.generate_one", { name: tables[0].name })
|
|
@@ -189,10 +189,22 @@
|
|
|
189
189
|
<code>{{ node.name }}</code>
|
|
190
190
|
<div class="ops-node-meters">
|
|
191
191
|
<div v-for="metric in node.metrics" :key="metric.label" class="ops-meter">
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
>
|
|
195
|
-
|
|
192
|
+
<div class="ops-meter-head">
|
|
193
|
+
<span class="ops-meter-label">{{ metric.label }}</span>
|
|
194
|
+
<span class="ops-meter-stats">
|
|
195
|
+
<span>
|
|
196
|
+
{{ t("system.ops_monitoring.node_metric.current") }} {{ metric.usedLabel }} /
|
|
197
|
+
{{ t("system.ops_monitoring.node_metric.total") }} {{ metric.totalLabel }}
|
|
198
|
+
</span>
|
|
199
|
+
<strong>{{ metric.percentageLabel }}</strong>
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
<el-progress
|
|
203
|
+
:percentage="metric.percentage"
|
|
204
|
+
:show-text="false"
|
|
205
|
+
:stroke-width="10"
|
|
206
|
+
:color="metric.color"
|
|
207
|
+
/>
|
|
196
208
|
</div>
|
|
197
209
|
</div>
|
|
198
210
|
</div>
|
|
@@ -278,12 +290,28 @@ interface EndpointItem {
|
|
|
278
290
|
healthy: boolean;
|
|
279
291
|
}
|
|
280
292
|
|
|
281
|
-
/**
|
|
293
|
+
/** 实例资源单项指标。 */
|
|
294
|
+
interface NodeMetricItem {
|
|
295
|
+
/** 指标名称。 */
|
|
296
|
+
label: string;
|
|
297
|
+
/** 指标使用百分比。 */
|
|
298
|
+
percentage: number;
|
|
299
|
+
/** 两位小数百分比。 */
|
|
300
|
+
percentageLabel: string;
|
|
301
|
+
/** 当前已用容量。 */
|
|
302
|
+
usedLabel: string;
|
|
303
|
+
/** 总容量。 */
|
|
304
|
+
totalLabel: string;
|
|
305
|
+
/** 进度条颜色。 */
|
|
306
|
+
color: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** 实例资源信息。 */
|
|
282
310
|
interface NodeItem {
|
|
283
311
|
/** 实例名称。 */
|
|
284
312
|
name: string;
|
|
285
313
|
/** 实例资源指标。 */
|
|
286
|
-
metrics:
|
|
314
|
+
metrics: NodeMetricItem[];
|
|
287
315
|
}
|
|
288
316
|
|
|
289
317
|
/** 折线图中的流量趋势点。 */
|
|
@@ -334,6 +362,12 @@ const alerts = ref<OpsAlertsResponse>();
|
|
|
334
362
|
const windowMinutes = ref(15);
|
|
335
363
|
const lastCollectedAt = ref("");
|
|
336
364
|
const trendGridLines = [16, 62, 108, 154, 198];
|
|
365
|
+
const nodeMetricLabelKeys: Record<string, string> = {
|
|
366
|
+
堆内存: "system.ops_monitoring.node_metric.heap_memory",
|
|
367
|
+
内存: "system.ops_monitoring.node_metric.memory",
|
|
368
|
+
硬盘: "system.ops_monitoring.node_metric.disk"
|
|
369
|
+
};
|
|
370
|
+
const byteUnits = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
337
371
|
const trafficSummary = computed(() => traffic.value?.traffic);
|
|
338
372
|
|
|
339
373
|
const kpiItems = computed<KpiItem[]>(() => [
|
|
@@ -579,14 +613,46 @@ function mapEndpoint(endpoint: OpsEndpoint): EndpointItem {
|
|
|
579
613
|
function mapNode(node: OpsNode): NodeItem {
|
|
580
614
|
return {
|
|
581
615
|
name: node.name,
|
|
582
|
-
metrics: (node.metrics ?? []).map(metric =>
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
616
|
+
metrics: (node.metrics ?? []).map(metric => {
|
|
617
|
+
const percentage = Math.max(0, Math.min(metric.value, 100));
|
|
618
|
+
return {
|
|
619
|
+
label: translateNodeMetricLabel(metric.label),
|
|
620
|
+
percentage,
|
|
621
|
+
percentageLabel: `${formatFixedNumber(percentage)}%`,
|
|
622
|
+
usedLabel: formatBytes(metric.used_bytes),
|
|
623
|
+
totalLabel: formatBytes(metric.total_bytes),
|
|
624
|
+
color: percentage >= 70 ? "var(--el-color-warning)" : "var(--el-color-primary)"
|
|
625
|
+
};
|
|
626
|
+
})
|
|
587
627
|
};
|
|
588
628
|
}
|
|
589
629
|
|
|
630
|
+
/** 将字节数格式化为保留两位小数的可读容量。 */
|
|
631
|
+
function formatBytes(value: number) {
|
|
632
|
+
if (!Number.isFinite(value) || value < 0) return "--";
|
|
633
|
+
let unitIndex = 0;
|
|
634
|
+
let displayValue = value;
|
|
635
|
+
while (displayValue >= 1024 && unitIndex < byteUnits.length - 1) {
|
|
636
|
+
displayValue /= 1024;
|
|
637
|
+
unitIndex += 1;
|
|
638
|
+
}
|
|
639
|
+
return `${formatFixedNumber(displayValue)} ${byteUnits[unitIndex]}`;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** 将数值格式化为固定两位小数。 */
|
|
643
|
+
function formatFixedNumber(value: number) {
|
|
644
|
+
return new Intl.NumberFormat(getCurrentLocale(), {
|
|
645
|
+
minimumFractionDigits: 2,
|
|
646
|
+
maximumFractionDigits: 2
|
|
647
|
+
}).format(value);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** 翻译实例资源指标名称,未知指标保留后端原值。 */
|
|
651
|
+
function translateNodeMetricLabel(label: string) {
|
|
652
|
+
const localeKey = nodeMetricLabelKeys[label];
|
|
653
|
+
return localeKey ? t(localeKey) : label;
|
|
654
|
+
}
|
|
655
|
+
|
|
590
656
|
function mapAlert(alert: OpsAlert) {
|
|
591
657
|
return {
|
|
592
658
|
title: alert.title,
|
|
@@ -654,6 +720,7 @@ onBeforeUnmount(stopMonitoring);
|
|
|
654
720
|
.ops-panel,
|
|
655
721
|
.ops-storage-card,
|
|
656
722
|
.ops-alert-panel {
|
|
723
|
+
min-width: 0;
|
|
657
724
|
background: var(--ops-card-bg);
|
|
658
725
|
border: 1px solid var(--ops-card-border);
|
|
659
726
|
border-radius: var(--admin-page-radius);
|
|
@@ -934,7 +1001,7 @@ onBeforeUnmount(stopMonitoring);
|
|
|
934
1001
|
margin-bottom: 12px;
|
|
935
1002
|
}
|
|
936
1003
|
.ops-storage-grid {
|
|
937
|
-
grid-template-columns:
|
|
1004
|
+
grid-template-columns: minmax(0, 1.4fr) minmax(280px, 0.9fr);
|
|
938
1005
|
}
|
|
939
1006
|
.ops-storage-card {
|
|
940
1007
|
min-width: 0;
|
|
@@ -1040,37 +1107,70 @@ onBeforeUnmount(stopMonitoring);
|
|
|
1040
1107
|
.ops-node-list {
|
|
1041
1108
|
display: grid;
|
|
1042
1109
|
gap: 16px;
|
|
1110
|
+
min-width: 0;
|
|
1043
1111
|
padding-top: 3px;
|
|
1044
1112
|
}
|
|
1045
1113
|
.ops-node-row {
|
|
1046
1114
|
display: grid;
|
|
1047
|
-
grid-template-columns:
|
|
1048
|
-
gap:
|
|
1049
|
-
align-items:
|
|
1115
|
+
grid-template-columns: minmax(110px, 0.24fr) minmax(0, 1fr);
|
|
1116
|
+
gap: 18px;
|
|
1117
|
+
align-items: start;
|
|
1118
|
+
min-width: 0;
|
|
1050
1119
|
}
|
|
1051
1120
|
.ops-node-row > code {
|
|
1121
|
+
overflow: hidden;
|
|
1052
1122
|
font-family: var(--el-font-family-monospace);
|
|
1053
1123
|
font-size: 11px;
|
|
1054
1124
|
color: var(--ops-text-secondary);
|
|
1125
|
+
text-overflow: ellipsis;
|
|
1126
|
+
white-space: nowrap;
|
|
1055
1127
|
}
|
|
1056
1128
|
.ops-node-meters {
|
|
1057
1129
|
display: grid;
|
|
1058
|
-
gap:
|
|
1130
|
+
gap: 13px;
|
|
1131
|
+
min-width: 0;
|
|
1059
1132
|
}
|
|
1060
1133
|
.ops-meter {
|
|
1061
1134
|
display: grid;
|
|
1062
|
-
grid-template-columns: 34px minmax(0, 1fr) 35px;
|
|
1063
1135
|
gap: 7px;
|
|
1064
|
-
|
|
1065
|
-
font-size:
|
|
1136
|
+
min-width: 0;
|
|
1137
|
+
font-size: 11px;
|
|
1066
1138
|
color: var(--ops-text-placeholder);
|
|
1067
1139
|
}
|
|
1068
|
-
.ops-meter
|
|
1069
|
-
|
|
1070
|
-
|
|
1140
|
+
.ops-meter-head,
|
|
1141
|
+
.ops-meter-stats {
|
|
1142
|
+
display: flex;
|
|
1143
|
+
flex-wrap: wrap;
|
|
1144
|
+
gap: 4px 10px;
|
|
1145
|
+
min-width: 0;
|
|
1146
|
+
align-items: center;
|
|
1147
|
+
justify-content: space-between;
|
|
1148
|
+
}
|
|
1149
|
+
.ops-meter-label {
|
|
1150
|
+
flex: none;
|
|
1151
|
+
font-weight: 600;
|
|
1071
1152
|
color: var(--ops-text-secondary);
|
|
1153
|
+
}
|
|
1154
|
+
.ops-meter-stats {
|
|
1155
|
+
flex: 1;
|
|
1156
|
+
justify-content: flex-end;
|
|
1072
1157
|
text-align: right;
|
|
1073
1158
|
}
|
|
1159
|
+
.ops-meter-stats > span {
|
|
1160
|
+
overflow-wrap: anywhere;
|
|
1161
|
+
}
|
|
1162
|
+
.ops-meter-stats strong {
|
|
1163
|
+
flex: none;
|
|
1164
|
+
font-size: 11px;
|
|
1165
|
+
font-weight: 650;
|
|
1166
|
+
color: var(--ops-text-secondary);
|
|
1167
|
+
font-variant-numeric: tabular-nums;
|
|
1168
|
+
}
|
|
1169
|
+
.ops-meter :deep(.el-progress),
|
|
1170
|
+
.ops-meter :deep(.el-progress-bar) {
|
|
1171
|
+
min-width: 0;
|
|
1172
|
+
width: 100%;
|
|
1173
|
+
}
|
|
1074
1174
|
.ops-alert-panel {
|
|
1075
1175
|
margin-bottom: 0;
|
|
1076
1176
|
}
|
|
@@ -1125,6 +1225,7 @@ onBeforeUnmount(stopMonitoring);
|
|
|
1125
1225
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1126
1226
|
}
|
|
1127
1227
|
.ops-primary-grid,
|
|
1228
|
+
.ops-storage-grid,
|
|
1128
1229
|
.ops-secondary-grid {
|
|
1129
1230
|
grid-template-columns: 1fr;
|
|
1130
1231
|
}
|
|
@@ -1159,8 +1260,13 @@ onBeforeUnmount(stopMonitoring);
|
|
|
1159
1260
|
margin-left: 54px;
|
|
1160
1261
|
}
|
|
1161
1262
|
.ops-node-row {
|
|
1162
|
-
grid-template-columns:
|
|
1163
|
-
gap:
|
|
1263
|
+
grid-template-columns: 1fr;
|
|
1264
|
+
gap: 10px;
|
|
1265
|
+
}
|
|
1266
|
+
.ops-meter-stats {
|
|
1267
|
+
flex-basis: 100%;
|
|
1268
|
+
justify-content: flex-start;
|
|
1269
|
+
text-align: left;
|
|
1164
1270
|
}
|
|
1165
1271
|
.ops-alert-row {
|
|
1166
1272
|
grid-template-columns: 8px minmax(0, 1fr);
|