migrate to nextjs

This commit is contained in:
Fran Jurmanović
2025-08-27 20:38:19 +02:00
parent 965e13a0bf
commit b269144ee7
120 changed files with 10393 additions and 8526 deletions

33
src/lib/actions/auth.ts Normal file
View File

@@ -0,0 +1,33 @@
'use server';
import { redirect } from 'next/navigation';
import { loginUser } from '@/lib/api/server/auth';
import { login, logout } from '@/lib/auth/server';
export async function loginAction(formData: FormData) {
try {
const username = formData.get('username') as string;
const password = formData.get('password') as string;
if (!username || !password) {
throw new Error('Username and password are required');
}
const result = await loginUser(username, password);
if (result.token && result.user) {
await login(result.token, result.user);
} else {
throw new Error('Invalid credentials');
}
} catch (error) {
throw new Error(error instanceof Error ? error.message : 'Authentication failed');
}
redirect('/dashboard');
}
export async function logoutAction() {
await logout();
redirect('/login');
}

View File

@@ -0,0 +1,197 @@
'use server';
import { revalidatePath } from 'next/cache';
import { requireAuth } from '@/lib/auth/server';
import { updateServerConfiguration } from '@/lib/api/server/configuration';
import { ConfigFile } from '@/lib/types/config';
import type {
Configuration,
AssistRules,
EventConfig,
EventRules,
ServerSettings
} from '@/lib/types/config';
export async function updateConfigurationAction(serverId: string, formData: FormData) {
try {
const session = await requireAuth();
const restart = formData.get('restart') === 'on';
const config: Configuration = {
udpPort: parseInt(formData.get('udpPort') as string),
tcpPort: parseInt(formData.get('tcpPort') as string),
maxConnections: parseInt(formData.get('maxConnections') as string),
lanDiscovery: parseInt(formData.get('lanDiscovery') as string),
registerToLobby: parseInt(formData.get('registerToLobby') as string),
configVersion: parseInt(formData.get('configVersion') as string) || 1
};
await updateServerConfiguration(
session.token!,
serverId,
ConfigFile.configuration,
config,
restart
);
revalidatePath(`/dashboard/server/${serverId}`);
return { success: true, message: 'Configuration updated successfully' };
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to update configuration'
};
}
}
export async function updateAssistRulesAction(serverId: string, formData: FormData) {
try {
const session = await requireAuth();
const restart = formData.get('restart') === 'on';
const config: AssistRules = {
stabilityControlLevelMax: parseInt(formData.get('stabilityControlLevelMax') as string),
disableAutosteer: parseInt(formData.get('disableAutosteer') as string),
disableAutoLights: parseInt(formData.get('disableAutoLights') as string),
disableAutoWiper: parseInt(formData.get('disableAutoWiper') as string),
disableAutoEngineStart: parseInt(formData.get('disableAutoEngineStart') as string),
disableAutoPitLimiter: parseInt(formData.get('disableAutoPitLimiter') as string),
disableAutoGear: parseInt(formData.get('disableAutoGear') as string),
disableAutoClutch: parseInt(formData.get('disableAutoClutch') as string),
disableIdealLine: parseInt(formData.get('disableIdealLine') as string)
};
await updateServerConfiguration(
session.token!,
serverId,
ConfigFile.assistRules,
config,
restart
);
revalidatePath(`/dashboard/server/${serverId}`);
return { success: true, message: 'Assist rules updated successfully' };
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to update assist rules'
};
}
}
export async function updateServerSettingsAction(serverId: string, formData: FormData) {
try {
const session = await requireAuth();
const restart = formData.get('restart') === 'on';
const config: ServerSettings = {
serverName: formData.get('serverName') as string,
adminPassword: formData.get('adminPassword') as string,
carGroup: formData.get('carGroup') as string,
trackMedalsRequirement: parseInt(formData.get('trackMedalsRequirement') as string),
safetyRatingRequirement: parseInt(formData.get('safetyRatingRequirement') as string),
racecraftRatingRequirement: parseInt(formData.get('racecraftRatingRequirement') as string),
password: formData.get('password') as string,
spectatorPassword: formData.get('spectatorPassword') as string,
maxCarSlots: parseInt(formData.get('maxCarSlots') as string),
dumpLeaderboards: parseInt(formData.get('dumpLeaderboards') as string),
isRaceLocked: parseInt(formData.get('isRaceLocked') as string),
randomizeTrackWhenEmpty: parseInt(formData.get('randomizeTrackWhenEmpty') as string),
centralEntryListPath: formData.get('centralEntryListPath') as string,
allowAutoDQ: parseInt(formData.get('allowAutoDQ') as string),
shortFormationLap: parseInt(formData.get('shortFormationLap') as string),
formationLapType: parseInt(formData.get('formationLapType') as string),
ignorePrematureDisconnects: parseInt(formData.get('ignorePrematureDisconnects') as string)
};
await updateServerConfiguration(session.token!, serverId, ConfigFile.settings, config, restart);
revalidatePath(`/dashboard/server/${serverId}`);
return { success: true, message: 'Server settings updated successfully' };
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to update server settings'
};
}
}
export async function updateEventConfigAction(serverId: string, formData: FormData) {
try {
const session = await requireAuth();
const restart = formData.get('restart') === 'on';
const sessionsData = formData.get('sessions') as string;
const sessions = sessionsData ? JSON.parse(sessionsData) : [];
const config: EventConfig = {
track: formData.get('track') as string,
preRaceWaitingTimeSeconds: parseInt(formData.get('preRaceWaitingTimeSeconds') as string),
sessionOverTimeSeconds: parseInt(formData.get('sessionOverTimeSeconds') as string),
ambientTemp: parseInt(formData.get('ambientTemp') as string),
cloudLevel: parseFloat(formData.get('cloudLevel') as string),
rain: parseFloat(formData.get('rain') as string),
weatherRandomness: parseInt(formData.get('weatherRandomness') as string),
postQualySeconds: parseInt(formData.get('postQualySeconds') as string),
postRaceSeconds: parseInt(formData.get('postRaceSeconds') as string),
simracerWeatherConditions: parseInt(formData.get('simracerWeatherConditions') as string),
isFixedConditionQualification: parseInt(
formData.get('isFixedConditionQualification') as string
),
sessions
};
await updateServerConfiguration(session.token!, serverId, ConfigFile.event, config, restart);
revalidatePath(`/dashboard/server/${serverId}`);
return {
success: true,
message: 'Event configuration updated successfully'
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to update event configuration'
};
}
}
export async function updateEventRulesAction(serverId: string, formData: FormData) {
try {
const session = await requireAuth();
const restart = formData.get('restart') === 'on';
const config: EventRules = {
qualifyStandingType: parseInt(formData.get('qualifyStandingType') as string),
pitWindowLengthSec: parseInt(formData.get('pitWindowLengthSec') as string),
driverStintTimeSec: parseInt(formData.get('driverStintTimeSec') as string),
mandatoryPitstopCount: parseInt(formData.get('mandatoryPitstopCount') as string),
maxTotalDrivingTime: parseInt(formData.get('maxTotalDrivingTime') as string),
isRefuellingAllowedInRace: formData.get('isRefuellingAllowedInRace') === 'true',
isRefuellingTimeFixed: formData.get('isRefuellingTimeFixed') === 'true',
isMandatoryPitstopRefuellingRequired:
formData.get('isMandatoryPitstopRefuellingRequired') === 'true',
isMandatoryPitstopTyreChangeRequired:
formData.get('isMandatoryPitstopTyreChangeRequired') === 'true',
isMandatoryPitstopSwapDriverRequired:
formData.get('isMandatoryPitstopSwapDriverRequired') === 'true',
tyreSetCount: parseInt(formData.get('tyreSetCount') as string)
};
await updateServerConfiguration(
session.token!,
serverId,
ConfigFile.eventRules,
config,
restart
);
revalidatePath(`/dashboard/server/${serverId}`);
return { success: true, message: 'Event rules updated successfully' };
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to update event rules'
};
}
}

