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

756 lines
28 KiB
Vue

<script>
/**
* NAT 建站/端口转发共用面板:kind 决定 web 或 acl 协议,父级提供模块能力配额和实例活动态。
* 只管理列表与创建/删除,不产生订单;所有变更仍由后端校验权限及实际配额。
*/
import "~/assets/styles/MfCloudNat.C1uXa4fX.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 { reactive } from "vue";
import { isRef } from "vue";
import { createElementBlock } from "vue";
import { Fragment } 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 inputnumber from "primevue/inputnumber";
import column from "primevue/column";
import { a as cloud_instance_management_a } from "~/lib/cloud-instance-management.js";
import { i as cloud_instance_management_i } from "~/lib/cloud-instance-management.js";
var A = {
class: `nat-panel network-section`,
},
j = {
class: `network-head`,
},
M = {
class: `network-heading`,
},
N = {
key: 0,
class: `network-count`,
},
ne = {
class: `network-actions`,
},
re = {
key: 0,
class: `muted`,
},
ie = {
class: `form`,
},
ae = [`for`],
P = [`for`],
F = [`for`],
I = [`for`],
L = [`for`],
R = Object.assign(
_plugin_vue_export_helper_t(
defineComponent({
__name: `MfCloudNat`,
props: {
hostId: {},
module: {},
kind: {},
limit: {},
active: {
type: Boolean,
},
},
setup(_) {
let C = _,
R = useApi(),
z = useId(),
B = ref([]),
V = ref(!1),
H = ref(!1),
U = ref(``),
W = ref(``),
G = ref(!1),
K = ref(null),
// 外部端口 null 表示由后端分配,与显式填写 0 不同;内部端口默认 80。
q = reactive({
name: ``,
domain: ``,
int_port: 80,
ext_port: null,
protocol: 1,
}),
J = computed(() => (C.kind === `acl` ? `NAT 转发` : `NAT 建站`)),
// 功能开关和配额共同决定是否查询/开放操作,不能仅凭列表为空判断可创建。
Y = computed(
() =>
cloud_instance_management_i(C.module) && Number(C.limit) > 0,
),
X = computed(() => `/${C.module}/${C.hostId}/nat_${C.kind}`),
Z = 0,
Q = !1;
/** 刷新当前实例当前 kind 的列表;无能力时清空,响应形状异常时保留错误状态。 */
async function $() {
let e = ++Z,
t = X.value;
if (((V.value = !0), (U.value = ``), !Y.value)) {
((B.value = []), (V.value = !1));
return;
}
try {
let n = await R.get(t);
if (Q || e !== Z) return;
if (!Array.isArray(n.list))
throw Error(`${J.value}列表无效,请重试`);
B.value = n.list;
} catch (t) {
!Q &&
e === Z &&
(U.value = t instanceof Error ? t.message : `加载失败`);
} finally {
!Q && e === Z && (V.value = !1);
}
}
// 只有列表已可靠加载且未用满配额才打开创建表单,避免在加载失败时误判剩余数量。
function oe() {
!Y.value ||
!C.active ||
H.value ||
V.value ||
U.value ||
B.value.length >= C.limit ||
(Object.assign(q, {
name: ``,
domain: ``,
int_port: 80,
ext_port: null,
protocol: 1,
}),
(W.value = ``),
(G.value = !0));
}
/** 通过统一 NAT 校验器生成最小请求体,成功后重读列表,不猜测后端分配的端口。 */
async function se() {
if (
Q ||
H.value ||
!G.value ||
!Y.value ||
!C.active ||
V.value ||
U.value ||
B.value.length >= C.limit
)
return;
W.value = ``;
let e;
try {
e = cloud_instance_management_a(C.kind, q);
} catch (e) {
W.value = e.message;
return;
}
let t = X.value,
n = Z;
H.value = !0;
try {
if ((await R.post(t, e), Q || n !== Z)) return;
((G.value = !1), await $());
} catch (e) {
!Q &&
t === X.value &&
(W.value = e instanceof Error ? e.message : `创建失败`);
} finally {
!Q && t === X.value && (H.value = !1);
}
}
/** 删除前确认目标仍在当前列表,以 kind 特定的参数名提交,成功后重新取最新配额。 */
async function ce() {
let e = K.value;
if (
!e ||
H.value ||
!Y.value ||
!C.active ||
!B.value.some((t) => t.id === e.id)
)
return;
let t = X.value,
n = C.kind,
r = Z;
((H.value = !0), (W.value = ``));
try {
if (
(await R.del(t, {
[`nat_${n}_id`]: e.id,
}),
Q || r !== Z)
)
return;
((K.value = null), await $());
} catch (e) {
!Q &&
t === X.value &&
(W.value = e instanceof Error ? e.message : `删除失败`);
} finally {
!Q && t === X.value && (H.value = !1);
}
}
return (
watch(
() => [C.hostId, C.module, C.kind, C.limit],
() => {
((G.value = !1),
(K.value = null),
(H.value = !1),
(B.value = []),
$());
},
),
onMounted($),
onBeforeUnmount(() => {
((Q = !0), Z++);
}),
// 创建与删除使用独立确认弹窗、共享提交锁;acl 显示协议/名称,web 显示域名。
(t, n) => {
let r = button,
i = message,
o = column,
l = datatable,
u = inputtext,
b = select,
C = inputnumber,
O = dialog;
return (
openBlock(),
createElementBlock(`section`, A, [
createElementVNode(`div`, j, [
createElementVNode(`div`, M, [
createElementVNode(
`h4`,
null,
toDisplayString(unref(J)),
1,
),
unref(Y)
? (openBlock(),
createElementBlock(
`span`,
N,
toDisplayString(unref(B).length) +
`/` +
toDisplayString(_.limit),
1,
))
: createCommentVNode(``, !0),
]),
createElementVNode(`div`, ne, [
createVNode(
r,
{
label: `创建${unref(J)}`,
icon: `pi pi-plus`,
size: `small`,
disabled:
!unref(Y) ||
!_.active ||
unref(H) ||
unref(V) ||
!!unref(U) ||
unref(B).length >= _.limit,
onClick: oe,
},
null,
8,
[`label`, `disabled`],
),
unref(Y)
? (openBlock(),
createBlock(
r,
{
key: 0,
label: `刷新`,
text: ``,
size: `small`,
loading: unref(V),
disabled: unref(H),
onClick: $,
},
null,
8,
[`loading`, `disabled`],
))
: createCommentVNode(``, !0),
]),
]),
unref(Y)
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(
`p`,
re,
`当前实例未开通` + toDisplayString(unref(J)) + `能力。`,
1,
)),
unref(U)
? (openBlock(),
createBlock(
i,
{
key: 1,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(U)), 1),
createVNode(r, {
label: `重试`,
text: ``,
size: `small`,
onClick: $,
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(Y)
? (openBlock(),
createBlock(
l,
{
key: 2,
class: `network-table`,
value: unref(B),
loading: unref(V),
size: `small`,
scrollable: ``,
},
{
empty: withCtx(() => [
createElementVNode(
`span`,
null,
`暂无` + toDisplayString(unref(J)) + `记录`,
1,
),
]),
default: withCtx(() => [
createVNode(
o,
{
field: _.kind === `acl` ? `name` : `domain`,
header: _.kind === `acl` ? `名称` : `域名`,
},
null,
8,
[`field`, `header`],
),
_.kind === `acl`
? (openBlock(),
createBlock(o, {
key: 0,
field: `ip`,
header: `外网地址与端口`,
}))
: (openBlock(),
createBlock(o, {
key: 1,
field: `ext_port`,
header: `外部端口`,
})),
createVNode(o, {
field: `int_port`,
header: `内部端口`,
}),
_.kind === `acl`
? (openBlock(),
createBlock(
o,
{
key: 2,
header: `协议`,
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(
{
1: `TCP`,
2: `UDP`,
3: `TCP + UDP`,
}[t.protocol] || `—`,
),
1,
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
createVNode(
o,
{
header: `操作`,
},
{
body: withCtx(({ data: e }) => [
createVNode(
r,
{
label: `删除`,
text: ``,
severity: `danger`,
size: `small`,
disabled: unref(H) || !_.active,
onClick: (t) => {
((K.value = e), (W.value = ``));
},
},
null,
8,
[`disabled`, `onClick`],
),
]),
_: 1,
},
),
]),
_: 1,
},
8,
[`value`, `loading`],
))
: createCommentVNode(``, !0),
createVNode(
O,
{
visible: unref(G),
"onUpdate:visible": (n[6] ||= (e) =>
isRef(G) ? (G.value = e) : null),
header: `创建${unref(J)}`,
modal: ``,
closable: !unref(H),
"close-on-escape": !unref(H),
style: {
width: `460px`,
},
},
{
footer: withCtx(() => [
createVNode(
r,
{
label: `取消`,
text: ``,
severity: `secondary`,
disabled: unref(H),
onClick: (n[5] ||= (e) => (G.value = !1)),
},
null,
8,
[`disabled`],
),
createVNode(
r,
{
label: `创建`,
loading: unref(H),
onClick: se,
},
null,
8,
[`loading`],
),
]),
default: withCtx(() => [
createElementVNode(`div`, ie, [
_.kind === `acl`
? (openBlock(),
createElementBlock(
Fragment,
{
key: 0,
},
[
createElementVNode(
`label`,
{
for: `${unref(z)}-name`,
},
`转发名称`,
8,
ae,
),
createVNode(
u,
{
id: `${unref(z)}-name`,
modelValue: unref(q).name,
"onUpdate:modelValue": (n[0] ||= (e) =>
(unref(q).name = e)),
disabled: unref(H),
},
null,
8,
[`id`, `modelValue`, `disabled`],
),
createElementVNode(
`label`,
{
for: `${unref(z)}-protocol`,
},
`协议`,
8,
P,
),
createVNode(
b,
{
"input-id": `${unref(z)}-protocol`,
modelValue: unref(q).protocol,
"onUpdate:modelValue": (n[1] ||= (e) =>
(unref(q).protocol = e)),
options: [
{
label: `TCP`,
value: 1,
},
{
label: `UDP`,
value: 2,
},
{
label: `TCP + UDP`,
value: 3,
},
],
"option-label": `label`,
"option-value": `value`,
disabled: unref(H),
},
null,
8,
[`input-id`, `modelValue`, `disabled`],
),
createElementVNode(
`label`,
{
for: `${unref(z)}-ext`,
},
`外部端口(留空自动分配)`,
8,
F,
),
createVNode(
C,
{
"input-id": `${unref(z)}-ext`,
modelValue: unref(q).ext_port,
"onUpdate:modelValue": (n[2] ||= (e) =>
(unref(q).ext_port = e)),
min: 0,
max: 65535,
"use-grouping": !1,
disabled: unref(H),
},
null,
8,
[`input-id`, `modelValue`, `disabled`],
),
],
64,
))
: (openBlock(),
createElementBlock(
Fragment,
{
key: 1,
},
[
createElementVNode(
`label`,
{
for: `${unref(z)}-domain`,
},
`域名`,
8,
I,
),
createVNode(
u,
{
id: `${unref(z)}-domain`,
modelValue: unref(q).domain,
"onUpdate:modelValue": (n[3] ||= (e) =>
(unref(q).domain = e)),
placeholder: `example.com`,
disabled: unref(H),
},
null,
8,
[`id`, `modelValue`, `disabled`],
),
],
64,
)),
createElementVNode(
`label`,
{
for: `${unref(z)}-int`,
},
`内部端口`,
8,
L,
),
createVNode(
C,
{
"input-id": `${unref(z)}-int`,
modelValue: unref(q).int_port,
"onUpdate:modelValue": (n[4] ||= (e) =>
(unref(q).int_port = e)),
min: 1,
max: 65535,
"use-grouping": !1,
disabled: unref(H),
},
null,
8,
[`input-id`, `modelValue`, `disabled`],
),
unref(W)
? (openBlock(),
createBlock(
i,
{
key: 2,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(W)),
1,
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]),
]),
_: 1,
},
8,
[`visible`, `header`, `closable`, `close-on-escape`],
),
createVNode(
O,
{
visible: !!unref(K),
header: `删除${unref(J)}`,
modal: ``,
closable: !unref(H),
"close-on-escape": !unref(H),
"onUpdate:visible": (n[8] ||= (e) => {
!e && !unref(H) && (K.value = null);
}),
},
{
footer: withCtx(() => [
createVNode(
r,
{
label: `取消`,
text: ``,
severity: `secondary`,
disabled: unref(H),
onClick: (n[7] ||= (e) => (K.value = null)),
},
null,
8,
[`disabled`],
),
createVNode(
r,
{
label: `确认删除`,
severity: `danger`,
loading: unref(H),
onClick: ce,
},
null,
8,
[`loading`],
),
]),
default: withCtx(() => [
createElementVNode(
`p`,
null,
`确认删除 ` +
toDisplayString(
unref(K)?.domain || unref(K)?.name,
) +
`?相关访问规则将立即失效。`,
1,
),
unref(W)
? (openBlock(),
createBlock(
i,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(W)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]),
_: 1,
},
8,
[`visible`, `header`, `closable`, `close-on-escape`],
),
])
);
}
);
},
}),
[[`__scopeId`, `data-v-1ff078ab`]],
),
{
__name: `MfCloudNat`,
},
);
export {};
export default R;
</script>