mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-29 06:27:14 +00:00
fix(frontend): Phase 9 — restore index dashboard, fix login/CSRF, port legacy styles
- Index dashboard regains the 8 cards that were lost in the SPA port (3X-UI panel info, Operation Hours, System Load, Usage, Overall Speed, Total Data, IP Addresses, Connection Stats), plus a Config button that shows the live xray config.json. Version display falls back through panelUpdateInfo → window.__X_UI_CUR_VER__ → '?' so dev mode isn't blank. - Xray config no longer hangs on load: useXraySetting surfaces failures instead of leaving a perpetual spinner, and the Vite dev proxy stops hijacking POST requests to migrated routes (only GETs get bypassed). - Inbound page no longer throws __asyncLoader/emitsOptions errors — inbound.js was missing imports (NumberFormatter, SizeFormatter, Wireguard) and InboundList kept emitting after unmount. - Login round-trip works after logout: a public /csrf-token endpoint bootstraps the SPA before authentication, axios caches the token module-level, and the dev 401 handler navigates to /login.html instead of reloading the dashboard into a redirect loop. - legacy.css mirrors the legacy panel's surface/text variables so dark and ultra-dark themes match main; every SPA entry imports it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -134,34 +134,26 @@ watch(() => props.active, (next) => { if (next) loadList(); }, { immediate: true
|
||||
|
||||
<template>
|
||||
<div class="custom-geo-section">
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
class="mb-10"
|
||||
:message="t('pages.index.customGeoRoutingHint')"
|
||||
/>
|
||||
<a-alert type="info" show-icon class="mb-10" :message="t('pages.index.customGeoRoutingHint')" />
|
||||
|
||||
<div class="toolbar">
|
||||
<a-button type="primary" :loading="loading" @click="openAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
{{ t('pages.index.customGeoAdd') }}
|
||||
</a-button>
|
||||
<a-button :loading="updatingAll" :disabled="!list.length" @click="updateAll">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
{{ t('pages.index.geofilesUpdateAll') }}
|
||||
</a-button>
|
||||
<span v-if="list.length" class="custom-geo-count">{{ list.length }}</span>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="list"
|
||||
:pagination="false"
|
||||
:row-key="(r) => r.id"
|
||||
:loading="loading"
|
||||
size="small"
|
||||
:scroll="{ x: 760 }"
|
||||
>
|
||||
<a-table :columns="columns" :data-source="list" :pagination="false" :row-key="(r) => r.id" :loading="loading"
|
||||
size="small" :scroll="{ x: 760 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'alias'">
|
||||
<div class="custom-geo-alias-cell">
|
||||
@@ -199,22 +191,23 @@ watch(() => props.active, (next) => { if (next) loadList(); }, { immediate: true
|
||||
<a-space size="small">
|
||||
<a-tooltip :title="t('pages.index.customGeoEdit')">
|
||||
<a-button type="link" size="small" @click="openEdit(record)">
|
||||
<template #icon><EditOutlined /></template>
|
||||
<template #icon>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('pages.index.customGeoDownload')">
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
:loading="actionId === record.id"
|
||||
@click="downloadOne(record.id)"
|
||||
>
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<a-button type="link" size="small" :loading="actionId === record.id" @click="downloadOne(record.id)">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('pages.index.customGeoDelete')">
|
||||
<a-button type="link" size="small" danger @click="confirmDelete(record)">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
@@ -229,16 +222,14 @@ watch(() => props.active, (next) => { if (next) loadList(); }, { immediate: true
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<CustomGeoFormModal
|
||||
v-model:open="formOpen"
|
||||
:record="editingRecord"
|
||||
@saved="loadList"
|
||||
/>
|
||||
<CustomGeoFormModal v-model:open="formOpen" :record="editingRecord" @saved="loadList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mb-10 { margin-bottom: 10px; }
|
||||
.mb-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
@@ -256,6 +247,7 @@ watch(() => props.active, (next) => { if (next) loadList(); }, { immediate: true
|
||||
font-size: 12px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
:global(body.dark) .custom-geo-count {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
@@ -265,10 +257,12 @@ watch(() => props.active, (next) => { if (next) loadList(); }, { immediate: true
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.custom-geo-alias {
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.custom-geo-type-tag {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -286,12 +280,15 @@ watch(() => props.active, (next) => { if (next) loadList(); }, { immediate: true
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.custom-geo-copyable:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:global(body.dark) .custom-geo-ext-code {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
:global(body.dark) .custom-geo-copyable:hover {
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
@@ -305,6 +302,7 @@ watch(() => props.active, (next) => { if (next) loadList(); }, { immediate: true
|
||||
padding: 18px 0;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.custom-geo-empty-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
Reference in New Issue
Block a user