Add the Loki query UI, access policies, ingress insights, alert and registry settings, and their backend APIs. Include the related Kubernetes deployment and collector updates required by the integrated application. Validated with go test ./..., npm run build, and npm run lint.
209 lines
4.8 KiB
Go
209 lines
4.8 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"opsxloki/models"
|
|
"opsxloki/pkg/crypto"
|
|
"opsxloki/repository"
|
|
"opsxloki/utils"
|
|
)
|
|
|
|
type HostService struct{}
|
|
|
|
func NewHostService() *HostService {
|
|
return &HostService{}
|
|
}
|
|
|
|
func (s *HostService) Create(name, hostIP string, port int, user, authType, password, privateKey, keyPhrase, os, arch, labels string) (*models.Host, error) {
|
|
if port == 0 {
|
|
port = 22
|
|
}
|
|
host := models.Host{
|
|
ID: repository.GetDB().NextID("hosts"),
|
|
Name: name,
|
|
HostIP: hostIP,
|
|
Port: port,
|
|
User: user,
|
|
AuthType: authType,
|
|
OS: os,
|
|
Arch: arch,
|
|
Labels: labels,
|
|
Status: "pending",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
if password != "" {
|
|
enc, err := crypto.EncryptField(password)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("密码加密失败: %w", err)
|
|
}
|
|
host.Password = enc
|
|
}
|
|
if privateKey != "" {
|
|
enc, err := crypto.EncryptField(privateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("私钥加密失败: %w", err)
|
|
}
|
|
host.PrivateKey = enc
|
|
}
|
|
if keyPhrase != "" {
|
|
enc, err := crypto.EncryptField(keyPhrase)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("私钥口令加密失败: %w", err)
|
|
}
|
|
host.KeyPhrase = enc
|
|
}
|
|
|
|
db := repository.GetDB()
|
|
db.Lock()
|
|
db.Hosts = append(db.Hosts, host)
|
|
db.Unlock()
|
|
db.RequestSave()
|
|
|
|
go s.Validate(host.ID)
|
|
return &host, nil
|
|
}
|
|
|
|
func (s *HostService) Get(id uint) (*models.Host, error) {
|
|
db := repository.GetDB()
|
|
db.RLock()
|
|
defer db.RUnlock()
|
|
for i := range db.Hosts {
|
|
if db.Hosts[i].ID == id {
|
|
h := db.Hosts[i]
|
|
return &h, nil
|
|
}
|
|
}
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
|
|
func (s *HostService) List(page, pageSize int) ([]models.Host, int64, error) {
|
|
db := repository.GetDB()
|
|
db.RLock()
|
|
defer db.RUnlock()
|
|
|
|
total := int64(len(db.Hosts))
|
|
start := (page - 1) * pageSize
|
|
if start < 0 || start >= len(db.Hosts) {
|
|
return []models.Host{}, total, nil
|
|
}
|
|
end := start + pageSize
|
|
if end > len(db.Hosts) {
|
|
end = len(db.Hosts)
|
|
}
|
|
result := make([]models.Host, end-start)
|
|
copy(result, db.Hosts[start:end])
|
|
return result, total, nil
|
|
}
|
|
|
|
func (s *HostService) Update(id uint, updates map[string]interface{}) error {
|
|
db := repository.GetDB()
|
|
db.Lock()
|
|
defer db.Unlock()
|
|
for i := range db.Hosts {
|
|
if db.Hosts[i].ID == id {
|
|
if v, ok := updates["name"].(string); ok {
|
|
db.Hosts[i].Name = v
|
|
}
|
|
if v, ok := updates["status"].(string); ok {
|
|
db.Hosts[i].Status = v
|
|
}
|
|
if v, ok := updates["statusMessage"].(string); ok {
|
|
db.Hosts[i].StatusMessage = v
|
|
}
|
|
if v, ok := updates["os"].(string); ok {
|
|
db.Hosts[i].OS = v
|
|
}
|
|
if v, ok := updates["arch"].(string); ok {
|
|
db.Hosts[i].Arch = v
|
|
}
|
|
if v, ok := updates["hostIP"].(string); ok && v != "" {
|
|
db.Hosts[i].HostIP = v
|
|
}
|
|
if v, ok := updates["port"].(float64); ok && v > 0 {
|
|
db.Hosts[i].Port = int(v)
|
|
}
|
|
if v, ok := updates["user"].(string); ok && v != "" {
|
|
db.Hosts[i].User = v
|
|
}
|
|
if v, ok := updates["labels"].(string); ok {
|
|
db.Hosts[i].Labels = v
|
|
}
|
|
// 凭证编辑约定:非空才更新(留空=保留旧密文)
|
|
for _, field := range []string{"password", "privateKey", "keyPhrase"} {
|
|
if v, ok := updates[field].(string); ok && v != "" {
|
|
if enc, err := crypto.EncryptField(v); err == nil {
|
|
switch field {
|
|
case "password":
|
|
db.Hosts[i].Password = enc
|
|
case "privateKey":
|
|
db.Hosts[i].PrivateKey = enc
|
|
case "keyPhrase":
|
|
db.Hosts[i].KeyPhrase = enc
|
|
}
|
|
}
|
|
}
|
|
}
|
|
db.Hosts[i].UpdatedAt = time.Now()
|
|
db.RequestSave()
|
|
return nil
|
|
}
|
|
}
|
|
return utils.ErrNotFound
|
|
}
|
|
|
|
func (s *HostService) Delete(id uint) error {
|
|
db := repository.GetDB()
|
|
db.Lock()
|
|
defer db.Unlock()
|
|
for i, h := range db.Hosts {
|
|
if h.ID == id {
|
|
db.Hosts = append(db.Hosts[:i], db.Hosts[i+1:]...)
|
|
db.RequestSave()
|
|
return nil
|
|
}
|
|
}
|
|
return utils.ErrNotFound
|
|
}
|
|
|
|
func (s *HostService) Validate(id uint) {
|
|
host, err := s.Get(id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
client, err := NewSSHClientWithAuth(HostSSHAuth(host))
|
|
if err != nil {
|
|
s.Update(id, map[string]interface{}{"status": "error", "statusMessage": err.Error()})
|
|
return
|
|
}
|
|
defer client.Close()
|
|
|
|
if info, err := client.GetHostInfo(ctx); err == nil {
|
|
s.Update(id, map[string]interface{}{"status": "active", "os": info.OS, "arch": info.Arch})
|
|
} else {
|
|
s.Update(id, map[string]interface{}{"status": "active"})
|
|
}
|
|
}
|
|
|
|
func (s *HostService) ExecuteCommand(id uint, command string) (string, string, error) {
|
|
host, err := s.Get(id)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
client, err := NewSSHClientWithAuth(HostSSHAuth(host))
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
defer client.Close()
|
|
return client.Execute(ctx, command)
|
|
}
|