feat: add 'Other sites' module for tracking server/plugin/support/SMS of miscellaneous sites

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 16:52:32 +03:30
co-authored by Cursor
parent bef5da6239
commit c5e989811a
15 changed files with 268 additions and 63 deletions
+20 -6
View File
@@ -8,12 +8,14 @@ import {
CustomerServiceOutlined,
TeamOutlined,
HomeOutlined,
GlobalOutlined,
LoginOutlined,
UserOutlined,
} from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import api from '@/api/http'
import { useAuthStore } from '@/stores/auth'
import { productLabel, type Product } from '@/utils/product'
type BalanceLevel = 'healthy' | 'warning' | 'critical' | 'unknown'
@@ -34,7 +36,7 @@ type ServerSummary = {
type Tenant = {
id: string
product: 'hamkar' | 'khanegi'
product: Product
slug?: string
nameFa?: string
serverAccountId?: string
@@ -50,7 +52,7 @@ type ExpiringPlugin = {
expiryLabel: string
tenant: {
id: string
product: 'hamkar' | 'khanegi'
product: Product
nameFa: string
}
}
@@ -68,6 +70,7 @@ const expiringSupports = ref<ExpiringSupport[]>([])
const hamkarCount = computed(() => tenants.value.filter((t) => t.product === 'hamkar').length)
const khanegiCount = computed(() => tenants.value.filter((t) => t.product === 'khanegi').length)
const othersCount = computed(() => tenants.value.filter((t) => t.product === 'other').length)
const lowestSmsTenants = computed(() => smsTenants.value.slice(0, 5))
const canSms = computed(() => auth.can('sms:read'))
@@ -77,6 +80,7 @@ const canSupport = computed(() => auth.can('support:read'))
const canAdmins = computed(() => auth.can('admins:read'))
const canHamkar = computed(() => auth.can('hamkar:read'))
const canKhanegi = computed(() => auth.can('khanegi:read'))
const canOthers = computed(() => auth.can('others:read'))
const modules = computed(() =>
[
@@ -110,6 +114,16 @@ const modules = computed(() =>
to: { name: 'product-versions' as const, params: { product: 'khanegi' } },
visible: canKhanegi.value,
},
{
key: 'others',
className: 'others',
title: 'سایت‌های دیگر',
desc: 'سایت‌های متفرقه؛ فقط مشخصات و پیگیری سرور، افزونه، پشتیبانی و پیامک',
meta: `${othersCount.value} سایت`,
icon: GlobalOutlined,
to: { name: 'product-versions' as const, params: { product: 'other' } },
visible: canOthers.value,
},
].filter((item) => item.visible),
)
@@ -118,7 +132,7 @@ async function load() {
try {
const tasks: Array<Promise<void>> = []
if (canHamkar.value || canKhanegi.value) {
if (canHamkar.value || canKhanegi.value || canOthers.value) {
tasks.push(
api.get('/tenants').then((res) => {
tenants.value = res.data
@@ -234,7 +248,7 @@ onMounted(load)
<div class="login-kicker">داشبورد کنترل</div>
<h1 class="page-title">سلام، خوش آمدید</h1>
<p class="page-sub">
از اینجا به پنل پیامکی، سرور، افزونه، پشتیبانی، مدیران و نسخه‌های همکار / خانگی دسترسی دارید.
از اینجا به پنل پیامکی، سرور، افزونه، پشتیبانی، مدیران، نسخه‌های همکار / خانگی و سایت‌های دیگر دسترسی دارید.
</p>
<a-spin :spinning="loading">
@@ -257,7 +271,7 @@ onMounted(load)
@click.stop="openSmsPanel(tenant.id)"
>
<strong>{{ tenant.nameFa }}</strong>
<span>{{ tenant.product === 'hamkar' ? 'همکار' : 'خانگی' }}</span>
<span>{{ productLabel(tenant.product) }}</span>
<small>{{ tenant.sms?.balanceLabel || 'نامشخص' }}</small>
</button>
<div v-if="!lowestSmsTenants.length" class="sms-mini-empty">
@@ -292,7 +306,7 @@ onMounted(load)
>
<strong>{{ tenant.nameFa }}</strong>
<span>
{{ tenant.product === 'hamkar' ? 'همکار' : 'خانگی' }}
{{ productLabel(tenant.product) }}
<template v-if="tenant.server?.serverName">
· {{ tenant.server.serverName }}
</template>