View File

@@ -0,0 +1,41 @@
'use server';
import { revalidatePath } from 'next/cache';
import { requireAuth } from '@/lib/auth/server';
import { createUser, deleteUser } from '@/lib/api/server/membership';
export async function createUserAction(formData: FormData) {
try {
const session = await requireAuth();
const username = formData.get('username') as string;
const password = formData.get('password') as string;
const role = formData.get('role') as string;
await createUser(session.token!, { username, password, role });
revalidatePath('/dashboard/membership');
return { success: true, message: 'User created successfully' };
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to create user'
};
}
}
export async function deleteUserAction(formData: FormData) {
try {
const session = await requireAuth();
const userId = formData.get('id') as string;
await deleteUser(session.token!, userId);
revalidatePath('/dashboard/membership');
return { success: true, message: 'User deleted successfully' };
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to delete user'
};
}
}

View File

@@ -0,0 +1,38 @@
'use server';
import { revalidatePath } from 'next/cache';
import { requireAuth } from '@/lib/auth/server';
import { startService, stopService, restartService } from '@/lib/api/server/servers';
export async function startServerAction(serverId: string) {
try {
const session = await requireAuth();
await startService(session.token!, serverId);
revalidatePath('/dashboard');
revalidatePath(`/dashboard/server/${serverId}`);
} catch (error) {
throw new Error(error instanceof Error ? error.message : 'Failed to start server');
}
}
export async function stopServerAction(serverId: string) {
try {
const session = await requireAuth();
await stopService(session.token!, serverId);
revalidatePath('/dashboard');
revalidatePath(`/dashboard/server/${serverId}`);
} catch (error) {
throw new Error(error instanceof Error ? error.message : 'Failed to stop server');
}
}
export async function restartServerAction(serverId: string) {
try {
const session = await requireAuth();
await restartService(session.token!, serverId);
revalidatePath('/dashboard');
revalidatePath(`/dashboard/server/${serverId}`);
} catch (error) {
throw new Error(error instanceof Error ? error.message : 'Failed to restart server');
}
}

