Merge branch 'main' into i18n

This commit is contained in:
unitwk 2024-01-09 14:56:20 +08:00
commit 2873784c7f
21 changed files with 192 additions and 61 deletions

30
common/global.d.ts vendored
View File

@ -24,20 +24,7 @@ export interface IGlobalInstanceConfig {
autoRestart: boolean;
ignore: boolean;
};
docker: {
containerName?: string;
image?: string;
memory?: number;
ports?: string[];
extraVolumes?: string[];
maxSpace?: number;
network?: number;
io?: number;
networkMode?: string;
networkAliases?: string[];
cpusetCpus?: string;
cpuUsage?: number;
};
docker: IGlobalInstanceDockerConfig;
pingConfig: {
ip?: string;
port?: number;
@ -49,6 +36,21 @@ export interface IGlobalInstanceConfig {
};
}
export interface IGlobalInstanceDockerConfig {
containerName?: string;
image?: string;
memory?: number;
ports?: string[];
extraVolumes?: string[];
maxSpace?: number;
network?: number;
io?: number;
networkMode?: string;
networkAliases?: string[];
cpusetCpus?: string;
cpuUsage?: number;
}
export interface IPanelResponseProtocol {
data: any;
timestamp: number;

View File

@ -18,6 +18,8 @@ declare module 'vue' {
ACol: typeof import('ant-design-vue/es')['Col']
ActionButton: typeof import('./src/components/ActionButton.vue')['default']
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
ADrawer: typeof import('ant-design-vue/es')['Drawer']
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
AEmpty: typeof import('ant-design-vue/es')['Empty']

View File

@ -1,10 +1,11 @@
<script setup lang="ts">
import { t } from "@/lang/i18n";
import { CopyOutlined } from "@ant-design/icons-vue";
import { message } from "ant-design-vue";
import { Modal, message } from "ant-design-vue";
import { reportError } from "@/tools/validator";
import type { ButtonType } from "ant-design-vue/es/button";
import type { SizeType } from "ant-design-vue/es/config-provider";
import { h } from "vue";
const props = defineProps<{
size?: string;
@ -13,7 +14,13 @@ const props = defineProps<{
}>();
const copy = async () => {
if (!navigator.clipboard) return reportError(t("TXT_CODE_ca07c84c"));
if (!navigator.clipboard) {
Modal.warning({
title: t("TXT_CODE_ca07c84c"),
content: [h("span", t("TXT_CODE_2452016e")), h("br"), h("span", props.value)]
});
return;
}
try {
await navigator.clipboard.writeText(props.value);
message.success(t("TXT_CODE_b858d78a"));

View File

@ -122,9 +122,8 @@ const handleChangePassword = async () => {
{{ t("TXT_CODE_b2dbf778") }}
</a-typography-paragraph>
<a-typography-paragraph v-if="state.userInfo?.apiKey">
<pre class="flex flex-between">
{{ state.userInfo.apiKey }}
<CopyButton size="small" type="text" :value="state.userInfo.apiKey" /></pre>
<pre
class="flex flex-between align-center">{{ state.userInfo.apiKey }}<CopyButton size="small" type="text" :value="state.userInfo.apiKey" /></pre>
</a-typography-paragraph>
<a-typography-paragraph v-else>
<pre>{{ t("TXT_CODE_d7dbc7c2") }}</pre>

View File

@ -70,7 +70,7 @@ onMounted(() => {
<template>
<a-modal
v-model:open="open"
:title="t('上传文件')"
:title="t('TXT_CODE_e00c858c')"
:closable="false"
:destroy-on-close="true"
width="400px"
@ -85,7 +85,7 @@ onMounted(() => {
>
<a-button type="primary" :loading="percentComplete > 0">
<FolderOpenOutlined v-if="percentComplete === 0" />
{{ percentComplete > 0 ? t("TXT_CODE_b625dbf0") + percentComplete + "%" : t("选择文件") }}
{{ percentComplete > 0 ? t("TXT_CODE_b625dbf0") + percentComplete + "%" : t("TXT_CODE_335ba209") }}
</a-button>
</a-upload>
<a-button class="ml-16" @click="cancel">{{ t("TXT_CODE_a0451c97") }}</a-button>

View File

@ -479,10 +479,10 @@ export function getLayoutCardPool() {
permission: ROLE.GUEST,
meta: {},
type: "PluginCard",
title: t("扩展页面卡片"),
title: t("TXT_CODE_5ebec0db"),
width: 4,
description: t(
"此卡片可以上传自定义 HTML 页面并直接执行 Javascript 脚本,可以直接使用网页上所有元素,适用于 Web 前端开发人员。"
"TXT_CODE_cb84b22"
),
height: LayoutCardHeight.SMALL,
category: NEW_CARD_TYPE.COMMON

View File

@ -100,3 +100,30 @@ function leftZero4(str: string) {
}
return str || "";
}
export const dockerPortsParse = (list: string[]) => {
let line = [];
list.forEach((v, index) => {
if (index >= 50) return;
const tmp = v.split("/");
if (tmp.length != 2) return;
const protocol = tmp[1];
const p = tmp[0].split(":");
if (p.length >= 2) {
line.push({
p1: p[0],
p2: p[1],
protocol: String(protocol).toUpperCase()
});
}
});
if (list.length >= 50) {
line.push({
p1: null,
p2: null,
protocol: null,
more: true
});
}
return line;
};

View File

@ -1,7 +1,7 @@
import { t } from "@/lang/i18n";
export function computeNodeName(ip: string, available: boolean, remark?: string) {
if (!ip) return t("请选择节点");
if (!ip) return t("TXT_CODE_aa373641");
const online = available ? "" : t("TXT_CODE_836addb9");
return remark ? `${remark} - ${ip} ${online}` : `${ip} ${online}`;
}

View File

@ -7,7 +7,7 @@ export function emptyValueValidator(value: string | number) {
}
export function isNumberValidator(value: any) {
if (!value || isNaN(Number(value))) throw new Error(t("字段必须是数字"));
if (!value || isNaN(Number(value))) throw new Error(t("TXT_CODE_a9bcbde9"));
return Promise.resolve();
}

View File

@ -462,7 +462,7 @@ onMounted(async () => {
v-else-if="instancesMoreInfo.length === 0"
class="flex align-center justify-center h-100 w-100"
>
<Empty :description="t('无内容,请在右上角下拉框选择节点,或点击新建应用')" />
<Empty :description="t('TXT_CODE_5415f009')" />
</div>
</a-row>
</div>

View File

@ -38,7 +38,7 @@ const loginStep = ref(0);
const handleLogin = async () => {
if (!formData.username.trim() || !formData.password.trim()) {
return message.error(t("请完善账号信息!"));
return message.error(t("TXT_CODE_c846074d"));
}
loginStep.value++;
@ -136,7 +136,7 @@ const loginSuccess = () => {
</a>
</div>
<a-button size="large" type="primary" style="min-width: 95px" @click="handleLogin">
{{ t("确定") }}
{{ t("TXT_CODE_d507abff") }}
</a-button>
</div>
</div>

View File

@ -24,7 +24,7 @@ const columns = [
dataIndex: "remark"
},
{
title: t("TXT_CODE_2c2712a4"),
title: "CPU",
dataIndex: "cpu"
},
{

View File

@ -51,12 +51,12 @@ const menus = [
},
{
title: t("TXT_CODE_9c3ca8f"),
key: "api",
key: "security",
icon: LockOutlined
},
{
title: t("TXT_CODE_3b4b656d"),
key: "password",
key: "about",
icon: QuestionCircleOutlined
}
];
@ -64,11 +64,11 @@ const menus = [
// DO NOT I18N
const allLanguages = [
{
label: t("TXT_CODE_5a09ed37"),
label: "中文",
value: "zh_cn"
},
{
label: t("TXT_CODE_e246e5b"),
label: "English",
value: "en_us"
}
];
@ -208,7 +208,7 @@ onMounted(async () => {
</div>
</template>
<template #api>
<template #security>
<div :style="{ maxHeight: card.height, overflowY: 'auto' }">
<a-typography-title :level="4" class="mb-24">
{{ t("TXT_CODE_9c3ca8f") }}
@ -299,7 +299,7 @@ onMounted(async () => {
</div>
</template>
<template #password>
<template #about>
<div :style="{ maxHeight: card.height, overflowY: 'auto' }">
<a-typography-title :level="4" class="mb-24">
{{ t("TXT_CODE_3b4b656d") }}

View File

@ -79,7 +79,7 @@ const columns = computed(() => {
},
{
align: "center",
title: t("TXT_CODE_6372e25c"),
title: t("TXT_CODE_d7ee9ba"),
dataIndex: "loginTime",
key: "loginTime",
minWidth: 200,
@ -323,7 +323,7 @@ onMounted(async () => {
<a-input v-model:value="formData.userName" :placeholder="t('TXT_CODE_4ea93630')" />
</a-form-item>
<a-form-item :required="isAddMode" name="passWord" :label="t('TXT_CODE_5c605130')">
<a-form-item :required="isAddMode" name="passWord" :label="t('TXT_CODE_551b0348')">
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ !isAddMode ? t("TXT_CODE_af1f921d") : t("TXT_CODE_1f2062c7") }}
@ -374,7 +374,7 @@ onMounted(async () => {
{{ t("TXT_CODE_e83ffa03") }}
</a-menu-item>
<a-menu-item key="2" @click="handleBatchDelete()">
{{ t("TXT_CODE_760f00f5") }}
{{ t("TXT_CODE_ecbd7449") }}
</a-menu-item>
</a-menu>
</template>
@ -455,7 +455,7 @@ onMounted(async () => {
@confirm="handleDeleteUser(record)"
>
<a-menu-item key="3">
{{ t("TXT_CODE_760f00f5") }}
{{ t("TXT_CODE_ecbd7449") }}
</a-menu-item>
</a-popconfirm>
</a-menu>

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import { ref } from "vue";
import type { LayoutCard } from "@/types";
import { useLayoutCardTools } from "../../hooks/useCardTools";
import { onMounted, computed } from "vue";
@ -7,11 +8,14 @@ import { useInstanceInfo } from "@/hooks/useInstance";
import { CheckCircleOutlined, ExclamationCircleOutlined } from "@ant-design/icons-vue";
import { GLOBAL_INSTANCE_NAME } from "../../config/const";
import { parseTimestamp } from "../../tools/time";
import { dockerPortsParse } from "@/tools/common";
import DockerInfo from "./dialogs/DockerInfo.vue";
const props = defineProps<{
card: LayoutCard;
}>();
const DockerInfoDialog = ref<InstanceType<typeof DockerInfo>>();
const { getMetaOrRouteValue } = useLayoutCardTools(props.card);
const instanceId = getMetaOrRouteValue("instanceId");
@ -73,6 +77,29 @@ onMounted(async () => {
<a-typography-paragraph>
{{ t("TXT_CODE_46f575ae") }}{{ parseTimestamp(instanceInfo?.config.lastDatetime) }}
</a-typography-paragraph>
<a-typography-paragraph v-if="instanceInfo?.config.docker.image">
{{ t("TXT_CODE_4f917a65") }}
<a href="javascript:;" @click="DockerInfoDialog?.openDialog()">查看</a>
</a-typography-paragraph>
<a-typography-paragraph v-if="instanceInfo?.config.docker.image">
{{ t("TXT_CODE_2e4469f6") }}
<div style="padding: 10px 0px 0px 16px">
<div
v-for="(item, index) in dockerPortsParse(instanceInfo?.config.docker.ports ?? [])"
:key="index"
style="margin-bottom: 2px"
>
<template v-if="!item.more">
<span>{{ t("TXT_CODE_8dfc41ef") }}: {{ item.p1 }}</span>
<span style="margin-left: 6px">{{ t("TXT_CODE_8f8103b7") }}: {{ item.p2 }}</span>
<span style="margin-left: 8px">
<a-tag color="green">{{ item.protocol }}</a-tag>
</span>
</template>
<template v-else>...</template>
</div>
</div>
</a-typography-paragraph>
<a-typography-paragraph>
{{ t("TXT_CODE_ae747cc0") }}{{ parseTimestamp(instanceInfo?.config.endTime) }}
</a-typography-paragraph>
@ -90,4 +117,6 @@ onMounted(async () => {
</a-typography-paragraph>
</template>
</CardPanel>
<DockerInfo ref="DockerInfoDialog" :docker-info="instanceInfo?.config.docker" />
</template>

View File

@ -0,0 +1,55 @@
<script setup lang="ts">
import { ref } from "vue";
import { t } from "@/lang/i18n";
import type { IGlobalInstanceDockerConfig } from "../../../../../common/global";
import { dockerPortsParse } from "@/tools/common";
const props = defineProps<{
dockerInfo?: IGlobalInstanceDockerConfig;
}>();
const open = ref(false);
const openDialog = () => {
open.value = true;
};
defineExpose({
openDialog
});
</script>
<template>
<a-modal v-model:open="open" centered :title="t('TXT_CODE_a7f6b0e0')">
<a-descriptions :column="{ md: 2, sm: 2, xs: 1 }">
<a-descriptions-item :label="t('TXT_CODE_dd238854')">
{{ props.dockerInfo?.memory }} MB
</a-descriptions-item>
<a-descriptions-item :label="t('TXT_CODE_efcef926')">
{{ props.dockerInfo?.networkMode }}
</a-descriptions-item>
<a-descriptions-item :label="t('TXT_CODE_77000411')">
{{ props.dockerInfo?.image }}
</a-descriptions-item>
<a-descriptions-item :label="t('TXT_CODE_c3a3b6b1')">
{{ props.dockerInfo?.containerName }}
</a-descriptions-item>
<a-descriptions-item v-if="props.dockerInfo?.ports" :label="t('TXT_CODE_d32301c1')">
<div>
<div
v-for="(item, index) in dockerPortsParse(props.dockerInfo.ports)"
:key="index"
style="margin-bottom: 2px"
>
<template v-if="!item.more">
<span>{{ t("TXT_CODE_8dfc41ef") }}: {{ item.p1 }}</span>
<span style="margin-left: 6px">{{ t("TXT_CODE_8f8103b7") }}: {{ item.p2 }}</span>
<span style="margin-left: 8px">
<a-tag color="green">{{ item.protocol }}</a-tag>
</span>
</template>
<template v-else>...</template>
</div>
</div>
</a-descriptions-item>
</a-descriptions>
</a-modal>
</template>

View File

@ -61,8 +61,8 @@ const dialog = reactive({
},
delete: async () => {
try {
dialog.loading = true;
await deleteNode(dialog.uuid);
dialog.close();
message.success(t("TXT_CODE_a00e84d7"));
} catch (error: any) {
message.success(error.message ?? error);
@ -139,7 +139,7 @@ defineExpose({ openDialog });
:title="t('TXT_CODE_fb267b0b')"
ok-text="Yes"
cancel-text="No"
@confirm="deleteNode(dialog.uuid)"
@confirm="dialog.delete()"
>
<a-button v-if="editMode" key="delete" danger>{{ t("TXT_CODE_8b937b23") }}</a-button>
</a-popconfirm>

View File

@ -77,7 +77,7 @@ const detailList = (node: ComputedNodeInfo) => [
value: node.memText
},
{
title: t("TXT_CODE_2c2712a4"),
title: "CPU",
value: node.cpuInfo
},
{
@ -207,7 +207,7 @@ const nodeOperations = computed(() =>
<template #title>
{{
t(
"远程节点版本与面板端所需版本不一致,这可能会导致工作异常,请立即更新远程节点!"
"TXT_CODE_e520908a"
)
}}
</template>

View File

@ -75,16 +75,16 @@ onUnmounted(() => {
<div v-if="containerState.isDesignMode">
<a-typography-paragraph>
<p>
{{ t("支持上传 HTML 文件,此卡片会加载 HTML 并运行 Javascript 代码。") }}
{{ t("TXT_CODE_c565b2e0") }}
<br />
{{ t("使用其他人分享的文件可能会导致面板被入侵。") }}
{{ t("TXT_CODE_6750d9d3") }}
</p>
<div v-if="originUrl">{{ t("HTML 文件:") }}</div>
<div v-if="originUrl">{{ t("TXT_CODE_6345aa71") }}</div>
<div v-if="originUrl" class="mt-16 mb-16">
<a :href="originUrl" target="_blank" rel="noopener noreferrer">{{ originUrl }}</a>
</div>
<a-button class="mt-8" type="primary" @click="uploadHtmlFile">
{{ t("上传 HTML 文件") }}
{{ t("TXT_CODE_40a87804") }}
</a-button>
</a-typography-paragraph>
</div>

View File

@ -8,7 +8,6 @@
"TXT_CODE_1f246be3": "最小高度:",
"TXT_CODE_2c69ab15": "退出",
"TXT_CODE_3efe364e": "格",
"TXT_CODE_46913a7f": "卧槽123",
"TXT_CODE_524e3036": "终端",
"TXT_CODE_5db4e96b": "扩展高度(同行最高的高度为最终高度)",
"TXT_CODE_8145d82": "保存卡片布局",
@ -80,9 +79,7 @@
"TXT_CODE_environment_router.crateSuccess": "创建镜像 {{name}}:{{tag}} 完毕",
"TXT_CODE_environment_router.delImage": "守护进程正在删除镜像 {{imageId}}",
"TXT_CODE_environment_router.dockerInfoErr": "无法获取镜像信息请确保您已正确安装Docker环境",
"TXT_CODE_f2686627": "测试345",
"TXT_CODE_f5b9d58f": "管理面板",
"TXT_CODE_f7d18982": "你好",
"TXT_CODE_fd5ca298": "收缩高度(同行必须一致才可改变最低高度)",
"TXT_CODE_file_router.instanceNotExist": "实例 {{instanceUuid}} 不存在",
"TXT_CODE_file_router.unzipLimit": "超出最大同时解压缩任务量,最大准许{{maxFileTask}}个,目前有{{fileLock}}个任务正在进行,请耐心等待",
@ -238,7 +235,7 @@
"TXT_CODE_6b6f1d3": "您可以自由设计出所想要的一切。过多的元素在同一个页面可能会导致性能下降,请合理分配。",
"TXT_CODE_e6856d81": "成功退出",
"TXT_CODE_b128afa6": "请输入新的卡片标题",
"TXT_CODE_ca07c84c": "您当前的浏览器不支持 Clipboard API",
"TXT_CODE_ca07c84c": "您当前的浏览器不支持自动复制",
"TXT_CODE_b858d78a": "复制成功",
"TXT_CODE_81b9b599": "复制失败:",
"TXT_CODE_13ae6a93": "复制",
@ -342,7 +339,6 @@
"TXT_CODE_d31a684c": "启动时间:",
"TXT_CODE_f52079a0": "连接地址",
"TXT_CODE_593ee330": "内存",
"TXT_CODE_2c2712a4": "CPU",
"TXT_CODE_3d602459": "实例状态",
"TXT_CODE_c9609785": "在线状态",
"TXT_CODE_823bfe63": "在线",
@ -439,7 +435,6 @@
"TXT_CODE_cd978243": "管理员",
"TXT_CODE_7c76dbf": "被封禁",
"TXT_CODE_511aea70": "权限",
"TXT_CODE_6372e25c": "最后登录时间",
"TXT_CODE_28190dbc": "删除成功",
"TXT_CODE_d78ad17a": "请选择要删除的用户",
"TXT_CODE_e83ffa03": "新增用户",
@ -448,12 +443,10 @@
"TXT_CODE_c855fc29": "新增用户成功",
"TXT_CODE_5246d704": "新增用户失败:",
"TXT_CODE_1987587b": "必填3 到 12 个字符,支持任何语言",
"TXT_CODE_5c605130": "用户密码",
"TXT_CODE_1f2062c7": "必填9 到 36 个字符,必须包含大小写字母和数字",
"TXT_CODE_21b8b71a": "普通权限适用于商业用户,最高权限适用于管理人员",
"TXT_CODE_9e9d3767": "若您从事出租商业活动,请务必保证应用实例运行在 Linux 的 Docker 虚拟容器中,否则将有安全隐患。",
"TXT_CODE_b01f8383": "了解更多",
"TXT_CODE_760f00f5": "删除用户",
"TXT_CODE_4d934e3a": "分配资源",
"TXT_CODE_f7084f84": "用户操作",
"TXT_CODE_2471b9c": "根据用户名搜索",
@ -1344,7 +1337,6 @@
"TXT_CODE_e3db239d": "小时",
"TXT_CODE_3b1bb444": "分钟",
"TXT_CODE_76750199": "每星期",
"TXT_CODE_32973a9e": "的",
"TXT_CODE_6cb9bb04": "月",
"TXT_CODE_ca923eba": "日",
"TXT_CODE_2d542e4c": "任务名",
@ -1485,8 +1477,6 @@
"TXT_CODE_365aabd4": "换行符",
"TXT_CODE_20cec54": "回车换行符",
"TXT_CODE_b19bf004": "必填,请输入内容",
"TXT_CODE_5a09ed37": "中文",
"TXT_CODE_e246e5b": "英语",
"TXT_CODE_8575f7c": "权限:",
"TXT_CODE_b67197fc": "所有用户",
"TXT_CODE_b488372f": "所有人",
@ -1724,5 +1714,25 @@
"TXT_CODE_76d20724": "用户所有实例",
"TXT_CODE_9393b484": "分配实例",
"TXT_CODE_6c274bdc": "此用户没有开启 APIKEY 功能。",
"TXT_CODE_e99ab99a": "你确定要删除这个用户吗?"
"TXT_CODE_e99ab99a": "你确定要删除这个用户吗?",
"TXT_CODE_2452016e": "请手动复制以下内容",
"TXT_CODE_5ebec0db": "扩展页面卡片",
"TXT_CODE_cb84b22": "此卡片可以上传自定义 HTML 页面并直接执行 Javascript 脚本,可以直接使用网页上所有元素,适用于 Web 前端开发人员。",
"TXT_CODE_aa373641": "请选择节点",
"TXT_CODE_a9bcbde9": "字段必须是数字",
"TXT_CODE_4f917a65": "资源限制:",
"TXT_CODE_2e4469f6": "可用端口:",
"TXT_CODE_8dfc41ef": "主机",
"TXT_CODE_8f8103b7": "容器",
"TXT_CODE_a7f6b0e0": "容器资源限制信息",
"TXT_CODE_77000411": "镜像名",
"TXT_CODE_d32301c1": "可用端口",
"TXT_CODE_5415f009": "无内容,请在右上角下拉框选择节点,或点击新建应用",
"TXT_CODE_c846074d": "请完善账号信息!",
"TXT_CODE_e520908a": "远程节点版本与面板端所需版本不一致,这可能会导致工作异常,请立即更新远程节点!",
"TXT_CODE_c565b2e0": "支持上传 HTML 文件,此卡片会加载 HTML 并运行 Javascript 代码。",
"TXT_CODE_6750d9d3": "使用其他人分享的文件可能会导致面板被入侵。",
"TXT_CODE_6345aa71": "HTML 文件:",
"TXT_CODE_40a87804": "上传 HTML 文件",
"TXT_CODE_ccb60658": "身份验证"
}

View File

@ -354,7 +354,7 @@ function getDefaultFrontendLayoutConfig(): IPageLayoutConfig[] {
type: "Settings",
title: t("TXT_CODE_3fe97dcc"),
width: 8,
height: LayoutCardHeight.MEDIUM,
height: LayoutCardHeight.LARGE,
disableDelete: true
}
]
@ -526,7 +526,7 @@ function getDefaultFrontendLayoutConfig(): IPageLayoutConfig[] {
id: getRandomId(),
meta: {},
type: "LoginCard",
title: t("身份验证"),
title: t("TXT_CODE_ccb60658"),
width: 4,
height: LayoutCardHeight.AUTO,
disableDelete: true