chore: enhance data source configuration and mock data handling
Docker images / backend-image (push) Successful in 39s
Docker images / frontend-image (push) Successful in 1m38s
Docker images / updater-image (push) Successful in 35s
Docker images / notify-webhook (push) Has been skipped

- 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:
Denozordec
2026-05-12 15:38:27 +07:00
parent 4489dfb2d6
commit 1fefed2aa0
5 changed files with 48 additions and 23 deletions
+1
View File
@@ -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 }}
+2
View File
@@ -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
+30 -20
View File
@@ -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
+5
View File
@@ -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
View File
@@ -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>