756 lines
28 KiB
Vue
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>
|