256 lines
7.5 KiB
Go
256 lines
7.5 KiB
Go
package system
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/atlasos/calypso/internal/common/logger"
|
|
"github.com/atlasos/calypso/internal/tasks"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Handler handles system management API requests
|
|
type Handler struct {
|
|
service *Service
|
|
taskEngine *tasks.Engine
|
|
logger *logger.Logger
|
|
}
|
|
|
|
// NewHandler creates a new system handler
|
|
func NewHandler(log *logger.Logger, taskEngine *tasks.Engine) *Handler {
|
|
return &Handler{
|
|
service: NewService(log),
|
|
taskEngine: taskEngine,
|
|
logger: log,
|
|
}
|
|
}
|
|
|
|
// ListServices lists all system services
|
|
func (h *Handler) ListServices(c *gin.Context) {
|
|
services, err := h.service.ListServices(c.Request.Context())
|
|
if err != nil {
|
|
h.logger.Error("Failed to list services", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list services"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"services": services})
|
|
}
|
|
|
|
// GetServiceStatus retrieves status of a specific service
|
|
func (h *Handler) GetServiceStatus(c *gin.Context) {
|
|
serviceName := c.Param("name")
|
|
|
|
status, err := h.service.GetServiceStatus(c.Request.Context(), serviceName)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "service not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, status)
|
|
}
|
|
|
|
// RestartService restarts a system service
|
|
func (h *Handler) RestartService(c *gin.Context) {
|
|
serviceName := c.Param("name")
|
|
|
|
if err := h.service.RestartService(c.Request.Context(), serviceName); err != nil {
|
|
h.logger.Error("Failed to restart service", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "service restarted successfully"})
|
|
}
|
|
|
|
// GetServiceLogs retrieves journald logs for a service
|
|
func (h *Handler) GetServiceLogs(c *gin.Context) {
|
|
serviceName := c.Param("name")
|
|
linesStr := c.DefaultQuery("lines", "100")
|
|
lines, err := strconv.Atoi(linesStr)
|
|
if err != nil {
|
|
lines = 100
|
|
}
|
|
|
|
logs, err := h.service.GetJournalLogs(c.Request.Context(), serviceName, lines)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get logs", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get logs"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"logs": logs})
|
|
}
|
|
|
|
// GenerateSupportBundle generates a diagnostic support bundle
|
|
func (h *Handler) GenerateSupportBundle(c *gin.Context) {
|
|
userID, _ := c.Get("user_id")
|
|
|
|
// Create async task
|
|
taskID, err := h.taskEngine.CreateTask(c.Request.Context(),
|
|
tasks.TaskTypeSupportBundle, userID.(string), map[string]interface{}{
|
|
"operation": "generate_support_bundle",
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"})
|
|
return
|
|
}
|
|
|
|
// Run bundle generation in background
|
|
go func() {
|
|
ctx := c.Request.Context()
|
|
h.taskEngine.StartTask(ctx, taskID)
|
|
h.taskEngine.UpdateProgress(ctx, taskID, 50, "Collecting system information...")
|
|
|
|
outputPath := "/tmp/calypso-support-bundle-" + taskID
|
|
if err := h.service.GenerateSupportBundle(ctx, outputPath); err != nil {
|
|
h.taskEngine.FailTask(ctx, taskID, err.Error())
|
|
return
|
|
}
|
|
|
|
h.taskEngine.UpdateProgress(ctx, taskID, 100, "Support bundle generated")
|
|
h.taskEngine.CompleteTask(ctx, taskID, "Support bundle generated successfully")
|
|
}()
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{"task_id": taskID})
|
|
}
|
|
|
|
// ListNetworkInterfaces lists all network interfaces
|
|
func (h *Handler) ListNetworkInterfaces(c *gin.Context) {
|
|
interfaces, err := h.service.ListNetworkInterfaces(c.Request.Context())
|
|
if err != nil {
|
|
h.logger.Error("Failed to list network interfaces", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list network interfaces"})
|
|
return
|
|
}
|
|
|
|
// Ensure we return an empty array instead of null
|
|
if interfaces == nil {
|
|
interfaces = []NetworkInterface{}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"interfaces": interfaces})
|
|
}
|
|
|
|
// SaveNTPSettings saves NTP configuration to the OS
|
|
func (h *Handler) SaveNTPSettings(c *gin.Context) {
|
|
var settings NTPSettings
|
|
if err := c.ShouldBindJSON(&settings); err != nil {
|
|
h.logger.Error("Invalid request body", "error", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
// Validate timezone
|
|
if settings.Timezone == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "timezone is required"})
|
|
return
|
|
}
|
|
|
|
// Validate NTP servers
|
|
if len(settings.NTPServers) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "at least one NTP server is required"})
|
|
return
|
|
}
|
|
|
|
if err := h.service.SaveNTPSettings(c.Request.Context(), settings); err != nil {
|
|
h.logger.Error("Failed to save NTP settings", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "NTP settings saved successfully"})
|
|
}
|
|
|
|
// GetNTPSettings retrieves current NTP configuration
|
|
func (h *Handler) GetNTPSettings(c *gin.Context) {
|
|
settings, err := h.service.GetNTPSettings(c.Request.Context())
|
|
if err != nil {
|
|
h.logger.Error("Failed to get NTP settings", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get NTP settings"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"settings": settings})
|
|
}
|
|
|
|
// UpdateNetworkInterface updates a network interface configuration
|
|
func (h *Handler) UpdateNetworkInterface(c *gin.Context) {
|
|
ifaceName := c.Param("name")
|
|
if ifaceName == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "interface name is required"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
IPAddress string `json:"ip_address" binding:"required"`
|
|
Subnet string `json:"subnet" binding:"required"`
|
|
Gateway string `json:"gateway,omitempty"`
|
|
DNS1 string `json:"dns1,omitempty"`
|
|
DNS2 string `json:"dns2,omitempty"`
|
|
Role string `json:"role,omitempty"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.logger.Error("Invalid request body", "error", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
// Convert to service request
|
|
serviceReq := UpdateNetworkInterfaceRequest{
|
|
IPAddress: req.IPAddress,
|
|
Subnet: req.Subnet,
|
|
Gateway: req.Gateway,
|
|
DNS1: req.DNS1,
|
|
DNS2: req.DNS2,
|
|
Role: req.Role,
|
|
}
|
|
|
|
updatedIface, err := h.service.UpdateNetworkInterface(c.Request.Context(), ifaceName, serviceReq)
|
|
if err != nil {
|
|
h.logger.Error("Failed to update network interface", "interface", ifaceName, "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"interface": updatedIface})
|
|
}
|
|
|
|
// GetSystemLogs retrieves recent system logs
|
|
func (h *Handler) GetSystemLogs(c *gin.Context) {
|
|
limitStr := c.DefaultQuery("limit", "30")
|
|
limit, err := strconv.Atoi(limitStr)
|
|
if err != nil || limit <= 0 || limit > 100 {
|
|
limit = 30
|
|
}
|
|
|
|
logs, err := h.service.GetSystemLogs(c.Request.Context(), limit)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get system logs", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get system logs"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"logs": logs})
|
|
}
|
|
|
|
// GetNetworkThroughput retrieves network throughput data from RRD
|
|
func (h *Handler) GetNetworkThroughput(c *gin.Context) {
|
|
// Default to last 5 minutes
|
|
durationStr := c.DefaultQuery("duration", "5m")
|
|
duration, err := time.ParseDuration(durationStr)
|
|
if err != nil {
|
|
duration = 5 * time.Minute
|
|
}
|
|
|
|
data, err := h.service.GetNetworkThroughput(c.Request.Context(), duration)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get network throughput", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get network throughput"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": data})
|
|
}
|