Feat: Select template function

This commit is contained in:
Unitwk 2024-04-24 16:05:26 +08:00
parent 7c92b882b6
commit cd8d3777d2
16 changed files with 112 additions and 67 deletions

2
common/global.d.ts vendored
View File

@ -206,7 +206,7 @@ export interface IQuickStartPackages {
size: string;
hardware: string;
remark: string;
targetLink: string;
targetLink?: string;
author: string;
setupInfo?: IJsonData;
}

View File

@ -106,7 +106,7 @@ export default class Instance extends EventEmitter {
}
if (cfg?.processType && cfg?.processType !== this.config.processType) {
if (this.status() != Instance.STATUS_STOP)
if (this.status() !== Instance.STATUS_STOP && this.status() !== Instance.STATUS_BUSY)
throw new Error($t("TXT_CODE_instanceConf.cantModifyProcessType"));
configureEntityParams(this.config, cfg, "processType", String);
this.forceExec(new FunctionDispatcher());

View File

@ -482,7 +482,7 @@ routerApp.on("instance/outputlog", async (ctx, data) => {
return protocol.response(ctx, text);
}
protocol.responseError(ctx, new Error($t("TXT_CODE_Instance_router.terminalLogNotExist")), {
notPrintErr: true
disablePrint: true
});
} catch (err: any) {
protocol.responseError(ctx, err);

View File

@ -33,7 +33,9 @@ routerApp.use(async (event, ctx, _, next) => {
event: event
})
);
return protocol.error(ctx, "error", IGNORE);
return protocol.error(ctx, "error", IGNORE, {
disablePrint: true
});
});
// authentication controller

View File

@ -24,7 +24,9 @@ routerApp.use(async (event, ctx, data, next) => {
) {
return await next();
}
return protocol.error(ctx, "error", IGNORE);
return protocol.error(ctx, "error", IGNORE, {
disablePrint: true
});
}
return await next();
});
@ -48,7 +50,7 @@ routerApp.on("stream/auth", (ctx, data) => {
protocol.response(ctx, true);
} catch (error: any) {
protocol.responseError(ctx, error, {
notPrintErr: true
disablePrint: true
});
}
});

View File

