chore: enhance data source configuration and mock data handling
- Added support for mock data mode in the frontend by introducing a new environment variable in the Dockerfile and workflow configuration. - Updated the settings page to conditionally display options based on the availability of mock data, improving user experience. - Refactored data source logic to include a check for mock data availability, ensuring proper mode handling in the application.
This commit is contained in:
@@ -129,6 +129,7 @@ jobs:
|
|||||||
BACKEND_INTERNAL_URL=http://backend:8000
|
BACKEND_INTERNAL_URL=http://backend:8000
|
||||||
NEXT_PUBLIC_BACKEND_URL=same-origin
|
NEXT_PUBLIC_BACKEND_URL=same-origin
|
||||||
NEXT_PUBLIC_DEFAULT_DATA_SOURCE=live
|
NEXT_PUBLIC_DEFAULT_DATA_SOURCE=live
|
||||||
|
NEXT_PUBLIC_ALLOW_MOCK_DATA=false
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.IMAGE_NAME }}:latest
|
${{ env.IMAGE_NAME }}:latest
|
||||||
${{ env.IMAGE_NAME }}:${{ gitea.sha }}
|
${{ env.IMAGE_NAME }}:${{ gitea.sha }}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ FROM deps AS build
|
|||||||
ARG BACKEND_INTERNAL_URL=http://backend:8000
|
ARG BACKEND_INTERNAL_URL=http://backend:8000
|
||||||
ARG NEXT_PUBLIC_BACKEND_URL=same-origin
|
ARG NEXT_PUBLIC_BACKEND_URL=same-origin
|
||||||
ARG NEXT_PUBLIC_DEFAULT_DATA_SOURCE=live
|
ARG NEXT_PUBLIC_DEFAULT_DATA_SOURCE=live
|
||||||
|
ARG NEXT_PUBLIC_ALLOW_MOCK_DATA=false
|
||||||
ENV BACKEND_INTERNAL_URL=$BACKEND_INTERNAL_URL
|
ENV BACKEND_INTERNAL_URL=$BACKEND_INTERNAL_URL
|
||||||
ENV NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL
|
ENV NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL
|
||||||
ENV NEXT_PUBLIC_DEFAULT_DATA_SOURCE=$NEXT_PUBLIC_DEFAULT_DATA_SOURCE
|
ENV NEXT_PUBLIC_DEFAULT_DATA_SOURCE=$NEXT_PUBLIC_DEFAULT_DATA_SOURCE
|
||||||
|
ENV NEXT_PUBLIC_ALLOW_MOCK_DATA=$NEXT_PUBLIC_ALLOW_MOCK_DATA
|
||||||
COPY packages/contracts packages/contracts
|
COPY packages/contracts packages/contracts
|
||||||
COPY next.config.ts tsconfig.json postcss.config.mjs components.json ./
|
COPY next.config.ts tsconfig.json postcss.config.mjs components.json ./
|
||||||
COPY app app
|
COPY app app
|
||||||
|
|||||||
@@ -825,7 +825,7 @@ export default function SettingsPage() {
|
|||||||
const [copied, setCopied] = useState<string | null>(null)
|
const [copied, setCopied] = useState<string | null>(null)
|
||||||
|
|
||||||
// data source
|
// data source
|
||||||
const { mode, setMode, backendUrl, setBackendUrl, backendUrlLocked, backendStatus, checkBackend } = useDataSource()
|
const { mode, setMode, backendUrl, setBackendUrl, backendUrlLocked, mockModeAvailable, backendStatus, checkBackend } = useDataSource()
|
||||||
const evo = useEvoBGP()
|
const evo = useEvoBGP()
|
||||||
const [evoBaseDraft, setEvoBaseDraft] = useState("")
|
const [evoBaseDraft, setEvoBaseDraft] = useState("")
|
||||||
const [evoEnabledDraft, setEvoEnabledDraft] = useState(false)
|
const [evoEnabledDraft, setEvoEnabledDraft] = useState(false)
|
||||||
@@ -969,30 +969,40 @@ export default function SettingsPage() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">Источник данных</CardTitle>
|
<CardTitle className="text-base">Источник данных</CardTitle>
|
||||||
<CardDescription className="text-xs">
|
<CardDescription className="text-xs">
|
||||||
Переключите между живыми данными от бекенда и суррогатными моками из lib/data.ts
|
{mockModeAvailable
|
||||||
|
? "Переключите между живыми данными от бекенда и суррогатными моками из lib/data.ts"
|
||||||
|
: "В продакшен-образе доступны только живые данные от бекенда"}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="divide-y px-5">
|
<CardContent className="divide-y px-5">
|
||||||
|
|
||||||
{/* mode toggle */}
|
{/* mode toggle */}
|
||||||
<SettingRow label="Режим данных" description="Моковые — статичные тестовые данные; Живые — RouterOS REST API через бекенд">
|
{mockModeAvailable ? (
|
||||||
<div className="flex rounded-md border border-input overflow-hidden h-8">
|
<SettingRow label="Режим данных" description="Моковые — статичные тестовые данные; Живые — RouterOS REST API через бекенд">
|
||||||
{([["mock", "Моковые"], ["live", "Живые"]] as const).map(([v, l], i) => (
|
<div className="flex rounded-md border border-input overflow-hidden h-8">
|
||||||
<button key={v} onClick={() => setMode(v)}
|
{([["mock", "Моковые"], ["live", "Живые"]] as const).map(([v, l], i) => (
|
||||||
className={cn(
|
<button key={v} onClick={() => setMode(v)}
|
||||||
"px-3 text-xs transition-colors border-input",
|
className={cn(
|
||||||
i === 0 && "border-r",
|
"px-3 text-xs transition-colors border-input",
|
||||||
mode === v
|
i === 0 && "border-r",
|
||||||
? v === "live"
|
mode === v
|
||||||
? "bg-emerald-600 text-white dark:bg-emerald-500"
|
? v === "live"
|
||||||
: "bg-primary text-primary-foreground"
|
? "bg-emerald-600 text-white dark:bg-emerald-500"
|
||||||
: "text-muted-foreground hover:bg-muted",
|
: "bg-primary text-primary-foreground"
|
||||||
)}>
|
: "text-muted-foreground hover:bg-muted",
|
||||||
{l}
|
)}>
|
||||||
</button>
|
{l}
|
||||||
))}
|
</button>
|
||||||
</div>
|
))}
|
||||||
</SettingRow>
|
</div>
|
||||||
|
</SettingRow>
|
||||||
|
) : (
|
||||||
|
<SettingRow label="Режим данных" description="В Docker-образе приложение работает только с живыми данными бекенда">
|
||||||
|
<span className="inline-flex h-8 items-center rounded-md border border-input bg-emerald-600 px-3 text-xs text-white dark:bg-emerald-500">
|
||||||
|
Живые
|
||||||
|
</span>
|
||||||
|
</SettingRow>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* backend URL — visible in both modes so user can configure before switching */}
|
{/* backend URL — visible in both modes so user can configure before switching */}
|
||||||
<SettingRow
|
<SettingRow
|
||||||
|
|||||||
@@ -13,9 +13,14 @@ export function configuredBackendUrl(): ConfiguredBackendUrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function defaultDataSourceMode(): "mock" | "live" {
|
export function defaultDataSourceMode(): "mock" | "live" {
|
||||||
|
if (!isMockDataSourceAvailable()) return "live"
|
||||||
return process.env.NEXT_PUBLIC_DEFAULT_DATA_SOURCE === "live" ? "live" : "mock"
|
return process.env.NEXT_PUBLIC_DEFAULT_DATA_SOURCE === "live" ? "live" : "mock"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isMockDataSourceAvailable(): boolean {
|
||||||
|
return process.env.NEXT_PUBLIC_ALLOW_MOCK_DATA !== "false"
|
||||||
|
}
|
||||||
|
|
||||||
export function isBackendUrlLocked(): boolean {
|
export function isBackendUrlLocked(): boolean {
|
||||||
const cfg = configuredBackendUrl()
|
const cfg = configuredBackendUrl()
|
||||||
return cfg.kind === "same-origin" || cfg.kind === "fixed"
|
return cfg.kind === "same-origin" || cfg.kind === "fixed"
|
||||||
|
|||||||
+10
-3
@@ -5,6 +5,7 @@ import {
|
|||||||
configuredBackendUrl,
|
configuredBackendUrl,
|
||||||
defaultDataSourceMode,
|
defaultDataSourceMode,
|
||||||
isBackendUrlLocked,
|
isBackendUrlLocked,
|
||||||
|
isMockDataSourceAvailable,
|
||||||
LOCAL_DEFAULT_BACKEND_URL,
|
LOCAL_DEFAULT_BACKEND_URL,
|
||||||
resolveStoredBackendUrl,
|
resolveStoredBackendUrl,
|
||||||
} from "@/lib/backend-url"
|
} from "@/lib/backend-url"
|
||||||
@@ -19,6 +20,7 @@ interface DataSourceContextValue {
|
|||||||
backendUrl: string
|
backendUrl: string
|
||||||
setBackendUrl: (url: string) => void
|
setBackendUrl: (url: string) => void
|
||||||
backendUrlLocked: boolean
|
backendUrlLocked: boolean
|
||||||
|
mockModeAvailable: boolean
|
||||||
/** undefined = не проверялось, true = OK, false = недоступен */
|
/** undefined = не проверялось, true = OK, false = недоступен */
|
||||||
backendStatus: boolean | undefined
|
backendStatus: boolean | undefined
|
||||||
checkBackend: () => Promise<void>
|
checkBackend: () => Promise<void>
|
||||||
@@ -32,9 +34,12 @@ const LS_MODE = "routerlists:data-source"
|
|||||||
const LS_BACKEND = "routerlists:backend-url"
|
const LS_BACKEND = "routerlists:backend-url"
|
||||||
|
|
||||||
function readStoredMode(): DataSourceMode {
|
function readStoredMode(): DataSourceMode {
|
||||||
|
if (!isMockDataSourceAvailable()) return "live"
|
||||||
if (typeof window === "undefined") return defaultDataSourceMode()
|
if (typeof window === "undefined") return defaultDataSourceMode()
|
||||||
const stored = localStorage.getItem(LS_MODE) as DataSourceMode | null
|
const stored = localStorage.getItem(LS_MODE) as DataSourceMode | null
|
||||||
return stored === "mock" || stored === "live" ? stored : defaultDataSourceMode()
|
if (stored === "live") return "live"
|
||||||
|
if (stored === "mock") return "mock"
|
||||||
|
return defaultDataSourceMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
function readStoredBackendUrl(): string {
|
function readStoredBackendUrl(): string {
|
||||||
@@ -55,6 +60,7 @@ export function DataSourceProvider({ children }: { children: React.ReactNode })
|
|||||||
)
|
)
|
||||||
const [backendStatus, setBackendStatus] = useState<boolean | undefined>(undefined)
|
const [backendStatus, setBackendStatus] = useState<boolean | undefined>(undefined)
|
||||||
const backendUrlLocked = isBackendUrlLocked()
|
const backendUrlLocked = isBackendUrlLocked()
|
||||||
|
const mockModeAvailable = isMockDataSourceAvailable()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (configuredBackendUrl().kind !== "same-origin") return
|
if (configuredBackendUrl().kind !== "same-origin") return
|
||||||
@@ -62,9 +68,10 @@ export function DataSourceProvider({ children }: { children: React.ReactNode })
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const setMode = useCallback((m: DataSourceMode) => {
|
const setMode = useCallback((m: DataSourceMode) => {
|
||||||
|
if (!mockModeAvailable && m === "mock") return
|
||||||
setModeState(m)
|
setModeState(m)
|
||||||
localStorage.setItem(LS_MODE, m)
|
localStorage.setItem(LS_MODE, m)
|
||||||
}, [])
|
}, [mockModeAvailable])
|
||||||
|
|
||||||
const setBackendUrl = useCallback((url: string) => {
|
const setBackendUrl = useCallback((url: string) => {
|
||||||
if (backendUrlLocked) return
|
if (backendUrlLocked) return
|
||||||
@@ -89,7 +96,7 @@ export function DataSourceProvider({ children }: { children: React.ReactNode })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataSourceContext.Provider
|
<DataSourceContext.Provider
|
||||||
value={{ mode, setMode, backendUrl, setBackendUrl, backendUrlLocked, backendStatus, checkBackend }}
|
value={{ mode, setMode, backendUrl, setBackendUrl, backendUrlLocked, mockModeAvailable, backendStatus, checkBackend }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</DataSourceContext.Provider>
|
</DataSourceContext.Provider>
|
||||||
|
|||||||
Reference in New Issue
Block a user