diff --git a/Dockerfile b/Dockerfile index 26bd38e..3adcbae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,47 +1,51 @@ # Builder Stage -FROM node:20-alpine AS builder +FROM --platform=$BUILDPLATFORM node:20-alpine AS builder + +ARG TARGETARCH # Wird automatisch von Buildx gesetzt WORKDIR /app +RUN case ${TARGETARCH} in \ + "amd64") export PRISMA_CLI_BINARY_TARGETS="linux-musl-x64-openssl-3.0.x" ;; \ + "arm64") export PRISMA_CLI_BINARY_TARGETS="linux-musl-arm64-openssl-3.0.x" ;; \ + "arm") export PRISMA_CLI_BINARY_TARGETS="linux-musl-arm-openssl-3.0.x" ;; \ + *) echo "Unsupported ARCH: ${TARGETARCH}" && exit 1 ;; \ + esac + COPY package.json package-lock.json* ./ COPY ./prisma ./prisma -# Install all dependencies (including devDependencies) RUN npm install - -# Generate Prisma client RUN npx prisma generate -# Build the application COPY . . RUN npm run build # Production Stage -FROM node:20-alpine AS production +FROM --platform=$TARGETPLATFORM node:20-alpine AS production WORKDIR /app ENV NODE_ENV production +ENV PRISMA_CLI_BINARY_TARGETS="linux-musl-arm64-openssl-3.0.x" -# Copy package files -COPY package.json package-lock.json* ./ - -# Copy node_modules from builder COPY --from=builder /app/node_modules ./node_modules - -# Remove dev dependencies -RUN npm prune --production - -# Copy Prisma files COPY --from=builder /app/prisma ./prisma - -# Copy built application COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/next.config.js* ./ -EXPOSE 3000 +RUN npm prune --production -# Run migrations and start +EXPOSE 3000 CMD ["sh", "-c", "npx prisma migrate deploy && npm start"] + + +# - - BUILD COMMAND - - + # docker buildx build \ + # --platform linux/amd64,linux/arm64,linux/arm/v7 \ + # -t haedlessdev/corecontrol:1.0.0 \ + # -t haedlessdev/corecontrol:latest \ + # --push \ + # . \ No newline at end of file diff --git a/agent/Dockerfile b/agent/Dockerfile index 706bdd7..2fbaea4 100644 --- a/agent/Dockerfile +++ b/agent/Dockerfile @@ -1,25 +1,45 @@ # --- Build Stage --- -FROM golang:1.19-alpine AS builder +# Multi-Arch Builder mit expliziter Plattform-Angabe +FROM --platform=$BUILDPLATFORM golang:1.19-alpine AS builder + +ARG TARGETOS TARGETARCH WORKDIR /app -ENV GO111MODULE=on +ENV GO111MODULE=on \ + CGO_ENABLED=0 \ + GOOS=$TARGETOS \ + GOARCH=$TARGETARCH COPY go.mod go.sum ./ RUN go mod download COPY . . -RUN go build -o app ./cmd/agent +# Cross-Compile für Zielarchitektur +RUN go build -ldflags="-w -s" -o app ./cmd/agent # --- Run Stage --- +# Multi-Arch Laufzeit-Image FROM alpine:latest -RUN apk --no-cache add ca-certificates +# Notwendig für TLS/SSL-Zertifikate +RUN apk --no-cache add ca-certificates gcompat WORKDIR /root/ COPY --from=builder /app/app . +# Security Hardening +USER nobody:nobody +ENV GOMAXPROCS=1 + CMD ["./app"] - \ No newline at end of file + +# - - BUILD COMMAND - - +# docker buildx build \ +# --platform linux/amd64,linux/arm64,linux/arm/v7 \ +# -t haedlessdev/corecontrol-agent:1.0.0 \ +# -t haedlessdev/corecontrol-agent:latest \ +# --push \ +# . \ No newline at end of file diff --git a/agent/internal/database/database.go b/agent/internal/database/database.go index 42330a1..5423707 100644 --- a/agent/internal/database/database.go +++ b/agent/internal/database/database.go @@ -86,7 +86,7 @@ func LoadNotifications(db *sql.DB) ([]models.Notification, error) { rows, err := db.Query( `SELECT id, enabled, type, "smtpHost", "smtpPort", "smtpFrom", "smtpUser", "smtpPass", "smtpSecure", "smtpTo", "telegramChatId", "telegramToken", "discordWebhook", "gotifyUrl", "gotifyToken", "ntfyUrl", "ntfyToken", - "pushoverUrl", "pushoverToken", "pushoverUser" + "pushoverUrl", "pushoverToken", "pushoverUser", "echobellURL" FROM notification WHERE enabled = true`, ) @@ -103,7 +103,7 @@ func LoadNotifications(db *sql.DB) ([]models.Notification, error) { &n.SMTPHost, &n.SMTPPort, &n.SMTPFrom, &n.SMTPUser, &n.SMTPPass, &n.SMTPSecure, &n.SMTPTo, &n.TelegramChatID, &n.TelegramToken, &n.DiscordWebhook, &n.GotifyUrl, &n.GotifyToken, &n.NtfyUrl, &n.NtfyToken, - &n.PushoverUrl, &n.PushoverToken, &n.PushoverUser, + &n.PushoverUrl, &n.PushoverToken, &n.PushoverUser, &n.EchobellURL, ); err != nil { fmt.Printf("Error scanning notification: %v\n", err) continue diff --git a/agent/internal/models/models.go b/agent/internal/models/models.go index 4411255..6bb27e1 100644 --- a/agent/internal/models/models.go +++ b/agent/internal/models/models.go @@ -21,6 +21,8 @@ type Server struct { CpuUsage sql.NullFloat64 RamUsage sql.NullFloat64 DiskUsage sql.NullFloat64 + GpuUsage sql.NullFloat64 + Temp sql.NullFloat64 Uptime sql.NullString } @@ -51,6 +53,26 @@ type UptimeResponse struct { Value string `json:"value"` } +type GPUResponse struct { + Proc float64 `json:"proc"` +} + +type TemperatureResponse struct { + Composite []struct { + Label string `json:"label"` + Unit string `json:"unit"` + Value float64 `json:"value"` + Warning float64 `json:"warning"` + Critical float64 `json:"critical"` + Type string `json:"type"` + Key string `json:"key"` + } `json:"Composite"` +} + +type TempResponse struct { + Value float64 `json:"value"` +} + type Notification struct { ID int Enabled bool @@ -72,4 +94,5 @@ type Notification struct { PushoverUrl sql.NullString PushoverToken sql.NullString PushoverUser sql.NullString + EchobellURL sql.NullString } diff --git a/agent/internal/notifications/notifications.go b/agent/internal/notifications/notifications.go index 37b459d..0e9ca28 100644 --- a/agent/internal/notifications/notifications.go +++ b/agent/internal/notifications/notifications.go @@ -84,6 +84,10 @@ func (ns *NotificationSender) SendSpecificNotification(n models.Notification, me if n.PushoverUrl.Valid && n.PushoverToken.Valid && n.PushoverUser.Valid { ns.sendPushover(n, message) } + case "echobell": + if n.EchobellURL.Valid { + ns.sendEchobell(n, message) + } } } @@ -237,3 +241,26 @@ func (ns *NotificationSender) sendPushover(n models.Notification, message string fmt.Printf("Pushover: ERROR status code: %d\n", resp.StatusCode) } } + +func (ns *NotificationSender) sendEchobell(n models.Notification, message string) { + jsonData := fmt.Sprintf(`{"message": "%s"}`, message) + req, err := http.NewRequest("POST", n.EchobellURL.String, strings.NewReader(jsonData)) + if err != nil { + fmt.Printf("Echobell: ERROR creating request: %v\n", err) + return + } + + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Do(req) + if err != nil { + fmt.Printf("Echobell: ERROR sending request: %v\n", err) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + fmt.Printf("Echobell: ERROR status code: %d\n", resp.StatusCode) + } +} diff --git a/agent/internal/server/monitor.go b/agent/internal/server/monitor.go index 45d2acc..2403594 100644 --- a/agent/internal/server/monitor.go +++ b/agent/internal/server/monitor.go @@ -8,12 +8,21 @@ import ( "io" "net/http" "strings" + "sync" "time" "github.com/corecontrol/agent/internal/models" "github.com/corecontrol/agent/internal/notifications" ) +// notificationState tracks the last known status for each server +var notificationState = struct { + sync.RWMutex + lastStatus map[int]bool +}{ + lastStatus: make(map[int]bool), +} + // MonitorServers checks and updates the status of all servers func MonitorServers(db *sql.DB, client *http.Client, servers []models.Server, notifSender *notifications.NotificationSender) { var notificationTemplate string @@ -31,16 +40,18 @@ func MonitorServers(db *sql.DB, client *http.Client, servers []models.Server, no fmt.Printf("%s Checking...\n", logPrefix) baseURL := strings.TrimSuffix(server.MonitoringURL.String, "/") - var cpuUsage, ramUsage, diskUsage float64 + var cpuUsage, ramUsage, diskUsage, gpuUsage, temp float64 var online = true var uptimeStr string // Get CPU usage online, cpuUsage = fetchCPUUsage(client, baseURL, logPrefix) if !online { - updateServerStatus(db, server.ID, false, 0, 0, 0, "") - sendStatusChangeNotification(server, online, notificationTemplate, notifSender) - addServerHistoryEntry(db, server.ID, false, 0, 0, 0) + updateServerStatus(db, server.ID, false, 0, 0, 0, 0, 0, "") + if shouldSendNotification(server.ID, online) { + sendStatusChangeNotification(server, online, notificationTemplate, notifSender) + } + addServerHistoryEntry(db, server.ID, false, 0, 0, 0, 0, 0) continue } @@ -51,9 +62,11 @@ func MonitorServers(db *sql.DB, client *http.Client, servers []models.Server, no memOnline, memUsage := fetchMemoryUsage(client, baseURL, logPrefix) if !memOnline { online = false - updateServerStatus(db, server.ID, false, 0, 0, 0, "") - sendStatusChangeNotification(server, online, notificationTemplate, notifSender) - addServerHistoryEntry(db, server.ID, false, 0, 0, 0) + updateServerStatus(db, server.ID, false, 0, 0, 0, 0, 0, "") + if shouldSendNotification(server.ID, online) { + sendStatusChangeNotification(server, online, notificationTemplate, notifSender) + } + addServerHistoryEntry(db, server.ID, false, 0, 0, 0, 0, 0) continue } ramUsage = memUsage @@ -62,29 +75,55 @@ func MonitorServers(db *sql.DB, client *http.Client, servers []models.Server, no diskOnline, diskUsageVal := fetchDiskUsage(client, baseURL, logPrefix) if !diskOnline { online = false - updateServerStatus(db, server.ID, false, 0, 0, 0, "") - sendStatusChangeNotification(server, online, notificationTemplate, notifSender) - addServerHistoryEntry(db, server.ID, false, 0, 0, 0) + updateServerStatus(db, server.ID, false, 0, 0, 0, 0, 0, "") + if shouldSendNotification(server.ID, online) { + sendStatusChangeNotification(server, online, notificationTemplate, notifSender) + } + addServerHistoryEntry(db, server.ID, false, 0, 0, 0, 0, 0) continue } diskUsage = diskUsageVal + // Get GPU usage + _, gpuUsageVal := fetchGPUUsage(client, baseURL, logPrefix) + gpuUsage = gpuUsageVal + + // Get Temperature + _, tempVal := fetchTemperature(client, baseURL, logPrefix) + temp = tempVal + // Check if status changed and send notification if needed - if online != server.Online { + if online != server.Online && shouldSendNotification(server.ID, online) { sendStatusChangeNotification(server, online, notificationTemplate, notifSender) } // Update server status with metrics - updateServerStatus(db, server.ID, online, cpuUsage, ramUsage, diskUsage, uptimeStr) + updateServerStatus(db, server.ID, online, cpuUsage, ramUsage, diskUsage, gpuUsage, temp, uptimeStr) // Add entry to server history - addServerHistoryEntry(db, server.ID, online, cpuUsage, ramUsage, diskUsage) + addServerHistoryEntry(db, server.ID, online, cpuUsage, ramUsage, diskUsage, gpuUsage, temp) - fmt.Printf("%s Updated - CPU: %.2f%%, RAM: %.2f%%, Disk: %.2f%%, Uptime: %s\n", - logPrefix, cpuUsage, ramUsage, diskUsage, uptimeStr) + fmt.Printf("%s Updated - CPU: %.2f%%, RAM: %.2f%%, Disk: %.2f%%, GPU: %.2f%%, Temp: %.2f°C, Uptime: %s\n", + logPrefix, cpuUsage, ramUsage, diskUsage, gpuUsage, temp, uptimeStr) } } +// shouldSendNotification checks if a notification should be sent based on status change +func shouldSendNotification(serverID int, online bool) bool { + notificationState.Lock() + defer notificationState.Unlock() + + lastStatus, exists := notificationState.lastStatus[serverID] + + // If this is the first check or status has changed + if !exists || lastStatus != online { + notificationState.lastStatus[serverID] = online + return true + } + + return false +} + // Helper function to fetch CPU usage func fetchCPUUsage(client *http.Client, baseURL, logPrefix string) (bool, float64) { cpuResp, err := client.Get(fmt.Sprintf("%s/api/4/cpu", baseURL)) @@ -194,6 +233,56 @@ func fetchUptime(client *http.Client, baseURL, logPrefix string) string { return uptimeStr } +// Helper function to fetch GPU usage +func fetchGPUUsage(client *http.Client, baseURL, logPrefix string) (bool, float64) { + gpuResp, err := client.Get(fmt.Sprintf("%s/api/4/gpu", baseURL)) + if err != nil { + fmt.Printf("%s GPU request failed: %v\n", logPrefix, err) + return true, 0 // Return true to indicate server is still online + } + defer gpuResp.Body.Close() + + if gpuResp.StatusCode != http.StatusOK { + fmt.Printf("%s Bad GPU status code: %d\n", logPrefix, gpuResp.StatusCode) + return true, 0 // Return true to indicate server is still online + } + + var gpuData models.GPUResponse + if err := json.NewDecoder(gpuResp.Body).Decode(&gpuData); err != nil { + fmt.Printf("%s Failed to parse GPU JSON: %v\n", logPrefix, err) + return true, 0 // Return true to indicate server is still online + } + + return true, gpuData.Proc +} + +// Helper function to fetch temperature +func fetchTemperature(client *http.Client, baseURL, logPrefix string) (bool, float64) { + tempResp, err := client.Get(fmt.Sprintf("%s/api/4/sensors/label/value/Composite", baseURL)) + if err != nil { + fmt.Printf("%s Temperature request failed: %v\n", logPrefix, err) + return true, 0 // Return true to indicate server is still online + } + defer tempResp.Body.Close() + + if tempResp.StatusCode != http.StatusOK { + fmt.Printf("%s Bad temperature status code: %d\n", logPrefix, tempResp.StatusCode) + return true, 0 // Return true to indicate server is still online + } + + var tempData models.TemperatureResponse + if err := json.NewDecoder(tempResp.Body).Decode(&tempData); err != nil { + fmt.Printf("%s Failed to parse temperature JSON: %v\n", logPrefix, err) + return true, 0 // Return true to indicate server is still online + } + + if len(tempData.Composite) > 0 { + return true, tempData.Composite[0].Value + } + + return true, 0 +} + // Helper function to send notification about status change func sendStatusChangeNotification(server models.Server, online bool, template string, notifSender *notifications.NotificationSender) { status := "offline" @@ -208,14 +297,14 @@ func sendStatusChangeNotification(server models.Server, online bool, template st } // Helper function to update server status -func updateServerStatus(db *sql.DB, serverID int, online bool, cpuUsage, ramUsage, diskUsage float64, uptime string) { +func updateServerStatus(db *sql.DB, serverID int, online bool, cpuUsage, ramUsage, diskUsage, gpuUsage, temp float64, uptime string) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := db.ExecContext(ctx, - `UPDATE server SET online = $1, "cpuUsage" = $2::float8, "ramUsage" = $3::float8, "diskUsage" = $4::float8, "uptime" = $5 - WHERE id = $6`, - online, cpuUsage, ramUsage, diskUsage, uptime, serverID, + `UPDATE server SET online = $1, "cpuUsage" = $2::float8, "ramUsage" = $3::float8, "diskUsage" = $4::float8, "gpuUsage" = $5::float8, "temp" = $6::float8, "uptime" = $7 + WHERE id = $8`, + online, cpuUsage, ramUsage, diskUsage, gpuUsage, temp, uptime, serverID, ) if err != nil { fmt.Printf("Failed to update server status (ID: %d): %v\n", serverID, err) @@ -223,15 +312,16 @@ func updateServerStatus(db *sql.DB, serverID int, online bool, cpuUsage, ramUsag } // Helper function to add server history entry -func addServerHistoryEntry(db *sql.DB, serverID int, online bool, cpuUsage, ramUsage, diskUsage float64) { +func addServerHistoryEntry(db *sql.DB, serverID int, online bool, cpuUsage, ramUsage, diskUsage, gpuUsage, temp float64) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := db.ExecContext(ctx, `INSERT INTO server_history( - "serverId", online, "cpuUsage", "ramUsage", "diskUsage", "createdAt" - ) VALUES ($1, $2, $3, $4, $5, now())`, - serverID, online, fmt.Sprintf("%.2f", cpuUsage), fmt.Sprintf("%.2f", ramUsage), fmt.Sprintf("%.2f", diskUsage), + "serverId", online, "cpuUsage", "ramUsage", "diskUsage", "gpuUsage", "temp", "createdAt" + ) VALUES ($1, $2, $3, $4, $5, $6, $7, now())`, + serverID, online, fmt.Sprintf("%.2f", cpuUsage), fmt.Sprintf("%.2f", ramUsage), + fmt.Sprintf("%.2f", diskUsage), fmt.Sprintf("%.2f", gpuUsage), fmt.Sprintf("%.2f", temp), ) if err != nil { fmt.Printf("Failed to insert server history (ID: %d): %v\n", serverID, err) diff --git a/app/api/notifications/add/route.ts b/app/api/notifications/add/route.ts index ff59cca..db6a271 100644 --- a/app/api/notifications/add/route.ts +++ b/app/api/notifications/add/route.ts @@ -21,12 +21,13 @@ interface AddRequest { pushoverUrl?: string; pushoverToken?: string; pushoverUser?: string; + echobellURL?: string; } export async function POST(request: NextRequest) { try { const body: AddRequest = await request.json(); - const { type, name, smtpHost, smtpPort, smtpSecure, smtpUsername, smtpPassword, smtpFrom, smtpTo, telegramToken, telegramChatId, discordWebhook, gotifyUrl, gotifyToken, ntfyUrl, ntfyToken, pushoverUrl, pushoverToken, pushoverUser } = body; + const { type, name, smtpHost, smtpPort, smtpSecure, smtpUsername, smtpPassword, smtpFrom, smtpTo, telegramToken, telegramChatId, discordWebhook, gotifyUrl, gotifyToken, ntfyUrl, ntfyToken, pushoverUrl, pushoverToken, pushoverUser, echobellURL } = body; const notification = await prisma.notification.create({ data: { @@ -49,6 +50,7 @@ export async function POST(request: NextRequest) { pushoverUrl: pushoverUrl, pushoverToken: pushoverToken, pushoverUser: pushoverUser, + echobellURL: echobellURL } }); diff --git a/app/api/servers/get/route.ts b/app/api/servers/get/route.ts index 08533c7..ac17d42 100644 --- a/app/api/servers/get/route.ts +++ b/app/api/servers/get/route.ts @@ -132,6 +132,8 @@ export async function POST(request: NextRequest) { cpu: number[], ram: number[], disk: number[], + gpu: number[], + temp: number[], online: boolean[] }>(); @@ -142,6 +144,8 @@ export async function POST(request: NextRequest) { cpu: [], ram: [], disk: [], + gpu: [], + temp: [], online: [] }); }); @@ -167,6 +171,8 @@ export async function POST(request: NextRequest) { interval.cpu.push(parseUsageValue(record.cpuUsage)); interval.ram.push(parseUsageValue(record.ramUsage)); interval.disk.push(parseUsageValue(record.diskUsage)); + interval.gpu.push(parseUsageValue(record.gpuUsage)); + interval.temp.push(parseUsageValue(record.temp)); interval.online.push(record.online); } }); @@ -178,6 +184,8 @@ export async function POST(request: NextRequest) { cpu: [], ram: [], disk: [], + gpu: [], + temp: [], online: [] }; @@ -189,6 +197,8 @@ export async function POST(request: NextRequest) { cpu: average(data.cpu), ram: average(data.ram), disk: average(data.disk), + gpu: average(data.gpu), + temp: average(data.temp), online: data.online.length ? data.online.filter(Boolean).length / data.online.length >= 0.5 : null @@ -221,6 +231,14 @@ export async function POST(request: NextRequest) { const data = historyMap.get(d.toISOString())?.disk || []; return data.length ? Math.round((data.reduce((a, b) => a + b) / data.length) * 100) / 100 : null; }), + gpu: intervals.map(d => { + const data = historyMap.get(d.toISOString())?.gpu || []; + return data.length ? Math.round((data.reduce((a, b) => a + b) / data.length) * 100) / 100 : null; + }), + temp: intervals.map(d => { + const data = historyMap.get(d.toISOString())?.temp || []; + return data.length ? Math.round((data.reduce((a, b) => a + b) / data.length) * 100) / 100 : null; + }), online: intervals.map(d => { const data = historyMap.get(d.toISOString())?.online || []; return data.length ? data.filter(Boolean).length / data.length >= 0.5 : null; diff --git a/app/api/servers/monitoring/route.ts b/app/api/servers/monitoring/route.ts index a6b3d7d..2a905d5 100644 --- a/app/api/servers/monitoring/route.ts +++ b/app/api/servers/monitoring/route.ts @@ -11,6 +11,8 @@ export async function GET() { cpuUsage: true, ramUsage: true, diskUsage: true, + gpuUsage: true, + temp: true, uptime: true } }); @@ -21,13 +23,17 @@ export async function GET() { cpuUsage: string | null; ramUsage: string | null; diskUsage: string | null; + gpuUsage: string | null; + temp: string | null; uptime: string | null; }) => ({ id: server.id, online: server.online, - cpuUsage: server.cpuUsage ? parseInt(server.cpuUsage) : 0, - ramUsage: server.ramUsage ? parseInt(server.ramUsage) : 0, - diskUsage: server.diskUsage ? parseInt(server.diskUsage) : 0, + cpuUsage: server.cpuUsage ? parseFloat(server.cpuUsage) : 0, + ramUsage: server.ramUsage ? parseFloat(server.ramUsage) : 0, + diskUsage: server.diskUsage ? parseFloat(server.diskUsage) : 0, + gpuUsage: server.gpuUsage ? parseFloat(server.gpuUsage) : 0, + temp: server.temp ? parseFloat(server.temp) : 0, uptime: server.uptime || "" })); diff --git a/app/dashboard/Dashboard.tsx b/app/dashboard/Dashboard.tsx index 1275bbf..f2b0e53 100644 --- a/app/dashboard/Dashboard.tsx +++ b/app/dashboard/Dashboard.tsx @@ -17,6 +17,7 @@ import { Separator } from "@/components/ui/separator" import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" +import { useTranslations } from "next-intl" interface StatsResponse { serverCountNoVMs: number @@ -26,6 +27,7 @@ interface StatsResponse { } export default function Dashboard() { + const t = useTranslations('Dashboard') const [serverCountNoVMs, setServerCountNoVMs] = useState(0) const [serverCountOnlyVMs, setServerCountOnlyVMs] = useState(0) const [applicationCount, setApplicationCount] = useState(0) @@ -62,22 +64,22 @@ export default function Dashboard() { - Dashboard + {t('Title')}
-

