feat: UI switch for server access kill switch
Add SuperAdmin toggle to cut app/admin access for a sold version with push status feedback. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -54,12 +54,15 @@ const tenant = ref<{
|
|||||||
slug: string
|
slug: string
|
||||||
nameFa: string
|
nameFa: string
|
||||||
domain: string
|
domain: string
|
||||||
|
serverAccessBlocked?: boolean
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const servers = ref<ServerRow[]>([])
|
const servers = ref<ServerRow[]>([])
|
||||||
const selectedId = ref<string | null>(null)
|
const selectedId = ref<string | null>(null)
|
||||||
const summary = ref<Summary | null>(null)
|
const summary = ref<Summary | null>(null)
|
||||||
const charges = ref<any[]>([])
|
const charges = ref<any[]>([])
|
||||||
const planOptions = ref<{ months: number; label: string }[]>([])
|
const planOptions = ref<{ months: number; label: string }[]>([])
|
||||||
|
const accessBlocking = ref(false)
|
||||||
|
const serverAccessBlocked = ref(false)
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
isDedicated: false,
|
isDedicated: false,
|
||||||
@@ -140,6 +143,7 @@ async function loadDetail() {
|
|||||||
try {
|
try {
|
||||||
const { data } = await api.get(`/servers/tenants/${tenantId.value}`)
|
const { data } = await api.get(`/servers/tenants/${tenantId.value}`)
|
||||||
tenant.value = data.tenant
|
tenant.value = data.tenant
|
||||||
|
serverAccessBlocked.value = Boolean(data.tenant?.serverAccessBlocked)
|
||||||
servers.value = data.servers || []
|
servers.value = data.servers || []
|
||||||
planOptions.value = data.planOptions || []
|
planOptions.value = data.planOptions || []
|
||||||
|
|
||||||
@@ -272,6 +276,48 @@ async function renewServer() {
|
|||||||
|
|
||||||
watch(() => route.params.tenantId, loadDetail)
|
watch(() => route.params.tenantId, loadDetail)
|
||||||
onMounted(loadDetail)
|
onMounted(loadDetail)
|
||||||
|
|
||||||
|
async function onServerAccessToggle(checked: boolean) {
|
||||||
|
if (!canWrite.value || !tenantId.value) return
|
||||||
|
accessBlocking.value = true
|
||||||
|
const previous = serverAccessBlocked.value
|
||||||
|
serverAccessBlocked.value = checked
|
||||||
|
try {
|
||||||
|
const { data } = await api.post(`/servers/tenants/${tenantId.value}/server-access`, {
|
||||||
|
blocked: checked,
|
||||||
|
})
|
||||||
|
serverAccessBlocked.value = Boolean(data?.tenant?.serverAccessBlocked)
|
||||||
|
if (tenant.value) {
|
||||||
|
tenant.value = {
|
||||||
|
...tenant.value,
|
||||||
|
serverAccessBlocked: serverAccessBlocked.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data?.push?.ok === false) {
|
||||||
|
message.warning(
|
||||||
|
data.push.message ||
|
||||||
|
'قفل در رجیستری ثبت شد ولی push فوری به سرور نسخه ناموفق بود',
|
||||||
|
)
|
||||||
|
} else if (data?.push?.stub) {
|
||||||
|
message.warning(
|
||||||
|
data.push.message ||
|
||||||
|
'قفل ثبت شد؛ remoteApiToken نیست — push فوری انجام نشد',
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
message.success(
|
||||||
|
serverAccessBlocked.value
|
||||||
|
? 'دسترسی اپ/ادمین قطع شد'
|
||||||
|
: 'دسترسی اپ/ادمین وصل شد',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
serverAccessBlocked.value = previous
|
||||||
|
const msg = e?.response?.data?.message
|
||||||
|
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'تغییر دسترسی ناموفق')
|
||||||
|
} finally {
|
||||||
|
accessBlocking.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -292,6 +338,26 @@ onMounted(loadDetail)
|
|||||||
<TenantPanelShortcuts :tenant-id="tenantId" current="server" />
|
<TenantPanelShortcuts :tenant-id="tenantId" current="server" />
|
||||||
|
|
||||||
<a-spin :spinning="loading">
|
<a-spin :spinning="loading">
|
||||||
|
<section class="form-section access-kill">
|
||||||
|
<div class="access-kill-row" :class="{ blocked: serverAccessBlocked }">
|
||||||
|
<div>
|
||||||
|
<h3>قطع دسترسی اپ و ادمین</h3>
|
||||||
|
<p class="field-hint">
|
||||||
|
سرور فیزیکی خاموش نمیشود؛ فقط اپ و ادمین این نسخه مثل قطعی سرور از کار میافتند.
|
||||||
|
نسخههای همسرور دیگر تحت تأثیر نیستند.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a-switch
|
||||||
|
:checked="serverAccessBlocked"
|
||||||
|
:loading="accessBlocking"
|
||||||
|
:disabled="!canWrite"
|
||||||
|
checked-children="قطع"
|
||||||
|
un-checked-children="وصل"
|
||||||
|
@change="onServerAccessToggle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="form-section server-picker">
|
<section class="form-section server-picker">
|
||||||
<div class="picker-head">
|
<div class="picker-head">
|
||||||
<h3>سرورهای این نسخه</h3>
|
<h3>سرورهای این نسخه</h3>
|
||||||
@@ -465,6 +531,32 @@ onMounted(loadDetail)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.access-kill {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.access-kill-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.access-kill-row.blocked {
|
||||||
|
background: #fef2f2;
|
||||||
|
border-color: #fecaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.access-kill-row h3 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.server-picker {
|
.server-picker {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user