@ -27,20 +27,21 @@ export class QuickInstallTask extends AsyncTask {
constructor(
public instanceName: string,
public targetLink: string,
public targetLink?: string,
public buildParams?: Partial<InstanceConfig>,
curInstance?: Instance
) {
super();
const config = new InstanceConfig();
config.nickname = instanceName;
config.cwd = "";
config.stopCommand = "^c";
config.type = Instance.TYPE_MINECRAFT_JAVA;
if (!curInstance) {
config.cwd = "";
this.instance = InstanceSubsystem.createInstance(config);
} else {
this.instance = curInstance;
config.cwd = this.instance.config.cwd;
this.instance.config = config;
}
this.taskId = `${QuickInstallTask.TYPE}-${this.instance.instanceUuid}-${v4()}`;
this.type = QuickInstallTask.TYPE;
@ -49,6 +50,7 @@ export class QuickInstallTask extends AsyncTask {
private download(): Promise<boolean> {
return new Promise(async (resolve, reject) => {
try {
if (!this.targetLink) reject(new Error("No targetLink!"));
this.zipPath = path.normalize(path.join(this.instance.config.cwd, this.TMP_ZIP_NAME));
const writeStream = fs.createWriteStream(this.zipPath);
const response = await axios<Readable>({
@ -75,13 +77,15 @@ export class QuickInstallTask extends AsyncTask {
async onStarted() {
const fileManager = getFileManager(this.instance.instanceUuid);
try {
let result = await this.download();
result = await fileManager.unzip(this.TMP_ZIP_NAME, ".", "UTF-8");
if (!result) throw new Error($t("TXT_CODE_quick_install.unzipError"));
if (this.targetLink) {
let result = await this.download();
result = await fileManager.unzip(this.TMP_ZIP_NAME, ".", "UTF-8");
if (!result) throw new Error($t("TXT_CODE_quick_install.unzipError"));
}
let config: Partial<InstanceConfig>;
if (this.buildParams?.startCommand) {
config = this.buildParams;
if (this.buildParams?.startCommand || !fs.existsSync(this.ZIP_CONFIG_JSON)) {
config = this.buildParams || {};
} else {
config = JSON.parse(await fileManager.readFile(this.ZIP_CONFIG_JSON));
}
@ -135,11 +139,11 @@ export class QuickInstallTask extends AsyncTask {
}
export function createQuickInstallTask(
targetLink: string,
instanceName: string,
targetLink?: string,
instanceName?: string,
buildParams?: any
) {
if (!targetLink || !instanceName) throw new Error("targetLink or instanceName is null!");
if (!instanceName) throw new Error("Instance name is empty!");
const task = new QuickInstallTask(instanceName, targetLink, buildParams);
TaskCenter.addTask(task);
return task;

View File

@ -18,7 +18,7 @@ export interface IPacket {
}
export interface IResponseErrorConfig {
notPrintErr: boolean;
disablePrint: boolean;
}
// global socket storage
@ -50,9 +50,8 @@ export function responseError(
else errinfo = err;
const packet = new Packet(ctx.uuid, STATUS_ERR, ctx.event, errinfo);
// Ignore
if (err.toString().includes(IGNORE) && ctx.event) return ctx.socket.emit(ctx.event, packet);
if (!config?.notPrintErr)
if (String(err).includes(IGNORE) && ctx.event) return ctx.socket.emit(ctx.event, packet);
if (!config?.disablePrint)
logger.warn(
$t("TXT_CODE_protocol.socketErr", {
id: ctx.socket.id,
@ -69,19 +68,20 @@ export function msg(ctx: RouterContext, event: string, data: any) {
ctx.socket.emit(event, packet);
}
export function error(ctx: RouterContext, event: string, err: any) {
export function error(ctx: RouterContext, event: string, err: any, config?: IResponseErrorConfig) {
const packet = new Packet(ctx.uuid, STATUS_ERR, event, err);
// Ignore
if (err.toString().includes(IGNORE) && ctx.event) return ctx.socket.emit(ctx.event, packet);
if (String(err).includes(IGNORE) && ctx.event) return ctx.socket.emit(ctx.event, packet);
if (!config?.disablePrint)
logger.warn(
$t("TXT_CODE_protocol.socketErr", {
id: ctx.socket.id,
address: ctx.socket.handshake.address,
event: ctx.event
}),
err
);
logger.warn(
$t("TXT_CODE_protocol.socketErr", {
id: ctx.socket.id,
address: ctx.socket.handshake.address,
event: ctx.event
}),
err
);
ctx.socket.emit(event, packet);
}

View File

@ -245,7 +245,7 @@ export const createAsyncTask = useDefineApi<
data: {
time: number;
newInstanceName: string;
targetLink: string;
targetLink?: string;
setupInfo?: JsonData;
};
},
@ -441,7 +441,9 @@ export const reinstallInstance = useDefineApi<
uuid: string;
};
data: {
targetUrl: string;
targetUrl?: string;
title: string;
description: string;
};
},
boolean

View File

@ -51,17 +51,10 @@ const updateCmd = computed(() => (instanceInfo.value?.config.updateCommand ? tru
const instanceStatusText = computed(() => INSTANCE_STATUS[instanceInfo.value?.status ?? -1]);
const quickOperations = computed(() =>
arrayFilter([
{
title: t("TXT_CODE_b19ed1dd"),
icon: InteractionOutlined,
type: "danger",
click: () => reinstallDialog.value?.openDialog(),
props: {},
condition: () => isStopped.value
},
{
title: t("TXT_CODE_57245e94"),
icon: PlayCircleOutlined,
noConfirm: false,
type: "default",
click: async () => {
try {
@ -108,6 +101,7 @@ const instanceOperations = computed(() =>
title: t("TXT_CODE_47dcfa5"),
icon: RedoOutlined,
type: "default",
noConfirm: false,
click: async () => {
try {
await restartInstance().execute({
@ -161,6 +155,14 @@ const instanceOperations = computed(() =>
}
},
condition: () => isStopped.value && updateCmd.value
},
{
title: t("TXT_CODE_b19ed1dd"),
icon: InteractionOutlined,
noConfirm: true,
click: () => reinstallDialog.value?.openDialog(),
props: {},
condition: () => isStopped.value
}
])
);
@ -228,17 +230,28 @@ onMounted(async () => {
</template>
<template #right>
<div v-if="!isPhone">
<a-popconfirm
v-for="item in [...quickOperations, ...instanceOperations]"
:key="item.title"
:title="t('TXT_CODE_276756b2')"
@confirm="item.click"
>
<a-button class="ml-8" :danger="item.type === 'danger'">
<template v-for="item in [...quickOperations, ...instanceOperations]" :key="item.title">
<a-button
v-if="item.noConfirm"
class="ml-8"
:danger="item.type === 'danger'"
@click="item.click"
>
<component :is="item.icon" />
{{ item.title }}
</a-button>
</a-popconfirm>
<a-popconfirm
v-else
:key="item.title"
:title="t('TXT_CODE_276756b2')"
@confirm="item.click"
>
<a-button class="ml-8" :danger="item.type === 'danger'">
<component :is="item.icon" />
{{ item.title }}
</a-button>
</a-popconfirm>
</template>
</div>
<a-dropdown v-else>

View File

@ -40,7 +40,9 @@ const handleSelectTemplate = (item: QuickStartPackages) => {
uuid: props.instanceId
},
data: {
targetUrl: item.targetLink
targetUrl: item.targetLink,
title: item.title,
description: item.description
}
});
cancel();
@ -65,7 +67,7 @@ defineExpose({
<a-modal
v-model:open="open"
centered
width="100%"
width="95%"
:cancel-text="t('TXT_CODE_3b1cc020')"
:mask-closable="false"
:confirm-loading="false"
@ -73,6 +75,8 @@ defineExpose({
@cancel="cancel"
>
<template #title> {{ t("TXT_CODE_5e10537a") }} </template>
<AppPackages ref="appPackages" @handle-select-template="handleSelectTemplate" />
<div class="mt-20">
<AppPackages ref="appPackages" @handle-select-template="handleSelectTemplate" />
</div>
</a-modal>
</template>

View File

@ -127,7 +127,7 @@ onMounted(() => {
</div>
</template>
<template #body>
<div style="min-height: 220px; position: relative">
<div style="min-height: 120px; position: relative">
<a-typography-paragraph
:ellipsis="{ rows: 3, expandable: true }"
:content="item.description"
@ -135,9 +135,11 @@ onMounted(() => {
</a-typography-paragraph>
<a-typography-paragraph>
<a-typography-text class="color-info">
<div>{{ t("TXT_CODE_18b94497") }}: {{ item.runtime }}</div>
<div>{{ t("TXT_CODE_683e3033") }}: {{ item.hardware }}</div>
<div>{{ t("TXT_CODE_94bb113a") }}: {{ item.size }}</div>
<div v-if="item.runtime">{{ t("TXT_CODE_18b94497") }}: {{ item.runtime }}</div>
<div v-if="item.hardware">
{{ t("TXT_CODE_683e3033") }}: {{ item.hardware }}
</div>
<div v-if="item.size">{{ t("TXT_CODE_94bb113a") }}: {{ item.size }}</div>
</a-typography-text>
<br />
<a-typography-text class="color-info"> </a-typography-text>
@ -160,7 +162,7 @@ onMounted(() => {
block
type="primary"
ghost
style="max-width: 180px"
style="max-width: 120px"
@click="emit('handleSelectTemplate', item)"
>
<template #icon>

View File

@ -42,7 +42,7 @@ const handleSelectTemplate = async (item: QuickStartPackages) => {
data: {
time: Date.now(),
newInstanceName: instanceName,
targetLink: item.targetLink,
targetLink: item.targetLink || "",
setupInfo: item.setupInfo
}
});

View File

@ -403,7 +403,7 @@
"TXT_CODE_4ea93630": "Please enter content",
"TXT_CODE_b5b33dd4": "Custom Text on the Login Page",
"TXT_CODE_c26e5fb7": "Support Markdown format",
"TXT_CODE_b2767aa2": "Preset Packages",
"TXT_CODE_b2767aa2": "Template Packages Config",
"TXT_CODE_b1f833f3": "Source for the quick install function. This can be modified to support custom predefined server packages. ",
"TXT_CODE_514e064a": "Panel Binding IP",
"TXT_CODE_328191e": "Maybe useful in a multi-IP environment. If there's only one IP available on the host, please leave it empty.",
@ -1877,7 +1877,17 @@
"TXT_CODE_1c2efd38": "The network connection cannot be established with the remote node. Please check whether the remote node is running normally? \nIs the network configured correctly?",
"TXT_CODE_6915f2a": "Dependency files are missing and cannot be started. Please reinstall the program:",
"TXT_CODE_9337bed1": "It doesn't look like there are any remote nodes here. Maybe there was a problem during the installation process. Restarting your panel and daemon will solve it!",
"TXT_CODE_8a30e150": "All Packages",
"TXT_CODE_8a30e150": "All Languages",
"TXT_CODE_ac225d07": "Build instance parameters:",
"TXT_CODE_e5ba712d": "Building the server as a preset package:"
"TXT_CODE_e5ba712d": "Building the server as a preset package:",
"TXT_CODE_2375f010": "Reinstall",
"TXT_CODE_5e10537a": "Change Instance Template",
"TXT_CODE_617ce69c": "Warn",
"TXT_CODE_906c5d6a": "Deletion Completed!",
"TXT_CODE_b19ed1dd": "Change Template",
"TXT_CODE_b9ca022b": "Installing instance files, please wait patiently...",
"TXT_CODE_cbc235ad": "Clearing the existing files of the instance, please wait patiently...",
"TXT_CODE_e4878221": "Missing remote node ID",
"TXT_CODE_ed3fc23": "Continue",
"TXT_CODE_f220ed78": "Installation is complete!"
}

View File

@ -403,7 +403,7 @@
"TXT_CODE_4ea93630": "请输入内容",
"TXT_CODE_b5b33dd4": "登录页文字展示",
"TXT_CODE_c26e5fb7": "支持 Markdown 格式",
"TXT_CODE_b2767aa2": "预设资源下载点",
"TXT_CODE_b2767aa2": "实例模板配置",
"TXT_CODE_b1f833f3": "快速安装服务器时的下载源列表,您可以通过更改此地址实现自定义服务端预设下载站。",
"TXT_CODE_514e064a": "面板绑定IP",
"TXT_CODE_328191e": "适用于主机上拥有多张网卡多个IP地址的情况如果您只有一个公网IP那么请不要配置此项。",
@ -1879,7 +1879,7 @@
"TXT_CODE_1c2efd38": "无法与远程节点建立网络连接,请检查远程节点是否运行正常?网络是否正确配置?",
"TXT_CODE_e5ba712d": "正在以预设包的方式构建服务器:",
"TXT_CODE_ac225d07": "构建包实例参数:",
"TXT_CODE_8a30e150": "所有",
"TXT_CODE_8a30e150": "所有语言",
"TXT_CODE_cbc235ad": "正在清空实例现有文件,请耐心等待...",
"TXT_CODE_906c5d6a": "删除完成!",
"TXT_CODE_b9ca022b": "正在安装实例文件,请耐心等待...",

View File

@ -403,7 +403,7 @@
"TXT_CODE_4ea93630": "請輸入內容",
"TXT_CODE_b5b33dd4": "登入頁文字展示",
"TXT_CODE_c26e5fb7": "支援 Markdown 格式",
"TXT_CODE_b2767aa2": "預設資源下載點",
"TXT_CODE_b2767aa2": "預設模板配置",
"TXT_CODE_b1f833f3": "快速安裝伺服器時的下載來源列表,您可以透過變更此位址來實現自訂伺服端預設下載站。",
"TXT_CODE_514e064a": "面板綁定IP",
"TXT_CODE_328191e": "適用於主機上擁有多張網路卡多個IP位址的情況如果您只有一個公網IP那麼請不要設定此項目。",

View File

@ -472,12 +472,16 @@ router.get(
router.post(
"/install_instance",
permission({ level: ROLE.USER, speedLimit: true }),
validator({ query: { daemonId: String, uuid: String }, body: { targetUrl: String } }),
validator({
query: { daemonId: String, uuid: String },
body: { description: String, title: String }
}),
async (ctx) => {
try {
const daemonId = String(ctx.query.daemonId);
const instanceUuid = String(ctx.query.uuid);
const targetUrl = String(ctx.request.body.targetUrl);
const description = String(ctx.request.body.description);
const title = String(ctx.request.body.title);
const presetUrl = systemConfig?.quickInstallAddr;
if (!presetUrl) throw new Error("Preset Addr is empty!");
@ -490,7 +494,9 @@ router.post(
const packages = presetConfig.packages;
if (!(packages instanceof Array)) throw new Error("Preset Config is not array!");
const targetPresetConfig = packages.find((v) => v.targetLink === targetUrl);
const targetPresetConfig = packages.find(
(v) => v.title === title && v.description === description
);
if (!targetPresetConfig) throw new Error("Preset Config is not found!");
const remoteService = RemoteServiceSubsystem.getInstance(daemonId);