feat(admin): overlay system settings from SuperAdmin program-settings
Fetch /api/public/program-settings with 3-host retry and hide the local advanced-configuration editor in favor of SuperAdmin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
export type SuperAdminSettingRow = {
|
||||
id?: number
|
||||
key?: string
|
||||
value?: string | number | null
|
||||
title_fa?: string
|
||||
description?: string
|
||||
is_active?: boolean | number | string
|
||||
type?: string
|
||||
can_edit?: number | boolean | string
|
||||
}
|
||||
|
||||
const SUPERADMIN_BASES = [
|
||||
'https://adm.montzar.ir',
|
||||
'https://adm.montech.ir',
|
||||
'https://adm.montest.ir',
|
||||
] as const
|
||||
|
||||
const SA_RETRY_ROUNDS = 3
|
||||
const SA_PROGRAM_SETTINGS_PATH = '/api/public/program-settings'
|
||||
|
||||
function resolveTenantDomain() {
|
||||
const host = String(window.location.hostname || '').trim()
|
||||
if (!host || host === 'localhost' || host === '127.0.0.1') {
|
||||
return 'k.montzar.ir'
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
function parseSettingsPayload(data: unknown): SuperAdminSettingRow[] {
|
||||
if (Array.isArray(data)) return data as SuperAdminSettingRow[]
|
||||
if (data && typeof data === 'object') {
|
||||
const obj = data as Record<string, unknown>
|
||||
if (Array.isArray(obj.settings)) return obj.settings as SuperAdminSettingRow[]
|
||||
if (Array.isArray(obj.items)) return obj.items as SuperAdminSettingRow[]
|
||||
if (Array.isArray(obj.data)) return obj.data as SuperAdminSettingRow[]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
async function fetchFromUrl(url: string, timeout = 5000): Promise<SuperAdminSettingRow[]> {
|
||||
const controller = new AbortController()
|
||||
const timer = window.setTimeout(() => controller.abort(), timeout)
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
signal: controller.signal,
|
||||
headers: { Accept: 'application/json' },
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`)
|
||||
}
|
||||
const contentType = res.headers.get('content-type') || ''
|
||||
if (!contentType.includes('application/json')) {
|
||||
throw new Error('SuperAdmin response is not JSON')
|
||||
}
|
||||
return parseSettingsPayload(await res.json())
|
||||
} finally {
|
||||
window.clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
|
||||
/** تنظیمات پیشرفته نسخه از سوپرادمین — فقط GET /api/public/program-settings?domain= */
|
||||
export async function fetchSuperAdminProgramSettings(): Promise<SuperAdminSettingRow[]> {
|
||||
const domain = encodeURIComponent(resolveTenantDomain())
|
||||
let lastError: unknown
|
||||
for (let round = 1; round <= SA_RETRY_ROUNDS; round++) {
|
||||
for (const base of SUPERADMIN_BASES) {
|
||||
try {
|
||||
return await fetchFromUrl(`${base}${SA_PROGRAM_SETTINGS_PATH}?domain=${domain}`)
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
}
|
||||
}
|
||||
}
|
||||
console.warn('SuperAdmin program-settings failed after retries:', lastError)
|
||||
return []
|
||||
}
|
||||
+37
-21
@@ -18,8 +18,8 @@
|
||||
const VERSION_UPDATE_LOCK_KEY = 'version_update_lock';
|
||||
const VERSION_LAST_RELOAD_KEY = 'version_last_reload';
|
||||
const AUTH_TOKEN_KEY = 'peransa_token';
|
||||
const VERSION_RETRY_ROUNDS = 5;
|
||||
const CONFIG_RETRY_ROUNDS = 5;
|
||||
const VERSION_RETRY_ROUNDS = 3;
|
||||
const CONFIG_RETRY_ROUNDS = 3;
|
||||
const CONFIG_TIMEOUT = 12000;
|
||||
|
||||
let configTimeoutId;
|
||||
@@ -37,33 +37,50 @@
|
||||
return host;
|
||||
}
|
||||
|
||||
function isDevHost() {
|
||||
const host = String(window.location.hostname || '').trim();
|
||||
return host === 'localhost' || host === '127.0.0.1';
|
||||
}
|
||||
|
||||
function getVersionUrls() {
|
||||
const domain = encodeURIComponent(resolveTenantDomain());
|
||||
|
||||
return [
|
||||
const urls = [
|
||||
'https://adm.montzar.ir/get_version?domain=' + domain,
|
||||
'https://adm.montech.ir/get_version?domain=' + domain,
|
||||
'https://adm.montest.ir/get_version?domain=' + domain,
|
||||
'https://tablo.talasevin.ir/get_version',
|
||||
'https://abshode-service.montgit.ir/server/get_version',
|
||||
getProtocol() + '//212.23.201.173:8381/get_version',
|
||||
getProtocol() + '//91.212.174.137:4040/server/get_version'
|
||||
'https://adm.montest.ir/get_version?domain=' + domain
|
||||
];
|
||||
|
||||
if (isDevHost()) {
|
||||
urls.push(
|
||||
'https://tablo.talasevin.ir/get_version',
|
||||
'https://abshode-service.montgit.ir/server/get_version',
|
||||
getProtocol() + '//212.23.201.173:8381/get_version',
|
||||
getProtocol() + '//91.212.174.137:4040/server/get_version'
|
||||
);
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
function getConfigUrls() {
|
||||
const protocol = getProtocol();
|
||||
const domain = encodeURIComponent(resolveTenantDomain());
|
||||
|
||||
return [
|
||||
const urls = [
|
||||
'https://adm.montzar.ir/get_user?domain=' + domain,
|
||||
'https://adm.montech.ir/get_user?domain=' + domain,
|
||||
'https://adm.montest.ir/get_user?domain=' + domain,
|
||||
protocol + '//tablo.talasevin.ir/get_user',
|
||||
protocol + '//abshode-service.montgit.ir/server/get_user',
|
||||
protocol + '//212.23.201.173:8381/get_user',
|
||||
protocol + '//91.212.174.137:4040/server/get_user'
|
||||
'https://adm.montest.ir/get_user?domain=' + domain
|
||||
];
|
||||
|
||||
if (isDevHost()) {
|
||||
urls.push(
|
||||
protocol + '//tablo.talasevin.ir/get_user',
|
||||
protocol + '//abshode-service.montgit.ir/server/get_user',
|
||||
protocol + '//212.23.201.173:8381/get_user',
|
||||
protocol + '//91.212.174.137:4040/server/get_user'
|
||||
);
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
function sortUrls(urls, successKey) {
|
||||
@@ -367,8 +384,9 @@
|
||||
'<div style="min-height:100vh;display:flex;align-items:center;justify-content:center;direction:rtl;font-family:sans-serif;padding:24px;background:#fff;color:#111827;">' +
|
||||
'<div style="width:min(420px,100%);text-align:center;border:1px solid rgba(15,23,42,0.08);border-radius:24px;padding:28px 22px;">' +
|
||||
'<div style="font-size:42px;margin-bottom:12px;">⚠️</div>' +
|
||||
'<h2 style="margin:0 0 20px;font-size:20px;">خطا در برقراری ارتباط با سرور</h2>' +
|
||||
'<button type="button" onclick="location.reload()" style="padding:11px 22px;border:0;border-radius:12px;background:#2563eb;color:#fff;cursor:pointer;font-weight:700;">تلاش مجدد</button>' +
|
||||
'<h2 style="margin:0 0 12px;font-size:20px;">خطا در برقراری ارتباط با سرور</h2>' +
|
||||
'<p style="margin:0 0 20px;color:rgba(15,23,42,0.62);line-height:2;font-size:14px;">برقراری ارتباط با سرور امکانپذیر نیست. لطفاً اتصال اینترنت خود را بررسی کرده و صفحه را تازهسازی نمایید.</p>' +
|
||||
'<button type="button" onclick="location.reload()" style="padding:11px 22px;border:0;border-radius:12px;background:#2563eb;color:#fff;cursor:pointer;font-weight:700;">تازهسازی صفحه</button>' +
|
||||
'</div></div>';
|
||||
}
|
||||
|
||||
@@ -477,9 +495,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
window.__APP_CONFIG__ = {};
|
||||
window.__CONFIG_READY__ = true;
|
||||
finishConfigLoad({}, error);
|
||||
showServerUnavailable();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { isDeployAdmin } from '@/utils/deployAdmin'
|
||||
import { hasSmsPanelAccess } from '@/utils/smsPanelAccess'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { bannerEnabledSetting, trackingSystemsEnabledSetting, appNotificationEnabledSetting } from '@/utils/config'
|
||||
@@ -60,22 +59,10 @@ const Setting: RouteRecordRaw = {
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'advancedConfiguration',
|
||||
name: 'advancedConfigurationRedirect',
|
||||
path: 'advanced-configuration',
|
||||
component: () => import('@/pages/menuPages/configuration/index.vue'),
|
||||
props: { mode: 'system' },
|
||||
meta: {
|
||||
deployAdminOnly: true,
|
||||
icon: 'vuesax-linear:code-circle',
|
||||
title: 'تنظیمات پیشرفته',
|
||||
order: 3
|
||||
},
|
||||
beforeEnter() {
|
||||
const userStore = useUserStore()
|
||||
if (!isDeployAdmin(userStore.userInfo?.mobile)) {
|
||||
return { name: 'configuration' }
|
||||
}
|
||||
}
|
||||
redirect: { name: 'configuration' },
|
||||
meta: { hideMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'smsPanelMain',
|
||||
|
||||
+17
-2
@@ -332,9 +332,24 @@ async function bootstrapSettingsFromApi(): Promise<void> {
|
||||
|
||||
try {
|
||||
const { getConfiguration } = await import('@/api/configuration')
|
||||
const { fetchSuperAdminProgramSettings } = await import('@/api/superadminSettings')
|
||||
const { parseMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
||||
const rows = await getConfiguration()
|
||||
const list = Array.isArray(rows) ? rows : []
|
||||
const [localRows, saRows] = await Promise.all([
|
||||
getConfiguration().catch(() => []),
|
||||
fetchSuperAdminProgramSettings(),
|
||||
])
|
||||
const local = Array.isArray(localRows) ? localRows : []
|
||||
const merged = [...local]
|
||||
for (const item of saRows) {
|
||||
if (!item?.key) continue
|
||||
const index = merged.findIndex((row) => row.key === item.key)
|
||||
if (index >= 0) {
|
||||
merged[index] = { ...merged[index], ...item }
|
||||
} else {
|
||||
merged.push(item as (typeof merged)[number])
|
||||
}
|
||||
}
|
||||
const list = merged
|
||||
const isStoreRow = list.find((item) => item.key === 'is_store')
|
||||
const mainWalletRow = list.find((item) => item.key === 'main_wallet_products')
|
||||
const mainMetalRow = list.find((item) => item.key === 'main_metal')
|
||||
|
||||
Reference in New Issue
Block a user