Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2061bb29c0 | |||
| fe774f8009 | |||
| 2021399a10 | |||
| 2cefdaa85d | |||
| db0cde6497 | |||
| 89e353a135 | |||
| 1c60201ba8 | |||
| ea183a08b5 | |||
| eca6853b39 | |||
| 5bbd5745a8 | |||
| 0e4f439ba3 | |||
| ed25e4a200 | |||
| 641b503117 | |||
| be381205be | |||
| 36437b8b33 | |||
| 303f0ec9d3 | |||
| c93a9c2710 | |||
| b1f7e903d2 | |||
| 1a524e7b1f | |||
| 7695027b1e | |||
| 557630cf98 |
@@ -19,10 +19,15 @@ jobs:
|
|||||||
- name: Use Go
|
- name: Use Go
|
||||||
uses: https://github.com/actions/setup-go@v6
|
uses: https://github.com/actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: '1.25'
|
||||||
- run: go version
|
- run: go version
|
||||||
- run: ls -lha
|
- run: go mod download
|
||||||
- run: go build -v ./...
|
- run: CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o manage-servers .
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: manage-servers-binary
|
||||||
|
path: manage-servers
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: build
|
needs: build
|
||||||
@@ -40,6 +45,11 @@ jobs:
|
|||||||
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login git.hnrx.net \
|
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login git.hnrx.net \
|
||||||
--username "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
--username "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: manage-servers-binary
|
||||||
|
path: .
|
||||||
|
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -51,6 +61,8 @@ jobs:
|
|||||||
username = "${{ secrets.DOCKER_USERNAME }}"
|
username = "${{ secrets.DOCKER_USERNAME }}"
|
||||||
password = "${{ secrets.DOCKER_PASSWORD }}"
|
password = "${{ secrets.DOCKER_PASSWORD }}"
|
||||||
|
|
||||||
|
- run: ls -lha
|
||||||
|
|
||||||
- name: Build and Push Docker latest Image
|
- name: Build and Push Docker latest Image
|
||||||
if: gitea.ref == 'refs/heads/main'
|
if: gitea.ref == 'refs/heads/main'
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
|
|||||||
+11
-26
@@ -1,36 +1,21 @@
|
|||||||
# Stage 1: Builder
|
FROM docker.hnrx.net/alpine:latest
|
||||||
FROM golang:1.25.4-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
# Create application directory
|
||||||
|
RUN mkdir -p /app/config
|
||||||
|
|
||||||
# Copy go.mod and go.sum first to leverage Docker cache
|
# Set working directory
|
||||||
COPY go.mod .
|
WORKDIR /app/
|
||||||
COPY go.sum .
|
|
||||||
|
|
||||||
# Download dependencies
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy the rest of the application source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the application
|
|
||||||
# CGO_ENABLED=0 is important for static linking, resulting in a smaller image
|
|
||||||
# -o manage-servers specifies the output binary name
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o manage-servers .
|
|
||||||
|
|
||||||
# Stage 2: Runner
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
WORKDIR /root/
|
|
||||||
|
|
||||||
# Copy the compiled binary from the builder stage
|
# Copy the compiled binary from the builder stage
|
||||||
COPY --from=builder /app/manage-servers .
|
COPY manage-servers .
|
||||||
|
|
||||||
# Copy the index.html file for the web server
|
# Copy the index.html file for the web server
|
||||||
COPY --from=builder /app/index.html .
|
COPY index.html .
|
||||||
|
|
||||||
# Copy the servers.json file
|
RUN ls -la /app/
|
||||||
# COPY servers.json .
|
|
||||||
|
# Make the binary executable
|
||||||
|
RUN chmod +x ./manage-servers
|
||||||
|
|
||||||
# Expose the port the web server listens on
|
# Expose the port the web server listens on
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
+3
-1
@@ -2,7 +2,7 @@ version: '3.8'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
manage-servers:
|
manage-servers:
|
||||||
image: git.hnrx.net/hnrx/manage-servers:v0.0.1
|
image: git.hnrx.net/hnrx/manage-servers:v0.1.3
|
||||||
container_name: manage-servers
|
container_name: manage-servers
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
labels:
|
labels:
|
||||||
@@ -13,6 +13,8 @@ services:
|
|||||||
- "traefik.http.routers.manage-servers.tls=true"
|
- "traefik.http.routers.manage-servers.tls=true"
|
||||||
- "traefik.http.routers.manage-servers.tls.certresolver=cloudflare"
|
- "traefik.http.routers.manage-servers.tls.certresolver=cloudflare"
|
||||||
- "traefik.http.services.manage-servers.loadbalancer.server.port=8080"
|
- "traefik.http.services.manage-servers.loadbalancer.server.port=8080"
|
||||||
|
volumes:
|
||||||
|
- /volume1/docker-data/manage-servers:/app/config
|
||||||
networks:
|
networks:
|
||||||
- my-container-macvlan-200
|
- my-container-macvlan-200
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,18 @@ go 1.24.6
|
|||||||
|
|
||||||
require golang.org/x/crypto v0.45.0
|
require golang.org/x/crypto v0.45.0
|
||||||
|
|
||||||
require golang.org/x/sys v0.38.0 // indirect
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/spf13/viper v1.21.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
|
golang.org/x/text v0.31.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,31 @@
|
|||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
+124
-13
@@ -8,7 +8,7 @@
|
|||||||
<div class="container mx-auto p-8">
|
<div class="container mx-auto p-8">
|
||||||
<h1 class="text-4xl font-bold mb-8">Server Management</h1>
|
<h1 class="text-4xl font-bold mb-8">Server Management</h1>
|
||||||
|
|
||||||
<div class="bg-white shadow-md rounded-lg overflow-hidden">
|
<div id="server-table-container" class="bg-white shadow-md rounded-lg overflow-hidden">
|
||||||
<table class="min-w-full leading-normal">
|
<table class="min-w-full leading-normal">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -24,35 +24,131 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="no-servers-message" class="hidden bg-white shadow-md rounded-lg p-8 text-center">
|
||||||
|
<p class="text-gray-600">No servers found. Please check your configuration.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<button id="add-server-button" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
|
||||||
|
Add New Server
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="add-server-form-container" class="hidden mt-8 bg-white shadow-md rounded-lg p-8">
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Add New Server</h2>
|
||||||
|
<form id="add-server-form">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="name" class="block text-gray-700 text-sm font-bold mb-2">Name</label>
|
||||||
|
<input type="text" id="name" name="name" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="ip" class="block text-gray-700 text-sm font-bold mb-2">IP Address</label>
|
||||||
|
<input type="text" id="ip" name="ip" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="mac" class="block text-gray-700 text-sm font-bold mb-2">MAC Address</label>
|
||||||
|
<input type="text" id="mac" name="mac" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="ssh_user" class="block text-gray-700 text-sm font-bold mb-2">SSH User</label>
|
||||||
|
<input type="text" id="ssh_user" name="ssh_user" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="ssh_pass" class="block text-gray-700 text-sm font-bold mb-2">SSH Password</label>
|
||||||
|
<input type="password" id="ssh_pass" name="ssh_pass" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
|
||||||
|
Save Server
|
||||||
|
</button>
|
||||||
|
<button type="button" id="cancel-add-server" class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
fetchServers();
|
fetchServers();
|
||||||
|
|
||||||
|
const addServerButton = document.getElementById("add-server-button");
|
||||||
|
const addServerFormContainer = document.getElementById("add-server-form-container");
|
||||||
|
const cancelAddServerButton = document.getElementById("cancel-add-server");
|
||||||
|
const addServerForm = document.getElementById("add-server-form");
|
||||||
|
|
||||||
|
addServerButton.addEventListener("click", () => {
|
||||||
|
addServerFormContainer.classList.remove("hidden");
|
||||||
|
addServerButton.classList.add("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelAddServerButton.addEventListener("click", () => {
|
||||||
|
addServerFormContainer.classList.add("hidden");
|
||||||
|
addServerButton.classList.remove("hidden");
|
||||||
|
addServerForm.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
addServerForm.addEventListener("submit", function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = new FormData(addServerForm);
|
||||||
|
const serverData = Object.fromEntries(formData.entries());
|
||||||
|
|
||||||
|
fetch("/servers", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(serverData),
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
fetchServers();
|
||||||
|
addServerFormContainer.classList.add("hidden");
|
||||||
|
addServerButton.classList.remove("hidden");
|
||||||
|
addServerForm.reset();
|
||||||
|
} else {
|
||||||
|
alert("Failed to add server.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function fetchServers() {
|
function fetchServers() {
|
||||||
fetch("/servers")
|
fetch("/servers")
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
const tableContainer = document.getElementById("server-table-container");
|
||||||
|
const noServersMessage = document.getElementById("no-servers-message");
|
||||||
const tableBody = document.getElementById("server-table-body");
|
const tableBody = document.getElementById("server-table-body");
|
||||||
|
|
||||||
tableBody.innerHTML = ""; // Clear existing rows
|
tableBody.innerHTML = ""; // Clear existing rows
|
||||||
data.forEach(server => {
|
|
||||||
const row = document.createElement("tr");
|
if (data && data.length > 0) {
|
||||||
row.innerHTML = `
|
tableContainer.classList.remove("hidden");
|
||||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">${server.name}</td>
|
noServersMessage.classList.add("hidden");
|
||||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">${server.ip}</td>
|
|
||||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">${server.mac}</td>
|
data.forEach(server => {
|
||||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm" id="status-${server.name}">Checking...</td>
|
const row = document.createElement("tr");
|
||||||
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
row.innerHTML = `
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">${server.name}</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">${server.ip}</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">${server.mac}</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm" id="status-${server.name}">Checking...</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
<button onclick="wakeServer('${server.name}')" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Wake</button>
|
<button onclick="wakeServer('${server.name}')" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Wake</button>
|
||||||
<button onclick="shutdownServer('${server.name}')" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Shutdown</button>
|
<button onclick="shutdownServer('${server.name}')" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Shutdown</button>
|
||||||
<button onclick="rebootServer('${server.name}')" class="bg-yellow-500 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded">Reboot</button>
|
<button onclick="rebootServer('${server.name}')" class="bg-yellow-500 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded">Reboot</button>
|
||||||
|
<button onclick="deleteServer('${server.name}')" class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
tableBody.appendChild(row);
|
tableBody.appendChild(row);
|
||||||
checkServerStatus(server.name);
|
checkServerStatus(server.name);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
tableContainer.classList.add("hidden");
|
||||||
|
noServersMessage.classList.remove("hidden");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +184,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteServer(serverName) {
|
||||||
|
if (confirm(`Are you sure you want to delete ${serverName}? This action cannot be undone.`)) {
|
||||||
|
fetch(`/servers?name=${serverName}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
fetchServers(); // Refresh the server list
|
||||||
|
} else {
|
||||||
|
alert(`Failed to delete ${serverName}.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh server status every 30 seconds
|
// Refresh server status every 30 seconds
|
||||||
setInterval(fetchServers, 30000);
|
setInterval(fetchServers, 30000);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"manage-servers/server-actions"
|
serveractions "manage-servers/server-actions"
|
||||||
"manage-servers/webserver"
|
"manage-servers/webserver"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
servers, err := loadServers("servers.json")
|
servers, err := loadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error loading servers: %v\n", err)
|
fmt.Printf("Error loading servers: %v\n", err)
|
||||||
return
|
return
|
||||||
@@ -73,16 +74,25 @@ func printUsage() {
|
|||||||
fmt.Println(" serve - Start a web server to manage servers")
|
fmt.Println(" serve - Start a web server to manage servers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadServers(filename string) ([]serveractions.Server, error) {
|
func loadConfig() ([]serveractions.Server, error) {
|
||||||
file, err := os.ReadFile(filename)
|
viper.SetConfigName("servers") // name of config file (without extension)
|
||||||
if err != nil {
|
viper.SetConfigType("json") // or viper.SetConfigType("YAML")
|
||||||
return nil, err
|
viper.AddConfigPath("./config") // path to look for the config file in
|
||||||
|
viper.AddConfigPath(".") // optionally look for config in the working directory
|
||||||
|
err := viper.ReadInConfig() // Find and read the config file
|
||||||
|
if err != nil { // Handle errors reading the config file
|
||||||
|
// Check if the error is that the file doesn't exist
|
||||||
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||||
|
// Config file not found; ignore error and return empty server list
|
||||||
|
return []serveractions.Server{}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("fatal error config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var servers []serveractions.Server
|
var servers []serveractions.Server
|
||||||
err = json.Unmarshal(file, &servers)
|
err = viper.UnmarshalKey("servers", &servers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("unable to decode into struct, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers, nil
|
return servers, nil
|
||||||
@@ -91,6 +101,6 @@ func loadServers(filename string) ([]serveractions.Server, error) {
|
|||||||
func listServers(servers []serveractions.Server) {
|
func listServers(servers []serveractions.Server) {
|
||||||
fmt.Println("Configured Servers:")
|
fmt.Println("Configured Servers:")
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
fmt.Printf(" - %s (%s) - %s\n", s.Name, s.IP, s.Mac)
|
fmt.Printf(" - %s (%s) - %s - User: %s\n", s.Name, s.IP, s.Mac, s.SSHUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-1
@@ -1,3 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"packageNames": [
|
||||||
|
"@actions/artifact",
|
||||||
|
"actions/upload-artifact",
|
||||||
|
"actions/download-artifact"
|
||||||
|
],
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-3
@@ -5,7 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"manage-servers/server-actions"
|
serveractions "manage-servers/server-actions"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebServer struct {
|
type WebServer struct {
|
||||||
@@ -17,7 +19,7 @@ func StartWebServer(servers []serveractions.Server) {
|
|||||||
|
|
||||||
fmt.Println("Starting web server on :8080...")
|
fmt.Println("Starting web server on :8080...")
|
||||||
http.HandleFunc("/", ws.handleRoot)
|
http.HandleFunc("/", ws.handleRoot)
|
||||||
http.HandleFunc("/servers", ws.handleGetServers)
|
http.HandleFunc("/servers", ws.handleServers)
|
||||||
http.HandleFunc("/wake", ws.handleWakeServer)
|
http.HandleFunc("/wake", ws.handleWakeServer)
|
||||||
http.HandleFunc("/shutdown", ws.handleShutdownServer)
|
http.HandleFunc("/shutdown", ws.handleShutdownServer)
|
||||||
http.HandleFunc("/reboot", ws.handleRebootServer)
|
http.HandleFunc("/reboot", ws.handleRebootServer)
|
||||||
@@ -32,11 +34,83 @@ func (ws *WebServer) handleRoot(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.ServeFile(w, r, "index.html")
|
http.ServeFile(w, r, "index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *WebServer) handleGetServers(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebServer) handleServers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
ws.getServers(w, r)
|
||||||
|
case http.MethodPost:
|
||||||
|
ws.addServer(w, r)
|
||||||
|
case http.MethodDelete:
|
||||||
|
ws.deleteServer(w, r)
|
||||||
|
default:
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) getServers(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(ws.servers)
|
json.NewEncoder(w).Encode(ws.servers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) addServer(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var newServer serveractions.Server
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&newServer); err != nil {
|
||||||
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.servers = append(ws.servers, newServer)
|
||||||
|
|
||||||
|
viper.Set("servers", ws.servers)
|
||||||
|
if err := viper.WriteConfig(); err != nil {
|
||||||
|
// If the file does not exist, create it
|
||||||
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||||
|
if err = viper.SafeWriteConfig(); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("Error creating config file: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.Error(w, fmt.Sprintf("Error writing config file: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) deleteServer(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serverName := r.URL.Query().Get("name")
|
||||||
|
if serverName == "" {
|
||||||
|
http.Error(w, "Missing server name", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
var updatedServers []serveractions.Server
|
||||||
|
for _, server := range ws.servers {
|
||||||
|
if server.Name != serverName {
|
||||||
|
updatedServers = append(updatedServers, server)
|
||||||
|
} else {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
http.Error(w, "Server not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.servers = updatedServers
|
||||||
|
viper.Set("servers", ws.servers)
|
||||||
|
|
||||||
|
if err := viper.WriteConfig(); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("Error writing config file: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
func (ws *WebServer) handleWakeServer(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebServer) handleWakeServer(w http.ResponseWriter, r *http.Request) {
|
||||||
serverName := r.URL.Query().Get("name")
|
serverName := r.URL.Query().Get("name")
|
||||||
if serverName == "" {
|
if serverName == "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user