View File

@@ -0,0 +1,73 @@
'use client';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
async function fetchFromAPI(endpoint: string, method: string = 'GET', body?: unknown) {
const response = await fetch(endpoint, {
method,
headers: {
'Content-Type': 'application/json'
},
body: body ? JSON.stringify(body) : undefined
});
if (!response.ok) {
throw new Error(`API Error: ${response.statusText}`);
}
return response.json();
}
export function useServerStatus(serverId: string) {
return useQuery({
queryKey: ['server-status', serverId],
queryFn: () => fetchFromAPI(`/api/server/${serverId}/status`),
refetchInterval: 30000
});
}
export function useServerAction(serverId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ action }: { action: 'start' | 'stop' | 'restart' }) =>
fetchFromAPI(`/api/server/${serverId}/action`, 'POST', { action }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['server-status', serverId] });
queryClient.invalidateQueries({ queryKey: ['servers'] });
}
});
}
export function useUsers(params?: Record<string, string>) {
const searchParams = new URLSearchParams(params).toString();
const endpoint = `/api/membership${searchParams ? `?${searchParams}` : ''}`;
return useQuery({
queryKey: ['users', params],
queryFn: () => fetchFromAPI(endpoint)
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (userData: { username: string; password: string; role: string }) =>
fetchFromAPI('/api/membership', 'POST', userData),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
}
export function useDeleteUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (userId: string) => fetchFromAPI(`/api/membership/${userId}`, 'DELETE'),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
}

View File

@@ -0,0 +1,12 @@
'use client';
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: false
}
}
});

