diff --git a/frontend/src/components/admin/servers/RestartServer.tsx b/frontend/src/components/admin/servers/RestartServer.tsx
index 87bded7..3d7d20b 100644
--- a/frontend/src/components/admin/servers/RestartServer.tsx
+++ b/frontend/src/components/admin/servers/RestartServer.tsx
@@ -1,19 +1,72 @@
-import {Button} from "@/components/ui/button";
-import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
-import {RotateCcw} from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import axios from "axios";
+import { RotateCcw } from "lucide-react";
+import { useState } from "react";
+import { toast } from "sonner";
+
+export default function RestartServer(data: any) {
+ const token = localStorage.getItem("auth_token");
+ const [disable, setDisable] = useState(false);
+
+ const handleRestartServer = async (plant: string) => {
+ toast.success(`${plant} is being restarted please wait.`);
+ setDisable(true);
+ let data: any = {
+ processType: "restart",
+ plantToken: plant,
+ };
+
+ const url: string = window.location.host.split(":")[0];
+ if (url === "localhost" || url === "usmcd1vms036") {
+ data = { ...data, remote: "true" };
+ }
+
+ //console.log(data);
+
+ try {
+ const res = await axios.post("/api/server/serviceprocess", data, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+
+ //console.log(res);
+ if (res.status === 200) {
+ setTimeout(() => {
+ toast.success(`${plant} Has beed restarted.`);
+ setDisable(false);
+ }, 3000);
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ };
-export default function RestartServer() {
return (
-
- Restart Server ... Needs added still
+
+ Restart Server, note you might see the screen error
+ out for a second
+
diff --git a/frontend/src/components/admin/servers/ServerPage.tsx b/frontend/src/components/admin/servers/ServerPage.tsx
index e9203a9..81132e7 100644
--- a/frontend/src/components/admin/servers/ServerPage.tsx
+++ b/frontend/src/components/admin/servers/ServerPage.tsx
@@ -171,11 +171,17 @@ export default function ServerPage() {
server={server}
token={token as string}
/>
-
+
+
-
+
+
)}
diff --git a/frontend/src/components/admin/servers/StartServer.tsx b/frontend/src/components/admin/servers/StartServer.tsx
index 99a7fac..dbe995e 100644
--- a/frontend/src/components/admin/servers/StartServer.tsx
+++ b/frontend/src/components/admin/servers/StartServer.tsx
@@ -1,19 +1,71 @@
-import {Button} from "@/components/ui/button";
-import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
-import {Play} from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import axios from "axios";
+import { Play } from "lucide-react";
+import { useState } from "react";
+import { toast } from "sonner";
-export default function StartServer() {
+export default function StartServer(data: any) {
+ const token = localStorage.getItem("auth_token");
+ const [disable, setDisable] = useState(false);
+
+ const handleStartServer = async (plant: string) => {
+ toast.success(`${plant} is being started please wait.`);
+ setDisable(true);
+ let data: any = {
+ processType: "start",
+ plantToken: plant,
+ };
+
+ const url: string = window.location.host.split(":")[0];
+ if (url === "localhost" || url === "usmcd1vms036") {
+ data = { ...data, remote: "true" };
+ }
+
+ //console.log(data);
+
+ try {
+ const res = await axios.post("/api/server/serviceprocess", data, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+
+ //console.log(res);
+ if (res.status === 200) {
+ setTimeout(() => {
+ toast.success(`${plant} Has beed started.`);
+ setDisable(false);
+ }, 3000);
+ }
+ } catch (error: any) {
+ if (error.status === 429) {
+ toast.error(error.response.data.message);
+ setDisable(false);
+ }
+ }
+ };
return (
-
+
+ handleStartServer(data.plantData.plantToken)
+ }
+ >
- Start Server ... Needs added still
+ Start Server
diff --git a/frontend/src/components/admin/servers/StopServer.tsx b/frontend/src/components/admin/servers/StopServer.tsx
index d9b6219..7b0017c 100644
--- a/frontend/src/components/admin/servers/StopServer.tsx
+++ b/frontend/src/components/admin/servers/StopServer.tsx
@@ -7,24 +7,40 @@ import {
} from "@/components/ui/tooltip";
import axios from "axios";
import { Octagon } from "lucide-react";
+import { useState } from "react";
+import { toast } from "sonner";
-export default function StopServer(plantData: any) {
+export default function StopServer(data: any) {
const token = localStorage.getItem("auth_token");
+ const [disable, setDisable] = useState(false);
+
const handleStopServer = async (plant: string) => {
+ toast.success(`${plant} is being stopped please wait.`);
+ setDisable(true);
let data: any = {
processType: "stop",
plantToken: plant,
};
+
const url: string = window.location.host.split(":")[0];
- if (url === "localhost") {
+ if (url === "localhost" || url === "usmcd1vms036") {
data = { ...data, remote: "true" };
}
+
+ //console.log(data);
+
try {
const res = await axios.post("/api/server/serviceprocess", data, {
headers: { Authorization: `Bearer ${token}` },
});
- console.log(res);
+ //console.log(res);
+ if (res.status === 200) {
+ setTimeout(() => {
+ toast.success(`${plant} Has beed stopped.`);
+ setDisable(false);
+ }, 3000);
+ }
} catch (error) {
console.log(error);
}
@@ -37,15 +53,16 @@ export default function StopServer(plantData: any) {
- handleStopServer(plantData.plantToken)
+ handleStopServer(data.plantData.plantToken)
}
>
- Stop Server ... Needs added still
+ Stop Server
diff --git a/server/globalUtils/rateLimiter.ts b/server/globalUtils/rateLimiter.ts
new file mode 100644
index 0000000..cd4349f
--- /dev/null
+++ b/server/globalUtils/rateLimiter.ts
@@ -0,0 +1,77 @@
+import { Hono } from "hono";
+import { type Context, type Next } from "hono";
+
+const app = new Hono();
+
+// --- In-Memory Store for Rate Limits ---
+// This Map will store when each user/key last accessed a rate-limited endpoint.
+// Key: string (e.g., 'ip_address' or 'user_id_endpoint')
+// Value: number (timestamp of last access in milliseconds)
+const rateLimitStore = new Map
();
+
+// --- Configuration ---
+const FIFTEEN_MINUTES_MS = 5 * 60 * 1000; // 15 minutes in milliseconds
+
+// --- Rate Limiting Middleware ---
+export const simpleRateLimit = async (c: Context, next: Next) => {
+ // 1. Define a unique key for the rate limit
+ // For simplicity, we'll use a placeholder for user identification.
+ // In a real app:
+ // - If unauthenticated: Use c.req.header('x-forwarded-for') or c.req.ip (if configured/available)
+ // - If authenticated: Get user ID from c.req.user or similar after authentication middleware
+ const userIdentifier = c.req.header("x-forwarded-for") || "anonymous_user"; // Basic IP-like identifier
+
+ // You can also make the key specific to the route to have different limits per route
+ const routeKey = `${userIdentifier}:${c.req.path}`;
+
+ const now = Date.now();
+ const lastAccessTime = rateLimitStore.get(routeKey);
+
+ if (lastAccessTime) {
+ const timeElapsed = now - lastAccessTime;
+
+ if (timeElapsed < FIFTEEN_MINUTES_MS) {
+ // Limit exceeded
+ const timeRemainingMs = FIFTEEN_MINUTES_MS - timeElapsed;
+ const timeRemainingSeconds = Math.ceil(timeRemainingMs / 1000);
+
+ c.status(429); // HTTP 429: Too Many Requests
+ return c.json({
+ error: "Too Many Requests",
+ message: `Please wait ${timeRemainingSeconds} seconds before trying again.`,
+ retryAfter: timeRemainingSeconds, // Standard header for rate limiting clients
+ });
+ }
+ }
+
+ // If no previous access, or the 15 minutes have passed, allow the request
+ // and update the last access time.
+ rateLimitStore.set(routeKey, now);
+
+ // Continue to the next middleware or route handler
+ await next();
+};
+
+// --- Apply the Middleware to Specific Routes ---
+
+app.get("/", (c) => {
+ return c.text("Welcome! This is a public endpoint.");
+});
+
+// This endpoint will be rate-limited
+app.get("/privileged", simpleRateLimit, (c) => {
+ return c.text("You successfully accessed the privileged endpoint!");
+});
+
+// Another rate-limited endpoint
+app.post("/submit-data", simpleRateLimit, async (c) => {
+ // In a real app, you'd process form data or JSON here
+ return c.text("Data submitted successfully (rate-limited).");
+});
+
+// Example of an endpoint that is NOT rate-limited
+app.get("/health", (c) => {
+ return c.text("Server is healthy!");
+});
+
+export default app;
diff --git a/server/scripts/services.ps1 b/server/scripts/services.ps1
index 9b33606..dc4c030 100644
--- a/server/scripts/services.ps1
+++ b/server/scripts/services.ps1
@@ -4,7 +4,10 @@ param (
[string]$appPath,
[string]$command, # just the command like run start or what ever you have in npm.
[string]$description,
- [string]$remote
+ [string]$remote,
+ [string]$server,
+ [string]$username,
+ [string]$admpass
)
# Example string to run with the parameters in it.
@@ -15,108 +18,174 @@ param (
$nssmPath = $AppPath + "\nssm.exe"
$npmPath = "C:\Program Files\nodejs\npm.cmd" # Path to npm.cmd
+# Convert the plain-text password to a SecureString
+$securePass = ConvertTo-SecureString $admpass -AsPlainText -Force
+$credentials = New-Object System.Management.Automation.PSCredential($username, $securePass)
+
if($remote -eq "true"){
+
+# if(-not $username -or -not $admpass){
+# Write-host "Missing adm account info please try again."
+# exit 1
+# }
+
$plantFunness = {
param ($service, $processType, $location)
# Call your PowerShell script inside plantFunness
- & "$($location)\dist\server\scripts\services.ps1" -serviceName $service -option $processType -appPath $location
+ # & "$($location)\dist\server\scripts\services.ps1" -serviceName $service -option $processType -appPath $location
+
+ if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
+ Write-Host "Error: This script must be run as Administrator."
+ exit 1
+ }
+
+ if(-not $service -or -not $processType){
+ Write-host "The service name or option is missing please enter one of them and try again."
+ exit 1
+ }
+
+ if ($processType -eq "start"){
+ write-host "Starting $($service)."
+ Start-Service $service
+ }
+
+ if ($processType -eq "stop"){
+ write-host "Stoping $($service)."
+ Stop-Service $service
+ }
+
+ if ($processType -eq "restart"){
+ write-host "Stoping $($service) to be restarted"
+ Stop-Service $service
+ Start-Sleep 3 # so we give it enough time to fully stop
+ write-host "Starting $($service)"
+ Start-Service $service
+ }
+
+ if ($processType -eq "prodStop"){
+ if(-not $location){
+ Write-host "The path to the app is missing please add it in and try again."
+ exit 1
+ }
+ & $nssmPath stop $service
+ write-host "Removing $($service)"
+ #& $nssmPath remove $serviceName confirm
+ sc.exe config $service start= disabled
+
+ }
+
+ if ($processType -eq "prodStart"){
+ if(-not $location){
+ Write-host "The path to the app is missing please add it in and try again."
+ exit 1
+ }
+ & $nssmPath start $service
+ write-host "Removing $($service)"
+ #& $nssmPath remove $serviceName confirm
+ sc.exe config $service start= auto
+
+ }
+
}
- Invoke-Command -ComputerName $server -ScriptBlock $plantFunness -ArgumentList $service, $option, $appPath -Credential $credentials
-}
+ Invoke-Command -ComputerName $server -ScriptBlock $plantFunness -ArgumentList $serviceName, $option, $appPath -Credential $credentials
+} else {
-if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
- Write-Host "Error: This script must be run as Administrator."
- exit 1
-}
-
-if(-not $serviceName -or -not $option){
- Write-host "The service name or option is missing please enter one of them and try again."
- exit 1
-}
-
-if ($option -eq "start"){
- write-host "Starting $($serviceName)."
- Start-Service $serviceName
-}
-
-if ($option -eq "stop"){
- write-host "Stoping $($serviceName)."
- Stop-Service $serviceName
-}
-
-if ($option -eq "restart"){
- write-host "Stoping $($serviceName) to be restarted"
- Stop-Service $serviceName
- Start-Sleep 3 # so we give it enough time to fully stop
- write-host "Starting $($serviceName)"
- Start-Service $serviceName
-}
-
-if ($option -eq "delete"){
- if(-not $appPath){
- Write-host "The path to the app is missing please add it in and try again."
- exit 1
+ if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
+ Write-Host "Error: This script must be run as Administrator."
+ exit 1
}
- & $nssmPath stop $serviceName
- write-host "Removing $($serviceName)"
- & $nssmPath remove $serviceName confirm
-
-}
-
-if ($option -eq "prodStop"){
- if(-not $appPath){
- Write-host "The path to the app is missing please add it in and try again."
- exit 1
- }
- & $nssmPath stop $serviceName
- write-host "Removing $($serviceName)"
- #& $nssmPath remove $serviceName confirm
- sc.exe config $serviceName start= disabled
-
-}
-
-if ($option -eq "prodStart"){
- if(-not $appPath){
- Write-host "The path to the app is missing please add it in and try again."
- exit 1
- }
- & $nssmPath start $serviceName
- write-host "Removing $($serviceName)"
- #& $nssmPath remove $serviceName confirm
- sc.exe config $serviceName start= auto
-
-}
-
-if($option -eq "install"){
- if(-not $appPath -or -not $description -or -not $command){
- Write-host "Please check all parameters are passed to install the app.."
+
+ if(-not $serviceName -or -not $option){
+ Write-host "The service name or option is missing please enter one of them and try again."
exit 1
}
+ if ($option -eq "start"){
+ write-host "Starting $($serviceName)."
+ Start-Service $serviceName
+ }
- $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
-
- if(-not $service){
- write-host $serviceName "is not installed we will install it now"
+ if ($option -eq "stop"){
+ write-host "Stoping $($serviceName)."
+ Stop-Service $serviceName
+ }
- Write-Host "Installing $serviceName..."
- & $nssmPath install $serviceName $npmPath $command
- & $nssmPath set $serviceName AppDirectory $appPath
- & $nssmPath set $serviceName Description $description
- # Set recovery options
- sc.exe failure $serviceName reset= 0 actions= restart/5000/restart/5000/restart/5000
- & $nssmPath start $serviceName
- }else{
- write-host $serviceName "is already installed will push the updated info"
- Write-Host "Updating $serviceName..."
+ if ($option -eq "restart"){
+ write-host "Stoping $($serviceName) to be restarted"
+ Stop-Service $serviceName
+ Start-Sleep 3 # so we give it enough time to fully stop
+ write-host "Starting $($serviceName)"
+ Start-Service $serviceName
+ }
+
+ if ($option -eq "delete"){
+ if(-not $appPath){
+ Write-host "The path to the app is missing please add it in and try again."
+ exit 1
+ }
& $nssmPath stop $serviceName
- & $nssmPath set $serviceName AppDirectory $appPath
- & $nssmPath set $serviceName Description $description
- # Set recovery options
- sc.exe failure $serviceName reset= 0 actions= restart/5000/restart/5000/restart/5000
- Start-Sleep 4
+ write-host "Removing $($serviceName)"
+ & $nssmPath remove $serviceName confirm
+
+ }
+
+ if ($option -eq "prodStop"){
+ if(-not $appPath){
+ Write-host "The path to the app is missing please add it in and try again."
+ exit 1
+ }
+ & $nssmPath stop $serviceName
+ write-host "Removing $($serviceName)"
+ #& $nssmPath remove $serviceName confirm
+ sc.exe config $serviceName start= disabled
+
+ }
+
+ if ($option -eq "prodStart"){
+ if(-not $appPath){
+ Write-host "The path to the app is missing please add it in and try again."
+ exit 1
+ }
& $nssmPath start $serviceName
+ write-host "Removing $($serviceName)"
+ #& $nssmPath remove $serviceName confirm
+ sc.exe config $serviceName start= auto
+
+ }
+
+ if($option -eq "install"){
+ if(-not $appPath -or -not $description -or -not $command){
+ Write-host "Please check all parameters are passed to install the app.."
+ exit 1
+ }
+
+
+ $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
+
+ if(-not $service){
+ write-host $serviceName "is not installed we will install it now"
+
+ Write-Host "Installing $serviceName..."
+ & $nssmPath install $serviceName $npmPath $command
+ & $nssmPath set $serviceName AppDirectory $appPath
+ & $nssmPath set $serviceName Description $description
+ # Set recovery options
+ sc.exe failure $serviceName reset= 0 actions= restart/5000/restart/5000/restart/5000
+ & $nssmPath start $serviceName
+ }else{
+ write-host $serviceName "is already installed will push the updated info"
+ Write-Host "Updating $serviceName..."
+ & $nssmPath stop $serviceName
+ & $nssmPath set $serviceName AppDirectory $appPath
+ & $nssmPath set $serviceName Description $description
+ # Set recovery options
+ sc.exe failure $serviceName reset= 0 actions= restart/5000/restart/5000/restart/5000
+ Start-Sleep 4
+ & $nssmPath start $serviceName
+ }
}
}
+
+
diff --git a/server/services/server/controller/server/serviceControl.ts b/server/services/server/controller/server/serviceControl.ts
index 9487b73..b0542c8 100644
--- a/server/services/server/controller/server/serviceControl.ts
+++ b/server/services/server/controller/server/serviceControl.ts
@@ -2,7 +2,6 @@ import { spawn } from "child_process";
import { eq } from "drizzle-orm";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { db } from "../../../../../database/dbclient.js";
-import { settings } from "../../../../../database/schema/settings.js";
import { createLog } from "../../../logger/logger.js";
import { serverData } from "../../../../../database/schema/serverData.js";
import os from "os";
@@ -33,6 +32,12 @@ export const serviceControl = async (
scriptPath = `${process.env.DEVFOLDER}\\dist\\server\\scripts\\services.ps1`;
}
+ console.log(serverInfo[0].serverDNS);
+ const username = process.env.ADMUSER as string;
+ const password = process.env.ADMPASSWORD as string;
+
+ console.log(username, password);
+
const args = [
"-NoProfile",
"-ExecutionPolicy",
@@ -47,6 +52,12 @@ export const serviceControl = async (
serverInfo[0].serverLoc as string,
"-remote",
remote ?? "",
+ "-server",
+ serverInfo[0].serverDNS as string,
+ "-username",
+ username,
+ "-admpass",
+ password,
];
const scriptProcess = spawn("powershell", args);
diff --git a/server/services/server/route/servers/serverContorl.ts b/server/services/server/route/servers/serverContorl.ts
index fac342d..4cc9cf6 100644
--- a/server/services/server/route/servers/serverContorl.ts
+++ b/server/services/server/route/servers/serverContorl.ts
@@ -5,6 +5,7 @@ import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import hasCorrectRole from "../../../auth/middleware/roleCheck.js";
import { serviceControl } from "../../controller/server/serviceControl.js";
import { apiHit } from "../../../../globalUtils/apiHits.js";
+import { simpleRateLimit } from "../../../../globalUtils/rateLimiter.js";
// Define the request body schema
const requestSchema = z.object({
@@ -21,7 +22,11 @@ app.openapi(
summary: "Starts, Stops, Restarts the server.",
method: "post",
path: "/serviceprocess",
- middleware: [authMiddleware, hasCorrectRole(["systemAdmin"], "admin")],
+ middleware: [
+ authMiddleware,
+ hasCorrectRole(["systemAdmin"], "admin"),
+ simpleRateLimit,
+ ],
request: {
body: {
@@ -34,7 +39,7 @@ app.openapi(
}),
async (c) => {
const { data, error } = await tryCatch(c.req.json());
- //apiHit(c, { endpoint: `/serviceprocess`, lastBody: data });
+ //apiHit(c, { endpoint: "/bookin", lastBody: data });
if (error) {
return c.json({
success: false,