Dashboard

+

{t('Title')}

- Servers - Physical and virtual servers overview + {t('Servers.Title')} + {t('Servers.Description')}
@@ -91,7 +93,7 @@ export default function Dashboard() {
{serverCountNoVMs}
-

Physical Servers

+

{t('Servers.PhysicalServers')}

@@ -102,7 +104,7 @@ export default function Dashboard() {
{serverCountOnlyVMs}
-

Virtual Servers

+

{t('Servers.VirtualServers')}

@@ -115,7 +117,7 @@ export default function Dashboard() { asChild > - Manage Servers + {t('Servers.ManageServers')} @@ -125,15 +127,15 @@ export default function Dashboard() {
- Applications - Manage your deployed applications + {t('Applications.Title')} + {t('Applications.Description')}
{applicationCount}
-

Running applications

+

{t('Applications.OnlineApplications')}

@@ -153,8 +155,8 @@ export default function Dashboard() {
- Uptime - Monitor your service availability + {t('Uptime.Title')} + {t('Uptime.Description')}
@@ -177,7 +179,7 @@ export default function Dashboard() { }} > -

Online applications

+

{t('Uptime.OnlineApplications')}

@@ -188,7 +190,7 @@ export default function Dashboard() { asChild > - View uptime metrics + {t('Uptime.ViewUptimeMetrics')} @@ -198,15 +200,15 @@ export default function Dashboard() {
- Network - Manage network configuration + {t('Network.Title')} + {t('Network.Description')}
{serverCountNoVMs + serverCountOnlyVMs + applicationCount}
-

Active connections

+

{t('Network.ActiveConnections')}

diff --git a/app/dashboard/applications/Applications.tsx b/app/dashboard/applications/Applications.tsx index a8377d5..5b27a54 100644 --- a/app/dashboard/applications/Applications.tsx +++ b/app/dashboard/applications/Applications.tsx @@ -85,6 +85,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useTranslations } from "next-intl"; interface Application { id: number; @@ -112,6 +113,7 @@ interface ApplicationsResponse { } export default function Dashboard() { + const t = useTranslations(); const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [icon, setIcon] = useState(""); @@ -182,31 +184,28 @@ export default function Dashboard() { }; const handleItemsPerPageChange = (value: string) => { - // Clear any existing timer if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } - // Set a new timer debounceTimerRef.current = setTimeout(() => { const newItemsPerPage = parseInt(value); - // Ensure the value is within the valid range if (isNaN(newItemsPerPage) || newItemsPerPage < 1) { - toast.error("Please enter a number between 1 and 100"); + toast.error(t('Applications.Messages.NumberValidation')); return; } const validatedValue = Math.min(Math.max(newItemsPerPage, 1), 100); setItemsPerPage(validatedValue); - setCurrentPage(1); // Reset to first page when changing items per page + setCurrentPage(1); Cookies.set("itemsPerPage-app", String(validatedValue), { expires: 365, path: "/", sameSite: "strict", }); - }, 300); // 300ms delay + }, 300); }; const add = async () => { @@ -221,10 +220,10 @@ export default function Dashboard() { uptimecheckUrl: customUptimeCheck ? uptimecheckUrl : "", }); getApplications(); - toast.success("Application added successfully"); + toast.success(t('Applications.Messages.AddSuccess')); } catch (error: any) { console.log(error.response?.data); - toast.error("Failed to add application"); + toast.error(t('Applications.Messages.AddError')); } }; @@ -244,7 +243,7 @@ export default function Dashboard() { setLoading(false); } catch (error: any) { console.log(error.response?.data); - toast.error("Failed to get applications"); + toast.error(t('Applications.Messages.GetError')); } }; @@ -265,10 +264,10 @@ export default function Dashboard() { try { await axios.post("/api/applications/delete", { id }); getApplications(); - toast.success("Application deleted successfully"); + toast.success(t('Applications.Messages.DeleteSuccess')); } catch (error: any) { console.log(error.response?.data); - toast.error("Failed to delete application"); + toast.error(t('Applications.Messages.DeleteError')); } }; @@ -306,10 +305,10 @@ export default function Dashboard() { }); getApplications(); setEditId(null); - toast.success("Application edited successfully"); + toast.success(t('Applications.Messages.EditSuccess')); } catch (error: any) { console.log(error.response.data); - toast.error("Failed to edit application"); + toast.error(t('Applications.Messages.EditError')); } }; @@ -363,11 +362,11 @@ export default function Dashboard() { - My Infrastructure + {t('Applications.Breadcrumb.MyInfrastructure')} - Applications + {t('Applications.Breadcrumb.Applications')} @@ -376,11 +375,11 @@ export default function Dashboard() {
- Your Applications + {t('Applications.Title')}
- - + - Add an application + {t('Applications.Add.Title')}
- + setName(e.target.value)} />
- +