View File

@@ -0,0 +1,30 @@
import { User } from '@/lib/types';
import { fetchServerAPI } from './base';
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:8080';
const authRoute = '/auth';
export async function loginUser(username: string, password: string) {
const response = await fetch(`${BASE_URL}${authRoute}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
if (!response.ok) {
throw new Error(`Login failed: ${response.statusText} - ${BASE_URL}${authRoute}/login`);
}
const { token } = await response.json();
const userResponse = await getCurrentUser(token);
return { token, user: userResponse };
}
export async function getCurrentUser(token: string) {
return fetchServerAPI<User>(`${authRoute}/me`, token);
}

View File

@@ -0,0 +1,31 @@
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:8080';
export async function fetchServerAPI<T>(
endpoint: string,
token: string,
method: string = 'GET',
body?: object
): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
};
const response = await fetch(`${BASE_URL}${endpoint}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined
});
if (!response.ok) {
throw new Error(
`API Error: ${response.statusText} - ${method} - ${BASE_URL}${endpoint} - ${token}`
);
}
if (response.headers.get('Content-Type')?.includes('application/json')) {
return response.json();
}
return response.text() as T;
}

View File

@@ -0,0 +1,40 @@
import { fetchServerAPI } from './base';
import type {
Configurations,
Configuration,
AssistRules,
EventConfig,
EventRules,
ServerSettings,
ConfigFile
} from '@/lib/types/config';
const serverRoute = '/server';
export async function getServerConfigurations(
token: string,
serverId: string
): Promise<Configurations> {
return fetchServerAPI<Configurations>(`${serverRoute}/${serverId}/config`, token);
}
export async function getServerConfiguration(
token: string,
serverId: string,
configType: ConfigFile
): Promise<Configuration | AssistRules | EventConfig | EventRules | ServerSettings> {
return fetchServerAPI(`${serverRoute}/${serverId}/config/${configType}`, token);
}
export async function updateServerConfiguration(
token: string,
serverId: string,
configType: ConfigFile,
config: Configuration | AssistRules | EventConfig | EventRules | ServerSettings,
restart = false
): Promise<void> {
await fetchServerAPI(`${serverRoute}/${serverId}/config/${configType}`, token, 'PUT', {
...config,
restart
});
}

View File

@@ -0,0 +1,24 @@
import { fetchServerAPI } from './base';
import { Track, CarModel, CupCategory, DriverCategory, SessionType } from '@/lib/types';
const lookupRoute = '/lookup';
export async function getTracks(token: string): Promise<Track[]> {
return fetchServerAPI(`${lookupRoute}/tracks`, token);
}
export async function getCarModels(token: string): Promise<CarModel[]> {
return fetchServerAPI(`${lookupRoute}/car-models`, token);
}
export async function getCupCategories(token: string): Promise<CupCategory[]> {
return fetchServerAPI(`${lookupRoute}/cup-categories`, token);
}
export async function getDriverCategories(token: string): Promise<DriverCategory[]> {
return fetchServerAPI(`${lookupRoute}/driver-categories`, token);
}
export async function getSessionTypes(token: string): Promise<SessionType[]> {
return fetchServerAPI(`${lookupRoute}/session-types`, token);
}

View File

