platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
214
selective-vpn-api/app/transport_handlers_actions_virtual.go
Normal file
214
selective-vpn-api/app/transport_handlers_actions_virtual.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleTransportVirtualClientAction(w http.ResponseWriter, r *http.Request, id, action string) bool {
|
||||
cid := sanitizeID(id)
|
||||
if !isTransportPolicyVirtualClientID(cid) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "health":
|
||||
handleTransportVirtualClientHealthAction(w, r, cid)
|
||||
case "metrics":
|
||||
handleTransportVirtualClientMetricsAction(w, r, cid)
|
||||
case "provision":
|
||||
handleTransportVirtualClientProvisionAction(w, r, cid)
|
||||
case "start", "stop", "restart":
|
||||
handleTransportVirtualClientLifecycleAction(w, r, cid, action)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func handleTransportVirtualClientCardGet(w http.ResponseWriter, r *http.Request, id string) bool {
|
||||
cid := sanitizeID(id)
|
||||
if !isTransportPolicyVirtualClientID(cid) {
|
||||
return false
|
||||
}
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return true
|
||||
}
|
||||
item := transportVirtualAdGuardSnapshot()
|
||||
writeJSON(w, http.StatusOK, TransportClientsResponse{OK: true, Message: "ok", Item: &item})
|
||||
return true
|
||||
}
|
||||
|
||||
func transportVirtualClientReadOnlyResponse() (int, TransportClientsResponse) {
|
||||
return http.StatusOK, TransportClientsResponse{
|
||||
OK: false,
|
||||
Message: "virtual control-plane client is read-only",
|
||||
}
|
||||
}
|
||||
|
||||
func transportVirtualAdGuardSnapshot() TransportClient {
|
||||
if item, ok := resolveTransportPolicyVirtualClient(transportPolicyTargetAdGuardID); ok {
|
||||
return item
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
item := buildTransportPolicyAdGuardTargetFromObservation("inactive", "DISCONNECTED", "", now)
|
||||
item.Health.LastError = "adguard autoloop state unavailable"
|
||||
item.Runtime.LastError = TransportClientError{
|
||||
Code: "BACKEND_RUNTIME_ERROR",
|
||||
Message: item.Health.LastError,
|
||||
Retryable: true,
|
||||
At: item.Health.LastCheck,
|
||||
}
|
||||
item.Runtime.LastAction = "observe"
|
||||
item.Runtime.LastActionAt = item.Health.LastCheck
|
||||
item.UpdatedAt = item.Health.LastCheck
|
||||
return item
|
||||
}
|
||||
|
||||
func handleTransportVirtualClientHealthAction(w http.ResponseWriter, r *http.Request, id string) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
item := transportVirtualAdGuardSnapshot()
|
||||
resp := buildTransportHealthResponse(item, time.Now().UTC())
|
||||
if normalizeTransportStatus(item.Status) == TransportClientDown {
|
||||
resp.Code = "TRANSPORT_CLIENT_DOWN"
|
||||
}
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func handleTransportVirtualClientMetricsAction(w http.ResponseWriter, r *http.Request, id string) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
item := transportVirtualAdGuardSnapshot()
|
||||
writeJSON(w, http.StatusOK, buildTransportMetricsResponse(item, time.Now().UTC()))
|
||||
}
|
||||
|
||||
func handleTransportVirtualClientProvisionAction(w http.ResponseWriter, r *http.Request, id string) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
item := transportVirtualAdGuardSnapshot()
|
||||
now := time.Now().UTC()
|
||||
msg := "virtual adapter uses existing adguard autoloop runtime; explicit provision skipped"
|
||||
writeJSON(w, http.StatusOK, TransportClientLifecycleResponse{
|
||||
OK: true,
|
||||
Message: msg,
|
||||
Code: "",
|
||||
ClientID: item.ID,
|
||||
Kind: item.Kind,
|
||||
Action: "provision",
|
||||
StatusBefore: item.Status,
|
||||
StatusAfter: item.Status,
|
||||
Health: item.Health,
|
||||
Runtime: transportRuntimeSnapshot(item, now),
|
||||
})
|
||||
}
|
||||
|
||||
func handleTransportVirtualClientLifecycleAction(w http.ResponseWriter, r *http.Request, id, action string) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
status, resp := runTransportVirtualClientLifecycleAction(id, action)
|
||||
writeJSON(w, status, resp)
|
||||
}
|
||||
|
||||
func runTransportVirtualClientLifecycleAction(id, action string) (int, TransportClientLifecycleResponse) {
|
||||
status := http.StatusOK
|
||||
resp := TransportClientLifecycleResponse{}
|
||||
withTransportIfaceLock(transportPolicyTargetAdGuardIfaceID, func() {
|
||||
status, resp = executeTransportVirtualClientLifecycleAction(id, action)
|
||||
})
|
||||
return status, resp
|
||||
}
|
||||
|
||||
func executeTransportVirtualClientLifecycleAction(id, action string) (int, TransportClientLifecycleResponse) {
|
||||
before := transportVirtualAdGuardSnapshot()
|
||||
beforeStatus := normalizeTransportStatus(before.Status)
|
||||
now := time.Now().UTC()
|
||||
|
||||
cmdAction, ok := transportVirtualAdGuardSystemdAction(action)
|
||||
if !ok {
|
||||
return http.StatusNotFound, TransportClientLifecycleResponse{
|
||||
OK: false,
|
||||
Message: "unknown action",
|
||||
Code: "TRANSPORT_ACTION_UNKNOWN",
|
||||
}
|
||||
}
|
||||
|
||||
stdout, stderr, exitCode, err := runCommand("systemctl", cmdAction, adgvpnUnit)
|
||||
actionOK := err == nil && exitCode == 0
|
||||
|
||||
after := transportVirtualAdGuardSnapshot()
|
||||
afterStatus := normalizeTransportStatus(after.Status)
|
||||
if !actionOK && afterStatus == beforeStatus {
|
||||
afterStatus = TransportClientDegraded
|
||||
after.Status = afterStatus
|
||||
}
|
||||
|
||||
msg := strings.TrimSpace(stdout)
|
||||
if msg == "" {
|
||||
msg = strings.TrimSpace(stderr)
|
||||
}
|
||||
if msg == "" && err != nil {
|
||||
msg = strings.TrimSpace(err.Error())
|
||||
}
|
||||
if msg == "" {
|
||||
msg = fmt.Sprintf("adguardvpn %s done", cmdAction)
|
||||
}
|
||||
|
||||
code := ""
|
||||
if !actionOK {
|
||||
code = "TRANSPORT_ADGUARD_ACTION_FAILED"
|
||||
}
|
||||
|
||||
events.push("transport_client_state_changed", map[string]any{
|
||||
"id": id,
|
||||
"from": beforeStatus,
|
||||
"to": afterStatus,
|
||||
})
|
||||
publishTransportRuntimeObservabilitySnapshotChanged(
|
||||
"transport_client_state_changed",
|
||||
[]string{id},
|
||||
[]string{transportPolicyTargetAdGuardIfaceID},
|
||||
)
|
||||
_, _ = egressIdentitySWR.queueRefresh([]string{"adguardvpn", "system"}, true)
|
||||
|
||||
return http.StatusOK, TransportClientLifecycleResponse{
|
||||
OK: actionOK,
|
||||
Message: msg,
|
||||
Code: code,
|
||||
ExitCode: exitCode,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
ClientID: id,
|
||||
Kind: after.Kind,
|
||||
Action: action,
|
||||
StatusBefore: beforeStatus,
|
||||
StatusAfter: afterStatus,
|
||||
Health: after.Health,
|
||||
Runtime: transportRuntimeSnapshot(after, now),
|
||||
}
|
||||
}
|
||||
|
||||
func transportVirtualAdGuardSystemdAction(action string) (string, bool) {
|
||||
switch strings.ToLower(strings.TrimSpace(action)) {
|
||||
case "start":
|
||||
return "start", true
|
||||
case "stop":
|
||||
return "stop", true
|
||||
case "restart":
|
||||
return "restart", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user