Netpulse_SasS/server/internal/httpapi/ncm_profiles.go
byrsapty 444447d86b
Some checks are pending
CI / web (push) Waiting to run
CI / server (push) Waiting to run
CI / agent (push) Waiting to run
Профілі збору конфігів: перегляд, редагування, створення з прикладів
147 вбудованих профілів досі жили лише в базі — виправити команди під
свою прошивку було ніяк. Тепер є сторінка з пошуком і формою.

Вбудований не правиться, а перекривається власною копією з тим самим
ключем: вони спільні для всіх кабінетів, і правка під одну прошивку не
має міняти їх усім. resolveProfile уже віддавав перевагу тенантському,
тож нічого дописувати не довелося.

Профіль — це три регулярні вирази й перелік команд, і порожня форма з
такими полями не підказує нічого. Тому три робочі заготовки, кнопка
«За зразок» на кожному вбудованому й приклад у кожному полі.

Вирази компілюються при збереженні: інакше про друкарську помилку
дізнаються з бекапу, який завис, чекаючи неіснуючого запрошення.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 16:40:26 +03:00

102 lines
3.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package httpapi
import (
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/netpulse/netpulse/server/internal/store"
)
// Профілі збору конфігів: чим під'єднатись, що спитати, як упізнати
// запрошення й що викинути перед порівнянням версій.
var validTransports = map[string]bool{"ssh": true, "telnet": true}
func (s *Server) handleListNcmProfiles(w http.ResponseWriter, r *http.Request, p *Principal) {
if !requirePerm(w, p, "ncm:read") {
return
}
list, err := s.store.ListNcmProfiles(r.Context(), p.TenantID)
if err != nil {
s.writeStoreError(w, "профілі збору", err)
return
}
writeJSON(w, http.StatusOK, map[string]any{"profiles": list})
}
func (s *Server) handleSaveNcmProfile(w http.ResponseWriter, r *http.Request, p *Principal) {
if !requirePerm(w, p, "ncm:write") {
return
}
var in store.NcmProfile
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
writeError(w, http.StatusBadRequest, "bad_json", "не вдалося прочитати тіло запиту")
return
}
in.ID = r.PathValue("id")
in.Name = strings.TrimSpace(in.Name)
in.Key = strings.TrimSpace(in.Key)
if in.Key == "" {
in.Key = slugify(in.Name)
}
if in.Key == "" {
writeError(w, http.StatusBadRequest, "bad_request", "профіль без назви")
return
}
if in.Transport == "" {
in.Transport = "ssh"
}
if !validTransports[in.Transport] {
writeError(w, http.StatusBadRequest, "bad_transport",
"конфіги знімаються через ssh або telnet")
return
}
// Порожні рядки в переліку команд — сліди зайвого Enter у формі, а
// не команда. Пристрою вони віддадуться як порожній рядок і зіб'ють
// очікування запрошення.
in.Commands = nonEmpty(in.Commands)
in.ScrubPatterns = nonEmpty(in.ScrubPatterns)
in.RedactPatterns = nonEmpty(in.RedactPatterns)
id, err := s.store.SaveNcmProfile(r.Context(), p.TenantID, in)
if err != nil {
if isUniqueViolation(err) {
writeError(w, http.StatusConflict, "duplicate_key",
"профіль із ключем «"+in.Key+у вас уже є")
return
}
s.writeStoreError(w, "профіль збору", err)
return
}
writeJSON(w, http.StatusOK, map[string]any{"id": id})
}
func (s *Server) handleDeleteNcmProfile(w http.ResponseWriter, r *http.Request, p *Principal) {
if !requirePerm(w, p, "ncm:write") {
return
}
err := s.store.DeleteNcmProfile(r.Context(), p.TenantID, r.PathValue("id"))
if errors.Is(err, store.ErrBuiltin) {
writeError(w, http.StatusForbidden, "builtin",
"вбудований профіль спільний для всіх — його не видаляють, а перекривають своїм")
return
}
if err != nil {
s.writeStoreError(w, "профіль збору", err)
return
}
w.WriteHeader(http.StatusNoContent)
}
func nonEmpty(in []string) []string {
out := make([]string, 0, len(in))
for _, x := range in {
if v := strings.TrimSpace(x); v != "" {
out = append(out, v)
}
}
return out
}