bugfixing

This commit is contained in:
Stefan Etringer 2025-05-06 15:46:43 +00:00
parent 6ea4d31109
commit 2056479224
7 changed files with 326 additions and 127 deletions

105
main.go
View File

@ -2,22 +2,23 @@ package main
import (
"context"
"encoding/json"
"os"
"strings"
"time"
"fmt"
"database/sql"
"fmt"
"html/template"
"log"
"net/http"
"sync"
"syscall"
"gontrol/src/randomname"
"gontrol/src/server/database"
"gontrol/src/logger"
"gontrol/src/randomname"
api "gontrol/src/server/api"
"gontrol/src/server/database"
websocketserver "gontrol/src/server/websocket"
"os/signal"
@ -31,6 +32,8 @@ var (
db *sql.DB
)
type Config struct {
Database struct {
Username string `envconfig:"DB_USERNAME"`
@ -55,21 +58,13 @@ func processError(err error) {
func init() {
tmpl, _ = template.ParseGlob("templates/*.html")
// Sqlite3
err := logger.InitDB("/tmp/gontrol_logs.db")
if err != nil {
log.Fatal(err)
}
}
// func initDB (dbUser string, dbPassword string, dbHost string, dbPort int16, dbName string ) {
// var err error
// dbOpen := fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true", dbUser,dbPassword, dbHost, dbPort, dbName)
// db, err = sql.Open("mysql", dbOpen)
// if err != nil {
// log.Fatal(err)
// }
// if err = db.Ping(); err != nil {
// log.Fatal(err)
// }
// log.Println("Connected to database")
// }
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
t, err := template.ParseFiles(tmpl)
@ -119,7 +114,24 @@ func getHomepage(w http.ResponseWriter, r *http.Request) {
}
func listAgents(w http.ResponseWriter, r *http.Request) {
var agents []api.Agent
agents, err := api.GetAgents(db)
currentAgents := getAgentsStatus()
for _, currAgent := range currentAgents {
for i, agent := range agents {
if currAgent == agent.AgentName {
// log.Printf("%s online", agent.AgentName)
// logger.InsertLog(logger.Debug, fmt.Sprintf("%s online after page refresh", agent.AgentName))
// agents[i].Status = fmt.Sprint("<span class=\"badge bg-success\">Connected</span>")
agents[i].Status = "Connected"
} else {
// agent.Status = fmt.Sprintf("<span class=\"badge bg-danger\">Disconnected</span>")
agents[i].Status = "Disconnected"
}
}
}
if err != nil {
http.Error(w, "Failed to fetch agents", http.StatusInternalServerError)
return
@ -128,6 +140,23 @@ func listAgents(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, "templates/partials/agent_list.html", agents)
}
func getAgentsStatus() []string {
resp, err := http.Get("http://localhost:5555/agentNames")
if err != nil {
log.Println("Error fetching agent names:", err)
logger.InsertLog(logger.Error, "Error fetching agent names from websocketServer")
}
defer resp.Body.Close()
var agentNames []string
if err := json.NewDecoder(resp.Body).Decode(&agentNames); err != nil {
log.Println("Error decoding response:", err)
return []string{}
}
return agentNames
}
func getAgentNames(w http.ResponseWriter, r *http.Request) {
api.GetAgentNames(db, w, r)
return
@ -139,25 +168,38 @@ func getAgentIds(w http.ResponseWriter, r *http.Request) {
}
func logsHandler(w http.ResponseWriter, r *http.Request) {
var logs[] logger.LogEntry
logs, err := logger.FetchLogs(10)
// for i, logLine := range logs {
// logs[i].Message = strings.ReplaceAll(logLine.Message, `"`, `\"`)
// }
if err != nil {
http.Error(w, "Error fetching logs", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "<div>")
// for _, logEntry := range logsToSend {
for _, logEntry := range logs {
fmt.Fprintf(w, "<p><strong>[%s] [%s]</strong> %s</p>", logEntry.Timestamp, logEntry.Level, logEntry.Message)
}
fmt.Fprintf(w, "</div>")
renderTemplate(w, "templates/partials/logs_partial.html", logs)
// fmt.Fprintf(w, "<div>")
// // for _, logEntry := range logsToSend {
// for _, logEntry := range logs {
// fmt.Fprintf(w, "<p><strong>[%s] [%s]</strong> %s</p>", logEntry.Timestamp, logEntry.Level, logEntry.Message)
// }
// fmt.Fprintf(w, "</div>")
// w.Header().Set("Content-Type", "application/json")
// json.NewEncoder(w).Encode(logs)
}
func main() {
// sqlite3 has been initialized in init()
defer logger.CloseDB()
var cfg Config
readEnv(&cfg)
@ -176,18 +218,11 @@ func main() {
webMux.HandleFunc("/agents/{agentId}", agentsHandler)
webMux.HandleFunc("/logs", logsHandler)
// Sqlite3
err := logger.InitDB("/tmp/gontrol_logs.db")
if err != nil {
log.Fatal(err)
}
defer logger.CloseDB()
// initDB (cfg.Database.Username, cfg.Database.Password, cfg.Database.Host, cfg.Database.Port, cfg.Database.Name)
// mysql
db = database.InitDB (cfg.Database.Username, cfg.Database.Password, cfg.Database.Host, cfg.Database.Port, cfg.Database.Name)
defer db.Close()
name := randomname.GenerateRandomName()
fmt.Println(name)
fmt.Sprintf("Server instance: %s", name)
webServer := &http.Server {
Addr: ":3333",
@ -202,7 +237,7 @@ func main() {
if err := websocketServer.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Websocket server failed: %s", err)
}
err = logger.InsertLog(logger.Info, logLine)
err := logger.InsertLog(logger.Info, logLine)
if err != nil {
log.Println("Error inserting log:", err)
}
@ -218,7 +253,7 @@ func main() {
log.Fatalf("Web server failed: %s", err)
}
err = logger.InsertLog(logger.Info, logLine)
err := logger.InsertLog(logger.Info, logLine)
if err != nil {
log.Println("Error inserting log:", err)
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
// "net/http"
"strings"
"sync"
"time"
@ -17,16 +18,16 @@ var (
lite_dbMutex sync.Mutex
logMutex sync.Mutex
logLimit = 10
logLimit = 100
// LogEntries []string
)
const (
Debug LogLevel = "DEBUG"
Info LogLevel = "INFO"
Warning LogLevel = "WARNING"
Error LogLevel = "ERROR"
Fatal LogLevel = "FATAL"
Debug LogLevel = "debug"
Info LogLevel = "info"
Warning LogLevel = "warning"
Error LogLevel = "error"
Fatal LogLevel = "fatal"
)
type LogLevel string
@ -74,6 +75,8 @@ func InsertLog(level LogLevel, message string) error {
return fmt.Errorf("Error starting transaction: %w", err)
}
message = strings.ReplaceAll(message, `"`, `\"`)
// insertQuery := `INSERT INTO logs (message) VALUES (?)`
insertQuery := `INSERT INTO logs (message, level) VALUES (?, ?)`
// _, err := Lite_db.Exec(insertQuery, message, level)

View File

@ -60,21 +60,22 @@ func registerAgent(agentName, agentId, agentIp, agentType string) error {
resp, err := http.PostForm(registerURL, form)
if err != nil {
log.Printf("Error registering agent: %v", err)
logger.InsertLog(logger.Error, fmt.Sprintf("Error registering agent: %v", err))
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusCreated {
logLine := fmt.Sprintf("Agent %s successfully registered.", agentName)
log.Printf(logLine)
// logLine = logger.ToLog(logLine)
logger.InsertLog(logger.Info, logLine)
log.Printf("Agent %s successfully registered.", agentName)
logger.InsertLog(logger.Info, fmt.Sprintf("Agent %s successfully registered.", agentName))
return nil
} else if resp.StatusCode == http.StatusOK {
log.Printf("Agent %s already registered.", agentName)
logger.InsertLog(logger.Info, fmt.Sprintf("Agent %s already registered.", agentName))
return nil
} else {
log.Printf("Failed to register agent, status: %v", resp.Status)
logger.InsertLog(logger.Error, fmt.Sprintf("Failed to register agent, status: %v", resp.Status))
return err
}
@ -87,6 +88,7 @@ func getAgentDetails(agentId string) (*api.Agent, error) {
resp, err := http.Get(agentURL)
if err != nil {
log.Printf("Failed to make GET request: %s", err)
logger.InsertLog(logger.Error, fmt.Sprintf("Failed to make GET request: %s", err))
return nil, err
}
defer resp.Body.Close()
@ -94,6 +96,7 @@ func getAgentDetails(agentId string) (*api.Agent, error) {
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
log.Printf("Failed to parse HTML: %s", err)
logger.InsertLog(logger.Error, fmt.Sprintf("Failed to parse HTML: %s", err))
return nil, err
}
@ -104,6 +107,7 @@ func getAgentDetails(agentId string) (*api.Agent, error) {
agent.AgentID, err = strconv.Atoi(strings.TrimSpace(strings.TrimPrefix(text, "ID:")))
if err != nil {
log.Printf("Converting string to integer failed in getAgentDetails(): %s", err)
logger.InsertLog(logger.Error, fmt.Sprintf("Converting string to integer failed in getAgentDetails(): %s", err))
}
} else if strings.HasPrefix(text, "Name:") {
agent.AgentName = strings.TrimSpace(strings.TrimPrefix(text, "Name:"))
@ -130,24 +134,28 @@ func getAgentIds() ([]string, error) {
resp, err := http.Get(idURL)
if err != nil {
log.Printf("Failed to make GET request: %s", err)
logger.InsertLog(logger.Error, fmt.Sprintf("Failed to make GET request: %s", err))
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Printf("Unexpected status code: %d", resp.StatusCode)
logger.InsertLog(logger.Info, fmt.Sprintf("Unexpected status code: %d", resp.StatusCode))
return nil, nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("Failed to read response body: %s", err)
logger.InsertLog(logger.Error, fmt.Sprintf("Failed to read response body: %s", err))
return nil, err
}
var agentIds []string
if err := json.Unmarshal(body, &agentIds); err != nil {
log.Printf("Failed to parse JSON response: %s", err)
logger.InsertLog(logger.Error, fmt.Sprintf("Failed to parse JSON response: %s", err))
return nil, err
}
@ -159,6 +167,7 @@ func (wsh webSocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
c, err := wsh.upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error %s when upgrading connection to websocket", err)
logger.InsertLog(logger.Error, fmt.Sprintf("Error %s when upgrading connection to websocket", err))
return
}
@ -183,13 +192,13 @@ func (wsh webSocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
if agentName == "" || agentIP == "" {
log.Printf("Missing agentName or IPv4Address in query parameters")
logger.InsertLog(logger.Info, fmt.Sprintf("Missing agentName or IPv4Address in query parameters"))
c.Close()
return
}
logLine := fmt.Sprintf("Agent %s connected: %s (%s)", agentId, agentName, agentIP)
log.Printf(logLine)
logger.InsertLog(logger.Info, logLine)
log.Printf("Agent %s connected: %s (%s)", agentId, agentName, agentIP)
logger.InsertLog(logger.Info, fmt.Sprintf("Agent %s connected: %s (%s)", agentId, agentName, agentIP))
agentSocketsMutex.Lock()
agentSockets[agentName] = c
@ -200,22 +209,19 @@ func (wsh webSocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
delete(agentSockets, agentName)
agentSocketsMutex.Unlock()
c.Close()
logLine := fmt.Sprintf("Agent disconnected: %s (%s)", agentName, agentIP)
log.Printf(logLine)
logger.InsertLog(logger.Info, logLine)
log.Printf("Agent disconnected: %s (%s)", agentName, agentIP)
logger.InsertLog(logger.Info, fmt.Sprintf("Agent disconnected: %s (%s)", agentName, agentIP))
}()
for {
_, message, err := c.ReadMessage()
if err != nil {
logLine := fmt.Sprintf("Error reading from agent %s: %v", agentName, err)
log.Printf(logLine)
logger.InsertLog(logger.Error, logLine)
log.Printf("Error reading from agent %s: %v", agentName, err)
logger.InsertLog(logger.Error, fmt.Sprintf("Error reading from agent %s: %v", agentName, err))
break
}
logLine := fmt.Sprintf("Message from agent %s: %s", agentName, message)
log.Printf(logLine)
logger.InsertLog(logger.Debug, logLine)
log.Printf("Message from agent %s: %s", agentName, message)
logger.InsertLog(logger.Info, fmt.Sprintf("Message from agent %s: %s", agentName, message))
if ch, ok := responseChannels.Load(agentName); ok {
responseChan := ch.(chan string)
@ -233,6 +239,7 @@ var executeCommand http.HandlerFunc = func(w http.ResponseWriter, r *http.Reques
err := r.ParseForm()
if err != nil {
http.Error(w, "Invalid form data", http.StatusBadRequest)
logger.InsertLog(logger.Info, "Invalid form data")
return
}
@ -245,6 +252,7 @@ var executeCommand http.HandlerFunc = func(w http.ResponseWriter, r *http.Reques
if !ok {
http.Error(w, "Agent not connected", http.StatusNotFound)
logger.InsertLog(logger.Info, "Agent not connected")
return
}
@ -262,6 +270,7 @@ var executeCommand http.HandlerFunc = func(w http.ResponseWriter, r *http.Reques
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
http.Error(w, "Failed to send command to the agent", http.StatusInternalServerError)
logger.InsertLog(logger.Error, "Failed to send command to the agent")
return
}
@ -275,13 +284,15 @@ var executeCommand http.HandlerFunc = func(w http.ResponseWriter, r *http.Reques
payload, ok := parsedResponse["payload"]
if !ok {
http.Error(w, "Invalid response structure", http.StatusInternalServerError)
logger.InsertLog(logger.Error, "Invalid response structure")
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(payload))
case <- time.After(10 * time.Second):
http.Error(w, "Agent repsonse timed out", http.StatusGatewayTimeout)
http.Error(w, "Agent response timed out", http.StatusGatewayTimeout)
logger.InsertLog(logger.Info, "Agent response timed out")
}
}

View File

@ -11,45 +11,119 @@
<title>g2: gommand & gontrol</title>
<script>
// Query Agents for the Dropdown Menu
<!-- document.addEventListener('DOMContentLoaded', () => { -->
<!-- fetch('/agentNames') -->
<!-- .then(response => response.json()) -->
<!-- .then(agentNames => { -->
<!-- const dropdown = document.getElementById('agentName'); -->
<!-- agentNames.forEach(name => { -->
<!-- const option = document.createElement('option'); -->
<!-- option.value = name; -->
<!-- option.textContent = name; -->
<!-- dropdown.appendChild(option); -->
<!-- }); -->
<!-- }) -->
<!-- .catch(error => console.error('Error fetching agent names:', error)); -->
<!-- }); -->
<!-- // Query agents currently connected to the websocket and put status into the table -->
<!-- const updateAgentStatuses = () => { -->
<!-- fetch('http://localhost:5555/agentNames') -->
<!-- .then(response => response.json()) -->
<!-- .then(agentNames => { -->
<!-- console.log("Agent names fetched:", agentNames); -->
<!-- const tableRows = document.querySelectorAll('#agentList table tbody tr'); -->
<!-- tableRows.forEach(row => { -->
<!-- const nameCell = row.querySelector('td:nth-child(2)'); -->
<!-- const statusCell = row.querySelector('td:nth-child(5)'); -->
<!-- if (nameCell && statusCell) { -->
<!-- const agentName = nameCell.textContent.trim(); -->
<!-- if (agentNames.includes(agentName)) { -->
<!-- statusCell.innerHTML = '<span class="badge bg-success">Connected</span>'; -->
<!-- } else { -->
<!-- statusCell.innerHTML = '<span class="badge bg-danger">Disconnected</span>'; -->
<!-- } -->
<!-- } -->
<!-- }); -->
<!-- }) -->
<!-- .catch(error => console.error('Error fetching agent names:', error)); -->
<!-- }; -->
<!-- updateAgentStatuses(); -->
<!-- setInterval(updateAgentStatuses, 5000); -->
<!-- document.body.addEventListener('htmx:afterSwap', function(evt) { -->
<!-- if (evt.detail.xhr.status === 200) { -->
<!-- const tableRows = document.querySelectorAll('#agentList table tbody tr'); -->
<!-- tableRows.forEach(row => { -->
<!-- const nameCell = row.querySelector('td:nth-child(2)'); -->
<!-- const statusCell = row.querySelector('td:nth-child(5)'); -->
<!-- if (nameCell && statusCell) { -->
<!-- const agentName = nameCell.textContent.trim(); -->
<!-- if ("Connected" === statusCell.innerHTML) { -->
<!-- statusCell.innerHTML = '<span class="badge bg-success">Connected</span>'; -->
<!-- } else { -->
<!-- statusCell.innerHTML = '<span class="badge bg-danger">Disconnected</span>'; -->
<!-- } -->
<!-- } -->
<!-- } -->
<!-- } -->
<!-- }); -->
document.addEventListener('DOMContentLoaded', () => {
fetch('/agentNames')
.then(response => response.json())
.then(agentNames => {
const dropdown = document.getElementById('agentName');
agentNames.forEach(name => {
const option = document.createElement('option');
document.body.addEventListener('htmx:afterSwap', function(event) {
if (event.target.id === "agentList") {
updateAgentDropdown();
}
});
function updateAgentDropdown() {
const select = document.getElementById("agentName");
const optionValues = Array.from(select.options).map(opt => opt.value);
const rows = document.querySelectorAll("#agentList tbody tr");
rows.forEach(row => {
const status = row.cells[4].textContent.trim();
const name = row.cells[1].textContent.trim();
if (status === "Connected") {
row.cells[4].innerHTML = '<span class="badge bg-success">Connected</span>';
const option = document.createElement("option");
if (!(optionValues.includes(name))) {
option.value = name;
option.textContent = name;
dropdown.appendChild(option);
});
})
.catch(error => console.error('Error fetching agent names:', error));
});
// Query agents currently connected to the websocket and put status into the table
const updateAgentStatuses = () => {
fetch('http://localhost:5555/agentNames')
.then(response => response.json())
.then(agentNames => {
console.log("Agent names fetched:", agentNames);
const tableRows = document.querySelectorAll('#agentList table tbody tr');
tableRows.forEach(row => {
const nameCell = row.querySelector('td:nth-child(2)');
const statusCell = row.querySelector('td:nth-child(5)');
if (nameCell && statusCell) {
const agentName = nameCell.textContent.trim();
if (agentNames.includes(agentName)) {
statusCell.innerHTML = '<span class="badge bg-success">Connected</span>';
} else {
statusCell.innerHTML = '<span class="badge bg-danger">Disconnected</span>';
select.appendChild(option);
}
}
if (status === "Disconnected") {
row.cells[4].innerHTML = '<span class="badge bg-danger">Disconnected</span>';
const option = Array.from(select.options).find(opt => opt.value === name);
if(option) {
select.removeChild(option);
}
}
});
})
.catch(error => console.error('Error fetching agent names:', error));
};
updateAgentStatuses();
setInterval(updateAgentStatuses, 1000);
}
});
</script>
<style>
.log-info {
color: green;
}
.log-warning {
color: orange;
}
.log-error {
color: red;
}
.log-fatal {
color: blue;
}
.log-debug {
color: violet;
}
</style>
</head>
<body>
<div class="container">
@ -58,15 +132,17 @@
<h2>Agents</h2>
<!-- Agent List -->
<!-- <div id="agentList" hx-get="/agents" hx-trigger="load, every 2s" hx-swap="innerHTML"></div> -->
<div id="agentList" hx-get="/agents" hx-trigger="load" hx-swap="innerHTML"></div>
<div id="agentList" hx-get="/agents" hx-trigger="load, every 2s" hx-swap="innerHTML"></div>
<!-- <div id="agentList" hx-get="/agents" hx-trigger="load" hx-swap="innerHTML"></div> -->
<!-- Agent Commands -->
<div id="agentCommands">
<h3>Command Execution</h3>
<form hx-post="http://localhost:5555/executeCommand" hx-target="#commandOutput" hx-encoding="application/x-www-form-urlencoded" hx-swap="innerHTML">
<div class="mb-3">
<label for="agentName" class="form-label">Agent Name</label>
<select class="form-select" id="agentName" name="agentName" required>
<!-- <select class="form-select" id="agentName" name="agentName" required> -->
<select id="agentName" class="form-select" name="agentName" hx-on="htmx:afterSwap:updateAgentDropdown" required>
<option value="" disabled selected>Select an Agent</option>
<!-- Dynamically populated with agent names -->
</select>
</div>
@ -80,33 +156,46 @@
</div>
<!-- Add Agent Form -->
<button class="btn btn-primary mt-3" data-bs-toggle="collapse" data-bs-target="#addAgentForm">Add Agent</button>
<div id="addAgentForm" class="collapse mt-2">
<form hx-post="/agents" hx-target="#agentList" hx-swap="innerHTML">
<div class="mb-3">
<label for="agentId" class="form-label">Agent Id</label>
<input type="text" class="form-control" id="agentId" name="agentId" required>
</div>
<div class="mb-3">
<label for="agentName" class="form-label">Agent Name</label>
<input type="text" class="form-control" id="agentName" name="agentName" required>
</div>
<div class="mb-3">
<label for="IPv4Address" class="form-label">IPv4 Address</label>
<input type="text" class="form-control" id="IPv4Address" name="IPv4Address" required>
</div>
<div class="mb-3">
<label for="initialContact" class="form-label">Initial Contact</label>
<input type="datetime-local" class="form-control" id="initialContact" name="initialContact" required>
</div>
<div class="mb-3">
<label for="lastContact" class="form-label">Last Contact</label>
<input type="datetime-local" class="form-control" id="lastContact" name="lastContact" required>
</div>
<button type="submit" class="btn btn-success">Add Agent</button>
</form>
</div>
</div>
<!-- <button class="btn btn-primary mt-3" data-bs-toggle="collapse" data-bs-target="#addAgentForm">Add Agent</button> -->
<!-- <div id="addAgentForm" class="collapse mt-2"> -->
<!-- <form hx-post="/agents" hx-target="#agentList" hx-swap="innerHTML"> -->
<!-- <div class="mb-3"> -->
<!-- <label for="agentId" class="form-label">Agent Id</label> -->
<!-- <input type="text" class="form-control" id="agentId" name="agentId" required> -->
<!-- </div> -->
<!-- <div class="mb-3"> -->
<!-- <label for="agentName" class="form-label">Agent Name</label> -->
<!-- <input type="text" class="form-control" id="agentName" name="agentName" required> -->
<!-- </div> -->
<!-- <div class="mb-3"> -->
<!-- <label for="IPv4Address" class="form-label">IPv4 Address</label> -->
<!-- <input type="text" class="form-control" id="IPv4Address" name="IPv4Address" required> -->
<!-- </div> -->
<!-- <div class="mb-3"> -->
<!-- <label for="initialContact" class="form-label">Initial Contact</label> -->
<!-- <input type="datetime-local" class="form-control" id="initialContact" name="initialContact" required> -->
<!-- </div> -->
<!-- <div class="mb-3"> -->
<!-- <label for="lastContact" class="form-label">Last Contact</label> -->
<!-- <input type="datetime-local" class="form-control" id="lastContact" name="lastContact" required> -->
<!-- </div> -->
<!-- <button type="submit" class="btn btn-success">Add Agent</button> -->
<!-- </form> -->
<!-- </div> -->
<!-- </div> -->
<!-- Logs Section -->
<h3>Logs</h3>
<div id="logs-container" hx-get="/logs" hx-target="#logs-container" hx-swap="innerHTML" hx-trigger="every 3s">
<!-- Logs will be injected here -->
<!-- </div> -->
<!-- <button hx-get="/logs" hx-target="#logs-container" hx-swap="innerHTML"> -->
<!-- Load Logs -->
<!-- </button> -->
<!-- <button hx-get="/logs" hx-target="#logs-container" hx-swap="innerHTML" hx-trigger="every 8s"> -->
<!-- Auto-Refresh Logs -->
<!-- </button> -->
<!-- </div> -->
<!-- Agent Details -->
<div class="col" id="agentDetails">

View File

@ -5,6 +5,23 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Server Logs</title>
<script src="https://unpkg.com/htmx.org@1.8.5"></script>
<style>
.log-info {
color: green;
}
.log-warning {
color: orange;
}
.log-error {
color: red;
}
.log-fatal {
color: blue;
}
.log-debug {
color: violet;
}
</style>
</head>
<body>
<h1>Web Server Logs</h1>
@ -13,12 +30,51 @@
<!-- Logs will be injected here -->
</div>
<button hx-get="/logs" hx-target="#logs-container" hx-swap="outerHTML">
<button hx-get="/logs" hx-target="#logs-container" hx-swap="innerHTML">
Load Logs
</button>
<button hx-get="/logs" hx-target="#logs-container" hx-swap="outerHTML" hx-trigger="every 2s">
<button hx-get="/logs" hx-target="#logs-container" hx-swap="innerHTML" hx-trigger="every 2s">
Auto-Refresh Logs
</button>
<script>
function renderLogs(logs) {
const container = document.getElementById('logs-container');
container.innerHTML = '';
logs.forEarch(log => {
const logElement = document.createElement('p');
logElement.innerHTML = `<strong>ts=${log.timestamp} level=${log.level}</strong> msg=${log.message}`;
if (log.level ==== 'INFO') {
logElement.classList.add('log-info');
} else if (log.level === 'WARNING') {
logElement.classList.add('log'warning);
} else if (log.level === 'ERROR') {
logElement.classList.add('log-warning');
} else if (log.level === 'FATAL') {
logElement.classList.add('log-fatal');
} else if (log.level === 'DEBUG') {
logElement.classList.add('log-debug');
}
container.appendChild(logElement);
});
}
function fetchLogs() {
fetch('/logs')
.then(response -> response.json())
.then(data => renderLogs(data))
.catch(error -> console.error('Error fetching logs:', error));
}
document.body.addEventListener('htmx:afterSwap', function(event){
if (event.target.id === 'logs-container') {
fetchLogs();
}
});
</script>
</body>
</html>

View File

@ -22,7 +22,7 @@
<!-- <td>{{.LastContact}}</td> -->
<!-- <td><span class="badge bg-success">Connected</span></td> -->
<!-- <td><span class="badge bg-danger">Disconnected</span></td> -->
<td></td>
<td>{{.Status}}</td>
<td>
<button class="btn btn-warning" hx-get="/agents/{{.AgentID}}" hx-target="#agentDetails" hx-swap="innerHTML">View</button>

View File

@ -0,0 +1,5 @@
{{range .}}
<p class="log-{{.Level}}">
<strong>ts={{.Timestamp}} level={{.Level}}</strong> msg="{{.Message}}"
</p>
{{end}}