@@ -0,0 +1,51 @@
import { fetchServerAPI } from './base';
import { User, Role } from '@/lib/types';
export interface UserListParams {
username?: string;
role_name?: string;
sort_by?: string;
sort_desc?: boolean;
page?: number;
limit?: number;
}
const membershipRoute = '/membership';
export async function getUsers(token: string, params: UserListParams = {}): Promise<User[]> {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
searchParams.set(key, value.toString());
}
});
const queryString = searchParams.toString();
const endpoint = `${membershipRoute}${queryString ? `?${queryString}` : ''}`;
return fetchServerAPI(endpoint, token);
}
export async function createUser(
token: string,
userData: { username: string; password: string; role: string }
) {
return fetchServerAPI(membershipRoute, token, 'POST', userData);
}
export async function getUserById(token: string, userId: string): Promise<User> {
return fetchServerAPI(`${membershipRoute}/${userId}`, token);
}
export async function updateUser(token: string, userId: string, userData: Partial<User>) {
return fetchServerAPI(`${membershipRoute}/${userId}`, token, 'PUT', userData);
}
export async function deleteUser(token: string, userId: string) {
return fetchServerAPI(`${membershipRoute}/${userId}`, token, 'DELETE');
}
export async function getRoles(token: string): Promise<Role[]> {
return fetchServerAPI(`${membershipRoute}/roles`, token);
}

View File

@@ -0,0 +1,30 @@
import { fetchServerAPI } from './base';
import { Server } from '@/lib/types/server';
const serverRoute = '/server';
export async function getServers(token: string): Promise<Server[]> {
const response = await fetchServerAPI<Server[]>(serverRoute, token);
return response;
}
export async function getServer(token: string, serverId: string): Promise<Server> {
const response = await fetchServerAPI<Server>(`${serverRoute}/${serverId}`, token);
return response;
}
export async function restartService(token: string, serverId: string) {
return fetchServerAPI(`${serverRoute}/${serverId}/service/restart`, token, 'POST');
}
export async function startService(token: string, serverId: string) {
return fetchServerAPI(`${serverRoute}/${serverId}/service/start`, token, 'POST');
}
export async function stopService(token: string, serverId: string) {
return fetchServerAPI(`${serverRoute}/${serverId}/service/stop`, token, 'POST');
}
export async function getServiceStatus(token: string, serverId: string) {
return fetchServerAPI(`${serverRoute}/${serverId}/service`, token);
}

View File

@@ -0,0 +1,16 @@
import { fetchServerAPI } from './base';
import type { StateHistoryStats } from '@/lib/types/statistics';
const serverRoute = '/server';
export async function getServerStatistics(
token: string,
serverId: string,
startDate: string,
endDate: string
): Promise<StateHistoryStats> {
return fetchServerAPI<StateHistoryStats>(
`${serverRoute}/${serverId}/state-history/statistics?start_date=${startDate}&end_date=${endDate}`,
token
);
}

31
src/lib/auth/server.ts Normal file
View File

@@ -0,0 +1,31 @@
import { getIronSession } from 'iron-session';
import { cookies } from 'next/headers';
import { SessionData, sessionOptions } from '@/lib/session/config';
import { redirect } from 'next/navigation';
export async function getSession() {
const session = await getIronSession<SessionData>(await cookies(), sessionOptions);
return session;
}
export async function requireAuth() {
const session = await getSession();
if (!session.token || !session.user) {
redirect('/login');
}
return session;
}
export async function login(token: string, user: SessionData['user']) {
const session = await getSession();
session.token = token;
session.user = user;
await session.save();
}
export async function logout() {
const session = await getSession();
session.destroy();
}

View File

@@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

17
src/lib/session/config.ts Normal file
View File

@@ -0,0 +1,17 @@
import { SessionOptions } from 'iron-session';
import { User } from '@/lib/types';
export interface SessionData {
token?: string;
user?: User;
}
export const sessionOptions: SessionOptions = {
password: process.env.SECRET || 'development-secret-key-must-be-32-characters-long',
cookieName: 'acc-session',
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 30 * 24 * 60 * 60 // 30 days
}
};

105
src/lib/types/config.ts Normal file
View File

