299 lines
7.3 KiB
Go
299 lines
7.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"alpine-router/config"
|
|
"alpine-router/network"
|
|
)
|
|
|
|
type apiResp struct {
|
|
Success bool `json:"success"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|
|
|
|
func ok(w http.ResponseWriter, data interface{}) {
|
|
writeJSON(w, http.StatusOK, apiResp{Success: true, Data: data})
|
|
}
|
|
|
|
func fail(w http.ResponseWriter, status int, msg string) {
|
|
writeJSON(w, status, apiResp{Error: msg})
|
|
}
|
|
|
|
func HandleInterfaces(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
names, err := network.GetInterfaces()
|
|
if err != nil {
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
fileCfg, _ := network.ParseConfig()
|
|
|
|
type iface struct {
|
|
*network.InterfaceStats
|
|
Pending bool `json:"pending"`
|
|
Label string `json:"label,omitempty"`
|
|
}
|
|
|
|
appCfg, _ := config.Load()
|
|
|
|
result := make([]iface, 0, len(names))
|
|
existingNames := map[string]bool{}
|
|
for _, name := range names {
|
|
existingNames[name] = true
|
|
s, err := network.GetInterfaceStats(name)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if cfg, ok := fileCfg[name]; ok {
|
|
s.Mode = cfg.Mode
|
|
}
|
|
hasPending := network.GetPendingConfig(name) != nil
|
|
label := ""
|
|
if appCfg != nil && appCfg.Interfaces != nil {
|
|
if ic, ok := appCfg.Interfaces[name]; ok {
|
|
label = ic.Label
|
|
}
|
|
}
|
|
result = append(result, iface{s, hasPending, label})
|
|
}
|
|
|
|
// Also include pending VLAN configs not yet present in the system.
|
|
for name, cfg := range network.GetAllPending() {
|
|
if existingNames[name] || !network.IsVLAN(name) {
|
|
continue
|
|
}
|
|
s := &network.InterfaceStats{
|
|
Name: name,
|
|
State: "unknown",
|
|
Mode: cfg.Mode,
|
|
IPv6: []string{},
|
|
}
|
|
if cfg.Mode == "static" {
|
|
s.IPv4 = cfg.Address
|
|
s.IPv4Mask = cfg.Netmask
|
|
s.Gateway = cfg.Gateway
|
|
}
|
|
label := cfg.Label
|
|
if label == "" && appCfg != nil && appCfg.Interfaces != nil {
|
|
if ic, ok := appCfg.Interfaces[name]; ok {
|
|
label = ic.Label
|
|
}
|
|
}
|
|
result = append(result, iface{s, true, label})
|
|
}
|
|
|
|
ok(w, result)
|
|
}
|
|
|
|
func HandleInterfaceSingle(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
name := strings.TrimPrefix(r.URL.Path, "/api/interfaces/")
|
|
s, err := network.GetInterfaceStats(name)
|
|
if err != nil {
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
ok(w, s)
|
|
}
|
|
|
|
func HandleInterfaceAction(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
suffix := strings.TrimPrefix(r.URL.Path, "/api/interfaces/")
|
|
parts := strings.SplitN(suffix, "/", 2)
|
|
if len(parts) != 2 {
|
|
fail(w, http.StatusBadRequest, "invalid path")
|
|
return
|
|
}
|
|
name, action := parts[0], parts[1]
|
|
|
|
var err error
|
|
switch action {
|
|
case "up":
|
|
err = network.IfUp(name)
|
|
case "down":
|
|
err = network.IfDown(name)
|
|
case "restart":
|
|
err = network.IfRestart(name)
|
|
case "delete":
|
|
if !network.IsVLAN(name) {
|
|
fail(w, http.StatusBadRequest, "delete is only supported for VLAN interfaces")
|
|
return
|
|
}
|
|
network.ClearPendingConfig(name)
|
|
err = network.DeleteVLAN(name)
|
|
default:
|
|
fail(w, http.StatusBadRequest, "unknown action: "+action)
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
ok(w, map[string]string{"message": action + " ok"})
|
|
}
|
|
|
|
func HandleConfig(w http.ResponseWriter, r *http.Request) {
|
|
name := strings.TrimPrefix(r.URL.Path, "/api/config/")
|
|
if name == "" {
|
|
fail(w, http.StatusBadRequest, "interface name required")
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
appCfg, _ := config.Load()
|
|
label := ""
|
|
if appCfg != nil && appCfg.Interfaces != nil {
|
|
if ic, ok2 := appCfg.Interfaces[name]; ok2 {
|
|
label = ic.Label
|
|
}
|
|
}
|
|
if cfg := network.GetPendingConfig(name); cfg != nil {
|
|
if cfg.Label != "" {
|
|
label = cfg.Label
|
|
}
|
|
ok(w, map[string]interface{}{"config": cfg, "pending": true, "label": label})
|
|
return
|
|
}
|
|
fileCfg, err := network.ParseConfig()
|
|
if err != nil {
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if cfg, exists := fileCfg[name]; exists {
|
|
ok(w, map[string]interface{}{"config": cfg, "pending": false, "label": label})
|
|
} else {
|
|
ok(w, map[string]interface{}{
|
|
"config": &network.InterfaceConfig{Name: name, Auto: true, Mode: "dhcp", Extra: map[string]string{}},
|
|
"pending": false,
|
|
"label": label,
|
|
})
|
|
}
|
|
|
|
case http.MethodPost:
|
|
var cfg network.InterfaceConfig
|
|
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
|
|
fail(w, http.StatusBadRequest, "invalid json: "+err.Error())
|
|
return
|
|
}
|
|
cfg.Name = name
|
|
if cfg.Extra == nil {
|
|
cfg.Extra = map[string]string{}
|
|
}
|
|
network.SetPendingConfig(&cfg)
|
|
|
|
appCfg, err := config.Load()
|
|
if err != nil {
|
|
fail(w, http.StatusInternalServerError, "load config: "+err.Error())
|
|
return
|
|
}
|
|
if appCfg.Interfaces == nil {
|
|
appCfg.Interfaces = map[string]*config.InterfaceConfig{}
|
|
}
|
|
appCfg.Interfaces[name] = &config.InterfaceConfig{
|
|
Label: cfg.Label,
|
|
Auto: cfg.Auto,
|
|
Mode: cfg.Mode,
|
|
Address: cfg.Address,
|
|
Netmask: cfg.Netmask,
|
|
Gateway: cfg.Gateway,
|
|
DNS: cfg.DNS,
|
|
Extra: cfg.Extra,
|
|
}
|
|
if err := config.Save(appCfg); err != nil {
|
|
fail(w, http.StatusInternalServerError, "save config.yaml: "+err.Error())
|
|
return
|
|
}
|
|
|
|
ok(w, map[string]string{"message": "saved as pending"})
|
|
|
|
case http.MethodDelete:
|
|
network.ClearPendingConfig(name)
|
|
ok(w, map[string]string{"message": "pending cleared"})
|
|
|
|
default:
|
|
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func HandlePending(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
p := network.GetAllPending()
|
|
names := make([]string, 0, len(p))
|
|
for n := range p {
|
|
names = append(names, n)
|
|
}
|
|
ok(w, names)
|
|
}
|
|
|
|
func HandleApply(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
errs := network.ApplyPending()
|
|
if len(errs) > 0 {
|
|
msgs := map[string]string{}
|
|
for k, e := range errs {
|
|
msgs[k] = e.Error()
|
|
}
|
|
writeJSON(w, http.StatusInternalServerError, apiResp{Error: "partial failure", Data: msgs})
|
|
return
|
|
}
|
|
ok(w, map[string]string{"message": "applied"})
|
|
}
|
|
|
|
func HandleConfigYAML(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
data, err := config.Load()
|
|
if err != nil {
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
ok(w, data)
|
|
|
|
case http.MethodPut:
|
|
var newCfg config.AppConfig
|
|
if err := json.NewDecoder(r.Body).Decode(&newCfg); err != nil {
|
|
fail(w, http.StatusBadRequest, "invalid json: "+err.Error())
|
|
return
|
|
}
|
|
config.EnsureDefaults(&newCfg)
|
|
if err := config.Save(&newCfg); err != nil {
|
|
fail(w, http.StatusInternalServerError, "save: "+err.Error())
|
|
return
|
|
}
|
|
ok(w, map[string]string{"message": "config updated"})
|
|
|
|
default:
|
|
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
} |