Add Factorio World Management feature to GSM

- Add gsm-frontend to repository (React + Vite + TailwindCSS)
- New "Worlds" tab for Factorio server with:
  - List saved worlds with Start/Delete actions
  - Create new world with full map generation parameters
  - Preset selection (Default, Rich Resources, Rail World, etc.)
  - Save custom configurations as templates
- Show which save will be loaded in Overview tab
- Lock world management while server is running
- Backend changes deployed to server separately

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alexander Zielonka
2026-01-05 15:42:14 +01:00
parent 2b1fbb9f02
commit ff6adb093b
44 changed files with 7242 additions and 1234 deletions

181
gsm-frontend/src/api.js Normal file
View File

@@ -0,0 +1,181 @@
const API_URL = import.meta.env.VITE_API_URL || 'https://monitor.dimension47.de/api'
async function fetchAPI(endpoint, options = {}) {
const response = await fetch(`${API_URL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
})
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Request failed' }))
throw new Error(error.message || `HTTP ${response.status}`)
}
return response.json()
}
// Auth
export async function login(username, password) {
return fetchAPI('/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
})
}
export async function getMe(token) {
return fetchAPI('/auth/me', {
headers: { Authorization: `Bearer ${token}` },
})
}
export async function changePassword(currentPassword, newPassword) {
const token = localStorage.getItem('gsm_token')
return fetchAPI('/auth/change-password', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ currentPassword, newPassword }),
})
}
// Servers
export async function getServers(token) {
return fetchAPI('/servers', {
headers: { Authorization: `Bearer ${token}` },
})
}
export async function serverAction(token, serverId, action, body = null) {
return fetchAPI(`/servers/${serverId}/${action}`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
...(body && { body: JSON.stringify(body) }),
})
}
export async function getWhitelist(token, serverId) {
const headers = token ? { Authorization: `Bearer ${token}` } : {}
return fetchAPI(`/servers/${serverId}/whitelist`, { headers })
}
export async function sendRcon(token, serverId, command) {
return fetchAPI(`/servers/${serverId}/rcon`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ command }),
})
}
export async function getServerLogs(token, serverId, lines = 50) {
return fetchAPI(`/servers/${serverId}/logs?lines=${lines}`, {
headers: { Authorization: `Bearer ${token}` },
})
}
// Metrics
export async function getMetricsHistory(token, serverId, range = '1h') {
return fetchAPI(`/servers/${serverId}/metrics/history?range=${range}`, {
headers: { Authorization: `Bearer ${token}` },
})
}
// Users (admin only)
export async function getUsers(token) {
return fetchAPI('/auth/users', {
headers: { Authorization: `Bearer ${token}` },
})
}
export async function createUser(token, userData) {
return fetchAPI('/auth/users', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify(userData),
})
}
export async function updateUserRole(token, userId, role) {
return fetchAPI(`/auth/users/${userId}/role`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ role }),
})
}
export async function updateUserPassword(token, userId, password) {
return fetchAPI(`/auth/users/${userId}/password`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ password }),
})
}
export async function deleteUser(token, userId) {
return fetchAPI(`/auth/users/${userId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
})
}
// Factorio World Management
export async function getFactorioSaves(token) {
return fetchAPI('/servers/factorio/saves', {
headers: { Authorization: `Bearer ${token}` },
})
}
export async function getFactorioPresets(token) {
return fetchAPI('/servers/factorio/presets', {
headers: { Authorization: `Bearer ${token}` },
})
}
export async function getFactorioPreset(token, name) {
return fetchAPI(`/servers/factorio/presets/${name}`, {
headers: { Authorization: `Bearer ${token}` },
})
}
export async function getFactorioTemplates(token) {
return fetchAPI('/servers/factorio/templates', {
headers: { Authorization: `Bearer ${token}` },
})
}
export async function createFactorioTemplate(token, name, settings) {
return fetchAPI('/servers/factorio/templates', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ name, settings }),
})
}
export async function deleteFactorioTemplate(token, id) {
return fetchAPI(`/servers/factorio/templates/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
})
}
export async function createFactorioWorld(token, saveName, settings) {
return fetchAPI('/servers/factorio/create-world', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ saveName, settings }),
})
}
export async function deleteFactorioSave(token, saveName) {
return fetchAPI(`/servers/factorio/saves/${encodeURIComponent(saveName)}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
})
}
export async function getFactorioCurrentSave(token) {
return fetchAPI('/servers/factorio/current-save', {
headers: { Authorization: `Bearer ${token}` },
})
}