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(`
区域部署
每个区域一份部署,各自连自己的业务库。「升级」会 SSH 到绑定的环境节点,在部署目录执行 docker compose pull & up -d。
`);
if (!__props.deployments.length) {
_push(`
该应用还没有部署,点右上「+ 部署新环境」添加。
`);
} else {
_push(``);
}
_push(``);
ssrRenderList(__props.deployments, (d) => {
_push(`
部署${ssrInterpolate(d.name || "部署#" + d.id)}`);
if (d.status === "pending") {
_push(`待部署`);
} else {
_push(`已接入`);
}
if (!d.enabled) {
_push(`停用`);
} else {
_push(``);
}
_push(`
#${ssrInterpolate(d.id)} · ${ssrInterpolate(d.region || "未指定区域")} · 业务库 ${ssrInterpolate(dbName(d.service_dsn))}
环境节点
当前版本
${ssrInterpolate(d.image_tag || "latest")}`);
if (!isPinned(d.image_tag)) {
_push(`未固定`);
} else {
_push(``);
}
_push(`
部署目录
${ssrInterpolate(d.deploy_dir || "未填")}
`);
});
_push(`
`);
};
}
});
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(`业务功能配置
登录 / 短信 / 邮件 / 支付等应用独立配置,保存在「${ssrInterpolate(__props.depName)}」这个部署的业务库(不进公共库)。密钥字段加密落库,显示时脱敏。
`);
if (dbError.value) {
_push(`
无法连接应用业务库:${ssrInterpolate(dbError.value)}
待部署的应用先完成部署、或检查业务库 DSN 连通后再配置。
`);
} else {
_push(``);
}
if (loading.value) {
_push(`
`);
} else {
_push(`
`);
}
_push(`
`);
};
}
});
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(`/${ssrInterpolate(appName.value)}
`);
if (loading.value) {
_push(`
`);
} else if (!product.value) {
_push(`
找不到应用「${ssrInterpolate(appName.value)}」
`);
} else {
_push(`
${ssrInterpolate((product.value.name || "?").charAt(0).toUpperCase())}
${ssrInterpolate(product.value.name)} ${ssrInterpolate(product.value.enabled ? "启用" : "停用")}🐳 ${ssrInterpolate(product.value.image_name || "未设置镜像")}
${ssrInterpolate(product.value.remark || "—")}
部署
${ssrInterpolate(deployments.value.length)}个区域
已接入
${ssrInterpolate(okCount.value)}
待部署
${ssrInterpolate(pendingCount.value)}
版本分布
${ssrInterpolate(versionSpread.value)}
`);
if (tab.value === "modules") {
_push(`
配置目标`);
ssrRenderList(deployments.value, (d) => {
_push(``);
});
_push(``);
if (!deployments.value.length) {
_push(`该应用还没有部署,无处存放配置`);
} else {
_push(``);
}
_push(`
配置存在该部署自己的业务库里,各区域互不影响 `);
} 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(`
先在「部署环境」里创建一个部署,才能配置。
`);
} else {
_push(``);
}
_push(``);
}
_push(`
`);
};
}
});
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