Files
console-dev/app/components/mfcloud/MfCloudVpc.vue
T

885 lines
33 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
/**
* 云实例 VPC 网络创建、分配/切换和删除;父级提供商品、机房、网络类型及实例活动态。
* 分配成功发 refresh 让父级重取实例资料;已有 host 的网络不可直接删除。
*/
import "~/assets/styles/MfCloudVpc.CpOEbuWs.css";
import { toDisplayString } from "vue";
import { onMounted } from "vue";
import { useId } from "vue";
import { onBeforeUnmount } from "vue";
import { watch } from "vue";
import { createBlock } from "vue";
import { ref } from "vue";
import { withCtx } from "vue";
import { createCommentVNode } from "vue";
import { computed } from "vue";
import { isRef } from "vue";
import { createElementBlock } from "vue";
import { createElementVNode } from "vue";
import { createTextVNode } from "vue";
import { unref } from "vue";
import { defineComponent } from "vue";
import { createVNode } from "vue";
import { openBlock } from "vue";
import { useApi } from "~/lib/auth.js";
import dialog from "primevue/dialog";
import button from "primevue/button";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
import message from "primevue/message";
import inputtext from "primevue/inputtext";
import datatable from "primevue/datatable";
import select from "primevue/select";
import column from "primevue/column";
import { c as cloud_instance_management_c } from "~/lib/cloud-instance-management.js";
import { i as cloud_instance_management_i } from "~/lib/cloud-instance-management.js";
var ie = {
class: `vpc-panel network-section`,
},
ae = {
class: `network-head`,
},
T = {
class: `network-actions`,
},
oe = {
key: 0,
class: `muted`,
},
se = {
class: `form`,
},
ce = [`for`],
E = [`for`],
le = {
key: 0,
class: `muted`,
},
ue = {
class: `form`,
},
de = [`for`],
D = Object.assign(
_plugin_vue_export_helper_t(
defineComponent({
__name: `MfCloudVpc`,
props: {
hostId: {},
productId: {},
module: {},
networkType: {},
dataCenterId: {},
active: {
type: Boolean,
},
},
emits: [`refresh`],
setup(h, { emit: y }) {
let D = h,
O = y,
k = useApi(),
A = useId(),
j = ref(!1),
// 已是 VPC 的实例直接开放面板;经典网络需先通过商品配置确认支持 VPC。
M = computed(
() =>
cloud_instance_management_i(D.module) &&
(D.networkType === `vpc` || j.value),
),
N = computed(() => `/${D.module}/${D.hostId}/vpc_network`),
// P 是当前网络列表,F 是允许分配的网络,I 是可选实例,L 是允许的 CIDR 掩码。
P = ref([]),
F = ref([]),
I = ref([]),
L = ref([]),
R = ref(!1),
z = ref(!1),
B = ref(``),
V = ref(``),
H = ref(!1),
U = ref(``),
W = ref(``),
G = ref(null),
K = ref(null),
q = ref(null),
J = ref(!1),
Y = 0,
X = 0,
Z = !1;
/**
* 核对 VPC 能力后分页拉取全部网络,再按商品/机房查询可分配网络。
* 商品能力字段未返回时明确报错;确认不支持 VPC 时不继续查询网络列表。
*/
async function Q() {
let e = ++Y,
t = N.value;
((B.value = ``), (R.value = !0));
try {
if (
cloud_instance_management_i(D.module) &&
D.networkType !== `vpc` &&
D.productId
) {
let t = await k.get(
`/product/${D.productId}/${D.module}/order_page`,
);
if (Z || e !== Y) return;
if (t.config?.support_vpc_network === void 0)
throw Error(`VPC 能力信息未返回,请重试`);
j.value = Number(t.config.support_vpc_network) === 1;
}
if (!M.value) {
((P.value = []), (F.value = []));
return;
}
let n = [],
r = 1;
for (;;) {
let i = await k.get(t, {
page: r,
limit: 100,
orderby: `id`,
sort: `desc`,
});
if (Z || e !== Y) return;
if (!Array.isArray(i.list)) throw Error(`VPC 列表无效,请重试`);
if (
(n.push(...i.list),
(L.value = i.vpc_cidr_limit ?? []),
i.count === void 0
? i.list.length < 100
: n.length >= Number(i.count))
)
break;
if (!i.list.length) throw Error(`VPC 列表不完整,请刷新重试`);
r++;
}
if (((P.value = n), (F.value = n), D.productId)) {
let t = await k.get(
`/product/${D.productId}/${D.module}/vpc_network/search`,
{
...(D.dataCenterId
? {
data_center_id: D.dataCenterId,
}
: {}),
},
);
if (Z || e !== Y) return;
if (!Array.isArray(t.list))
throw Error(`可用 VPC 网络列表无效,请重试`);
F.value = t.list;
}
} catch (t) {
!Z &&
e === Y &&
(B.value = t instanceof Error ? t.message : `加载 VPC 失败`);
} finally {
!Z && e === Y && (R.value = !1);
}
}
// 打开新建表单前检查加载状态和活动态;名称/网段不沿用上一次输入。
function fe() {
!M.value ||
z.value ||
R.value ||
B.value ||
!D.active ||
((U.value = ``), (W.value = ``), (V.value = ``), (H.value = !0));
}
/** 统一校验 VPC 名称与 CIDR,网段留空交后端分配,创建完成后重读网络列表。 */
async function pe() {
if (z.value || !H.value || !M.value || !D.active) return;
let e;
try {
e = cloud_instance_management_c(U.value, W.value, L.value);
} catch (e) {
V.value = e.message;
return;
}
let t = N.value,
n = Y;
((z.value = !0), (V.value = ``));
try {
if ((await k.post(t, e), Z || n !== Y)) return;
((H.value = !1), await Q());
} catch (e) {
!Z &&
t === N.value &&
(V.value = e instanceof Error ? e.message : `创建失败`);
} finally {
!Z && t === N.value && (z.value = !1);
}
}
/**
* 先设置分配弹窗目标;转售模块仅提供当前实例,本地 mf_cloud 再完整加载可选实例。
* X 独立隔离分配目标列表请求,避免切换目标期间旧结果覆盖新弹窗。
*/
async function $(e) {
if (
!M.value ||
z.value ||
B.value ||
!D.active ||
!F.value.some((t) => t.id === e.id) ||
((K.value = e),
(q.value = Number(D.hostId)),
(V.value = ``),
(I.value = [
{
id: Number(D.hostId),
name: `当前实例 #${D.hostId}`,
},
]),
D.module !== `mf_cloud`)
)
return;
let t = ++X;
J.value = !0;
try {
let e = [],
n = 1;
for (;;) {
let r = await k.get(`/mf_cloud/vpc_network/host`, {
page: n,
limit: 100,
...(D.dataCenterId
? {
data_center_id: D.dataCenterId,
}
: {}),
});
if (Z || t !== X) return;
if (!Array.isArray(r.list)) throw Error(`可分配实例列表无效`);
if (
(e.push(...r.list),
r.count === void 0
? r.list.length < 100
: e.length >= Number(r.count))
)
break;
if (!r.list.length) throw Error(`可分配实例列表不完整,请重试`);
n++;
}
((I.value = e),
e.some((e) => e.id === q.value) ||
(q.value = e[0]?.id ?? null));
} catch (e) {
!Z &&
t === X &&
((I.value = []),
(V.value =
e instanceof Error ? e.message : `加载可分配实例失败`));
} finally {
!Z && t === X && (J.value = !1);
}
}
/** 分配前同时确认实例与网络仍在各自候选集合,成功后通知父级更新实例网络信息。 */
async function me() {
let e = K.value,
t = q.value;
if (
!e ||
!t ||
z.value ||
J.value ||
!M.value ||
!D.active ||
!I.value.some((e) => e.id === t) ||
!F.value.some((t) => t.id === e.id)
)
return;
let n = N.value,
r = Y,
i = D.module;
((z.value = !0), (V.value = ``));
try {
if (
(await k.put(`/${i}/${t}/vpc_network`, {
vpc_network_id: e.id,
}),
Z || r !== Y)
)
return;
((K.value = null), await Q(), O(`refresh`));
} catch (e) {
!Z &&
n === N.value &&
(V.value = e instanceof Error ? e.message : `分配网络失败`);
} finally {
!Z && n === N.value && (z.value = !1);
}
}
/** 删除需要网络仍存在且无已分配实例;此处预检查不替代后端并发/权限校验。 */
async function he() {
let e = G.value;
if (
!e ||
z.value ||
!M.value ||
!D.active ||
e.host?.length ||
!P.value.some((t) => t.id === e.id)
)
return;
let t = N.value,
n = Y;
((z.value = !0), (V.value = ``));
try {
if ((await k.del(`${t}/${e.id}`), Z || n !== Y)) return;
((G.value = null), await Q());
} catch (e) {
!Z &&
t === N.value &&
(V.value = e instanceof Error ? e.message : `删除失败`);
} finally {
!Z && t === N.value && (z.value = !1);
}
}
return (
watch(
() => [D.hostId, D.module, D.networkType, D.productId],
() => {
(X++,
(j.value = !1),
(P.value = []),
(z.value = !1),
(H.value = !1),
(G.value = null),
(K.value = null),
Q());
},
),
onMounted(Q),
// 销毁后拒绝列表与可分配实例的迟到结果;实例/商品切换也会关闭全部旧弹窗。
onBeforeUnmount(() => {
((Z = !0), Y++, X++);
}),
// 主表展示已分配实例;创建、切换和删除各有确认弹窗,网络切换额外提示连接影响。
(t, n) => {
let r = button,
i = message,
o = column,
l = datatable,
v = inputtext,
y = dialog,
C = select;
return (
openBlock(),
createElementBlock(`section`, ie, [
createElementVNode(`div`, ae, [
(n[10] ||= createElementVNode(`h4`, null, `VPC 网络`, -1)),
createElementVNode(`div`, T, [
createVNode(
r,
{
label: `新建 VPC`,
icon: `pi pi-plus`,
size: `small`,
disabled:
!unref(M) ||
!h.active ||
unref(z) ||
unref(R) ||
!!unref(B),
onClick: fe,
},
null,
8,
[`disabled`],
),
unref(M)
? (openBlock(),
createBlock(
r,
{
key: 0,
label: `刷新`,
text: ``,
size: `small`,
loading: unref(R),
disabled: unref(z),
onClick: Q,
},
null,
8,
[`loading`, `disabled`],
))
: createCommentVNode(``, !0),
]),
]),
!unref(M) && !unref(R) && !unref(B)
? (openBlock(),
createElementBlock(
`p`,
oe,
`当前实例未开放 VPC 网络能力。`,
))
: createCommentVNode(``, !0),
unref(B)
? (openBlock(),
createBlock(
i,
{
key: 1,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(B)), 1),
createVNode(r, {
label: `重试`,
text: ``,
size: `small`,
onClick: Q,
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(M)
? (openBlock(),
createBlock(
l,
{
key: 2,
class: `network-table`,
value: unref(P),
loading: unref(R),
size: `small`,
scrollable: ``,
},
{
empty: withCtx(() => [
...(n[11] ||= [
createTextVNode(`暂无 VPC 网络`, -1),
]),
]),
default: withCtx(() => [
createVNode(o, {
field: `name`,
header: `名称`,
}),
createVNode(o, {
field: `ips`,
header: `网段`,
}),
createVNode(
o,
{
header: `已分配实例`,
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(
(t.host || [])
.map((e) => e.name)
.join(`、`) || `未分配`,
),
1,
),
]),
_: 1,
},
),
createVNode(
o,
{
header: `操作`,
},
{
body: withCtx(({ data: e }) => [
createVNode(
r,
{
label: `分配 / 切换`,
text: ``,
size: `small`,
disabled:
unref(z) ||
!h.active ||
!!unref(B) ||
!unref(F).some((t) => t.id === e.id),
onClick: (t) => $(e),
},
null,
8,
[`disabled`, `onClick`],
),
createVNode(
r,
{
label: `删除`,
text: ``,
severity: `danger`,
size: `small`,
disabled:
unref(z) ||
!h.active ||
!!e.host?.length,
onClick: (t) => {
((G.value = e), (V.value = ``));
},
},
null,
8,
[`disabled`, `onClick`],
),
]),
_: 1,
},
),
]),
_: 1,
},
8,
[`value`, `loading`],
))
: createCommentVNode(``, !0),
createVNode(
y,
{
visible: unref(H),
"onUpdate:visible": (n[3] ||= (e) =>
isRef(H) ? (H.value = e) : null),
header: `新建 VPC 网络`,
modal: ``,
closable: !unref(z),
"close-on-escape": !unref(z),
},
{
footer: withCtx(() => [
createVNode(
r,
{
label: `取消`,
text: ``,
severity: `secondary`,
disabled: unref(z),
onClick: (n[2] ||= (e) => (H.value = !1)),
},
null,
8,
[`disabled`],
),
createVNode(
r,
{
label: `创建`,
loading: unref(z),
onClick: pe,
},
null,
8,
[`loading`],
),
]),
default: withCtx(() => [
createElementVNode(`div`, se, [
createElementVNode(
`label`,
{
for: `${unref(A)}-name`,
},
`名称`,
8,
ce,
),
createVNode(
v,
{
id: `${unref(A)}-name`,
modelValue: unref(U),
"onUpdate:modelValue": (n[0] ||= (e) =>
isRef(U) ? (U.value = e) : null),
disabled: unref(z),
},
null,
8,
[`id`, `modelValue`, `disabled`],
),
createElementVNode(
`label`,
{
for: `${unref(A)}-cidr`,
},
`网段(留空自动分配)`,
8,
E,
),
createVNode(
v,
{
id: `${unref(A)}-cidr`,
modelValue: unref(W),
"onUpdate:modelValue": (n[1] ||= (e) =>
isRef(W) ? (W.value = e) : null),
placeholder: `10.0.0.0/16`,
disabled: unref(z),
},
null,
8,
[`id`, `modelValue`, `disabled`],
),
unref(L).length
? (openBlock(),
createElementBlock(
`p`,
le,
`允许掩码:` +
toDisplayString(unref(L).join(`、`)),
1,
))
: createCommentVNode(``, !0),
unref(V)
? (openBlock(),
createBlock(
i,
{
key: 1,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(V)),
1,
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]),
]),
_: 1,
},
8,
[`visible`, `closable`, `close-on-escape`],
),
createVNode(
y,
{
visible: !!unref(K),
header: `分配 / 切换 VPC`,
modal: ``,
closable: !unref(z),
"close-on-escape": !unref(z),
"onUpdate:visible": (n[7] ||= (e) => {
!e && !unref(z) && (K.value = null);
}),
},
{
footer: withCtx(() => [
createVNode(
r,
{
label: `取消`,
text: ``,
severity: `secondary`,
disabled: unref(z),
onClick: (n[6] ||= (e) => (K.value = null)),
},
null,
8,
[`disabled`],
),
createVNode(
r,
{
label: `确认分配`,
loading: unref(z),
disabled: !unref(q) || unref(J) || !unref(I).length,
onClick: me,
},
null,
8,
[`loading`, `disabled`],
),
]),
default: withCtx(() => [
createElementVNode(`div`, ue, [
createElementVNode(
`p`,
null,
`目标网络:` + toDisplayString(unref(K)?.name),
1,
),
createElementVNode(
`label`,
{
for: `${unref(A)}-host`,
},
`实例`,
8,
de,
),
createVNode(
C,
{
"input-id": `${unref(A)}-host`,
modelValue: unref(q),
"onUpdate:modelValue": (n[4] ||= (e) =>
isRef(q) ? (q.value = e) : null),
options: unref(I),
"option-label": `name`,
"option-value": `id`,
loading: unref(J),
disabled: unref(z),
},
null,
8,
[
`input-id`,
`modelValue`,
`options`,
`loading`,
`disabled`,
],
),
createVNode(
i,
{
severity: `warn`,
closable: !1,
},
{
default: withCtx(() => [
...(n[12] ||= [
createTextVNode(
`切换 VPC 会改变实例内网连接,请确认相关服务已准备好。`,
-1,
),
]),
]),
_: 1,
},
),
unref(V)
? (openBlock(),
createBlock(
i,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(V)),
1,
),
!unref(I).length && unref(K)
? (openBlock(),
createBlock(r, {
key: 0,
label: `重试`,
text: ``,
size: `small`,
onClick: (n[5] ||= (e) =>
$(unref(K))),
}))
: createCommentVNode(``, !0),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]),
]),
_: 1,
},
8,
[`visible`, `closable`, `close-on-escape`],
),
createVNode(
y,
{
visible: !!unref(G),
header: `删除 VPC 网络`,
modal: ``,
closable: !unref(z),
"close-on-escape": !unref(z),
"onUpdate:visible": (n[9] ||= (e) => {
!e && !unref(z) && (G.value = null);
}),
},
{
footer: withCtx(() => [
createVNode(
r,
{
label: `取消`,
text: ``,
severity: `secondary`,
disabled: unref(z),
onClick: (n[8] ||= (e) => (G.value = null)),
},
null,
8,
[`disabled`],
),
createVNode(
r,
{
label: `确认删除`,
severity: `danger`,
loading: unref(z),
onClick: he,
},
null,
8,
[`loading`],
),
]),
default: withCtx(() => [
createElementVNode(
`p`,
null,
`确认删除 ` + toDisplayString(unref(G)?.name) + `?`,
1,
),
unref(V)
? (openBlock(),
createBlock(
i,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(V)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]),
_: 1,
},
8,
[`visible`, `closable`, `close-on-escape`],
),
])
);
}
);
},
}),
[[`__scopeId`, `data-v-1d12fc4b`]],
),
{
__name: `MfCloudVpc`,
},
);
export {};
export default D;
</script>