@@ -0,0 +1,105 @@
export enum ConfigFile {
configuration = 'configuration.json',
assistRules = 'assistRules.json',
event = 'event.json',
eventRules = 'eventRules.json',
settings = 'settings.json'
}
export enum ServerTab {
statistics = 'statistics',
configuration = 'configuration',
assistRules = 'assistRules',
event = 'event',
eventRules = 'eventRules',
settings = 'settings'
}
// Configuration interfaces
export interface Configuration {
udpPort: number;
tcpPort: number;
maxConnections: number;
lanDiscovery: number;
registerToLobby: number;
configVersion: number;
}
export interface AssistRules {
stabilityControlLevelMax: number;
disableAutosteer: number;
disableAutoLights: number;
disableAutoWiper: number;
disableAutoEngineStart: number;
disableAutoPitLimiter: number;
disableAutoGear: number;
disableAutoClutch: number;
disableIdealLine: number;
}
export interface ServerSettings {
serverName: string;
adminPassword: string;
carGroup: string;
trackMedalsRequirement: number;
safetyRatingRequirement: number;
racecraftRatingRequirement: number;
password: string;
spectatorPassword: string;
maxCarSlots: number;
dumpLeaderboards: number;
isRaceLocked: number;
randomizeTrackWhenEmpty: number;
centralEntryListPath: string;
allowAutoDQ: number;
shortFormationLap: number;
formationLapType: number;
ignorePrematureDisconnects: number;
}
export interface Session {
hourOfDay: number;
dayOfWeekend: number;
timeMultiplier: number;
sessionType: string;
sessionDurationMinutes: number;
}
export interface EventConfig {
track: string;
preRaceWaitingTimeSeconds: number;
sessionOverTimeSeconds: number;
ambientTemp: number;
cloudLevel: number;
rain: number;
weatherRandomness: number;
postQualySeconds: number;
postRaceSeconds: number;
simracerWeatherConditions: number;
isFixedConditionQualification: number;
sessions: Session[];
}
export interface EventRules {
qualifyStandingType: number;
pitWindowLengthSec: number;
driverStintTimeSec: number;
mandatoryPitstopCount: number;
maxTotalDrivingTime: number;
isRefuellingAllowedInRace: boolean;
isRefuellingTimeFixed: boolean;
isMandatoryPitstopRefuellingRequired: boolean;
isMandatoryPitstopTyreChangeRequired: boolean;
isMandatoryPitstopSwapDriverRequired: boolean;
tyreSetCount: number;
}
export interface Configurations {
configuration: Configuration;
assistRules: AssistRules;
event: EventConfig;
eventRules: EventRules;
settings: ServerSettings;
}
export type Config = Configuration | AssistRules | EventConfig | EventRules | ServerSettings;

6
src/lib/types/index.ts Normal file
View File

@@ -0,0 +1,6 @@
// Re-export all types for easier imports
export * from './server';
export * from './user';
export * from './config';
export * from './statistics';
export * from './lookups';

25
src/lib/types/lookups.ts Normal file
View File

@@ -0,0 +1,25 @@
export interface Track {
track: string;
uniquePitBoxes: number;
privateServerSlots: number;
}
export interface CarModel {
value: number;
carModel: string;
}
export interface DriverCategory {
value: number;
category: string;
}
export interface CupCategory {
value: number;
category: string;
}
export interface SessionType {
value: number;
sessionType: string;
}

View File

