You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
538 lines
46 KiB
538 lines
46 KiB
import { defineComponent, computed, ref, reactive, watch, mergeProps, useSSRContext } from 'vue';
|
|
import { ssrRenderAttrs, ssrInterpolate, ssrRenderClass, ssrRenderStyle, ssrRenderAttr, ssrRenderList, ssrRenderComponent, ssrIncludeBooleanAttr, ssrLooseContain, ssrLooseEqual, ssrRenderDynamicModel } from 'vue/server-renderer';
|
|
import { u as useApi } from './useApi-D_CttG2t.mjs';
|
|
import { u as useToast } from './useToast-CS21XT61.mjs';
|
|
import { u as useAuthStore } from './auth-QFkCoU1C.mjs';
|
|
import { _ as _export_sfc, b as useRoute$1, a as useRouter } from './server.mjs';
|
|
import './useLoading-D4zrSa2m.mjs';
|
|
import 'pinia';
|
|
import '../nitro/nitro.mjs';
|
|
import 'node:http';
|
|
import 'node:https';
|
|
import 'node:events';
|
|
import 'node:buffer';
|
|
import 'node:fs';
|
|
import 'node:path';
|
|
import 'node:crypto';
|
|
import 'node:url';
|
|
import '../routes/renderer.mjs';
|
|
import 'vue-bundle-renderer/runtime';
|
|
import 'unhead/server';
|
|
import 'devalue';
|
|
import 'unhead/utils';
|
|
import 'perfect-debounce';
|
|
|
|
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
__name: "AppDeployTab",
|
|
__ssrInlineRender: true,
|
|
props: {
|
|
appName: {},
|
|
defaultImage: {},
|
|
deployments: {},
|
|
envs: {}
|
|
},
|
|
emits: ["refresh", "configure"],
|
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
const props = __props;
|
|
useApi();
|
|
useToast();
|
|
useAuthStore();
|
|
useRouter();
|
|
const REGIONS = ["中国", "美国", "东亚", "东南亚", "南亚", "中东", "欧洲", "俄罗斯", "非洲", "南美", "北美", "大洋洲", "巴西", "印度"];
|
|
function dbName(dsn) {
|
|
const m = (dsn || "").match(/\/([^/?]+)\?/);
|
|
return m ? m[1] : dsn ? "(自定义)" : "未配置";
|
|
}
|
|
function isPinned(tag) {
|
|
const t = (tag || "").trim();
|
|
return !!t && t !== "latest";
|
|
}
|
|
const envSavingId = ref(0);
|
|
const formDlg = ref(null);
|
|
const dIsEditing = ref(false);
|
|
const dMode = ref("existing");
|
|
const dForm = reactive({
|
|
id: 0,
|
|
name: "",
|
|
region: "",
|
|
env_id: 0,
|
|
base_url: "",
|
|
service_dsn: "",
|
|
deploy_dir: "",
|
|
image_name: "",
|
|
image_tag: "",
|
|
remark: "",
|
|
enabled: true
|
|
});
|
|
const testPassed = ref(false);
|
|
const testing = ref(false);
|
|
const testResult = reactive({ show: false, apiOk: false, apiMsg: "", dbOk: false, dbMsg: "" });
|
|
const availableRegions = computed(() => {
|
|
const used = new Set(props.deployments.filter((d) => d.id !== dForm.id).map((d) => d.region).filter(Boolean));
|
|
return REGIONS.filter((r) => !used.has(r) || r === dForm.region);
|
|
});
|
|
function resetTest() {
|
|
testPassed.value = false;
|
|
testResult.show = false;
|
|
}
|
|
function openNew() {
|
|
dIsEditing.value = false;
|
|
dMode.value = "existing";
|
|
Object.assign(dForm, {
|
|
id: 0,
|
|
name: "",
|
|
region: "",
|
|
env_id: 0,
|
|
base_url: "",
|
|
service_dsn: "",
|
|
deploy_dir: "",
|
|
image_name: props.defaultImage,
|
|
image_tag: "",
|
|
remark: "",
|
|
enabled: true
|
|
});
|
|
resetTest();
|
|
formDlg.value?.showModal();
|
|
}
|
|
ref(null);
|
|
ref(null);
|
|
const up = reactive({
|
|
id: 0,
|
|
label: "",
|
|
envName: "",
|
|
deployDir: "",
|
|
tag: "",
|
|
tags: [],
|
|
tagMode: "list",
|
|
tagsLoading: false,
|
|
tagsError: "",
|
|
log: "",
|
|
show: false,
|
|
running: false,
|
|
ok: false,
|
|
canRun: false,
|
|
btnText: "开始升级"
|
|
});
|
|
ref(null);
|
|
const az = reactive({
|
|
appId: 0,
|
|
label: "",
|
|
start: "",
|
|
end: "",
|
|
mode: "",
|
|
steps: [],
|
|
curIndex: 0,
|
|
curTotal: 0,
|
|
curName: "",
|
|
pct: 0,
|
|
errMsg: "",
|
|
report: null,
|
|
running: false,
|
|
btnText: "开始分析"
|
|
});
|
|
__expose({ openNew });
|
|
return (_ctx, _push, _parent, _attrs) => {
|
|
_push(`<div${ssrRenderAttrs(_attrs)} data-v-b8dfd02c><div class="card" data-v-b8dfd02c><div class="card-head" data-v-b8dfd02c><div data-v-b8dfd02c><div class="card-title" data-v-b8dfd02c>区域部署</div><div class="card-desc" data-v-b8dfd02c> 每个区域一份部署,各自连自己的业务库。「升级」会 SSH 到绑定的环境节点,在部署目录执行 <span class="font-mono" data-v-b8dfd02c>docker compose pull & up -d</span>。 </div></div><button class="btn btn-primary btn-xs text-white shrink-0" data-v-b8dfd02c>+ 部署新环境</button></div>`);
|
|
if (!__props.deployments.length) {
|
|
_push(`<div class="py-14 text-center text-slate-400 text-sm" data-v-b8dfd02c> 该应用还没有部署,点右上「+ 部署新环境」添加。 </div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<!--[-->`);
|
|
ssrRenderList(__props.deployments, (d) => {
|
|
_push(`<div class="${ssrRenderClass([{ pending: d.status === "pending" }, "dep"])}" data-v-b8dfd02c><div class="min-w-0" data-v-b8dfd02c><div class="dep-name" data-v-b8dfd02c><span class="chip chip-dep" data-v-b8dfd02c>部署</span><span class="truncate" data-v-b8dfd02c>${ssrInterpolate(d.name || "部署#" + d.id)}</span>`);
|
|
if (d.status === "pending") {
|
|
_push(`<span class="chip chip-warn" data-v-b8dfd02c>待部署</span>`);
|
|
} else {
|
|
_push(`<span class="chip chip-ok" data-v-b8dfd02c>已接入</span>`);
|
|
}
|
|
if (!d.enabled) {
|
|
_push(`<span class="chip" data-v-b8dfd02c>停用</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><div class="dep-sub" data-v-b8dfd02c>#${ssrInterpolate(d.id)} · ${ssrInterpolate(d.region || "未指定区域")} · 业务库 ${ssrInterpolate(dbName(d.service_dsn))}</div></div><div data-v-b8dfd02c><div class="field-l" data-v-b8dfd02c>环境节点</div><select class="select select-bordered select-xs w-full text-xs"${ssrIncludeBooleanAttr(envSavingId.value === d.id) ? " disabled" : ""} data-v-b8dfd02c><option${ssrRenderAttr("value", 0)} data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(d.env_id) ? ssrLooseContain(d.env_id, 0) : ssrLooseEqual(d.env_id, 0)) ? " selected" : ""}>未绑定</option><!--[-->`);
|
|
ssrRenderList(__props.envs, (e) => {
|
|
_push(`<option${ssrRenderAttr("value", e.id)} data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(d.env_id) ? ssrLooseContain(d.env_id, e.id) : ssrLooseEqual(d.env_id, e.id)) ? " selected" : ""}>${ssrInterpolate(e.name)}</option>`);
|
|
});
|
|
_push(`<!--]--></select></div><div data-v-b8dfd02c><div class="field-l" data-v-b8dfd02c>当前版本</div><div class="flex items-center gap-1.5" data-v-b8dfd02c><span class="${ssrRenderClass([isPinned(d.image_tag) ? "chip-ok" : "chip-warn", "chip chip-mono"])}" data-v-b8dfd02c>${ssrInterpolate(d.image_tag || "latest")}</span>`);
|
|
if (!isPinned(d.image_tag)) {
|
|
_push(`<span class="text-[10.5px] text-amber-700" title="latest 是可变 tag,升级后无法从列表看出实际版本" data-v-b8dfd02c>未固定</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div></div><div class="min-w-0" data-v-b8dfd02c><div class="field-l" data-v-b8dfd02c>部署目录</div><div class="${ssrRenderClass([d.deploy_dir ? "text-slate-500" : "text-red-500", "font-mono text-[11px] truncate"])}" data-v-b8dfd02c>${ssrInterpolate(d.deploy_dir || "未填")}</div></div><div class="dep-acts" data-v-b8dfd02c><button class="btn btn-xs btn-warning" data-v-b8dfd02c>升级</button><button class="btn btn-xs" data-v-b8dfd02c>配置</button><button class="btn btn-xs btn-ghost text-teal-600"${ssrIncludeBooleanAttr(!d.base_url) ? " disabled" : ""}${ssrRenderAttr("title", d.base_url ? "打开该部署的 Swagger 文档" : "该部署未配置对外地址(base_url)")} data-v-b8dfd02c>API 测试</button><button class="btn btn-xs btn-ghost" data-v-b8dfd02c>编辑</button><button class="btn btn-xs btn-ghost text-emerald-600"${ssrIncludeBooleanAttr(d.status === "pending") ? " disabled" : ""} data-v-b8dfd02c>分析</button><button class="btn btn-xs btn-ghost text-indigo-500"${ssrIncludeBooleanAttr(d.status === "pending") ? " disabled" : ""} data-v-b8dfd02c>发事件</button><button class="btn btn-xs btn-ghost text-red-500" data-v-b8dfd02c>删除</button></div></div>`);
|
|
});
|
|
_push(`<!--]--></div><dialog class="modal" data-v-b8dfd02c><div class="modal-box max-w-3xl" data-v-b8dfd02c><h3 class="font-bold text-lg" data-v-b8dfd02c>${ssrInterpolate(dIsEditing.value ? "编辑部署" : "部署新环境")}</h3>`);
|
|
if (!dIsEditing.value) {
|
|
_push(`<div class="tabs tabs-boxed bg-slate-100 w-fit my-3" data-v-b8dfd02c><a class="${ssrRenderClass([dMode.value === "existing" ? "tab-active" : "", "tab"])}" data-v-b8dfd02c>添加现有</a><a class="${ssrRenderClass([dMode.value === "deploy" ? "tab-active" : "", "tab"])}" data-v-b8dfd02c>部署到目标环境</a></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<p class="${ssrRenderClass([dIsEditing.value ? "mt-1" : "", "text-xs text-slate-400 mb-4"])}" data-v-b8dfd02c>`);
|
|
if (dIsEditing.value) {
|
|
_push(`<!--[-->改好后回列表点「升级」执行远程部署(拉新镜像并重启)。<!--]-->`);
|
|
} else if (dMode.value === "existing") {
|
|
_push(`<!--[-->把「已经部署好的应用区域」加进来:填地址与业务库 DSN,测试通过后保存。<!--]-->`);
|
|
} else {
|
|
_push(`<!--[-->选一个目标环境节点,先建关联并标记<b data-v-b8dfd02c>待部署</b>;保存后回列表点「升级」执行首次部署。<!--]-->`);
|
|
}
|
|
_push(`</p><form class="space-y-3" data-v-b8dfd02c><div class="grid grid-cols-2 gap-3" data-v-b8dfd02c><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>所属应用</span></label><input${ssrRenderAttr("value", __props.appName)} type="text" class="input input-bordered input-sm bg-slate-100" readonly data-v-b8dfd02c></div><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>部署名称 *</span></label><input${ssrRenderAttr("value", dForm.name)} type="text" class="input input-bordered input-sm" required${ssrRenderAttr("placeholder", `如 ${__props.appName}(国内)`)} data-v-b8dfd02c></div></div><div class="grid grid-cols-2 gap-3" data-v-b8dfd02c><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>区域</span></label><select class="select select-bordered select-sm" data-v-b8dfd02c><option value="" data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(dForm.region) ? ssrLooseContain(dForm.region, "") : ssrLooseEqual(dForm.region, "")) ? " selected" : ""}>未指定</option><!--[-->`);
|
|
ssrRenderList(availableRegions.value, (r) => {
|
|
_push(`<option${ssrRenderAttr("value", r)} data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(dForm.region) ? ssrLooseContain(dForm.region, r) : ssrLooseEqual(dForm.region, r)) ? " selected" : ""}>${ssrInterpolate(r)}</option>`);
|
|
});
|
|
_push(`<!--]--></select><span class="text-[11px] text-slate-400 mt-1" data-v-b8dfd02c>已部署的区域不再出现(一个应用每区域一份部署)。</span></div><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>部署环境${ssrInterpolate(dMode.value === "deploy" ? " *" : "")}</span></label><select class="select select-bordered select-sm" data-v-b8dfd02c><option${ssrRenderAttr("value", 0)} data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(dForm.env_id) ? ssrLooseContain(dForm.env_id, 0) : ssrLooseEqual(dForm.env_id, 0)) ? " selected" : ""}>${ssrInterpolate(dMode.value === "deploy" ? "请选择目标环境" : "未关联")}</option><!--[-->`);
|
|
ssrRenderList(__props.envs, (e) => {
|
|
_push(`<option${ssrRenderAttr("value", e.id)} data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(dForm.env_id) ? ssrLooseContain(dForm.env_id, e.id) : ssrLooseEqual(dForm.env_id, e.id)) ? " selected" : ""}>${ssrInterpolate(e.name)}(${ssrInterpolate(e.code || "-")})</option>`);
|
|
});
|
|
_push(`<!--]--></select></div></div><div class="grid grid-cols-3 gap-3" data-v-b8dfd02c><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>部署目录</span></label><input${ssrRenderAttr("value", dForm.deploy_dir)} type="text" class="input input-bordered input-sm font-mono text-xs" placeholder="/home/work/app" data-v-b8dfd02c></div><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>镜像名</span></label><input${ssrRenderAttr("value", dForm.image_name)} type="text" class="input input-bordered input-sm font-mono text-xs"${ssrRenderAttr("placeholder", __props.defaultImage || "默认取应用")} data-v-b8dfd02c></div><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>镜像 Tag</span></label><input${ssrRenderAttr("value", dForm.image_tag)} type="text" class="input input-bordered input-sm font-mono text-xs" placeholder="0.1.1 / latest" data-v-b8dfd02c></div></div><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>业务库 DSN${ssrInterpolate(dMode.value === "existing" ? " *" : "")}</span></label><input${ssrRenderAttr("value", dForm.service_dsn)} type="text" class="input input-bordered input-sm font-mono text-xs" placeholder="root:pwd@tcp(host:3306)/earphone?charset=utf8mb4&parseTime=True&loc=Local" data-v-b8dfd02c><span class="text-[11px] text-slate-400 mt-1" data-v-b8dfd02c>业务功能配置、会议记录服务配置都写在这个库里。</span></div>`);
|
|
if (dMode.value === "existing") {
|
|
_push(`<!--[--><div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>应用地址 *</span></label><input${ssrRenderAttr("value", dForm.base_url)} type="text" class="input input-bordered input-sm" placeholder="https://api.xxx.com/" data-v-b8dfd02c><span class="text-[11px] text-slate-400 mt-1" data-v-b8dfd02c>用于通知应用业务服务事件(如配置更新)、打开 Swagger。</span></div>`);
|
|
if (testResult.show) {
|
|
_push(`<div class="text-sm space-y-0.5" data-v-b8dfd02c><div class="${ssrRenderClass(testResult.apiOk ? "text-green-700" : "text-red-600")}" data-v-b8dfd02c>${ssrInterpolate(testResult.apiOk ? "✓" : "✗")} 应用地址:${ssrInterpolate(testResult.apiMsg)}</div><div class="${ssrRenderClass(testResult.dbOk ? "text-green-700" : "text-red-600")}" data-v-b8dfd02c>${ssrInterpolate(testResult.dbOk ? "✓" : "✗")} 业务库:${ssrInterpolate(testResult.dbMsg)}</div></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<!--]-->`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<div class="form-control" data-v-b8dfd02c><label class="label py-1" data-v-b8dfd02c><span class="label-text text-sm font-medium" data-v-b8dfd02c>备注</span></label><input${ssrRenderAttr("value", dForm.remark)} type="text" class="input input-bordered input-sm" data-v-b8dfd02c></div><label class="flex items-center gap-2 cursor-pointer" data-v-b8dfd02c><input${ssrIncludeBooleanAttr(Array.isArray(dForm.enabled) ? ssrLooseContain(dForm.enabled, null) : dForm.enabled) ? " checked" : ""} type="checkbox" class="toggle toggle-primary toggle-sm" data-v-b8dfd02c><span class="text-sm" data-v-b8dfd02c>启用</span></label><div class="modal-action" data-v-b8dfd02c><button type="button" class="btn btn-ghost btn-sm" data-v-b8dfd02c>取消</button>`);
|
|
if (dMode.value === "existing") {
|
|
_push(`<button type="button" class="btn btn-outline btn-sm"${ssrIncludeBooleanAttr(testing.value) ? " disabled" : ""} data-v-b8dfd02c>${ssrInterpolate(testing.value ? "测试中…" : "测试连接")}</button>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<button type="submit" class="btn btn-primary btn-sm text-white"${ssrIncludeBooleanAttr(!dIsEditing.value && dMode.value === "existing" && !testPassed.value) ? " disabled" : ""} data-v-b8dfd02c>保存</button></div>`);
|
|
if (!dIsEditing.value && dMode.value === "existing") {
|
|
_push(`<p class="text-[11px] text-slate-400 text-right" data-v-b8dfd02c> 需先「测试连接」通过(应用地址可达 + 业务库可连)才能保存。 </p>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</form></div><form method="dialog" class="modal-backdrop" data-v-b8dfd02c><button data-v-b8dfd02c>close</button></form></dialog><dialog class="modal" data-v-b8dfd02c><div class="modal-box max-w-2xl" data-v-b8dfd02c><h3 class="font-bold text-lg" data-v-b8dfd02c>升级部署</h3><p class="text-sm text-slate-500 mt-1" data-v-b8dfd02c>${ssrInterpolate(up.label)}</p><div class="text-xs text-amber-700 mt-3 bg-amber-50 border border-amber-100 rounded-lg p-3 leading-relaxed" data-v-b8dfd02c> 将 <b data-v-b8dfd02c>SSH 连到该部署绑定的环境节点</b>,进入部署目录执行 <span class="font-mono" data-v-b8dfd02c>docker login → compose pull → up -d → 清理旧镜像</span>。<br data-v-b8dfd02c> · 需服务器上已维护好 <span class="font-mono" data-v-b8dfd02c>docker-compose.yml / .env / confs</span>(本操作不上传真实配置)。<br data-v-b8dfd02c> · 会<b data-v-b8dfd02c>重启该部署的容器</b>,请确认无误后再执行。 </div><dl class="grid grid-cols-[76px_1fr] gap-x-3 gap-y-1 text-xs mt-4" data-v-b8dfd02c><dt class="text-slate-400" data-v-b8dfd02c>环境节点</dt><dd class="${ssrRenderClass(up.envName ? "font-medium text-slate-700" : "font-medium text-red-500")}" data-v-b8dfd02c>${ssrInterpolate(up.envName || "未绑定 —— 请先在「编辑」里选择环境节点")}</dd><dt class="text-slate-400" data-v-b8dfd02c>部署目录</dt><dd class="${ssrRenderClass(up.deployDir ? "font-mono text-slate-700" : "font-medium text-red-500")}" data-v-b8dfd02c>${ssrInterpolate(up.deployDir || "未填 —— 请先在「编辑」里填写远端部署目录")}</dd></dl><div class="mt-3" data-v-b8dfd02c><div class="flex items-center justify-between mb-1" data-v-b8dfd02c><label class="text-xs text-slate-500" data-v-b8dfd02c>镜像版本 Tag</label><button type="button" class="text-xs text-indigo-500 hover:underline"${ssrIncludeBooleanAttr(up.running) ? " disabled" : ""} data-v-b8dfd02c>${ssrInterpolate(up.tagMode === "list" ? "手动输入…" : "从仓库选择")}</button></div>`);
|
|
if (up.tagMode === "list") {
|
|
_push(`<div class="flex gap-2" data-v-b8dfd02c><select class="select select-bordered select-sm flex-1 font-mono"${ssrIncludeBooleanAttr(up.running || up.tagsLoading) ? " disabled" : ""} data-v-b8dfd02c><option value="" data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(up.tag) ? ssrLooseContain(up.tag, "") : ssrLooseEqual(up.tag, "")) ? " selected" : ""}>${ssrInterpolate(up.tagsLoading ? "加载可用版本中…" : "请选择版本")}</option>`);
|
|
if (up.tag && !up.tags.includes(up.tag)) {
|
|
_push(`<option${ssrRenderAttr("value", up.tag)} data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(up.tag) ? ssrLooseContain(up.tag, up.tag) : ssrLooseEqual(up.tag, up.tag)) ? " selected" : ""}>${ssrInterpolate(up.tag)}(当前记录)</option>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<!--[-->`);
|
|
ssrRenderList(up.tags, (t) => {
|
|
_push(`<option${ssrRenderAttr("value", t)} data-v-b8dfd02c${ssrIncludeBooleanAttr(Array.isArray(up.tag) ? ssrLooseContain(up.tag, t) : ssrLooseEqual(up.tag, t)) ? " selected" : ""}>${ssrInterpolate(t)}${ssrInterpolate(t === "latest" ? " · 不固定,不推荐" : "")}</option>`);
|
|
});
|
|
_push(`<!--]--></select><button type="button" class="btn btn-ghost btn-sm"${ssrIncludeBooleanAttr(up.running || up.tagsLoading) ? " disabled" : ""} data-v-b8dfd02c>刷新</button></div>`);
|
|
} else {
|
|
_push(`<input${ssrRenderAttr("value", up.tag)} type="text" class="input input-bordered input-sm w-full font-mono" placeholder="0.1.1"${ssrIncludeBooleanAttr(up.running) ? " disabled" : ""} data-v-b8dfd02c>`);
|
|
}
|
|
if (up.tagsError) {
|
|
_push(`<p class="text-xs text-amber-600 mt-1" data-v-b8dfd02c> 拉取仓库版本失败:${ssrInterpolate(up.tagsError)}(可点上方「手动输入」直接填版本号,或点「刷新」重试) </p>`);
|
|
} else if (!isPinned(up.tag)) {
|
|
_push(`<p class="text-xs text-amber-600 mt-1" data-v-b8dfd02c> ⚠ 未固定版本,升级后列表无法体现具体版本号,建议选明确版本(如 0.1.1)。 </p>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div>`);
|
|
if (up.show) {
|
|
_push(`<div class="mt-3" data-v-b8dfd02c><div class="flex items-center gap-2 text-xs mb-1" data-v-b8dfd02c>`);
|
|
if (up.running) {
|
|
_push(`<span class="loading loading-spinner loading-xs text-amber-500" data-v-b8dfd02c></span>`);
|
|
} else if (up.ok) {
|
|
_push(`<span class="text-green-600 font-semibold" data-v-b8dfd02c>✓ 部署完成</span>`);
|
|
} else {
|
|
_push(`<span class="text-red-600 font-semibold" data-v-b8dfd02c>✗ 部署失败</span>`);
|
|
}
|
|
_push(`<span class="text-slate-400" data-v-b8dfd02c>${ssrInterpolate(up.running ? "执行中…" : "")}</span></div><pre class="bg-slate-900 text-slate-100 text-[11px] leading-relaxed rounded-lg p-3 h-64 overflow-auto font-mono whitespace-pre-wrap" data-v-b8dfd02c>${ssrInterpolate(up.log || "(等待输出…)")}</pre></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<div class="modal-action" data-v-b8dfd02c><button class="btn btn-ghost btn-sm"${ssrIncludeBooleanAttr(up.running) ? " disabled" : ""} data-v-b8dfd02c>关闭</button><button class="btn btn-warning btn-sm"${ssrIncludeBooleanAttr(up.running) ? " disabled" : ""} data-v-b8dfd02c>${ssrInterpolate(up.btnText)}</button></div></div><form method="dialog" class="modal-backdrop" data-v-b8dfd02c><button${ssrIncludeBooleanAttr(up.running) ? " disabled" : ""} data-v-b8dfd02c>close</button></form></dialog><dialog class="modal" data-v-b8dfd02c><div class="modal-box" data-v-b8dfd02c><h3 class="font-bold text-lg" data-v-b8dfd02c>分析历史数据</h3><p class="text-sm text-slate-500 mt-1" data-v-b8dfd02c>${ssrInterpolate(az.label)}</p><div class="text-xs text-amber-700 mt-3 bg-amber-50 border border-amber-100 rounded-lg p-3 leading-relaxed" data-v-b8dfd02c> 扫描该应用业务库,按 <b data-v-b8dfd02c>天 + 产品 + 区域</b> 生成历史统计(新增用户 / 订单 / 资源发放·使用 / 激活绑定设备),汇入 console 主库。<br data-v-b8dfd02c> · <b data-v-b8dfd02c>分析前会先清理该应用+区域在所选区间内的旧统计行</b>,避免数据重叠。<br data-v-b8dfd02c> · 登录、活跃用户、AI对话等无每日原始记录,历史无法还原(由实时管道往后采集)。<br data-v-b8dfd02c> · 仅对实时管道上线前的区间运行。留空 = 全部历史。大库可能耗时。 </div><div class="grid grid-cols-2 gap-3 mt-4" data-v-b8dfd02c><div data-v-b8dfd02c><label class="text-xs text-slate-500" data-v-b8dfd02c>起始日(含)</label><input${ssrRenderAttr("value", az.start)} type="date" class="input input-bordered input-sm w-full" data-v-b8dfd02c></div><div data-v-b8dfd02c><label class="text-xs text-slate-500" data-v-b8dfd02c>结束日(含)</label><input${ssrRenderAttr("value", az.end)} type="date" class="input input-bordered input-sm w-full" data-v-b8dfd02c></div></div>`);
|
|
if (az.mode) {
|
|
_push(`<div class="text-sm mt-3" data-v-b8dfd02c>`);
|
|
if (az.mode === "progress") {
|
|
_push(`<div class="bg-slate-50 border border-slate-200 rounded-lg p-3" data-v-b8dfd02c><div class="flex justify-between items-center text-xs mb-1.5" data-v-b8dfd02c><span class="text-slate-600" data-v-b8dfd02c>`);
|
|
if (az.curName) {
|
|
_push(`<!--[--><span class="loading loading-spinner loading-xs" data-v-b8dfd02c></span> 正在:${ssrInterpolate(az.curName)} (${ssrInterpolate(az.curIndex)}/${ssrInterpolate(az.curTotal)})<!--]-->`);
|
|
} else {
|
|
_push(`<!--[-->准备中...<!--]-->`);
|
|
}
|
|
_push(`</span><span class="font-mono text-slate-400" data-v-b8dfd02c>${ssrInterpolate(az.pct)}%</span></div><progress class="progress progress-primary w-full h-2"${ssrRenderAttr("value", az.pct)} max="100" data-v-b8dfd02c></progress><div class="mt-2 text-xs space-y-0.5" data-v-b8dfd02c><!--[-->`);
|
|
ssrRenderList(az.steps, (nm, i) => {
|
|
_push(`<div class="${ssrRenderClass(i === az.curIndex - 1 ? "text-slate-600" : "text-green-600")}" data-v-b8dfd02c>`);
|
|
if (i === az.curIndex - 1) {
|
|
_push(`<!--[--><span class="loading loading-spinner loading-xs" data-v-b8dfd02c></span> ${ssrInterpolate(nm)}<!--]-->`);
|
|
} else {
|
|
_push(`<!--[-->✓ ${ssrInterpolate(nm)}<!--]-->`);
|
|
}
|
|
_push(`</div>`);
|
|
});
|
|
_push(`<!--]--></div></div>`);
|
|
} else if (az.mode === "error") {
|
|
_push(`<div class="text-red-600" data-v-b8dfd02c>✗ ${ssrInterpolate(az.errMsg)}</div>`);
|
|
} else if (az.mode === "done" && az.report) {
|
|
_push(`<div class="bg-green-50 border border-green-100 rounded-lg p-3" data-v-b8dfd02c><div class="font-semibold text-green-700 mb-2" data-v-b8dfd02c>✓ 分析完成</div><div class="grid grid-cols-2 gap-x-4 gap-y-1 text-xs text-slate-600 mb-2" data-v-b8dfd02c><div data-v-b8dfd02c>应用:<b data-v-b8dfd02c>${ssrInterpolate(az.report.app_name || "")}</b></div><div data-v-b8dfd02c>区域:${ssrInterpolate(az.report.region || "未指定")}</div><div data-v-b8dfd02c>区间:${ssrInterpolate((az.report.start_day || "不限") + " ~ " + (az.report.end_day || "不限"))}</div><div data-v-b8dfd02c>清理旧行:${ssrInterpolate(az.report.cleared_rows || 0)}</div><div data-v-b8dfd02c>写入天数:<b data-v-b8dfd02c>${ssrInterpolate(az.report.days_written || 0)}</b></div><div data-v-b8dfd02c>写入行数:${ssrInterpolate(az.report.rows_written || 0)}</div></div><div class="border-t border-green-100 pt-2 text-xs" data-v-b8dfd02c><!--[-->`);
|
|
ssrRenderList(az.report.steps || [], (s, i) => {
|
|
_push(`<div class="flex justify-between py-0.5" data-v-b8dfd02c><span class="text-slate-500" data-v-b8dfd02c>${ssrInterpolate(s.name)}</span><span class="font-mono text-slate-700" data-v-b8dfd02c>${ssrInterpolate((s.groups || 0) + " 组")}</span></div>`);
|
|
});
|
|
_push(`<!--]--></div></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<div class="modal-action" data-v-b8dfd02c><button class="btn btn-ghost btn-sm"${ssrIncludeBooleanAttr(az.running) ? " disabled" : ""} data-v-b8dfd02c>关闭</button><button class="btn btn-primary btn-sm text-white"${ssrIncludeBooleanAttr(az.running) ? " disabled" : ""} data-v-b8dfd02c>${ssrInterpolate(az.btnText)}</button></div></div><form method="dialog" class="modal-backdrop" data-v-b8dfd02c><button${ssrIncludeBooleanAttr(az.running) ? " disabled" : ""} data-v-b8dfd02c>close</button></form></dialog></div>`);
|
|
};
|
|
}
|
|
});
|
|
const _sfc_setup$2 = _sfc_main$2.setup;
|
|
_sfc_main$2.setup = (props, ctx) => {
|
|
const ssrContext = useSSRContext();
|
|
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("components/AppDeployTab.vue");
|
|
return _sfc_setup$2 ? _sfc_setup$2(props, ctx) : void 0;
|
|
};
|
|
const __nuxt_component_0 = /* @__PURE__ */ Object.assign(_export_sfc(_sfc_main$2, [["__scopeId", "data-v-b8dfd02c"]]), { __name: "AppDeployTab" });
|
|
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
__name: "AppModuleTab",
|
|
__ssrInlineRender: true,
|
|
props: {
|
|
appId: {},
|
|
depName: {}
|
|
},
|
|
setup(__props) {
|
|
useApi();
|
|
useToast();
|
|
const catalog = ref([]);
|
|
const curModule = ref("");
|
|
const values = reactive({});
|
|
const dataModules = reactive(/* @__PURE__ */ new Set());
|
|
const show = reactive({});
|
|
const loading = ref(false);
|
|
const saving = ref(false);
|
|
const dbError = ref("");
|
|
const curDef = computed(() => catalog.value.find((m) => m.module === curModule.value) || null);
|
|
const cur = computed(() => values[curModule.value] || {});
|
|
const groups = computed(() => {
|
|
const order = [];
|
|
const map = /* @__PURE__ */ new Map();
|
|
for (const m of catalog.value) {
|
|
if (!map.has(m.group)) {
|
|
map.set(m.group, []);
|
|
order.push(m.group);
|
|
}
|
|
map.get(m.group).push(m);
|
|
}
|
|
return order.map((name) => ({ name, modules: map.get(name) }));
|
|
});
|
|
return (_ctx, _push, _parent, _attrs) => {
|
|
_push(`<div${ssrRenderAttrs(mergeProps({ class: "card" }, _attrs))} data-v-ff0e4b71><div class="card-head" data-v-ff0e4b71><div data-v-ff0e4b71><div class="card-title" data-v-ff0e4b71>业务功能配置</div><div class="card-desc" data-v-ff0e4b71> 登录 / 短信 / 邮件 / 支付等应用独立配置,保存在<b data-v-ff0e4b71>「${ssrInterpolate(__props.depName)}」这个部署的业务库</b>(不进公共库)。密钥字段加密落库,显示时脱敏。 </div></div><button class="btn btn-outline btn-error btn-xs shrink-0"${ssrIncludeBooleanAttr(saving.value || !!dbError.value) ? " disabled" : ""} title="把该部署业务库里所有业务模块的配置,整体还原为业务服务配置文件(yaml)的初始值。用于升级/迁移把库写脏后的整体恢复。" data-v-ff0e4b71>全部重置为配置文件</button></div>`);
|
|
if (dbError.value) {
|
|
_push(`<div class="m-4 alert alert-warning py-2 text-sm" data-v-ff0e4b71><span data-v-ff0e4b71> 无法连接应用业务库:${ssrInterpolate(dbError.value)}<br data-v-ff0e4b71><span class="text-xs opacity-70" data-v-ff0e4b71>待部署的应用先完成部署、或检查业务库 DSN 连通后再配置。</span></span></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
if (loading.value) {
|
|
_push(`<div class="py-14 text-center" data-v-ff0e4b71><span class="loading loading-spinner loading-sm" data-v-ff0e4b71></span></div>`);
|
|
} else {
|
|
_push(`<div class="cfg" data-v-ff0e4b71><aside class="cfg-nav" data-v-ff0e4b71><!--[-->`);
|
|
ssrRenderList(groups.value, (grp) => {
|
|
_push(`<div data-v-ff0e4b71><div class="cfg-group" data-v-ff0e4b71>${ssrInterpolate(grp.name)}</div><!--[-->`);
|
|
ssrRenderList(grp.modules, (m) => {
|
|
_push(`<button class="cfg-item"${ssrRenderAttr("aria-current", curModule.value === m.module)} data-v-ff0e4b71><span data-v-ff0e4b71>${ssrInterpolate(m.name)}</span>`);
|
|
if (dataModules.has(m.module)) {
|
|
_push(`<span class="dot" title="库中已有配置" data-v-ff0e4b71></span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</button>`);
|
|
});
|
|
_push(`<!--]--></div>`);
|
|
});
|
|
_push(`<!--]--></aside><section class="cfg-form" data-v-ff0e4b71>`);
|
|
if (curDef.value) {
|
|
_push(`<!--[--><div class="flex items-start justify-between gap-3 mb-4 flex-wrap" data-v-ff0e4b71><div data-v-ff0e4b71><div class="text-sm font-semibold text-slate-800" data-v-ff0e4b71>${ssrInterpolate(curDef.value.name)}</div>`);
|
|
if (curDef.value.desc) {
|
|
_push(`<div class="text-[11px] text-slate-400 mt-0.5" data-v-ff0e4b71>${ssrInterpolate(curDef.value.desc)}</div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><div class="flex items-center gap-2" data-v-ff0e4b71><button class="btn btn-ghost btn-xs text-amber-600"${ssrIncludeBooleanAttr(saving.value) ? " disabled" : ""} title="丢弃库中该模块的全部配置,改用业务服务配置文件(yaml)里的初始值重新写入。" data-v-ff0e4b71>重置</button>`);
|
|
if (dataModules.has(curModule.value)) {
|
|
_push(`<button class="btn btn-ghost btn-xs text-rose-500"${ssrIncludeBooleanAttr(saving.value) ? " disabled" : ""} data-v-ff0e4b71>清空</button>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<button class="btn btn-primary btn-sm text-white"${ssrIncludeBooleanAttr(saving.value || !!dbError.value) ? " disabled" : ""} data-v-ff0e4b71>${ssrInterpolate(saving.value ? "保存中…" : "保存")}</button></div></div><div class="grid grid-cols-1 gap-3 max-w-2xl" data-v-ff0e4b71><!--[-->`);
|
|
ssrRenderList(curDef.value.fields, (f) => {
|
|
_push(`<div class="form-control" data-v-ff0e4b71><label class="label py-0.5" data-v-ff0e4b71><span class="label-text text-sm" data-v-ff0e4b71>${ssrInterpolate(f.label)} `);
|
|
if (f.required) {
|
|
_push(`<span class="text-rose-400" data-v-ff0e4b71>*</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
if (f.secret) {
|
|
_push(`<span class="lock-tag ml-1" data-v-ff0e4b71>🔒</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<span class="text-[10px] font-normal text-slate-300 ml-1 font-mono" data-v-ff0e4b71>${ssrInterpolate(f.key)}</span></span></label>`);
|
|
if (f.type === "bool") {
|
|
_push(`<label class="cursor-pointer flex items-center gap-2" data-v-ff0e4b71><input type="checkbox" class="toggle toggle-sm toggle-primary"${ssrIncludeBooleanAttr(cur.value[f.key] === "true") ? " checked" : ""} data-v-ff0e4b71><span class="text-xs text-slate-500" data-v-ff0e4b71>${ssrInterpolate(cur.value[f.key] === "true" ? "开启" : "关闭")}</span></label>`);
|
|
} else if (f.type === "select") {
|
|
_push(`<select class="select select-bordered select-sm w-full" data-v-ff0e4b71><option value="" data-v-ff0e4b71${ssrIncludeBooleanAttr(Array.isArray(cur.value[f.key]) ? ssrLooseContain(cur.value[f.key], "") : ssrLooseEqual(cur.value[f.key], "")) ? " selected" : ""}>请选择</option><!--[-->`);
|
|
ssrRenderList(f.options, (o) => {
|
|
_push(`<option${ssrRenderAttr("value", o.value)} data-v-ff0e4b71${ssrIncludeBooleanAttr(Array.isArray(cur.value[f.key]) ? ssrLooseContain(cur.value[f.key], o.value) : ssrLooseEqual(cur.value[f.key], o.value)) ? " selected" : ""}>${ssrInterpolate(o.label)}</option>`);
|
|
});
|
|
_push(`<!--]--></select>`);
|
|
} else if (f.type === "textarea") {
|
|
_push(`<textarea class="textarea textarea-bordered w-full font-mono text-xs" rows="3"${ssrRenderAttr("placeholder", f.secret ? "留空/保持 [encrypted] 则不变" : f.placeholder || "")} data-v-ff0e4b71>${ssrInterpolate(cur.value[f.key])}</textarea>`);
|
|
} else if (f.type === "password") {
|
|
_push(`<div class="relative" data-v-ff0e4b71><input${ssrRenderDynamicModel(show[f.key] ? "text" : "password", cur.value[f.key], null)}${ssrRenderAttr("type", show[f.key] ? "text" : "password")} class="input input-bordered input-sm w-full font-mono text-xs pr-14" placeholder="留空/保持 [encrypted] 则不变" data-v-ff0e4b71><button type="button" class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-400 hover:text-slate-700" data-v-ff0e4b71>${ssrInterpolate(show[f.key] ? "隐藏" : "显示")}</button></div>`);
|
|
} else {
|
|
_push(`<input${ssrRenderAttr("value", cur.value[f.key])} type="text" class="input input-bordered input-sm w-full font-mono text-xs"${ssrRenderAttr("placeholder", f.placeholder || "")} data-v-ff0e4b71>`);
|
|
}
|
|
if (f.desc) {
|
|
_push(`<span class="text-[11px] text-slate-400 mt-0.5" data-v-ff0e4b71>${ssrInterpolate(f.desc)}</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div>`);
|
|
});
|
|
_push(`<!--]--></div><!--]-->`);
|
|
} else {
|
|
_push(`<div class="py-10 text-center text-slate-400 text-sm" data-v-ff0e4b71>暂无可配置的业务模块。</div>`);
|
|
}
|
|
_push(`</section></div>`);
|
|
}
|
|
_push(`</div>`);
|
|
};
|
|
}
|
|
});
|
|
const _sfc_setup$1 = _sfc_main$1.setup;
|
|
_sfc_main$1.setup = (props, ctx) => {
|
|
const ssrContext = useSSRContext();
|
|
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("components/AppModuleTab.vue");
|
|
return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
|
|
};
|
|
const __nuxt_component_1 = /* @__PURE__ */ Object.assign(_export_sfc(_sfc_main$1, [["__scopeId", "data-v-ff0e4b71"]]), { __name: "AppModuleTab" });
|
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
__name: "[name]",
|
|
__ssrInlineRender: true,
|
|
setup(__props) {
|
|
const { consoleApi } = useApi();
|
|
const { error } = useToast();
|
|
const route = useRoute$1();
|
|
useRouter();
|
|
const appName = computed(() => String(route.params.name || ""));
|
|
const product = ref(null);
|
|
const deployments = ref([]);
|
|
const envs = ref([]);
|
|
const loading = ref(false);
|
|
const tab = ref("deps");
|
|
const curDep = ref(0);
|
|
const deployTab = ref(null);
|
|
const okCount = computed(() => deployments.value.filter((d) => d.status !== "pending").length);
|
|
const pendingCount = computed(() => deployments.value.filter((d) => d.status === "pending").length);
|
|
const curDepName = computed(() => {
|
|
const d = deployments.value.find((x) => x.id === curDep.value);
|
|
return d ? d.name || "部署#" + d.id : "";
|
|
});
|
|
const versionSpread = computed(() => {
|
|
const tags = [...new Set(deployments.value.filter((d) => d.status !== "pending").map((d) => d.image_tag || "latest"))];
|
|
return tags.length ? tags.join(" · ") : "—";
|
|
});
|
|
async function loadAll() {
|
|
loading.value = true;
|
|
try {
|
|
const [ps, ds, es] = await Promise.all([
|
|
consoleApi("products/list", {}),
|
|
consoleApi("apps/list", {}),
|
|
consoleApi("envs/list", {})
|
|
]);
|
|
product.value = (ps ?? []).find((p) => p.name === appName.value) ?? null;
|
|
deployments.value = (ds ?? []).filter((d) => d.app_name === appName.value);
|
|
envs.value = es ?? [];
|
|
if (!deployments.value.some((d) => d.id === curDep.value)) {
|
|
const first = deployments.value.find((d) => d.status !== "pending") ?? deployments.value[0];
|
|
curDep.value = first?.id ?? 0;
|
|
}
|
|
} catch (e) {
|
|
error(e?.message ?? "加载应用详情失败");
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
function onConfigure(id) {
|
|
curDep.value = id;
|
|
tab.value = "modules";
|
|
}
|
|
ref(null);
|
|
const pForm = reactive({ id: 0, name: "", image_name: "", remark: "", enabled: true });
|
|
watch(appName, loadAll);
|
|
return (_ctx, _push, _parent, _attrs) => {
|
|
const _component_AppDeployTab = __nuxt_component_0;
|
|
const _component_AppModuleTab = __nuxt_component_1;
|
|
_push(`<div${ssrRenderAttrs(_attrs)} data-v-3e1700b0><div class="flex items-center gap-2 mb-4 text-sm" data-v-3e1700b0><button class="btn btn-ghost btn-xs" data-v-3e1700b0>← 应用中心</button><span class="text-slate-300" data-v-3e1700b0>/</span><span class="font-semibold text-slate-700" data-v-3e1700b0>${ssrInterpolate(appName.value)}</span></div>`);
|
|
if (loading.value) {
|
|
_push(`<div class="bg-white rounded-xl border border-slate-100 py-16 text-center" data-v-3e1700b0><span class="loading loading-spinner loading-sm" data-v-3e1700b0></span></div>`);
|
|
} else if (!product.value) {
|
|
_push(`<div class="bg-white rounded-xl border border-slate-100 py-16 text-center" data-v-3e1700b0><p class="text-slate-500" data-v-3e1700b0>找不到应用「${ssrInterpolate(appName.value)}」</p><button class="btn btn-sm mt-4" data-v-3e1700b0>返回应用中心</button></div>`);
|
|
} else {
|
|
_push(`<!--[--><div class="hero" data-v-3e1700b0><div class="hero-logo" data-v-3e1700b0>${ssrInterpolate((product.value.name || "?").charAt(0).toUpperCase())}</div><div class="flex-1 min-w-[240px]" data-v-3e1700b0><div class="hero-name" data-v-3e1700b0>${ssrInterpolate(product.value.name)} <span class="${ssrRenderClass([product.value.enabled ? "chip-ok" : "", "chip"])}" data-v-3e1700b0>${ssrInterpolate(product.value.enabled ? "启用" : "停用")}</span><span class="chip chip-mono" data-v-3e1700b0>🐳 ${ssrInterpolate(product.value.image_name || "未设置镜像")}</span></div><div class="text-xs text-slate-500 mt-1.5" data-v-3e1700b0>${ssrInterpolate(product.value.remark || "—")}</div></div><div class="flex gap-2" data-v-3e1700b0><button class="btn btn-sm" data-v-3e1700b0>编辑应用</button><button class="btn btn-primary btn-sm text-white" data-v-3e1700b0>+ 部署新环境</button></div></div><div class="kpis" data-v-3e1700b0><div class="kpi accent" data-v-3e1700b0><div class="kpi-l" data-v-3e1700b0>部署</div><div class="kpi-v" data-v-3e1700b0>${ssrInterpolate(deployments.value.length)}<small data-v-3e1700b0>个区域</small></div></div><div class="kpi ok" data-v-3e1700b0><div class="kpi-l" data-v-3e1700b0>已接入</div><div class="kpi-v" data-v-3e1700b0>${ssrInterpolate(okCount.value)}</div></div><div class="${ssrRenderClass([pendingCount.value ? "warn" : "", "kpi"])}" data-v-3e1700b0><div class="kpi-l" data-v-3e1700b0>待部署</div><div class="kpi-v" data-v-3e1700b0>${ssrInterpolate(pendingCount.value)}</div></div><div class="kpi" data-v-3e1700b0><div class="kpi-l" data-v-3e1700b0>版本分布</div><div class="kpi-v" style="${ssrRenderStyle({ "font-size": "14px", "padding-top": "5px" })}" data-v-3e1700b0>${ssrInterpolate(versionSpread.value)}</div></div></div><div class="tablist" role="tablist" data-v-3e1700b0><button class="tab" role="tab"${ssrRenderAttr("aria-selected", tab.value === "deps")} data-v-3e1700b0> 部署环境 <span class="tab-n" data-v-3e1700b0>${ssrInterpolate(deployments.value.length)}</span></button><button class="tab" role="tab"${ssrRenderAttr("aria-selected", tab.value === "modules")} data-v-3e1700b0>业务功能配置</button></div>`);
|
|
if (tab.value === "modules") {
|
|
_push(`<div class="depbar" data-v-3e1700b0><span class="text-xs text-slate-500 font-semibold shrink-0" data-v-3e1700b0>配置目标</span><div class="flex gap-1.5 flex-wrap" data-v-3e1700b0><!--[-->`);
|
|
ssrRenderList(deployments.value, (d) => {
|
|
_push(`<button class="depchip"${ssrRenderAttr("aria-pressed", curDep.value === d.id)}${ssrRenderAttr("title", d.status === "pending" ? "待部署:业务库可能还未就绪" : "")} data-v-3e1700b0><span class="${ssrRenderClass([{ pending: d.status === "pending" }, "dot"])}" data-v-3e1700b0></span> ${ssrInterpolate(d.region || d.name || "部署#" + d.id)} <span class="text-[10.5px] text-slate-400 font-mono" data-v-3e1700b0>#${ssrInterpolate(d.id)}</span></button>`);
|
|
});
|
|
_push(`<!--]-->`);
|
|
if (!deployments.value.length) {
|
|
_push(`<span class="text-xs text-slate-400 py-1" data-v-3e1700b0>该应用还没有部署,无处存放配置</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div><span class="text-[11px] text-slate-400 ml-auto" data-v-3e1700b0>配置存在该部署自己的业务库里,各区域互不影响</span></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(ssrRenderComponent(_component_AppDeployTab, {
|
|
style: tab.value === "deps" ? null : { display: "none" },
|
|
ref_key: "deployTab",
|
|
ref: deployTab,
|
|
"app-name": appName.value,
|
|
"default-image": product.value.image_name || "",
|
|
deployments: deployments.value,
|
|
envs: envs.value,
|
|
onRefresh: loadAll,
|
|
onConfigure
|
|
}, null, _parent));
|
|
if (tab.value === "modules" && curDep.value) {
|
|
_push(ssrRenderComponent(_component_AppModuleTab, {
|
|
key: "m" + curDep.value,
|
|
"app-id": curDep.value,
|
|
"dep-name": curDepName.value
|
|
}, null, _parent));
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
if (tab.value === "modules" && !curDep.value) {
|
|
_push(`<div class="bg-white rounded-xl border border-slate-100 py-16 text-center text-slate-400" data-v-3e1700b0> 先在「部署环境」里创建一个部署,才能配置。 </div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<!--]-->`);
|
|
}
|
|
_push(`<dialog class="modal" data-v-3e1700b0><div class="modal-box max-w-lg" data-v-3e1700b0><h3 class="font-bold text-lg" data-v-3e1700b0>编辑应用</h3><p class="text-xs text-slate-400 mt-1" data-v-3e1700b0> 应用名是关联键:部署、账号作用域、第三方服务配置都按它挂载,改名会断开已有关联。 </p><form class="mt-4 space-y-3" data-v-3e1700b0><div class="form-control" data-v-3e1700b0><label class="label py-1" data-v-3e1700b0><span class="label-text text-sm font-medium" data-v-3e1700b0>应用名 *</span></label><input${ssrRenderAttr("value", pForm.name)} type="text" class="input input-bordered input-sm w-full" required data-v-3e1700b0></div><div class="form-control" data-v-3e1700b0><label class="label py-1" data-v-3e1700b0><span class="label-text text-sm font-medium" data-v-3e1700b0>默认镜像名</span></label><input${ssrRenderAttr("value", pForm.image_name)} type="text" class="input input-bordered input-sm w-full font-mono text-xs" data-v-3e1700b0></div><div class="form-control" data-v-3e1700b0><label class="label py-1" data-v-3e1700b0><span class="label-text text-sm font-medium" data-v-3e1700b0>备注</span></label><input${ssrRenderAttr("value", pForm.remark)} type="text" class="input input-bordered input-sm w-full" data-v-3e1700b0></div><label class="flex items-center gap-2 cursor-pointer pt-1" data-v-3e1700b0><input${ssrIncludeBooleanAttr(Array.isArray(pForm.enabled) ? ssrLooseContain(pForm.enabled, null) : pForm.enabled) ? " checked" : ""} type="checkbox" class="toggle toggle-primary toggle-sm" data-v-3e1700b0><span class="text-sm" data-v-3e1700b0>启用</span></label><div class="modal-action" data-v-3e1700b0><button type="button" class="btn btn-ghost btn-sm" data-v-3e1700b0>取消</button><button type="submit" class="btn btn-primary btn-sm text-white" data-v-3e1700b0>保存</button></div></form></div><form method="dialog" class="modal-backdrop" data-v-3e1700b0><button data-v-3e1700b0>close</button></form></dialog></div>`);
|
|
};
|
|
}
|
|
});
|
|
const _sfc_setup = _sfc_main.setup;
|
|
_sfc_main.setup = (props, ctx) => {
|
|
const ssrContext = useSSRContext();
|
|
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("pages/apps/[name].vue");
|
|
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
|
|
};
|
|
const _name_ = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-3e1700b0"]]);
|
|
|
|
export { _name_ as default };
|
|
//# sourceMappingURL=_name_-3YudcsGQ.mjs.map
|
|
|