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_UPDATE_LOCK_KEY = 'version_update_lock';
|
||||||
const VERSION_LAST_RELOAD_KEY = 'version_last_reload';
|
const VERSION_LAST_RELOAD_KEY = 'version_last_reload';
|
||||||
const AUTH_TOKEN_KEY = 'peransa_token';
|
const AUTH_TOKEN_KEY = 'peransa_token';
|
||||||
const VERSION_RETRY_ROUNDS = 5;
|
const VERSION_RETRY_ROUNDS = 3;
|
||||||
const CONFIG_RETRY_ROUNDS = 5;
|
const CONFIG_RETRY_ROUNDS = 3;
|
||||||
const CONFIG_TIMEOUT = 12000;
|
const CONFIG_TIMEOUT = 12000;
|
||||||
|
|
||||||
let configTimeoutId;
|
let configTimeoutId;
|
||||||
@@ -37,33 +37,50 @@
|
|||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDevHost() {
|
||||||
|
const host = String(window.location.hostname || '').trim();
|
||||||
|
return host === 'localhost' || host === '127.0.0.1';
|
||||||
|
}
|
||||||
|
|
||||||
function getVersionUrls() {
|
function getVersionUrls() {
|
||||||
const domain = encodeURIComponent(resolveTenantDomain());
|
const domain = encodeURIComponent(resolveTenantDomain());
|
||||||
|
const urls = [
|
||||||
return [
|
|
||||||
'https://adm.montzar.ir/get_version?domain=' + domain,
|
'https://adm.montzar.ir/get_version?domain=' + domain,
|
||||||
'https://adm.montech.ir/get_version?domain=' + domain,
|
'https://adm.montech.ir/get_version?domain=' + domain,
|
||||||
'https://adm.montest.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'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
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() {
|
function getConfigUrls() {
|
||||||
const protocol = getProtocol();
|
const protocol = getProtocol();
|
||||||
const domain = encodeURIComponent(resolveTenantDomain());
|
const domain = encodeURIComponent(resolveTenantDomain());
|
||||||
|
const urls = [
|
||||||
return [
|
|
||||||
'https://adm.montzar.ir/get_user?domain=' + domain,
|
'https://adm.montzar.ir/get_user?domain=' + domain,
|
||||||
'https://adm.montech.ir/get_user?domain=' + domain,
|
'https://adm.montech.ir/get_user?domain=' + domain,
|
||||||
'https://adm.montest.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'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
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) {
|
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="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="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>' +
|
'<div style="font-size:42px;margin-bottom:12px;">⚠️</div>' +
|
||||||
'<h2 style="margin:0 0 20px;font-size:20px;">خطا در برقراری ارتباط با سرور</h2>' +
|
'<h2 style="margin:0 0 12px;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>' +
|
'<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>';
|
'</div></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,9 +495,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__APP_CONFIG__ = {};
|
showServerUnavailable();
|
||||||
window.__CONFIG_READY__ = true;
|
|
||||||
finishConfigLoad({}, error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { isDeployAdmin } from '@/utils/deployAdmin'
|
|
||||||
import { hasSmsPanelAccess } from '@/utils/smsPanelAccess'
|
import { hasSmsPanelAccess } from '@/utils/smsPanelAccess'
|
||||||
import { useUserStore } from '@/store/userStore'
|
import { useUserStore } from '@/store/userStore'
|
||||||
import { bannerEnabledSetting, trackingSystemsEnabledSetting, appNotificationEnabledSetting } from '@/utils/config'
|
import { bannerEnabledSetting, trackingSystemsEnabledSetting, appNotificationEnabledSetting } from '@/utils/config'
|
||||||
@@ -60,22 +59,10 @@ const Setting: RouteRecordRaw = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'advancedConfiguration',
|
name: 'advancedConfigurationRedirect',
|
||||||
path: 'advanced-configuration',
|
path: 'advanced-configuration',
|
||||||
component: () => import('@/pages/menuPages/configuration/index.vue'),
|
redirect: { name: 'configuration' },
|
||||||
props: { mode: 'system' },
|
meta: { hideMenu: true }
|
||||||
meta: {
|
|
||||||
deployAdminOnly: true,
|
|
||||||
icon: 'vuesax-linear:code-circle',
|
|
||||||
title: 'تنظیمات پیشرفته',
|
|
||||||
order: 3
|
|
||||||
},
|
|
||||||
beforeEnter() {
|
|
||||||
const userStore = useUserStore()
|
|
||||||
if (!isDeployAdmin(userStore.userInfo?.mobile)) {
|
|
||||||
return { name: 'configuration' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'smsPanelMain',
|
name: 'smsPanelMain',
|
||||||
|
|||||||
+17
-2
@@ -332,9 +332,24 @@ async function bootstrapSettingsFromApi(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { getConfiguration } = await import('@/api/configuration')
|
const { getConfiguration } = await import('@/api/configuration')
|
||||||
|
const { fetchSuperAdminProgramSettings } = await import('@/api/superadminSettings')
|
||||||
const { parseMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
const { parseMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
||||||
const rows = await getConfiguration()
|
const [localRows, saRows] = await Promise.all([
|
||||||
const list = Array.isArray(rows) ? rows : []
|
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 isStoreRow = list.find((item) => item.key === 'is_store')
|
||||||
const mainWalletRow = list.find((item) => item.key === 'main_wallet_products')
|
const mainWalletRow = list.find((item) => item.key === 'main_wallet_products')
|
||||||
const mainMetalRow = list.find((item) => item.key === 'main_metal')
|
const mainMetalRow = list.find((item) => item.key === 'main_metal')
|
||||||
|
|||||||
Reference in New Issue
Block a user