Files
console-dev/app/pages/security/groups/index.vue
T

2404 lines
95 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>
/**
* 安全组:组列表、出入站规则与实例关联共用页面;主列表、规则、已关联和可关联主机分别用请求代次隔离,批量关联保留失败目标供重试。
*/
import "~/assets/styles/PageInfoNotice._16xRFrB.css";
import "~/assets/styles/groups.Buw8nwl1.css";
definePageMeta({
key: (e) => e.fullPath,
alias: [`/security/groups/group`],
});
import { toDisplayString } from "vue";
import { renderList } from "vue";
import { onMounted } from "vue";
import { useId } from "vue";
import { normalizeClass } 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 { withKeys } from "vue";
import { useRoute } from "#app";
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 { n as product_name_n } from "~/lib/product-name.js";
import { r as dict_r } from "~/lib/dict.js";
import datatable from "primevue/datatable";
import paginator from "primevue/paginator";
import select from "primevue/select";
import checkbox from "primevue/checkbox";
import column from "primevue/column";
import ListEmpty from "~/components/ListEmpty.vue";
import textarea from "primevue/textarea";
import tag from "primevue/tag";
import PageInfoNotice from "~/components/PageInfoNotice.vue";
import multiselect from "primevue/multiselect";
import { n as complete_list_n } from "~/lib/complete-list.js";
import { t as complete_list_t } from "~/lib/complete-list.js";
import { t as composables_t } from "~/lib/composables-2.js";
var Te = {
class: `list-page`,
},
Ee = {
key: 0,
class: `list-head`,
},
De = {
class: `list-head-actions`,
},
Oe = {
key: 2,
class: `list-toolbar`,
},
ke = {
key: 3,
class: `list-table`,
},
Ae = [`onClick`],
je = {
class: `row-actions`,
},
Me = {
key: 4,
class: `list-foot`,
},
Ne = {
class: `form`,
},
Pe = {
class: `field`,
},
Fe = [`for`],
Ie = {
class: `field`,
},
Le = [`for`],
Re = {
key: 5,
class: `group-detail`,
},
ze = {
class: `group-detail-head`,
},
Be = {
class: `group-meta`,
},
Ve = {
class: `row-actions`,
},
He = {
class: `rules`,
},
Ue = {
class: `dlg-tabs`,
},
We = {
class: `rule-toolbar`,
},
Ge = {
class: `mono`,
},
Ke = {
class: `rule-add`,
},
qe = {
class: `rule-form`,
},
Je = {
class: `rule-form batch-relate-form`,
},
Ye = {
class: `rule-form`,
},
Xe = {
class: `mono`,
},
Ze = {
class: `rule-form`,
},
Qe = {
class: `batch-dlg`,
},
$e = {
class: `quick-grid`,
},
et = {
class: `advanced-rules`,
},
b = _plugin_vue_export_helper_t(
defineComponent({
__name: `index`,
setup(re) {
let y = useApi(),
b = !1,
x = 0,
tt = 0,
nt = 0,
rt = 0;
onBeforeUnmount(() => {
((b = !0), x++, tt++, nt++, rt++);
});
let it = useId(),
at = useRoute();
composables_t({
alias: `/security/groups/group`,
key: (e) => e.fullPath,
});
let ot = ref([]),
S = ref(0),
st = ref(!0),
C = ref(1),
w = ref(10),
T = ref(``),
E = ref(``),
ct = ref(``),
lt = computed(() => ot.value),
D = ref(`in`),
O = ref(!1),
ut = [
{
name: `FTP`,
port: `20-21`,
},
{
name: `SSH`,
port: `22`,
},
{
name: `Telnet`,
port: `23`,
},
{
name: `TFTP`,
port: `69`,
},
{
name: `RDP`,
port: `3389`,
},
{
name: `VNC`,
port: `5900-5902`,
},
{
name: `Linux`,
port: `1024-65535`,
},
{
name: `HTTPS`,
port: `443`,
},
{
name: `SMTP`,
port: `25`,
},
{
name: `POP3`,
port: `110`,
},
{
name: `IMAP`,
port: `143`,
},
{
name: `HTTP`,
port: `80`,
},
{
name: `SQL Server`,
port: `1433`,
},
{
name: `Oracle`,
port: `1521`,
},
{
name: `MySQL`,
port: `3306`,
},
{
name: `PostgreSQL`,
port: `5432`,
},
{
name: `Redis`,
port: `6379`,
},
{
name: `MongoDB`,
port: `27017`,
},
],
k = ref([]);
// 常用端口快捷选择仅生成待确认的文本规则;默认 IP 为全网范围,生成并不立即下发。
function dt() {
$.value = ut
.filter((e) => k.value.includes(e.port))
.map(
(e) =>
`${D.value} ${e.port === `69` ? `udp` : `tcp`} ${e.port} 0.0.0.0/0 ${e.name}`,
).join(`
`);
}
// 打开新增规则弹窗,方向取当前入站/出站标签。
function ft() {
((R.value = null), (L.direction = D.value), (O.value = !0));
}
// 把已选规则复制到草稿后编辑,避免未保存时改动列表。
function pt(e) {
((R.value = e),
Object.assign(L, {
description: e.description || ``,
direction: e.direction || D.value,
protocol: e.protocol || `tcp`,
port: e.port || ``,
ip: e.ip || `0.0.0.0/0`,
}),
(T.value = ``),
(O.value = !0));
}
let A = ref(!1),
mt = ref(!1),
j = ref(null),
M = reactive({
name: ``,
description: ``,
}),
N = ref(!1),
P = ref(null),
ht = ref([]),
gt = ref(0),
F = ref(1),
_t = ref(20),
I = ref(``),
vt = ref(!1),
yt = ref(!1),
L = reactive({
description: ``,
direction: `in`,
protocol: `tcp`,
port: ``,
ip: `0.0.0.0/0`,
}),
R = ref(null),
bt = computed(() => ht.value.filter((e) => e.direction === D.value));
// 按关键词分页查询安全组,组件离开或新请求发起后废弃旧响应。
async function z() {
let e = ++x;
((st.value = !0), (T.value = ``));
try {
let t = await y.get(`/security_group`, {
page: C.value,
limit: w.value,
keywords: ct.value,
});
if (b || e !== x) return;
((ot.value = t.list), (S.value = t.count));
} catch (t) {
if (b || e !== x) return;
((T.value = t instanceof Error ? t.message : `加载安全组失败`),
(ot.value = []),
(S.value = 0));
} finally {
e === x && !b && (st.value = !1);
}
}
// 新建组前清空上一次编辑目标和名称描述。
function xt() {
((j.value = null),
(M.name = ``),
(M.description = ``),
(T.value = ``),
(A.value = !0));
}
// 编辑组时回填现有名称描述,保留原对象用于更新 ID。
function St(e) {
((j.value = e),
(M.name = e.name),
(M.description = e.description || ``),
(T.value = ``),
(A.value = !0));
}
// 名称必填且同一时间只提交一次;新增/更新后重新读取组列表。
async function Ct() {
if (!mt.value) {
if (((T.value = ``), !M.name.trim())) {
T.value = `请输入安全组名称`;
return;
}
mt.value = !0;
try {
(j.value
? await y.put(`/security_group/${j.value.id}`, {
id: j.value.id,
...M,
})
: await y.post(`/security_group`, {
...M,
}),
(A.value = !1),
await z());
} catch (e) {
T.value = e instanceof Error ? e.message : `保存失败`;
} finally {
mt.value = !1;
}
}
}
// 删除组后刷新列表,失败保留页面错误。
async function wt(e) {
try {
(await y.del(`/security_group/${e.id}`), await z());
} catch (e) {
T.value = e instanceof Error ? e.message : `删除失败`;
}
}
// 进入组详情时重置规则页码、草稿与当前标签,再加载该组规则。
async function Tt(e) {
((P.value = e),
(N.value = !0),
(V.value = `rule`),
(F.value = 1),
(I.value = ``),
(T.value = ``),
Object.assign(L, {
description: ``,
direction: `in`,
protocol: `tcp`,
port: ``,
ip: `0.0.0.0/0`,
}),
await B());
}
// 规则响应必须匹配组 ID 与请求代次,避免切组后展示上一组规则。
async function B() {
if (!P.value) return;
let e = ++tt,
t = P.value.id,
n = () => !b && e === tt && t === P.value?.id;
((vt.value = !0), (T.value = ``));
try {
let e = await y.get(`/security_group/${t}/rule`, {
page: F.value,
limit: _t.value,
keywords: I.value.trim(),
direction: D.value,
});
if (!n()) return;
((ht.value = e.list ?? []),
(gt.value = e.count ?? ht.value.length));
} catch (e) {
if (!n()) return;
((T.value = e instanceof Error ? e.message : `加载规则失败`),
(ht.value = []),
(gt.value = 0));
} finally {
n() && (vt.value = !1);
}
}
let V = ref(`rule`),
Et = ref([]),
H = ref(!1),
Dt = ref([]),
U = ref(null),
W = ref([]),
G = ref(!1),
Ot = ref(!1),
K = ref(``),
q = ref(``),
J = ref(``),
Y = ref(!1),
kt = ref(``),
At = computed(() =>
Et.value.filter((e) =>
`${e.name} ${e.ip || ``}`
.toLowerCase()
.includes(kt.value.trim().toLowerCase()),
),
),
X = computed(
() =>
H.value ||
Y.value ||
!!q.value ||
!!J.value ||
G.value ||
Ot.value,
);
// 读取当前组全部关联主机用于搜索与去重,而不是只拿关联列表第一页。
async function Z() {
if (!P.value) return;
let e = P.value.id,
t = ++nt,
n = () => !b && t === nt && e === P.value?.id;
((H.value = !0), (q.value = ``));
try {
let t = await complete_list_n(
(t, n) =>
y.get(`/security_group/${e}/host`, {
page: t,
limit: n,
}),
n,
);
n() && (Et.value = t);
} catch (e) {
if (!n()) return;
((Et.value = []),
(q.value = e instanceof Error ? e.message : `关联实例加载失败`));
} finally {
n() && (H.value = !1);
}
}
// 分页收集可用主机,失败时清空旧候选,避免沿用已失效的关联资格。
async function jt() {
let e = ++rt,
t = () => !b && e === rt;
((Y.value = !0), (J.value = ``));
try {
let e = await complete_list_n(
(e, t) =>
y.get(`/host`, {
page: e,
limit: t,
}),
t,
);
t() && (Dt.value = e);
} catch (e) {
if (!t()) return;
((Dt.value = []),
(J.value = e instanceof Error ? e.message : `可用实例加载失败`));
} finally {
t() && (Y.value = !1);
}
}
let Mt = [
`idcsmart_cloud`,
`common_cloud`,
`mf_cloud`,
`mf_cloud_mysql`,
`pve_cloud`,
`kky_ecs`,
],
Nt = computed(() =>
Dt.value
.filter((e) => Mt.includes(e.module ?? ``))
.filter((e) => !Et.value.some((t) => t.id === e.id))
.map((e) => ({
label: `${product_name_n(e.product_name)} ${e.name}`.trim(),
value: e.id,
})),
);
// 批量提交前核对所有选择仍可关联;解析部分成功结果后保留失败 ID,刷新已关联列表及组计数。
async function Pt() {
if (!P.value || !W.value.length || X.value) return;
let e = P.value.id,
t = [...new Set(W.value)];
if (t.some((e) => !Nt.value.some((t) => t.value === e))) {
T.value = `请选择当前可关联的实例`;
return;
}
((Ot.value = !0), (T.value = ``), (K.value = ``));
try {
let n = await y.post(`/security_group/${e}/host`, {
host_id: t,
});
if (b || P.value?.id !== e) return;
let r = complete_list_t(n, t);
if (
((W.value = r.failed),
await Promise.all([Z(), z()]),
b || P.value?.id !== e)
)
return;
((T.value = r.details.join(`;`)),
r.succeeded.length &&
(K.value = `已关联 ${r.succeeded.length} 台实例`),
K.value && setTimeout(() => (K.value = ``), 4e3));
} catch (t) {
if (b || P.value?.id !== e) return;
T.value = t instanceof Error ? t.message : `批量关联失败`;
} finally {
Ot.value = !1;
}
}
// 单主机关联仅接受当前候选项,提交后刷新关联及组列表。
async function Ft() {
if (
P.value &&
U.value &&
!X.value &&
Nt.value.some((e) => e.value === U.value)
) {
((G.value = !0), (T.value = ``));
try {
(await y.post(`/security_group/${P.value.id}/host/${U.value}`),
(U.value = null),
await Z(),
await z());
} catch (e) {
T.value = e instanceof Error ? e.message : `关联失败`;
} finally {
G.value = !1;
}
}
}
// 解除实例与组的关联后刷新状态,不在本地假定服务端已删除关系。
async function It(e) {
if (P.value && !X.value) {
((G.value = !0), (T.value = ``));
try {
(await y.del(`/security_group/${P.value.id}/host/${e.id}`),
await Z(),
await z());
} catch (e) {
T.value = e instanceof Error ? e.message : `取消关联失败`;
} finally {
G.value = !1;
}
}
}
let Q = ref(!1),
$ = ref(``),
Lt = ref(!1);
// 每行按方向、协议、端口、IP 和可选描述解析;空行忽略,不足四列明确报格式错误。
function Rt(e) {
let t = [];
for (let n of e.split(`
`)) {
let e = n.trim();
if (!e) continue;
let r = e.split(/\s+/);
if (r.length < 4) throw Error(`格式不正确:${e}`);
let [ee, i, te = ``, a = ``, ...o] = r;
t.push({
direction: ee === `out` ? `out` : `in`,
protocol: (i || `tcp`).toLowerCase(),
port: te,
ip: a,
description: o.join(` `) || void 0,
});
}
return t;
}
// 批量规则先完整解析再提交,至少需要一条;成功后清空文本并刷新规则和组计数。
async function zt() {
if (!P.value || Lt.value) return;
T.value = ``;
let e;
try {
e = Rt($.value);
} catch (e) {
T.value = e instanceof Error ? e.message : `格式不正确`;
return;
}
if (!e.length) {
T.value = `请输入至少一条规则`;
return;
}
Lt.value = !0;
try {
let t = await y.post(`/security_group/${P.value.id}/rule/batch`, {
rule: e,
});
((Q.value = !1),
($.value = ``),
await B(),
await z(),
(T.value = ``),
t?.success_num !== void 0 &&
((Bt.value = `成功添加 ${t.success_num} 条规则`),
setTimeout(() => (Bt.value = ``), 4e3)));
} catch (e) {
T.value = e instanceof Error ? e.message : `批量添加失败`;
} finally {
Lt.value = !1;
}
}
let Bt = ref(``);
watch(V, (e) => {
e === `host` && (Z(), jt());
});
// 单条规则按是否有编辑目标选择 PUT/POST;端口必填,成功后重置编辑器并刷新。
async function Vt() {
if (!yt.value) {
if (((T.value = ``), !P.value)) {
T.value = `请先选择安全组`;
return;
}
if (!L.port.trim()) {
T.value = `请输入端口(如 22 或 100-65535)`;
return;
}
yt.value = !0;
try {
(R.value
? await y.put(`/security_group/rule/${R.value.id}`, {
...L,
})
: await y.post(`/security_group/${P.value.id}/rule`, {
...L,
}),
Object.assign(L, {
description: ``,
direction: `in`,
protocol: `tcp`,
port: ``,
ip: `0.0.0.0/0`,
}),
(R.value = null),
(O.value = !1),
await B(),
await z());
} catch (e) {
T.value = e instanceof Error ? e.message : `添加规则失败`;
} finally {
yt.value = !1;
}
}
}
// 删除规则后同步规则列表与安全组统计。
async function Ht(e) {
try {
(await y.del(`/security_group/rule/${e.id}`), await B(), await z());
} catch (e) {
T.value = e instanceof Error ? e.message : `删除规则失败`;
}
}
// 把已知协议枚举转换为可读名称,未知协议保留大写标识。
function Ut(e) {
return (
{
tcp: `TCP`,
udp: `UDP`,
icmp: `ICMP`,
all: `全部`,
rdp: `RDP`,
ssh: `SSH`,
http: `HTTP`,
https: `HTTPS`,
}[e] || e.toUpperCase()
);
}
function Wt(e) {
((C.value = e.page + 1), (w.value = e.rows), z());
}
// 确认搜索词后回到第一页查询组列表。
function Gt() {
((ct.value = E.value.trim()), (C.value = 1), z());
}
// 清空搜索草稿及已应用词,并恢复第一页。
function Kt() {
((E.value = ``), (ct.value = ``), (C.value = 1), z());
}
// 切换规则方向时恢复规则第一页,再按服务端方向筛选读取。
function qt(e) {
((V.value = `rule`), (D.value = e), (F.value = 1), B());
}
function Jt(e) {
((F.value = e.page + 1), (_t.value = e.rows), B());
}
return (
onMounted(async () => {
await z();
let e = Number(at.query.id);
at.path === `/security/groups/group` &&
Number.isInteger(e) &&
e > 0 &&
(await Tt(
ot.value.find((t) => t.id === e) ?? {
id: e,
name: `安全组 #${e}`,
},
));
}),
// 组列表、规则详情、主机关联和批量规则各自使用独立草稿与提交锁,避免一个弹窗改变另一操作目标。
(n, r) => {
let i = button,
te = PageInfoNotice,
o = message,
l = inputtext,
u = column,
re = ListEmpty,
v = datatable,
ae = paginator,
y = textarea,
ue = dialog,
Se = tag,
Ce = select,
we = multiselect,
b = checkbox;
return (
openBlock(),
createElementBlock(`div`, Te, [
unref(N)
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(`div`, Ee, [
(r[30] ||= createElementVNode(`h2`, null, `安全组`, -1)),
createElementVNode(`div`, De, [
createVNode(i, {
label: `创建安全组`,
icon: `pi pi-plus`,
onClick: xt,
}),
]),
])),
createVNode(
te,
{
class: `security-group-notice`,
"aria-label": `安全组使用提示`,
},
{
default: withCtx(() => [
...(r[31] ||= [
createTextVNode(
` 服务器默认放行全部端口。如需限制端口或控制访问,建议优先配置服务器操作系统内的防火墙。平台安全组配置较为复杂,部分场景可能存在兼容性限制。 `,
-1,
),
]),
]),
_: 1,
},
),
unref(T) && !unref(A) && !unref(N)
? (openBlock(),
createBlock(
o,
{
key: 1,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(T)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(N)
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(`div`, Oe, [
createVNode(
l,
{
modelValue: unref(E),
"onUpdate:modelValue": (r[0] ||= (e) =>
isRef(E) ? (E.value = e) : null),
class: `list-search`,
"aria-label": `搜索安全组`,
placeholder: `输入关键字`,
onKeyup: withKeys(Gt, [`enter`]),
},
null,
8,
[`modelValue`],
),
createVNode(i, {
label: `搜索`,
icon: `pi pi-search`,
severity: `secondary`,
outlined: ``,
onClick: Gt,
}),
createVNode(i, {
label: `清除筛选`,
icon: `pi pi-filter-slash`,
severity: `secondary`,
outlined: ``,
onClick: Kt,
}),
])),
unref(N)
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(`div`, ke, [
createVNode(
v,
{
value: unref(lt),
loading: unref(st),
lazy: ``,
rows: unref(w),
totalRecords: unref(S),
first: (unref(C) - 1) * unref(w),
dataKey: `id`,
},
{
empty: withCtx(() => [
createVNode(re, {
icon: `pi pi-shield`,
title: `还没有安全组`,
description: `创建安全组后可在开通云服务器时选用`,
}),
]),
default: withCtx(() => [
createVNode(
u,
{
header: `ID`,
style: {
width: `100px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
`#` + toDisplayString(t.id),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `名称`,
style: {
width: `200px`,
},
},
{
body: withCtx(({ data: t }) => [
createElementVNode(
`button`,
{
type: `button`,
class: `group-name`,
onClick: (e) => Tt(t),
},
toDisplayString(t.name),
9,
Ae,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `描述`,
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(t.description || `--`),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `规则数`,
style: {
width: `90px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(t.rule_num ?? 0),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `关联实例`,
style: {
width: `100px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(t.host_num ?? 0),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `创建时间`,
style: {
width: `170px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(
t.create_time
? (`formatTime` in n
? n.formatTime
: unref(dict_r))(t.create_time)
: `--`,
),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `操作`,
style: {
width: `220px`,
},
},
{
body: withCtx(({ data: e }) => [
createElementVNode(`div`, je, [
createVNode(
i,
{
label: `规则`,
size: `small`,
severity: `secondary`,
outlined: ``,
onClick: (t) => Tt(e),
},
null,
8,
[`onClick`],
),
createVNode(
i,
{
label: `编辑`,
size: `small`,
severity: `secondary`,
text: ``,
onClick: (t) => St(e),
},
null,
8,
[`onClick`],
),
createVNode(
i,
{
icon: `pi pi-trash`,
"aria-label": `删除安全组`,
size: `small`,
severity: `danger`,
text: ``,
onClick: (t) => wt(e),
},
null,
8,
[`onClick`],
),
]),
]),
_: 1,
},
),
]),
_: 1,
},
8,
[`value`, `loading`, `rows`, `totalRecords`, `first`],
),
])),
!unref(N) && unref(S) > 0
? (openBlock(),
createElementBlock(`div`, Me, [
(r[32] ||= createElementVNode(
`div`,
{
class: `list-bulk`,
},
null,
-1,
)),
createVNode(
ae,
{
rows: unref(w),
totalRecords: unref(S),
first: (unref(C) - 1) * unref(w),
rowsPerPageOptions: [10, 20, 50],
onPage: Wt,
},
null,
8,
[`rows`, `totalRecords`, `first`],
),
]))
: createCommentVNode(``, !0),
createVNode(
ue,
{
visible: unref(A),
"onUpdate:visible": (r[4] ||= (e) =>
isRef(A) ? (A.value = e) : null),
header: unref(j) ? `编辑安全组` : `创建安全组`,
modal: ``,
draggable: !1,
style: {
width: `338px`,
},
},
{
footer: withCtx(() => [
createVNode(i, {
label: `取消`,
severity: `secondary`,
text: ``,
onClick: (r[3] ||= (e) => (A.value = !1)),
}),
createVNode(
i,
{
label: `保存`,
loading: unref(mt),
onClick: Ct,
},
null,
8,
[`loading`],
),
]),
default: withCtx(() => [
createElementVNode(`div`, Ne, [
createElementVNode(`div`, Pe, [
createElementVNode(
`label`,
{
for: `${unref(it)}-name`,
},
[
...(r[33] ||= [
createTextVNode(`名称 `, -1),
createElementVNode(
`span`,
{
class: `req`,
},
`*`,
-1,
),
]),
],
8,
Fe,
),
createVNode(
l,
{
id: `${unref(it)}-name`,
"aria-required": `true`,
modelValue: unref(M).name,
"onUpdate:modelValue": (r[1] ||= (e) =>
(unref(M).name = e)),
placeholder: `安全组名称`,
fluid: ``,
},
null,
8,
[`id`, `modelValue`],
),
]),
createElementVNode(`div`, Ie, [
createElementVNode(
`label`,
{
for: `${unref(it)}-description`,
},
`描述`,
8,
Le,
),
createVNode(
y,
{
id: `${unref(it)}-description`,
modelValue: unref(M).description,
"onUpdate:modelValue": (r[2] ||= (e) =>
(unref(M).description = e)),
placeholder: `输入描述`,
rows: `3`,
fluid: ``,
},
null,
8,
[`id`, `modelValue`],
),
]),
unref(T)
? (openBlock(),
createBlock(
o,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(T)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]),
]),
_: 1,
},
8,
[`visible`, `header`],
),
unref(N)
? (openBlock(),
createElementBlock(`section`, Re, [
createElementVNode(
`button`,
{
type: `button`,
class: `detail-back`,
onClick: (r[5] ||= (e) => (N.value = !1)),
},
[
...(r[34] ||= [
createElementVNode(
`i`,
{
class: `pi pi-arrow-left`,
},
null,
-1,
),
createTextVNode(` 返回安全组`, -1),
]),
],
),
createElementVNode(`div`, ze, [
createElementVNode(`div`, null, [
createElementVNode(`h2`, null, [
createTextVNode(
toDisplayString(unref(P)?.name || `安全组`) + ` `,
1,
),
createElementVNode(
`small`,
null,
`#` + toDisplayString(unref(P)?.id),
1,
),
]),
createElementVNode(`div`, Be, [
createElementVNode(`span`, null, [
(r[35] ||= createTextVNode(`创建时间`, -1)),
(r[36] ||= createElementVNode(
`br`,
null,
null,
-1,
)),
createElementVNode(
`b`,
null,
toDisplayString(
unref(P)?.create_time
? (`formatTime` in n
? n.formatTime
: unref(dict_r))(unref(P).create_time)
: `—`,
),
1,
),
]),
createElementVNode(`span`, null, [
(r[37] ||= createTextVNode(`描述`, -1)),
(r[38] ||= createElementVNode(
`br`,
null,
null,
-1,
)),
createElementVNode(
`b`,
null,
toDisplayString(unref(P)?.description || `—`),
1,
),
]),
]),
]),
createElementVNode(`div`, Ve, [
createVNode(i, {
label: `编辑安全组`,
icon: `pi pi-pencil`,
onClick: (r[6] ||= (e) => unref(P) && St(unref(P))),
}),
createVNode(i, {
label: `删除安全组`,
icon: `pi pi-trash`,
severity: `danger`,
onClick: (r[7] ||= (e) => unref(P) && wt(unref(P))),
}),
]),
]),
createElementVNode(`div`, He, [
createElementVNode(`div`, Ue, [
createElementVNode(
`button`,
{
type: `button`,
class: normalizeClass({
active:
unref(V) === `rule` && unref(D) === `in`,
}),
onClick: (r[8] ||= (e) => qt(`in`)),
},
`入方向`,
2,
),
createElementVNode(
`button`,
{
type: `button`,
class: normalizeClass({
active:
unref(V) === `rule` && unref(D) === `out`,
}),
onClick: (r[9] ||= (e) => qt(`out`)),
},
`出方向`,
2,
),
createElementVNode(
`button`,
{
type: `button`,
class: normalizeClass({
active: unref(V) === `host`,
}),
onClick: (r[10] ||= (e) => (V.value = `host`)),
},
`关联实例`,
2,
),
]),
unref(V) === `rule`
? (openBlock(),
createElementBlock(
Fragment,
{
key: 0,
},
[
createElementVNode(`div`, We, [
createVNode(
l,
{
modelValue: unref(I),
"onUpdate:modelValue": (r[11] ||= (e) =>
isRef(I) ? (I.value = e) : null),
placeholder: `搜索规则`,
"aria-label": `搜索安全组规则`,
onKeyup: (r[12] ||= withKeys(
(e) => {
((F.value = 1), B());
},
[`enter`],
)),
},
null,
8,
[`modelValue`],
),
createVNode(i, {
label: `搜索`,
icon: `pi pi-search`,
severity: `secondary`,
outlined: ``,
onClick: (r[13] ||= (e) => {
((F.value = 1), B());
}),
}),
createVNode(i, {
label: `快速添加`,
icon: `pi pi-plus`,
onClick: (r[14] ||= (e) => (Q.value = !0)),
}),
createVNode(i, {
label: `手动添加`,
icon: `pi pi-plus`,
severity: `secondary`,
text: ``,
onClick: ft,
}),
]),
unref(T)
? (openBlock(),
createBlock(
o,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(T)),
1,
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(Bt)
? (openBlock(),
createBlock(
o,
{
key: 1,
severity: `success`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(Bt)),
1,
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
createVNode(
v,
{
value: unref(bt),
loading: unref(vt),
dataKey: `id`,
scrollable: ``,
},
{
empty: withCtx(() => [
...(r[39] ||= [
createElementVNode(
`div`,
{
class: `empty-rule`,
},
`暂无规则`,
-1,
),
]),
]),
default: withCtx(() => [
createVNode(
u,
{
header: `方向`,
style: {
width: `80px`,
},
},
{
body: withCtx(({ data: e }) => [
createVNode(
Se,
{
value:
e.direction === `in`
? `入站`
: `出站`,
severity:
e.direction === `in`
? `info`
: `secondary`,
},
null,
8,
[`value`, `severity`],
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `授权策略`,
style: {
width: `110px`,
},
},
{
body: withCtx(() => [
createVNode(Se, {
value: `允许`,
severity: `success`,
}),
]),
_: 1,
},
),
createVNode(
u,
{
header: `协议`,
style: {
width: `90px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(Ut(t.protocol)),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `端口`,
style: {
width: `130px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(t.port),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `来源/目标 IP`,
style: {
width: `160px`,
},
},
{
body: withCtx(({ data: t }) => [
createElementVNode(
`span`,
Ge,
toDisplayString(t.ip),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `描述`,
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(
t.description || `--`,
),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `操作`,
style: {
width: `80px`,
},
},
{
body: withCtx(({ data: e }) => [
createVNode(
i,
{
icon: `pi pi-pencil`,
"aria-label": `编辑规则`,
size: `small`,
severity: `secondary`,
text: ``,
onClick: (t) => pt(e),
},
null,
8,
[`onClick`],
),
createVNode(
i,
{
icon: `pi pi-trash`,
"aria-label": `删除规则`,
size: `small`,
severity: `danger`,
text: ``,
onClick: (t) => Ht(e),
},
null,
8,
[`onClick`],
),
]),
_: 1,
},
),
]),
_: 1,
},
8,
[`value`, `loading`],
),
unref(gt) > 0
? (openBlock(),
createBlock(
ae,
{
key: 2,
rows: unref(_t),
totalRecords: unref(gt),
first: (unref(F) - 1) * unref(_t),
rowsPerPageOptions: [20, 50, 100],
onPage: Jt,
},
null,
8,
[`rows`, `totalRecords`, `first`],
))
: createCommentVNode(``, !0),
],
64,
))
: (openBlock(),
createElementBlock(
Fragment,
{
key: 1,
},
[
unref(q)
? (openBlock(),
createBlock(
o,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(q)) + ` `,
1,
),
createVNode(i, {
label: `重试关联实例`,
text: ``,
onClick: Z,
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(J)
? (openBlock(),
createBlock(
o,
{
key: 1,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(J)) + ` `,
1,
),
createVNode(i, {
label: `重试可用实例`,
text: ``,
onClick: jt,
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
createElementVNode(`div`, Ke, [
createElementVNode(`div`, qe, [
createVNode(
Ce,
{
modelValue: unref(U),
"onUpdate:modelValue": (r[15] ||= (
e,
) => (isRef(U) ? (U.value = e) : null)),
options: unref(Nt),
optionLabel: `label`,
optionValue: `value`,
filter: ``,
placeholder: `选择要关联的实例`,
class: `grow`,
},
null,
8,
[`modelValue`, `options`],
),
createVNode(
i,
{
label: `关联`,
size: `small`,
loading: unref(G),
disabled: !unref(U) || unref(X),
onClick: Ft,
},
null,
8,
[`loading`, `disabled`],
),
]),
createElementVNode(`div`, Je, [
createVNode(
we,
{
modelValue: unref(W),
"onUpdate:modelValue": (r[16] ||= (
e,
) => (isRef(W) ? (W.value = e) : null)),
options: unref(Nt),
optionLabel: `label`,
optionValue: `value`,
filter: ``,
display: `chip`,
maxSelectedLabels: 2,
placeholder: `批量选择多个实例`,
class: `grow`,
},
null,
8,
[`modelValue`, `options`],
),
createVNode(
i,
{
label: `批量关联`,
icon: `pi pi-link`,
size: `small`,
loading: unref(Ot),
disabled: !unref(W).length || unref(X),
onClick: Pt,
},
null,
8,
[`loading`, `disabled`],
),
]),
unref(T)
? (openBlock(),
createBlock(
o,
{
key: 0,
severity: `error`,
closable: !1,
class: `rule-error`,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(T)),
1,
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(K)
? (openBlock(),
createBlock(
o,
{
key: 1,
severity: `success`,
closable: !1,
class: `rule-error`,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(K)),
1,
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
(r[40] ||= createElementVNode(
`p`,
{
class: `sg-note`,
},
` 仅部分云服务器实例支持加入安全组。不受支持的实例,可在其详情页的「网络与安全」中查看其自带的安全策略。 `,
-1,
)),
]),
createElementVNode(`div`, Ye, [
createVNode(
l,
{
modelValue: unref(kt),
"onUpdate:modelValue": (r[17] ||= (e) =>
isRef(kt) ? (kt.value = e) : null),
placeholder: `搜索关联实例名称或 IP`,
},
null,
8,
[`modelValue`],
),
createVNode(
i,
{
label: `刷新实例`,
loading: unref(H) || unref(Y),
text: ``,
onClick: (r[18] ||= (e) => {
(Z(), jt());
}),
},
null,
8,
[`loading`],
),
]),
createVNode(
v,
{
value: unref(At),
loading: unref(H),
dataKey: `id`,
scrollable: ``,
paginator: ``,
rows: 20,
"rows-per-page-options": [20, 50, 100],
},
{
empty: withCtx(() => [
...(r[41] ||= [
createElementVNode(
`div`,
{
class: `empty-rule`,
},
`尚未关联实例`,
-1,
),
]),
]),
default: withCtx(() => [
createVNode(
u,
{
header: `实例名称`,
style: {
"min-width": `200px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(t.name),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `IP`,
style: {
width: `170px`,
},
},
{
body: withCtx(({ data: t }) => [
createElementVNode(
`span`,
Xe,
toDisplayString(t.ip || `--`),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `操作`,
style: {
width: `110px`,
},
},
{
body: withCtx(({ data: e }) => [
createVNode(
i,
{
label: `取消关联`,
size: `small`,
severity: `danger`,
text: ``,
onClick: (t) => It(e),
},
null,
8,
[`onClick`],
),
]),
_: 1,
},
),
]),
_: 1,
},
8,
[`value`, `loading`],
),
],
64,
)),
]),
]))
: createCommentVNode(``, !0),
createVNode(
ue,
{
visible: unref(O),
"onUpdate:visible": (r[25] ||= (e) =>
isRef(O) ? (O.value = e) : null),
header: unref(R) ? `编辑规则` : `手动添加`,
modal: ``,
draggable: !1,
style: {
width: `720px`,
},
},
{
footer: withCtx(() => [
createVNode(i, {
label: `取消`,
severity: `secondary`,
text: ``,
onClick: (r[24] ||= (e) => {
((O.value = !1), (R.value = null));
}),
}),
createVNode(
i,
{
label: unref(R) ? `保存修改` : `确认`,
icon: `pi pi-check`,
loading: unref(yt),
onClick: Vt,
},
null,
8,
[`label`, `loading`],
),
]),
default: withCtx(() => [
createVNode(
o,
{
severity: `info`,
closable: !1,
},
{
default: withCtx(() => [
...(r[42] ||= [
createTextVNode(
`请仅开放需要的端口,并将授权对象限制为可信的 IP 地址。`,
-1,
),
]),
]),
_: 1,
},
),
createElementVNode(`div`, Ze, [
(r[43] ||= createElementVNode(
`label`,
null,
`方向`,
-1,
)),
createVNode(
Ce,
{
"aria-label": `规则方向`,
modelValue: unref(L).direction,
"onUpdate:modelValue": (r[19] ||= (e) =>
(unref(L).direction = e)),
options: [
{
label: `入站`,
value: `in`,
},
{
label: `出站`,
value: `out`,
},
],
optionLabel: `label`,
optionValue: `value`,
class: `w-90`,
},
null,
8,
[`modelValue`],
),
(r[44] ||= createElementVNode(
`label`,
null,
`协议`,
-1,
)),
createVNode(
Ce,
{
"aria-label": `规则协议`,
modelValue: unref(L).protocol,
"onUpdate:modelValue": (r[20] ||= (e) =>
(unref(L).protocol = e)),
options: [
{
label: `TCP`,
value: `tcp`,
},
{
label: `UDP`,
value: `udp`,
},
{
label: `ICMP`,
value: `icmp`,
},
{
label: `全部`,
value: `all`,
},
],
optionLabel: `label`,
optionValue: `value`,
class: `w-100`,
},
null,
8,
[`modelValue`],
),
(r[45] ||= createElementVNode(
`label`,
null,
`端口范围`,
-1,
)),
createVNode(
l,
{
"aria-label": `端口范围`,
modelValue: unref(L).port,
"onUpdate:modelValue": (r[21] ||= (e) =>
(unref(L).port = e)),
placeholder: `端口 22 / 100-65535`,
class: `w-port`,
},
null,
8,
[`modelValue`],
),
(r[46] ||= createElementVNode(
`label`,
null,
`授权对象`,
-1,
)),
createVNode(
l,
{
"aria-label": `授权对象`,
modelValue: unref(L).ip,
"onUpdate:modelValue": (r[22] ||= (e) =>
(unref(L).ip = e)),
placeholder: `来源 IP`,
class: `w-160`,
},
null,
8,
[`modelValue`],
),
(r[47] ||= createElementVNode(
`label`,
null,
`描述`,
-1,
)),
createVNode(
l,
{
"aria-label": `规则描述`,
modelValue: unref(L).description,
"onUpdate:modelValue": (r[23] ||= (e) =>
(unref(L).description = e)),
placeholder: `描述(选填)`,
class: `grow`,
},
null,
8,
[`modelValue`],
),
]),
unref(T)
? (openBlock(),
createBlock(
o,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(T)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]),
_: 1,
},
8,
[`visible`, `header`],
),
createVNode(
ue,
{
visible: unref(Q),
"onUpdate:visible": (r[29] ||= (e) =>
isRef(Q) ? (Q.value = e) : null),
header: `快速添加`,
modal: ``,
draggable: !1,
style: {
width: `960px`,
},
},
{
footer: withCtx(() => [
createVNode(i, {
label: `取消`,
severity: `secondary`,
text: ``,
onClick: (r[28] ||= (e) => (Q.value = !1)),
}),
createVNode(
i,
{
label: `添加`,
loading: unref(Lt),
onClick: zt,
},
null,
8,
[`loading`],
),
]),
default: withCtx(() => [
createElementVNode(`div`, Qe, [
createVNode(
o,
{
severity: `info`,
closable: !1,
},
{
default: withCtx(() => [
...(r[48] ||= [
createTextVNode(
`请选择需要开放的服务。请勿添加重复规则,公网服务建议限制授权 IP 范围。`,
-1,
),
]),
]),
_: 1,
},
),
createElementVNode(`div`, $e, [
(openBlock(),
createElementBlock(
Fragment,
null,
renderList(ut, (t) =>
createElementVNode(
`label`,
{
key: t.port,
},
[
createVNode(
b,
{
modelValue: unref(k),
"onUpdate:modelValue": (r[26] ||= (e) =>
isRef(k) ? (k.value = e) : null),
value: t.port,
"input-id": `quick-port-${t.port}`,
onChange: dt,
},
null,
8,
[`modelValue`, `value`, `input-id`],
),
createElementVNode(
`span`,
null,
toDisplayString(t.name) +
` (` +
toDisplayString(t.port) +
`)`,
1,
),
],
),
),
64,
)),
]),
createElementVNode(`details`, et, [
(r[49] ||= createElementVNode(
`summary`,
null,
`高级规则编辑`,
-1,
)),
(r[50] ||= createElementVNode(
`p`,
{
class: `batch-hint`,
},
[
createTextVNode(` 每行一条规则,用空格分隔:`),
createElementVNode(
`code`,
null,
`方向 协议 端口 IP 描述`,
),
createElementVNode(`br`),
createTextVNode(` 方向 `),
createElementVNode(`code`, null, `in`),
createTextVNode(` 入站 / `),
createElementVNode(`code`, null, `out`),
createTextVNode(` 出站;协议支持 `),
createElementVNode(
`code`,
null,
`tcp udp icmp all`,
),
createTextVNode(` 等。 `),
],
-1,
)),
createVNode(
y,
{
modelValue: unref($),
"onUpdate:modelValue": (r[27] ||= (e) =>
isRef($) ? ($.value = e) : null),
rows: `8`,
placeholder: `in tcp 22 0.0.0.0/0 SSH
in tcp 80-443 0.0.0.0/0 Web`,
autoResize: ``,
fluid: ``,
},
null,
8,
[`modelValue`],
),
]),
unref(T)
? (openBlock(),
createBlock(
o,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(T)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
createVNode(
v,
{
value: ut.filter((e) => unref(k).includes(e.port)),
},
{
empty: withCtx(() => [
...(r[53] ||= [createTextVNode(`暂无数据`, -1)]),
]),
default: withCtx(() => [
createVNode(
u,
{
header: `方向`,
},
{
body: withCtx(() => [
createTextVNode(
toDisplayString(
unref(D) === `in` ? `入方向` : `出方向`,
),
1,
),
]),
_: 1,
},
),
createVNode(
u,
{
header: `授权策略`,
},
{
body: withCtx(() => [
...(r[51] ||= [
createTextVNode(`允许`, -1),
]),
]),
_: 1,
},
),
createVNode(
u,
{
header: `协议`,
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(
t.port === `69` ? `UDP` : `TCP`,
),
1,
),
]),
_: 1,
},
),
createVNode(u, {
field: `port`,
header: `端口范围`,
}),
createVNode(
u,
{
header: `授权对象`,
},
{
body: withCtx(() => [
...(r[52] ||= [
createTextVNode(
`所有 IPv4 (0.0.0.0/0)`,
-1,
),
]),
]),
_: 1,
},
),
]),
_: 1,
},
8,
[`value`],
),
]),
]),
_: 1,
},
8,
[`visible`],
),
])
);
}
);
},
}),
[[`__scopeId`, `data-v-fe145bae`]],
);
export {};
export default b;
</script>