diff --git a/index.html b/index.html index 8dd3789..82cd2ef 100644 --- a/index.html +++ b/index.html @@ -341,34 +341,47 @@ ] } + function isDevHost() { + var host = String(window.location.hostname || '').trim() + return host === 'localhost' || host === '127.0.0.1' + } + function getVersionUrls() { - var protocol = getProtocol() var domain = encodeURIComponent(resolveTenantDomain()) var urls = getSuperAdminBases().map(function (base) { return base + '/get_version?domain=' + domain }) - return urls.concat([ - 'https://tablo.talasevin.ir/get_version', - 'https://abshode-service.montgit.ir/server/get_version', - protocol + '//212.23.201.173:8381/get_version', - protocol + '//91.212.174.137:4040/server/get_version' - ]) + if (isDevHost()) { + var protocol = getProtocol() + urls = urls.concat([ + 'https://tablo.talasevin.ir/get_version', + 'https://abshode-service.montgit.ir/server/get_version', + protocol + '//212.23.201.173:8381/get_version', + protocol + '//91.212.174.137:4040/server/get_version' + ]) + } + + return urls } function getConfigUrls() { - var protocol = getProtocol() var domain = encodeURIComponent(resolveTenantDomain()) var urls = getSuperAdminBases().map(function (base) { return base + '/get_user?domain=' + domain }) - return urls.concat([ - 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()) { + var protocol = getProtocol() + urls = urls.concat([ + 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 sortVersionUrls(urls) { @@ -646,10 +659,10 @@ '
⚠️
' + '

خطا در بارگذاری برنامه

' + '

' + - 'ارتباط با سرور برقرار نشد یا فایل‌های برنامه به‌درستی دریافت نشدند.' + + 'برقراری ارتباط با سرور امکان‌پذیر نیست. لطفاً اتصال اینترنت خود را بررسی کرده و صفحه را تازه‌سازی نمایید.' + '

' + '' + ' diff --git a/src/config/load-config.ts b/src/config/load-config.ts index 340c037..e5f47d3 100644 --- a/src/config/load-config.ts +++ b/src/config/load-config.ts @@ -1,44 +1,49 @@ import type { AppConfig } from '@/config/types' import { normalizeAppConfig } from '@/config/app-config' import { readCachedAppConfig, writeCachedAppConfig } from '@/config/config-cache' +import { + allowSuperAdminFallback, + getSuperAdminBase, + getSuperAdminBases, + isSuperAdminUrl, + resolveTenantDomain, + SA_RETRY_ROUNDS, +} from '@/config/superadmin' const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL' -export const CONFIG_RETRY_ROUNDS = 5 - -const SUPERADMIN_BASES = [ - 'https://adm.montzar.ir', - 'https://adm.montech.ir', - 'https://adm.montest.ir', -] as const +export const CONFIG_RETRY_ROUNDS = SA_RETRY_ROUNDS function getProtocol() { return window.location.protocol === 'https:' ? 'https:' : 'http:' } -function resolveTenantDomain() { - const host = String(window.location.hostname || '').trim() - if (!host || host === 'localhost' || host === '127.0.0.1') { - return 'k.montzar.ir' - } - return host -} - export function getConfigUrls() { const protocol = getProtocol() const domain = encodeURIComponent(resolveTenantDomain()) + const urls = getSuperAdminBases().map((base) => `${base}/get_user?domain=${domain}`) - return [ - ...SUPERADMIN_BASES.map((base) => `${base}/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 (allowSuperAdminFallback()) { + 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 } export function sortConfigUrls(urls: string[]) { const lastSuccessUrl = localStorage.getItem(CONFIG_SUCCESS_URL_KEY) - if (!lastSuccessUrl || !urls.includes(lastSuccessUrl)) { + if ( + !lastSuccessUrl || + !urls.includes(lastSuccessUrl) || + !isSuperAdminUrl(lastSuccessUrl) + ) { + if (lastSuccessUrl && !isSuperAdminUrl(lastSuccessUrl)) { + localStorage.removeItem(CONFIG_SUCCESS_URL_KEY) + } return [...urls] } return [lastSuccessUrl, ...urls.filter((url) => url !== lastSuccessUrl)] @@ -91,7 +96,11 @@ export async function fetchFreshAppConfig(rounds = CONFIG_RETRY_ROUNDS): Promise const timeout = index === urls.length - 1 ? 7000 : 4000 const config = await fetchConfig(url, timeout) - localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url) + if (isSuperAdminUrl(url)) { + localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url) + } else { + clearStaleConfigSuccessUrl(url) + } writeCachedAppConfig(config) return config @@ -102,7 +111,11 @@ export async function fetchFreshAppConfig(rounds = CONFIG_RETRY_ROUNDS): Promise } } - throw new Error('Cannot load app config from any server') + throw new Error( + getSuperAdminBase() + ? `Cannot load from SuperAdmin (${getSuperAdminBase()}).` + : 'Cannot load app config from any server', + ) } export async function loadAppConfig(): Promise { diff --git a/src/config/load-version.ts b/src/config/load-version.ts index bed66e0..9ef753f 100644 --- a/src/config/load-version.ts +++ b/src/config/load-version.ts @@ -1,3 +1,11 @@ +import { + allowSuperAdminFallback, + getSuperAdminBases, + isSuperAdminUrl, + resolveTenantDomain, + SA_RETRY_ROUNDS, +} from '@/config/superadmin' + type VersionResponse = | string | number @@ -9,10 +17,9 @@ type VersionResponse = } const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL' -export const VERSION_RETRY_ROUNDS = 5 +export const VERSION_RETRY_ROUNDS = SA_RETRY_ROUNDS let versionFetchPromise: Promise | null = null -let cachedVersionResult: string | null | undefined function getProtocol() { return window.location.protocol === 'https:' ? 'https:' : 'http:' @@ -20,31 +27,34 @@ function getProtocol() { function getVersionUrls() { const protocol = getProtocol() - const host = String(window.location.hostname || '').trim() - const domain = encodeURIComponent( - !host || host === 'localhost' || host === '127.0.0.1' ? 'k.montzar.ir' : host, - ) + const domain = encodeURIComponent(resolveTenantDomain()) + const urls = getSuperAdminBases().map((base) => `${base}/get_version?domain=${domain}`) - return [ - `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', - `${protocol}//212.23.201.173:8381/get_version`, - `${protocol}//91.212.174.137:4040/server/get_version`, - ] + if (allowSuperAdminFallback()) { + urls.push( + 'https://tablo.talasevin.ir/get_version', + 'https://abshode-service.montgit.ir/server/get_version', + `${protocol}//212.23.201.173:8381/get_version`, + `${protocol}//91.212.174.137:4040/server/get_version`, + ) + } + + return urls } function sortVersionUrls(urls: string[]) { const lastSuccessUrl = localStorage.getItem(VERSION_SUCCESS_URL_KEY) - - if (!lastSuccessUrl) { - return urls + if ( + !lastSuccessUrl || + !urls.includes(lastSuccessUrl) || + !isSuperAdminUrl(lastSuccessUrl) + ) { + if (lastSuccessUrl && !isSuperAdminUrl(lastSuccessUrl)) { + localStorage.removeItem(VERSION_SUCCESS_URL_KEY) + } + return [...urls] } - - const others = urls.filter((url) => url !== lastSuccessUrl) - return [lastSuccessUrl, ...others] + return [lastSuccessUrl, ...urls.filter((url) => url !== lastSuccessUrl)] } function clearStaleVersionSuccessUrl(url: string) { @@ -95,10 +105,10 @@ async function fetchVersion(url: string, timeout = 3500): Promise const response = await fetch(url, { method: 'GET', headers: { - Accept: 'application/json' + Accept: 'application/json', }, cache: 'no-store', - signal: controller.signal + signal: controller.signal, }) if (!response.ok) { @@ -106,18 +116,22 @@ async function fetchVersion(url: string, timeout = 3500): Promise } const contentType = response.headers.get('content-type') ?? '' + if (!contentType.includes('application/json')) { + throw new Error('Version response is not JSON') + } - const data = contentType.includes('application/json') - ? ((await response.json()) as VersionResponse) - : ((await response.text()) as VersionResponse) - + const data = (await response.json()) as VersionResponse const version = normalizeVersionResponse(data) if (!version) { throw new Error('Version response is empty') } - localStorage.setItem(VERSION_SUCCESS_URL_KEY, url) + if (isSuperAdminUrl(url)) { + localStorage.setItem(VERSION_SUCCESS_URL_KEY, url) + } else { + clearStaleVersionSuccessUrl(url) + } return version } finally { @@ -149,7 +163,6 @@ async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise { - cachedVersionResult = version - return version - }) - .finally(() => { - versionFetchPromise = null - }) + versionFetchPromise = fetchVersionWithRetry(rounds).finally(() => { + versionFetchPromise = null + }) return versionFetchPromise } diff --git a/src/config/superadmin.ts b/src/config/superadmin.ts new file mode 100644 index 0000000..6efb26d --- /dev/null +++ b/src/config/superadmin.ts @@ -0,0 +1,83 @@ +/** سوپرادمین — مستر، بعد دو اسلیو */ +export const SUPERADMIN_BASES = [ + 'https://adm.montzar.ir', + 'https://adm.montech.ir', + 'https://adm.montest.ir', +] as const + +/** هر API: سه دامنه × سه دور */ +export const SA_RETRY_ROUNDS = 3 + +/** تنظیمات پیشرفته نسخه — همان دادهٔ /get_settings بدون نیاز به nginx جدا */ +export const SA_PROGRAM_SETTINGS_PATH = '/api/public/program-settings' + +function normalizeBase(url: string) { + return String(url || '').trim().replace(/\/$/, '') +} + +export function getSuperAdminBases(): string[] { + const fromEnv = normalizeBase(String(import.meta.env.VITE_SUPERADMIN_URL || '')) + const bases = [...SUPERADMIN_BASES] + if (fromEnv) { + return [fromEnv, ...bases.filter((base) => base !== fromEnv)] + } + return bases +} + +export function getSuperAdminBase(): string { + return getSuperAdminBases()[0] || '' +} + +export function resolveTenantDomain(): string { + const override = String(import.meta.env.VITE_TENANT_DOMAIN || '').trim() + if (override) return override + + const host = String(window.location.hostname || '').trim() + if (!host || host === 'localhost' || host === '127.0.0.1') { + return 'k.montzar.ir' + } + return host +} + +export function isSuperAdminUrl(url: string): boolean { + return getSuperAdminBases().some((base) => url.startsWith(base)) +} + +export function allowSuperAdminFallback(): boolean { + return Boolean(import.meta.env.DEV) +} + +export async function fetchSuperAdminJson( + buildUrl: (base: string) => string, + timeout = 5000, +): Promise { + let lastError: unknown + const bases = getSuperAdminBases() + for (let round = 1; round <= SA_RETRY_ROUNDS; round++) { + for (const base of bases) { + const controller = new AbortController() + const timer = window.setTimeout(() => controller.abort(), timeout) + try { + const res = await fetch(buildUrl(base), { + 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 (await res.json()) as T + } catch (error) { + lastError = error + } finally { + window.clearTimeout(timer) + } + } + } + throw lastError instanceof Error ? lastError : new Error('SuperAdmin unreachable') +} diff --git a/src/store/settingsStore.ts b/src/store/settingsStore.ts index 3760fa3..91d63cb 100644 --- a/src/store/settingsStore.ts +++ b/src/store/settingsStore.ts @@ -1,6 +1,7 @@ import { computed, ref } from 'vue' import { defineStore } from 'pinia' import { getSettings } from '@/api/configuration' +import { fetchSuperAdminProgramSettings } from '@/api/superadminSettings' import { resolveMeltedGoldLabel, resolveMetalTypeLabel } from '@/utils/metalLabels' import { resolveTradeWeightUnit } from '@/utils/weightUnit' @@ -65,9 +66,15 @@ export const useSettingsStore = defineStore('settings', () => { fetchPromise = (async () => { try { - const data = await getSettings() - if (Array.isArray(data)) { - mergeSettings(data) + const [local, remote] = await Promise.all([ + getSettings().catch(() => []), + fetchSuperAdminProgramSettings(), + ]) + if (Array.isArray(local)) { + mergeSettings(local) + } + if (remote.length) { + mergeSettings(remote as AppSetting[]) } } catch (error) { console.log(error)