feat: implement full backend + frontend server detail, settings, and create server pages

Backend:
- Complete FastAPI backend with 42+ REST endpoints (auth, servers, config,
  players, bans, missions, mods, games, system)
- Game adapter architecture with Arma 3 as first-class adapter
- WebSocket real-time events for status, metrics, logs, players
- Background thread system (process monitor, metrics, log tail, RCon poller)
- Fernet encryption for sensitive config fields at rest
- JWT auth with admin/viewer roles, bcrypt password hashing
- SQLite with WAL mode, parameterized queries, migration system
- APScheduler cleanup jobs for logs, metrics, events

Frontend:
- Server Detail page with 7 tabs (overview, config, players, bans,
  missions, mods, logs)
- Settings page with password change and admin user management
- Create Server wizard (4-step; known bug: silent validation failure)
- New hooks: useServerDetail, useAuth, useGames
- New components: ServerHeader, ConfigEditor, PlayerTable, BanTable,
  MissionList, ModList, LogViewer, PasswordChange, UserManager
- WebSocket onEvent callback for real-time log accumulation
- 120 unit tests passing (Vitest + React Testing Library)

Docs:
- Added .gitignore, CLAUDE.md, README.md
- Updated FRONTEND.md, ARCHITECTURE.md with current implementation state
- Added .env.example for backend configuration

Known issues:
- Create Server form: "Next" buttons don't validate before advancing,
  causing silent submit failure when fields are invalid
- Config sub-tabs need UX redesign for non-technical users
This commit is contained in:
Tran G. (Revernomad) Khoa
2026-04-17 11:58:34 +07:00
parent 620429c9b8
commit 6511353b55
119 changed files with 13752 additions and 5000 deletions

View File

@@ -20,8 +20,12 @@ apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem("languard_token");
window.location.href = "/login";
const url = error.config?.url ?? "";
// Don't redirect on auth endpoints — let the calling component handle the error
if (!url.startsWith("/api/auth/")) {
localStorage.removeItem("languard_token");
window.location.href = "/login";
}
}
return Promise.reject(error);
},

View File

@@ -0,0 +1,54 @@
/**
* Lightweight logger for the frontend.
*
* In development: logs to console with level-appropriate methods.
* In production: can be swapped for an error reporting service (Sentry, etc.)
* by replacing the transport in logger.ts.
*/
type LogLevel = "debug" | "info" | "warn" | "error";
const LOG_LEVELS: Record<LogLevel, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
};
const currentLevel: LogLevel =
(import.meta.env.VITE_LOG_LEVEL as LogLevel) ??
(import.meta.env.DEV ? "debug" : "warn");
function shouldLog(level: LogLevel): boolean {
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
}
function formatMessage(level: LogLevel, context: string, message: string): string {
return `[${level.toUpperCase()}] [${context}] ${message}`;
}
export const logger = {
debug(context: string, message: string, ...args: unknown[]) {
if (shouldLog("debug")) {
console.debug(formatMessage("debug", context, message), ...args);
}
},
info(context: string, message: string, ...args: unknown[]) {
if (shouldLog("info")) {
console.info(formatMessage("info", context, message), ...args);
}
},
warn(context: string, message: string, ...args: unknown[]) {
if (shouldLog("warn")) {
console.warn(formatMessage("warn", context, message), ...args);
}
},
error(context: string, message: string, ...args: unknown[]) {
if (shouldLog("error")) {
console.error(formatMessage("error", context, message), ...args);
}
},
};