|
|
|
@ -1,11 +1,13 @@
|
|
|
|
<script setup lang="ts">
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { Close, Lock, Minus, User } from "@element-plus/icons-vue";
|
|
|
|
import { Close, Lock, Minus, User } from "@element-plus/icons-vue";
|
|
|
|
import { ElForm, ElMessage, ElMessageBox } from "element-plus";
|
|
|
|
import { ElDialog, ElForm, ElMessage, ElMessageBox, ElProgress } from "element-plus";
|
|
|
|
import { getVersion } from "@tauri-apps/api/app";
|
|
|
|
import { getVersion } from "@tauri-apps/api/app";
|
|
|
|
import { writeText } from "@tauri-apps/api/clipboard";
|
|
|
|
import { writeText } from "@tauri-apps/api/clipboard";
|
|
|
|
|
|
|
|
import { listen } from "@tauri-apps/api/event";
|
|
|
|
import { invoke } from "@tauri-apps/api/tauri";
|
|
|
|
import { invoke } from "@tauri-apps/api/tauri";
|
|
|
|
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
|
|
|
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
|
|
|
import { api } from "../api";
|
|
|
|
import { api } from "../api";
|
|
|
|
|
|
|
|
import { confirmNative, showErrorNative } from "../host/dialog";
|
|
|
|
import { getAllConfig, mergeConfig } from "../host/config";
|
|
|
|
import { getAllConfig, mergeConfig } from "../host/config";
|
|
|
|
import { log } from "../host/logger";
|
|
|
|
import { log } from "../host/logger";
|
|
|
|
import { setSession } from "../host/session";
|
|
|
|
import { setSession } from "../host/session";
|
|
|
|
@ -31,9 +33,23 @@ const cachedWinKey = ref("");
|
|
|
|
const options = ref<Array<{ label: string; value: string }>>([]);
|
|
|
|
const options = ref<Array<{ label: string; value: string }>>([]);
|
|
|
|
const appVersion = ref("0.1.0");
|
|
|
|
const appVersion = ref("0.1.0");
|
|
|
|
const checkingUpdate = ref(false);
|
|
|
|
const checkingUpdate = ref(false);
|
|
|
|
const APT_SOURCE_ENTRY =
|
|
|
|
const ZYYUN_APT_SETUP_PROGRESS_EVENT = "zyyun-apt-setup-progress";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const aptSetupDialogVisible = ref(false);
|
|
|
|
|
|
|
|
const aptSetupPercent = ref(0);
|
|
|
|
|
|
|
|
const aptSetupHint = ref("");
|
|
|
|
|
|
|
|
const aptSetupFinished = ref(false);
|
|
|
|
|
|
|
|
const aptSetupProgressStatus = ref<"success" | "exception" | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 默认紫云 apt 源行;可通过配置项 apt_source_deb_line 覆盖(见 ~/.config/com.ziyun.callclient/config.json) */
|
|
|
|
|
|
|
|
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";
|
|
|
|
"deb [arch=amd64 signed-by=/usr/share/keyrings/zyyun-archive-keyring.gpg] http://80.12.140.29:80/apt v10 main";
|
|
|
|
const APT_SOURCE_SETUP_COMMAND = `echo "${APT_SOURCE_ENTRY}" | sudo tee /etc/apt/sources.list.d/zyyun.list && sudo apt update`;
|
|
|
|
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 {
|
|
|
|
interface AptUpdateCheckResult {
|
|
|
|
packageName: string;
|
|
|
|
packageName: string;
|
|
|
|
@ -45,6 +61,11 @@ interface AptUpdateCheckResult {
|
|
|
|
updateCommand: string;
|
|
|
|
updateCommand: string;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface AptSetupProgressPayload {
|
|
|
|
|
|
|
|
percent: number;
|
|
|
|
|
|
|
|
message: string;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let sessionState: SessionState = {
|
|
|
|
let sessionState: SessionState = {
|
|
|
|
empUid: null,
|
|
|
|
empUid: null,
|
|
|
|
winUid: null,
|
|
|
|
winUid: null,
|
|
|
|
@ -306,6 +327,34 @@ function handleInputFocus(event: Event): void {
|
|
|
|
target.select();
|
|
|
|
target.select();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 自动写入紫云 apt 源并 apt-get update;Rust 通过事件推送进度。
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function runZyyunAptSourceSetupWithProgress(debLine: string): Promise<void> {
|
|
|
|
|
|
|
|
aptSetupDialogVisible.value = true;
|
|
|
|
|
|
|
|
aptSetupFinished.value = false;
|
|
|
|
|
|
|
|
aptSetupPercent.value = 0;
|
|
|
|
|
|
|
|
aptSetupHint.value = "准备中…";
|
|
|
|
|
|
|
|
aptSetupProgressStatus.value = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const unlisten = await listen<AptSetupProgressPayload>(ZYYUN_APT_SETUP_PROGRESS_EVENT, (e) => {
|
|
|
|
|
|
|
|
const p = e.payload;
|
|
|
|
|
|
|
|
aptSetupPercent.value = Math.min(100, Math.max(0, Number(p.percent) || 0));
|
|
|
|
|
|
|
|
aptSetupHint.value = typeof p.message === "string" ? p.message : "";
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await invoke("setup_zyyun_apt_source", { debLine: debLine.trim() });
|
|
|
|
|
|
|
|
aptSetupProgressStatus.value = "success";
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
aptSetupProgressStatus.value = "exception";
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
unlisten();
|
|
|
|
|
|
|
|
aptSetupFinished.value = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* 检查 apt 仓库中是否有新版本,并引导复制升级命令。
|
|
|
|
* 检查 apt 仓库中是否有新版本,并引导复制升级命令。
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
@ -317,41 +366,98 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
checkingUpdate.value = true;
|
|
|
|
checkingUpdate.value = true;
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
await log("info", `检查更新开始: package=call-client, currentVersion=${appVersion.value}`);
|
|
|
|
await log("info", `检查更新开始: package=call-client, currentVersion=${appVersion.value}`);
|
|
|
|
const result = await invoke<AptUpdateCheckResult>("check_apt_update", {
|
|
|
|
|
|
|
|
packageName: "call-client",
|
|
|
|
const invokeCheck = (): Promise<AptUpdateCheckResult> =>
|
|
|
|
currentVersion: appVersion.value,
|
|
|
|
invoke<AptUpdateCheckResult>("check_apt_update", {
|
|
|
|
});
|
|
|
|
packageName: "call-client",
|
|
|
|
|
|
|
|
currentVersion: appVersion.value,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let result = await invokeCheck();
|
|
|
|
await log(
|
|
|
|
await log(
|
|
|
|
"info",
|
|
|
|
"info",
|
|
|
|
`检查更新结果: package=${result.packageName}, installed=${result.installedVersion}, candidate=${result.candidateVersion}, hasUpdate=${result.hasUpdate}, sourceAvailable=${result.sourceAvailable}`,
|
|
|
|
`检查更新结果: package=${result.packageName}, installed=${result.installedVersion}, candidate=${result.candidateVersion}, hasUpdate=${result.hasUpdate}, sourceAvailable=${result.sourceAvailable}`,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!result.sourceAvailable) {
|
|
|
|
if (!result.sourceAvailable) {
|
|
|
|
await log("warn", `检查更新提示: 未检测到可用更新源, package=${result.packageName}`);
|
|
|
|
const cfg = await getAllConfig();
|
|
|
|
|
|
|
|
const alreadyConfigured = cfg.zyyun_apt_source_configured === true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (alreadyConfigured) {
|
|
|
|
|
|
|
|
await log(
|
|
|
|
|
|
|
|
"warn",
|
|
|
|
|
|
|
|
"检查更新: 已记录自动配置过 apt 源,但候选版本仍不可用,请人工排查网络或 GPG 密钥",
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
showMessage(
|
|
|
|
|
|
|
|
"warning",
|
|
|
|
|
|
|
|
"本机已自动配置过紫云软件源,但 apt 仍无法解析候选版本。请在终端执行 sudo apt update 后重试,或检查 /usr/share/keyrings/zyyun-archive-keyring.gpg 是否存在。",
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
[
|
|
|
|
"未检测到可用更新源。是否使用管理员权限自动写入紫云软件源并执行 apt 更新?(需要桌面环境的 pkexec 授权)",
|
|
|
|
"未检测到可用更新源,请先配置固定更新地址:",
|
|
|
|
"配置更新源",
|
|
|
|
APT_SOURCE_ENTRY,
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
|
|
|
"点击“复制命令”后,在终端执行:",
|
|
|
|
|
|
|
|
APT_SOURCE_SETUP_COMMAND,
|
|
|
|
|
|
|
|
].join("\n"),
|
|
|
|
|
|
|
|
"更新源未配置",
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
confirmButtonText: "复制命令",
|
|
|
|
|
|
|
|
cancelButtonText: "关闭",
|
|
|
|
|
|
|
|
distinguishCancelAndClose: true,
|
|
|
|
|
|
|
|
type: "warning",
|
|
|
|
type: "warning",
|
|
|
|
|
|
|
|
confirmButtonText: "开始配置",
|
|
|
|
|
|
|
|
cancelButtonText: "取消",
|
|
|
|
|
|
|
|
distinguishCancelAndClose: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await writeText(APT_SOURCE_SETUP_COMMAND);
|
|
|
|
|
|
|
|
showMessage("success", "更新源初始化命令已复制到剪贴板");
|
|
|
|
|
|
|
|
} catch {
|
|
|
|
} catch {
|
|
|
|
// 用户取消不提示错误。
|
|
|
|
await log("info", "用户取消自动配置 apt 源");
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await runZyyunAptSourceSetupWithProgress(aptSourceDebLine.value);
|
|
|
|
|
|
|
|
await mergeConfig({ zyyun_apt_source_configured: true });
|
|
|
|
|
|
|
|
await log("info", "已写入配置: zyyun_apt_source_configured=true");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = await invokeCheck();
|
|
|
|
|
|
|
|
await log(
|
|
|
|
|
|
|
|
"info",
|
|
|
|
|
|
|
|
`自动配置后再次检查: installed=${result.installedVersion}, candidate=${result.candidateVersion}, sourceAvailable=${result.sourceAvailable}`,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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", "命令已复制到剪贴板");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!result.hasUpdate) {
|
|
|
|
if (!result.hasUpdate) {
|
|
|
|
@ -364,32 +470,28 @@ async function handleCheckUpdate(): Promise<void> {
|
|
|
|
"info",
|
|
|
|
"info",
|
|
|
|
`检查更新提示: 检测到新版本, installed=${result.installedVersion}, candidate=${result.candidateVersion}`,
|
|
|
|
`检查更新提示: 检测到新版本, installed=${result.installedVersion}, candidate=${result.candidateVersion}`,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
try {
|
|
|
|
const body = [
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
`检测到新版本:${result.candidateVersion}`,
|
|
|
|
[
|
|
|
|
`当前版本:${result.installedVersion}`,
|
|
|
|
`检测到新版本:${result.candidateVersion}`,
|
|
|
|
"",
|
|
|
|
`当前版本:${result.installedVersion}`,
|
|
|
|
"点击「复制命令」后,在终端执行升级:",
|
|
|
|
"",
|
|
|
|
result.updateCommand,
|
|
|
|
"点击“复制命令”后,在终端执行升级:",
|
|
|
|
].join("\n");
|
|
|
|
result.updateCommand,
|
|
|
|
const copy = await confirmNative({
|
|
|
|
].join("\n"),
|
|
|
|
title: "发现新版本",
|
|
|
|
"发现新版本",
|
|
|
|
message: body,
|
|
|
|
{
|
|
|
|
okLabel: "复制命令",
|
|
|
|
confirmButtonText: "复制命令",
|
|
|
|
cancelLabel: "关闭",
|
|
|
|
cancelButtonText: "关闭",
|
|
|
|
});
|
|
|
|
distinguishCancelAndClose: true,
|
|
|
|
if (copy) {
|
|
|
|
type: "info",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await writeText(result.updateCommand);
|
|
|
|
await writeText(result.updateCommand);
|
|
|
|
showMessage("success", "升级命令已复制到剪贴板");
|
|
|
|
showMessage("success", "升级命令已复制到剪贴板");
|
|
|
|
} catch {
|
|
|
|
|
|
|
|
// 用户取消不提示错误。
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
|
showMessage("error", `检查更新失败:${message}`);
|
|
|
|
await showErrorNative(`检查更新失败:${message}`, "检查更新", {
|
|
|
|
|
|
|
|
logActions: "file",
|
|
|
|
|
|
|
|
});
|
|
|
|
await log("error", `检查更新失败: ${message}`);
|
|
|
|
await log("error", `检查更新失败: ${message}`);
|
|
|
|
} finally {
|
|
|
|
} finally {
|
|
|
|
checkingUpdate.value = false;
|
|
|
|
checkingUpdate.value = false;
|
|
|
|
@ -404,6 +506,12 @@ onMounted(async () => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const config = await getAllConfig();
|
|
|
|
const config = await getAllConfig();
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
typeof config.apt_source_deb_line === "string" &&
|
|
|
|
|
|
|
|
config.apt_source_deb_line.trim() !== ""
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
aptSourceDebLine.value = config.apt_source_deb_line.trim();
|
|
|
|
|
|
|
|
}
|
|
|
|
if (typeof config.last_username === "string" && config.last_username.trim()) {
|
|
|
|
if (typeof config.last_username === "string" && config.last_username.trim()) {
|
|
|
|
loginForm.username = config.last_username;
|
|
|
|
loginForm.username = config.last_username;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -595,7 +703,24 @@ onUnmounted(() => {
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
|
|
v-model="aptSetupDialogVisible"
|
|
|
|
|
|
|
|
title="正在配置更新源"
|
|
|
|
|
|
|
|
width="440px"
|
|
|
|
|
|
|
|
class="apt-setup-dialog"
|
|
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
|
|
:close-on-press-escape="aptSetupFinished"
|
|
|
|
|
|
|
|
:show-close="aptSetupFinished"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<el-progress
|
|
|
|
|
|
|
|
:percentage="aptSetupPercent"
|
|
|
|
|
|
|
|
:status="aptSetupProgressStatus"
|
|
|
|
|
|
|
|
:stroke-width="16"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
<p class="apt-setup-hint">{{ aptSetupHint }}</p>
|
|
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
@ -785,6 +910,13 @@ onUnmounted(() => {
|
|
|
|
margin-top: 5px;
|
|
|
|
margin-top: 5px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.apt-setup-hint {
|
|
|
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
|
|
line-height: 1.45;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:global(.narrow-window-message) {
|
|
|
|
:global(.narrow-window-message) {
|
|
|
|
min-width: 0 !important;
|
|
|
|
min-width: 0 !important;
|
|
|
|
width: calc(100vw - 32px) !important;
|
|
|
|
width: calc(100vw - 32px) !important;
|
|
|
|
|