feat(panel): add 'Edit' button to tables and enhance layout (#4355)

- Move 'Edit' button from dropdown to the table since it's the most used action. Only for desktop.
- Increase column widths for action keys in Inbounds, Balancers, Outbounds and Routing tables.
- Slightly enhance layout for consistency.
This commit is contained in:
Black
2026-05-14 04:55:00 +05:00
committed by GitHub
parent 26accfd8f7
commit 194de8869e
5 changed files with 208 additions and 125 deletions
+21 -5
View File
@@ -230,7 +230,7 @@ const hasAnyRemark = computed(() =>
const desktopColumns = computed(() => {
const cols = [
sortableCol({ title: 'ID', dataIndex: 'id', key: 'id', align: 'right', width: 30 }, 'id'),
{ title: t('pages.inbounds.operate'), key: 'action', align: 'center', width: 30 },
{ title: t('pages.inbounds.operate'), key: 'action', align: 'center', width: 60 },
sortableCol({ title: t('pages.inbounds.enable'), key: 'enable', align: 'center', width: 35 }, 'enable'),
];
if (hasAnyRemark.value) {
@@ -571,13 +571,21 @@ function showQrCodeMenu(dbInbound) {
<template #bodyCell="{ column, record }">
<!-- ============== Action dropdown ============== -->
<template v-if="column.key === 'action'">
<div class="action-buttons">
<a-button type="text" size="small" @click.prevent="emit('row-action', {key: 'edit', dbInbound: record})">
<template #icon>
<EditOutlined />
</template>
</a-button>
<a-dropdown :trigger="['click']">
<MoreOutlined class="row-action-trigger" @click.prevent />
<a-button type="text" size="small" @click.prevent>
<template #icon>
<MoreOutlined />
</template>
</a-button>
<template #overlay>
<a-menu @click="(a) => emit('row-action', { key: a.key, dbInbound: record })">
<a-menu-item key="edit">
<EditOutlined /> {{ t('edit') }}
</a-menu-item>
<a-menu-item v-if="showQrCodeMenu(record)" key="qrcode">
<QrcodeOutlined /> {{ t('qrCode') }}
</a-menu-item>
@@ -624,6 +632,7 @@ function showQrCodeMenu(dbInbound) {
</a-menu>
</template>
</a-dropdown>
</div>
</template>
<!-- ============== Enable switch (desktop) ============== -->
@@ -764,6 +773,13 @@ function showQrCodeMenu(dbInbound) {
margin-bottom: 4px;
}
.action-buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.protocol-tags {
display: inline-flex;
flex-wrap: wrap;
+33 -7
View File
@@ -22,6 +22,7 @@ const { t } = useI18n();
const props = defineProps({
templateSettings: { type: Object, default: null },
clientReverseTags: { type: Array, default: () => [] },
isMobile: { type: Boolean, default: false },
});
const STRATEGY_LABELS = {
@@ -197,7 +198,7 @@ function confirmDelete(idx) {
}
const columns = computed(() => [
{ title: '#', key: 'action', align: 'center', width: 80 },
{ title: '#', key: 'action', align: 'center', width: 100 },
{ title: 'Tag', dataIndex: 'tag', key: 'tag', align: 'center', width: 160 },
{ title: 'Strategy', key: 'strategy', align: 'center', width: 140 },
{ title: 'Selector', key: 'selector', align: 'center' },
@@ -267,17 +268,29 @@ const obsText = computed({
{{ t('pages.xray.Balancers') }}
</a-button>
<a-table :columns="columns" :data-source="rows" :row-key="(r) => r.key" :pagination="false" size="small" bordered>
<a-table :columns="columns" :data-source="rows" :row-key="(r) => r.key" :pagination="false"
size="small" :scroll="{ x: 400 }">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'action'">
<div class="action-cell">
<span class="row-index">{{ index + 1 }}</span>
<div :class="!isMobile ? 'action-buttons' : ''">
<a-button v-if="!isMobile" shape="circle" size="small" @click="openEdit(index)">
<template #icon>
<EditOutlined />
</template>
</a-button>
<a-dropdown :trigger="['click']">
<a-button shape="circle" size="small" class="action-btn">
<a-button shape="circle" size="small">
<template #icon>
<MoreOutlined />
</template>
</a-button>
<template #overlay>
<a-menu>
<a-menu-item @click="openEdit(index)">
<a-menu-item v-if="isMobile" @click="openEdit(index)">
<EditOutlined /> {{ t('edit') }}
</a-menu-item>
<a-menu-item class="danger" @click="confirmDelete(index)">
@@ -286,6 +299,8 @@ const obsText = computed({
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</template>
<template v-else-if="column.key === 'strategy'">
@@ -316,14 +331,25 @@ const obsText = computed({
</template>
<style scoped>
.action-cell {
display: flex;
align-items: center;
gap: 6px;
}
.row-index {
font-weight: 500;
opacity: 0.7;
margin-right: 6px;
min-width: 18px;
text-align: right;
}
.action-btn {
vertical-align: middle;
.action-buttons {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
margin-left: auto;
}
.danger {
+27 -6
View File
@@ -157,9 +157,9 @@ function hasBreakdown(r) {
// === Columns ========================================================
// Computed so titles re-render after a locale swap.
const columns = computed(() => [
{ title: '#', key: 'action', align: 'center', width: 70 },
{ title: 'Tag', key: 'identity', align: 'left', width: 220 },
{ title: t('pages.inbounds.address'), key: 'address', align: 'left', width: 230 },
{ title: '#', key: 'action', align: 'center', width: 100 },
{ title: 'Tag', key: 'identity', align: 'left' },
{ title: t('pages.inbounds.address'), key: 'address', align: 'left' },
{ title: t('pages.inbounds.traffic'), key: 'traffic', align: 'left', width: 200 },
{ title: t('pages.xray.latency') !== 'pages.xray.latency' ? t('pages.xray.latency') : 'Latency', key: 'testResult', align: 'left', width: 140 },
{ title: t('check'), key: 'test', align: 'center', width: 80 },
@@ -322,18 +322,25 @@ const rows = computed(() => {
<template v-if="column.key === 'action'">
<div class="action-cell">
<span class="row-index">{{ index + 1 }}</span>
<div class="action-buttons">
<a-button shape="circle" size="small" @click="openEdit(index)">
<template #icon>
<EditOutlined />
</template>
</a-button>
<a-dropdown :trigger="['click']">
<a-button shape="circle" size="small">
<template #icon>
<MoreOutlined />
</template>
</a-button>
<template #overlay>
<a-menu>
<a-menu-item v-if="index > 0" @click="setFirst(index)">
<VerticalAlignTopOutlined /> Move to top
</a-menu-item>
<a-menu-item @click="openEdit(index)">
<EditOutlined /> Edit
</a-menu-item>
<a-menu-item :disabled="index === 0" @click="moveUp(index)">
<ArrowUpOutlined />
</a-menu-item>
@@ -350,6 +357,7 @@ const rows = computed(() => {
</template>
</a-dropdown>
</div>
</div>
</template>
<template v-else-if="column.key === 'identity'">
@@ -444,6 +452,11 @@ const rows = computed(() => {
justify-content: flex-end;
}
.toolbar-right :global(.ant-space),
.header-actions :global(.ant-space) {
margin-bottom: 0 !important;
}
.card-empty {
text-align: center;
opacity: 0.4;
@@ -526,6 +539,14 @@ const rows = computed(() => {
text-align: right;
}
.action-buttons {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
margin-left: auto;
}
.identity-cell {
display: flex;
flex-direction: column;
+21 -2
View File
@@ -220,7 +220,7 @@ function rowProps(_record, index) {
// === Columns =========================================================
// Computed so titles re-render after a locale swap.
const desktopColumns = computed(() => [
{ title: '#', align: 'center', width: 70, key: 'action' },
{ title: '#', align: 'center', width: 100, key: 'action' },
{ title: 'Source', align: 'left', width: 180, key: 'source' },
{ title: t('pages.inbounds.network'), align: 'left', width: 180, key: 'network' },
{ title: 'Destination', align: 'left', key: 'destination' },
@@ -340,13 +340,23 @@ function chipPreview(value) {
<HolderOutlined class="drag-handle" :title="t('drag') || 'Drag to reorder'"
@pointerdown="onHandlePointerDown(index, $event)" />
<span class="row-index">{{ index + 1 }}</span>
<div :class="!isMobile ? 'action-buttons' : ''">
<a-button v-if="!isMobile" shape="circle" size="small" @click="openEdit(index)">
<template #icon>
<EditOutlined />
</template>
</a-button>
<a-dropdown :trigger="['click']">
<a-button shape="circle" size="small">
<template #icon>
<MoreOutlined />
</template>
</a-button>
<template #overlay>
<a-menu>
<a-menu-item @click="openEdit(index)">
<a-menu-item v-if="isMobile" @click="openEdit(index)">
<EditOutlined /> {{ t('edit') }}
</a-menu-item>
<a-menu-item :disabled="index === 0" @click="moveUp(index)">
@@ -362,6 +372,7 @@ function chipPreview(value) {
</template>
</a-dropdown>
</div>
</div>
</template>
<!-- ============== Source ============== -->
@@ -550,6 +561,14 @@ function chipPreview(value) {
text-align: right;
}
.action-buttons {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
margin-left: auto;
}
.criterion-flow {
display: flex;
flex-direction: column;
+2 -1
View File
@@ -349,7 +349,8 @@ onBeforeUnmount(() => {
</a-tooltip>
<span v-if="!isMobile">{{ t('pages.xray.Balancers') }}</span>
</template>
<BalancersTab :template-settings="templateSettings" :client-reverse-tags="clientReverseTags" />
<BalancersTab :template-settings="templateSettings"
:client-reverse-tags="clientReverseTags" :is-mobile="isMobile" />
</a-tab-pane>
<a-tab-pane key="tpl-dns" class="tab-pane">