|
|
|
|
@ -1,13 +1,13 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { Close, Lock, Minus, User } from "@element-plus/icons-vue";
|
|
|
|
|
import { ElDialog, ElForm, ElMessage, ElMessageBox, ElProgress } from "element-plus";
|
|
|
|
|
import { ElDialog, ElForm, ElMessage, ElProgress } from "element-plus";
|
|
|
|
|
// 报错统一走 host/dialog 原生 message,勿用 ElMessage.error
|
|
|
|
|
import { getVersion } from "@tauri-apps/api/app";
|
|
|
|
|
import { writeText } from "@tauri-apps/api/clipboard";
|
|
|
|
|
import { listen } from "@tauri-apps/api/event";
|
|
|
|
|
import { invoke } from "@tauri-apps/api/tauri";
|
|
|
|
|
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
|
|
|
|
import { api } from "../api";
|
|
|
|
|
import { confirmNative, showErrorNative } from "../host/dialog";
|
|
|
|
|
import { confirmNative, showErrorNative, showInfoNative, showWarningNative } from "../host/dialog";
|
|
|
|
|
import { getAllConfig, mergeConfig } from "../host/config";
|
|
|
|
|
import { log } from "../host/logger";
|
|
|
|
|
import { setSession } from "../host/session";
|
|
|
|
|
@ -46,11 +46,6 @@ const DEFAULT_APT_SOURCE_DEB_LINE =
|
|
|
|
|
"deb [arch=amd64 signed-by=/usr/share/keyrings/zyyun-archive-keyring.gpg] http://80.12.140.29:80/apt v10 main";
|
|
|
|
|
const aptSourceDebLine = ref(DEFAULT_APT_SOURCE_DEB_LINE);
|
|
|
|
|
|
|
|
|
|
function buildAptSourceSetupCommand(debLine: string): string {
|
|
|
|
|
const escaped = debLine.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
|
|
|
return `echo "${escaped}" | sudo tee /etc/apt/sources.list.d/zyyun.list && sudo apt update`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface AptUpdateCheckResult {
|
|
|
|
|
packageName: string;
|
|
|
|
|
currentVersion: string;
|
|
|
|
|
@ -89,10 +84,7 @@ const isWindowSelected = computed(() => selectedWin.value !== "");
|
|
|
|
|
/**
|
|
|
|
|
* 在窄窗口中使用居中消息,避免提示框被裁剪。
|
|
|
|
|
*/
|
|
|
|
|
function showMessage(
|
|
|
|
|
type: "success" | "warning" | "error",
|
|
|
|
|
message: string,
|
|
|
|
|
): void {
|
|
|
|
|
function showMessage(type: "success" | "warning", message: string): void {
|
|
|
|
|
ElMessage({
|
|
|
|
|
type,
|
|
|
|
|
message,
|
|
|
|
|
@ -145,6 +137,7 @@ async function handleLogin(): Promise<void> {
|
|
|
|
|
"warn",
|
|
|
|
|
`登录失败: 接口未返回有效 queueToken, user=${loginForm.username}`,
|
|
|
|
|
);
|
|
|
|
|
await showErrorNative("登录失败:服务器未返回有效凭据,请稍后重试。", "登录");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -179,6 +172,7 @@ async function handleLogin(): Promise<void> {
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
|
await log("error", `登录失败: user=${loginForm.username}, ${message}`);
|
|
|
|
|
await showErrorNative(message || "登录失败", "登录");
|
|
|
|
|
} finally {
|
|
|
|
|
isLoading.value = false;
|
|
|
|
|
}
|
|
|
|
|
@ -219,7 +213,7 @@ async function handleWindowLogin(): Promise<void> {
|
|
|
|
|
? (initRes as { message?: string }).message
|
|
|
|
|
: null) ??
|
|
|
|
|
"窗口初始化失败,请重试或更换窗口";
|
|
|
|
|
showMessage("error", String(msg));
|
|
|
|
|
await showErrorNative(String(msg), "窗口初始化");
|
|
|
|
|
await log("warn", `call-terminal/init 未成功: windowUid=${winUid}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@ -275,7 +269,7 @@ async function handleWindowLogin(): Promise<void> {
|
|
|
|
|
await openMainWindow();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
|
showMessage("error", message || "打开主窗口失败");
|
|
|
|
|
await showErrorNative(message || "打开主窗口失败", "打开主窗口");
|
|
|
|
|
await log("error", `打开主窗口失败: ${message}`);
|
|
|
|
|
} finally {
|
|
|
|
|
isLoading.value = false;
|
|
|
|
|
@ -289,7 +283,7 @@ async function handleMinimizeClick(event: MouseEvent): Promise<void> {
|
|
|
|
|
await minimizeWindow();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
|
showMessage("error", message || "最小化窗口失败");
|
|
|
|
|
await showErrorNative(message || "最小化窗口失败", "最小化");
|
|
|
|
|
await log("error", `最小化窗口失败: ${message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -301,7 +295,7 @@ async function handleCloseClick(event: MouseEvent): Promise<void> {
|
|
|
|
|
await closeWindow();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
|
showMessage("error", message || "关闭窗口失败");
|
|
|
|
|
await showErrorNative(message || "关闭窗口失败", "关闭窗口");
|
|
|
|
|
await log("error", `关闭窗口失败: ${message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -374,7 +368,7 @@ async function runZyyunAptSourceSetupWithProgress(debLine: string): Promise<void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查 apt 仓库中是否有新版本,并引导复制升级命令。
|
|
|
|
|
* 检查 apt 仓库中是否有新版本;配源与升级均在应用内由固定 Rust 脚本 + pkexec 完成,不向用户暴露可手写的 shell 命令。
|
|
|
|
|
*/
|
|
|
|
|
async function handleCheckUpdate(): Promise<void> {
|
|
|
|
|
if (checkingUpdate.value) {
|
|
|
|
|
@ -409,44 +403,31 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
|
"warn",
|
|
|
|
|
"检查更新: 已记录自动配置过 apt 源,但候选版本仍不可用,请人工排查网络或 GPG 密钥",
|
|
|
|
|
);
|
|
|
|
|
showMessage(
|
|
|
|
|
"warning",
|
|
|
|
|
"本机已自动配置过紫云软件源,但 apt 仍无法解析候选版本。请在终端执行 sudo apt update 后重试,或检查 /usr/share/keyrings/zyyun-archive-keyring.gpg 是否存在。",
|
|
|
|
|
await showWarningNative(
|
|
|
|
|
"本机已自动配置过紫云软件源,但 apt 仍无法解析候选版本。请联系管理员检查内网仓库、网络或密钥文件;勿在终端自行执行来源不明的更新脚本。",
|
|
|
|
|
"检查更新",
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
"未检测到可用更新源。是否使用管理员权限自动写入紫云软件源并执行 apt 更新?(需要桌面环境的 pkexec 授权)",
|
|
|
|
|
"配置更新源",
|
|
|
|
|
{
|
|
|
|
|
type: "warning",
|
|
|
|
|
confirmButtonText: "开始配置",
|
|
|
|
|
cancelButtonText: "取消",
|
|
|
|
|
distinguishCancelAndClose: true,
|
|
|
|
|
},
|
|
|
|
|
const doConfigure = await confirmNative({
|
|
|
|
|
title: "配置更新源",
|
|
|
|
|
message:
|
|
|
|
|
"未检测到可用更新源。是否使用管理员权限由本程序自动写入紫云软件源并执行 apt 更新?(将弹出 pkexec 授权,请输入管理员密码;无需在终端自行输入命令。)",
|
|
|
|
|
okLabel: "开始配置",
|
|
|
|
|
cancelLabel: "取消",
|
|
|
|
|
});
|
|
|
|
|
if (!doConfigure) {
|
|
|
|
|
await log("info", "检查更新(前端): 用户在「配置更新源」对话框选择取消");
|
|
|
|
|
await showInfoNative(
|
|
|
|
|
[
|
|
|
|
|
"未执行自动配置。稍后可再次点击「检查更新」,在提示中选择「开始配置」并输入管理员密码,",
|
|
|
|
|
"由本程序自动完成软件源与索引更新。",
|
|
|
|
|
"",
|
|
|
|
|
"请勿在终端自行编写或执行未经验证的更新命令;若仍无法使用,请联系管理员。",
|
|
|
|
|
].join("\n"),
|
|
|
|
|
"更新源未配置",
|
|
|
|
|
);
|
|
|
|
|
} catch {
|
|
|
|
|
await log("info", "检查更新(前端): 用户在「配置更新源」对话框选择取消或关闭");
|
|
|
|
|
const setupCmd = buildAptSourceSetupCommand(aptSourceDebLine.value);
|
|
|
|
|
const body = [
|
|
|
|
|
"已取消自动配置。若需手动配置,可将下面一行写入软件源,并在终端执行复制出的命令:",
|
|
|
|
|
aptSourceDebLine.value,
|
|
|
|
|
"",
|
|
|
|
|
"点击「复制命令」后,在终端执行:",
|
|
|
|
|
setupCmd,
|
|
|
|
|
].join("\n");
|
|
|
|
|
const copy = await confirmNative({
|
|
|
|
|
title: "更新源未配置",
|
|
|
|
|
message: body,
|
|
|
|
|
okLabel: "复制命令",
|
|
|
|
|
cancelLabel: "关闭",
|
|
|
|
|
});
|
|
|
|
|
if (copy) {
|
|
|
|
|
await writeText(setupCmd);
|
|
|
|
|
showMessage("success", "更新源初始化命令已复制到剪贴板");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -463,21 +444,16 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
|
|
|
|
|
|
if (!result.sourceAvailable) {
|
|
|
|
|
await log("warn", "自动配置 apt 源后仍无法解析候选版本");
|
|
|
|
|
const setupCmd = buildAptSourceSetupCommand(aptSourceDebLine.value);
|
|
|
|
|
const body = [
|
|
|
|
|
"自动配置已完成,但 apt 仍无法解析候选版本。请检查网络、GPG 密钥,或在终端执行:",
|
|
|
|
|
setupCmd,
|
|
|
|
|
].join("\n");
|
|
|
|
|
const copy = await confirmNative({
|
|
|
|
|
title: "更新源仍不可用",
|
|
|
|
|
message: body,
|
|
|
|
|
okLabel: "复制命令",
|
|
|
|
|
cancelLabel: "关闭",
|
|
|
|
|
});
|
|
|
|
|
if (copy) {
|
|
|
|
|
await writeText(setupCmd);
|
|
|
|
|
showMessage("success", "命令已复制到剪贴板");
|
|
|
|
|
}
|
|
|
|
|
await showErrorNative(
|
|
|
|
|
[
|
|
|
|
|
"自动配置已完成,但 apt 仍无法解析候选版本。",
|
|
|
|
|
"请联系管理员检查内网 apt 仓库、GPG 与网络;请勿在终端自行执行未经验证的安装脚本。",
|
|
|
|
|
"",
|
|
|
|
|
"您可稍后再次点击「检查更新」重试。",
|
|
|
|
|
].join("\n"),
|
|
|
|
|
"更新源不可用",
|
|
|
|
|
{ logActions: "file" },
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -492,35 +468,20 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
|
"info",
|
|
|
|
|
`检查更新提示: 检测到新版本, installed=${result.installedVersion}, candidate=${result.candidateVersion}`,
|
|
|
|
|
);
|
|
|
|
|
const body = [
|
|
|
|
|
`检测到新版本:${result.candidateVersion}`,
|
|
|
|
|
`当前版本:${result.installedVersion}`,
|
|
|
|
|
"",
|
|
|
|
|
"可选:",
|
|
|
|
|
"· 点击「应用内升级」将弹出管理员授权(pkexec),执行固定的 apt 更新与仅升级 call-client(与终端命令等价,包名写死在程序内)。",
|
|
|
|
|
"· 点击「复制终端命令」可在终端自行执行 sudo。",
|
|
|
|
|
"",
|
|
|
|
|
"终端命令:",
|
|
|
|
|
result.updateCommand,
|
|
|
|
|
].join("\n");
|
|
|
|
|
try {
|
|
|
|
|
await ElMessageBox.confirm(body, "发现新版本", {
|
|
|
|
|
confirmButtonText: "应用内升级(需管理员密码)",
|
|
|
|
|
cancelButtonText: "复制终端命令",
|
|
|
|
|
distinguishCancelAndClose: true,
|
|
|
|
|
type: "info",
|
|
|
|
|
});
|
|
|
|
|
} catch (action: unknown) {
|
|
|
|
|
if (action === "cancel") {
|
|
|
|
|
await writeText(result.updateCommand);
|
|
|
|
|
await log("info", "检查更新(前端): 用户选择复制终端升级命令");
|
|
|
|
|
showMessage("success", "升级命令已复制到剪贴板");
|
|
|
|
|
} else {
|
|
|
|
|
await log(
|
|
|
|
|
"info",
|
|
|
|
|
`检查更新(前端): 关闭「发现新版本」或未应用内升级 action=${String(action)}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const doUpgrade = await confirmNative({
|
|
|
|
|
title: "发现新版本",
|
|
|
|
|
message: [
|
|
|
|
|
`检测到新版本:${result.candidateVersion}`,
|
|
|
|
|
`当前已安装:${result.installedVersion}`,
|
|
|
|
|
"",
|
|
|
|
|
"升级将由本程序在授权后固定执行(更新索引并仅升级 call-client),需在弹出框中输入管理员密码。",
|
|
|
|
|
"请勿在终端自行编写或执行更新脚本。",
|
|
|
|
|
].join("\n"),
|
|
|
|
|
okLabel: "应用内升级",
|
|
|
|
|
cancelLabel: "稍后再说",
|
|
|
|
|
});
|
|
|
|
|
if (!doUpgrade) {
|
|
|
|
|
await log("info", "检查更新(前端): 用户选择稍后再升级");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -537,7 +498,7 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
|
}
|
|
|
|
|
showMessage(
|
|
|
|
|
"success",
|
|
|
|
|
`升级命令已执行完毕。界面显示版本:${appVersion.value}。若仍为旧版本,请重启本客户端后再试。`,
|
|
|
|
|
`升级已执行。界面显示版本:${appVersion.value}。若仍为旧版本,请重启本客户端后再试。`,
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|