@@ -1,32 +1,56 @@
import type { ServiceStatus } from './serviceStatus';
export enum ServiceStatus {
Unknown,
Stopped,
Stopping,
Restarting,
Starting,
Running
}
export interface ServerState {
export const serviceStatusToString = (status: ServiceStatus): string => {
switch (status) {
case ServiceStatus.Running:
return 'Running';
case ServiceStatus.Stopped:
return 'Stopped';
case ServiceStatus.Starting:
return 'Starting';
case ServiceStatus.Stopping:
return 'Stopping';
case ServiceStatus.Restarting:
return 'Restarting';
default:
return 'Unknown';
}
};
export const getStatusColor = (status: ServiceStatus): string => {
switch (status) {
case ServiceStatus.Running:
return 'bg-green-500';
case ServiceStatus.Stopped:
return 'bg-red-500';
case ServiceStatus.Starting:
return 'bg-blue-500';
case ServiceStatus.Stopping:
return 'bg-yellow-500';
case ServiceStatus.Restarting:
return 'bg-purple-500';
default:
return 'bg-gray-500';
}
};
interface State {
session: string;
sessionStart: string;
playerCount: number;
track: string;
maxConnections: number;
}
export interface Server {
id: number;
id: string;
name: string;
status: ServiceStatus;
state: ServerState;
state: State;
}
// Helper function to parse server data from API
export const parseServer = (data: any): Server => {
return {
id: data.id,
name: data.name,
status: data.status,
state: {
session: data.state.session,
sessionStart: data.state.sessionStart,
playerCount: data.state.playerCount,
track: data.state.track,
maxConnections: data.state.maxConnections
}
};
};

View File

@@ -1,59 +0,0 @@
export enum ServiceStatus {
Unknown,
Stopped,
Stopping,
Restarting,
Starting,
Running
}
export const serviceStatusToString = (status: ServiceStatus): string => {
switch (status) {
case ServiceStatus.Running:
return 'Running';
case ServiceStatus.Stopped:
return 'Stopped';
case ServiceStatus.Starting:
return 'Starting';
case ServiceStatus.Stopping:
return 'Stopping';
case ServiceStatus.Restarting:
return 'Restarting';
default:
return 'Unknown';
}
};
export const parseServiceStatus = (status: string): ServiceStatus => {
switch (status) {
case 'Running':
return ServiceStatus.Running;
case 'SERVICE_STOPPED':
return ServiceStatus.Stopped;
case 'SERVICE_STARTING':
return ServiceStatus.Starting;
case 'SERVICE_STOPPING':
return ServiceStatus.Stopping;
case 'SERVICE_RESTARTING':
return ServiceStatus.Restarting;
default:
return ServiceStatus.Unknown;
}
};
export const getStatusColor = (status: ServiceStatus): string => {
switch (status) {
case ServiceStatus.Running:
return 'bg-green-500';
case ServiceStatus.Stopped:
return 'bg-red-500';
case ServiceStatus.Starting:
return 'bg-blue-500';
case ServiceStatus.Stopping:
return 'bg-yellow-500';
case ServiceStatus.Restarting:
return 'bg-purple-500';
default:
return 'bg-gray-500';
}
};

View File

@@ -0,0 +1,43 @@
export interface StateHistory {
dateCreated: string;
sessionStart: string;
playerCount: number;
track: string;
sessionDurationMinutes: number;
session: string;
}
interface SessionCount {
name: string;
count: number;
}
interface DailyActivity {
date: string;
sessionsCount: number;
}
interface PlayerCountPoint {
timestamp: string;
count: number;
}
interface RecentSession {
id: number;
date: string;
type: string;
track: string;
duration: number;
players: number;
}
export interface StateHistoryStats {
averagePlayers: number;
peakPlayers: number;
totalSessions: number;
totalPlaytime: number;
playerCountOverTime: PlayerCountPoint[];
sessionTypes: SessionCount[];
dailyActivity: DailyActivity[];
recentSessions: RecentSession[];
}

28
src/lib/types/user.ts Normal file
View File

@@ -0,0 +1,28 @@
export interface Permission {
id: string;
name: string;
}
export interface Role {
id: string;
name: string;
permissions: Permission[];
}
export interface User {
id: string;
username: string;
role_id: string;
role: Role;
}
export function hasPermission(user: User | null, permission: string): boolean {
if (!user || !user.role || !user.role.permissions) {
return false;
}
// Super Admins have all permissions
if (user.role.name === 'Super Admin') {
return true;
}
return user.role.permissions.some((p) => p.name === permission);
}

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}