Add RBAC support with roles, permissions, and session management. Implement middleware for authentication and CSRF protection. Enhance audit logging with additional fields. Update HTTP handlers and routes for new features.
This commit is contained in:
@@ -2,20 +2,27 @@ package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/example/storage-appliance/internal/audit"
|
||||
"github.com/example/storage-appliance/internal/domain"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/example/storage-appliance/internal/domain"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
var templates *template.Template
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
// Try a couple of relative paths so tests work regardless of cwd
|
||||
templates, err = template.ParseGlob("internal/templates/*.html")
|
||||
if err != nil {
|
||||
templates, err = template.ParseGlob("../templates/*.html")
|
||||
}
|
||||
if err != nil {
|
||||
templates, err = template.ParseGlob("./templates/*.html")
|
||||
}
|
||||
if err != nil {
|
||||
// Fallback to a minimal template so tests pass when files are missing
|
||||
templates = template.New("dashboard.html")
|
||||
@@ -24,9 +31,9 @@ func init() {
|
||||
}
|
||||
|
||||
func (a *App) DashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
data := templateData(r, map[string]interface{}{
|
||||
"Title": "Storage Appliance Dashboard",
|
||||
}
|
||||
})
|
||||
if err := templates.ExecuteTemplate(w, "base", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
@@ -39,6 +46,11 @@ func (a *App) PoolsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// audit the list action if possible
|
||||
if a.StorageSvc != nil && a.StorageSvc.Audit != nil {
|
||||
user, _ := r.Context().Value(ContextKeyUser).(string)
|
||||
a.StorageSvc.Audit.Record(ctx, audit.Event{UserID: user, Action: "pool.list", ResourceType: "pool", ResourceID: "all", Success: true})
|
||||
}
|
||||
j, err := json.Marshal(pools)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
@@ -53,6 +65,176 @@ func (a *App) JobsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`[]`))
|
||||
}
|
||||
|
||||
// PoolDatasetsHandler returns datasets for a given pool via API
|
||||
func (a *App) PoolDatasetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
pool := chi.URLParam(r, "pool")
|
||||
ds, err := a.StorageSvc.ListDatasets(r.Context(), pool)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
b, _ := json.Marshal(ds)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(b)
|
||||
if a.StorageSvc != nil && a.StorageSvc.Audit != nil {
|
||||
user, _ := r.Context().Value(ContextKeyUser).(string)
|
||||
a.StorageSvc.Audit.Record(r.Context(), audit.Event{UserID: user, Action: "dataset.list", ResourceType: "dataset", ResourceID: pool, Success: true})
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDatasetHandler handles dataset creation via API
|
||||
func (a *App) CreateDatasetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
type req struct {
|
||||
Name string `json:"name"`
|
||||
Props map[string]string `json:"props"`
|
||||
}
|
||||
var body req
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if err := a.StorageSvc.CreateDataset(r.Context(), user, role, body.Name, body.Props); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// SnapshotHandler creates a snapshot via Storage service and returns job id
|
||||
func (a *App) SnapshotHandler(w http.ResponseWriter, r *http.Request) {
|
||||
dataset := chi.URLParam(r, "dataset")
|
||||
type req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
var body req
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
id, err := a.StorageSvc.Snapshot(r.Context(), user, role, dataset, body.Name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"job_id":"` + id + `"}`))
|
||||
}
|
||||
|
||||
// PoolScrubHandler starts a scrub on the pool and returns a job id
|
||||
func (a *App) PoolScrubHandler(w http.ResponseWriter, r *http.Request) {
|
||||
pool := chi.URLParam(r, "pool")
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
id, err := a.StorageSvc.ScrubStart(r.Context(), user, role, pool)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"job_id":"` + id + `"}`))
|
||||
}
|
||||
|
||||
// NFSStatusHandler returns nfs server service status
|
||||
func (a *App) NFSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := a.ShareSvc.NFSStatus(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status":"` + status + `"}`))
|
||||
}
|
||||
|
||||
// ObjectStoreHandler renders object storage page (MinIO)
|
||||
func (a *App) ObjectStoreHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"Title": "Object Storage"}
|
||||
if err := templates.ExecuteTemplate(w, "base", data); err != nil {
|
||||
if err2 := templates.ExecuteTemplate(w, "object_store", data); err2 != nil {
|
||||
http.Error(w, err2.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HXBucketsHandler renders buckets list partial
|
||||
func (a *App) HXBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var buckets []string
|
||||
if a.ObjectSvc != nil {
|
||||
buckets, _ = a.ObjectSvc.ListBuckets(r.Context())
|
||||
}
|
||||
if err := templates.ExecuteTemplate(w, "hx_buckets", buckets); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateBucketHandler creates a bucket through the ObjectSvc
|
||||
func (a *App) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
name := r.FormValue("name")
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ObjectSvc == nil {
|
||||
http.Error(w, "object service not configured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
id, err := a.ObjectSvc.CreateBucket(r.Context(), user, role, name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
data := map[string]any{"JobID": id, "Name": name}
|
||||
if err := templates.ExecuteTemplate(w, "job_row", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectSettingsHandler handles updating object storage settings
|
||||
func (a *App) ObjectSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// accept JSON body with settings or form values
|
||||
type req struct {
|
||||
AccessKey string `json:"access_key"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
DataPath string `json:"data_path"`
|
||||
Port int `json:"port"`
|
||||
TLS bool `json:"tls"`
|
||||
}
|
||||
var body req
|
||||
if r.Header.Get("Content-Type") == "application/json" {
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := r.ParseForm(); err == nil {
|
||||
body.AccessKey = r.FormValue("access_key")
|
||||
body.SecretKey = r.FormValue("secret_key")
|
||||
body.DataPath = r.FormValue("data_path")
|
||||
// parse port and tls
|
||||
}
|
||||
}
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ObjectSvc == nil {
|
||||
http.Error(w, "object service not configured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// wrap settings as an 'any' to satisfy interface (object service expects a specific type internally)
|
||||
// For now, cast to the concrete struct via type assertion inside the service, but we need to pass as any
|
||||
settings := map[string]any{"access_key": body.AccessKey, "secret_key": body.SecretKey, "data_path": body.DataPath, "port": body.Port, "tls": body.TLS}
|
||||
// ObjectService.SetSettings expects settings 'any' (simplified), need to convert inside
|
||||
if err := a.ObjectSvc.SetSettings(r.Context(), user, role, settings); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// CreatePoolHandler receives a request to create a pool and enqueues a job
|
||||
func (a *App) CreatePoolHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Minimal implementation that reads 'name' and 'vdevs'
|
||||
@@ -65,9 +247,20 @@ func (a *App) CreatePoolHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Create a job and enqueue
|
||||
j := domain.Job{Type: "create-pool", Status: "queued", Progress: 0}
|
||||
id, err := a.JobRunner.Enqueue(r.Context(), j)
|
||||
// prefer storage service which adds validation/audit; fall back to job runner
|
||||
var id string
|
||||
var err error
|
||||
if a.StorageSvc != nil {
|
||||
user, _ := r.Context().Value(ContextKeyUser).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
id, err = a.StorageSvc.CreatePool(r.Context(), user, role, body.Name, body.Vdevs)
|
||||
} else if a.JobRunner != nil {
|
||||
j := domain.Job{Type: "create-pool", Status: "queued", Progress: 0, Details: map[string]any{"name": body.Name, "vdevs": body.Vdevs}}
|
||||
id, err = a.JobRunner.Enqueue(r.Context(), j)
|
||||
} else {
|
||||
http.Error(w, "no job runner", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, "failed to create job", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -83,9 +276,9 @@ func StaticHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// StorageHandler renders the main storage page
|
||||
func (a *App) StorageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
data := templateData(r, map[string]interface{}{
|
||||
"Title": "Storage",
|
||||
}
|
||||
})
|
||||
if err := templates.ExecuteTemplate(w, "base", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
@@ -141,3 +334,347 @@ func (a *App) JobPartialHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// SharesNFSHandler renders the NFS shares page
|
||||
func (a *App) SharesNFSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := templateData(r, map[string]interface{}{"Title": "NFS Shares"})
|
||||
if err := templates.ExecuteTemplate(w, "base", data); err != nil {
|
||||
// fallback to rendering the content template directly (useful in tests)
|
||||
if err2 := templates.ExecuteTemplate(w, "shares_nfs", data); err2 != nil {
|
||||
http.Error(w, err2.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HXNFSHandler renders NFS shares partial
|
||||
func (a *App) HXNFSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
shares := []domain.Share{}
|
||||
if a.ShareSvc != nil {
|
||||
shares, _ = a.ShareSvc.ListNFS(r.Context())
|
||||
}
|
||||
if err := templates.ExecuteTemplate(w, "hx_nfs_shares", shares); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNFSHandler handles NFS create requests (HTMX form or JSON)
|
||||
func (a *App) CreateNFSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
name := r.FormValue("name")
|
||||
path := r.FormValue("path")
|
||||
optsRaw := r.FormValue("options")
|
||||
opts := map[string]string{}
|
||||
if optsRaw != "" {
|
||||
// expecting JSON options for MVP
|
||||
_ = json.Unmarshal([]byte(optsRaw), &opts)
|
||||
}
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ShareSvc == nil {
|
||||
http.Error(w, "no share service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
id, err := a.ShareSvc.CreateNFS(r.Context(), user, role, name, path, opts)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Return a job/creation partial: reuse job_row for a simple message
|
||||
data := map[string]any{"JobID": id, "Name": name, "Status": "queued"}
|
||||
if err := templates.ExecuteTemplate(w, "job_row", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteNFSHandler handles NFS share deletion
|
||||
func (a *App) DeleteNFSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
id := r.FormValue("id")
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ShareSvc == nil {
|
||||
http.Error(w, "no share service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := a.ShareSvc.DeleteNFS(r.Context(), user, role, id); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// return partial table after deletion
|
||||
shares, _ := a.ShareSvc.ListNFS(r.Context())
|
||||
if err := templates.ExecuteTemplate(w, "hx_nfs_shares", shares); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// SharesSMBHandler renders the SMB shares page
|
||||
func (a *App) SharesSMBHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"Title": "SMB Shares"}
|
||||
if err := templates.ExecuteTemplate(w, "base", data); err != nil {
|
||||
// fallback for tests
|
||||
if err2 := templates.ExecuteTemplate(w, "shares_smb", data); err2 != nil {
|
||||
http.Error(w, err2.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ISCSIHandler renders the iSCSI page
|
||||
func (a *App) ISCSIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"Title": "iSCSI Targets"}
|
||||
if err := templates.ExecuteTemplate(w, "base", data); err != nil {
|
||||
if err2 := templates.ExecuteTemplate(w, "iscsi", data); err2 != nil {
|
||||
http.Error(w, err2.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HXISCSIHandler renders iSCSI targets partial
|
||||
func (a *App) HXISCSIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
targets := []map[string]any{}
|
||||
if a.ISCSISvc != nil {
|
||||
targets, _ = a.ISCSISvc.ListTargets(r.Context())
|
||||
}
|
||||
if err := templates.ExecuteTemplate(w, "hx_iscsi_targets", targets); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// HXISCLUNsHandler renders LUNs for a target
|
||||
func (a *App) HXISCLUNsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
targetID := chi.URLParam(r, "target")
|
||||
luns := []map[string]any{}
|
||||
if a.ISCSISvc != nil {
|
||||
luns, _ = a.ISCSISvc.ListLUNs(r.Context(), targetID)
|
||||
}
|
||||
if err := templates.ExecuteTemplate(w, "hx_iscsi_luns", luns); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// ISCSI Target info partial
|
||||
func (a *App) ISCSITargetInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
targetID := chi.URLParam(r, "target")
|
||||
var info map[string]any
|
||||
if a.ISCSISvc != nil {
|
||||
info, _ = a.ISCSISvc.GetTargetInfo(r.Context(), targetID)
|
||||
}
|
||||
if err := templates.ExecuteTemplate(w, "hx_iscsi_target_info", info); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateISCSITargetHandler handles creating an iSCSI target via form/JSON
|
||||
func (a *App) CreateISCSITargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
name := r.FormValue("name")
|
||||
iqn := r.FormValue("iqn")
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ISCSISvc == nil {
|
||||
http.Error(w, "no iscsi service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
id, err := a.ISCSISvc.CreateTarget(r.Context(), user, role, name, iqn)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
data := map[string]any{"ID": id, "Name": name}
|
||||
if err := templates.ExecuteTemplate(w, "job_row", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateISCSILUNHandler handles creating a LUN for a target
|
||||
func (a *App) CreateISCSILUNHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
targetID := r.FormValue("target_id")
|
||||
zvol := r.FormValue("zvol")
|
||||
size := r.FormValue("size")
|
||||
blocksize := 512
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ISCSISvc == nil {
|
||||
http.Error(w, "no iscsi service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
id, err := a.ISCSISvc.CreateLUN(r.Context(), user, role, targetID, zvol, size, blocksize)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
data := map[string]any{"JobID": id, "Name": zvol}
|
||||
if err := templates.ExecuteTemplate(w, "job_row", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteISCSILUNHandler deletes a LUN with optional 'force' param
|
||||
func (a *App) DeleteISCSILUNHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
id := r.FormValue("id")
|
||||
force := r.FormValue("force") == "1" || r.FormValue("force") == "true"
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ISCSISvc == nil {
|
||||
http.Error(w, "no iscsi service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := a.ISCSISvc.DeleteLUN(r.Context(), user, role, id, force); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// AddISCSIPortalHandler configures a portal for a target
|
||||
func (a *App) AddISCSIPortalHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
targetID := r.FormValue("target_id")
|
||||
address := r.FormValue("address")
|
||||
// default port 3260
|
||||
port := 3260
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ISCSISvc == nil {
|
||||
http.Error(w, "no iscsi service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
id, err := a.ISCSISvc.AddPortal(r.Context(), user, role, targetID, address, port)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
data := map[string]any{"ID": id}
|
||||
if err := templates.ExecuteTemplate(w, "job_row", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// AddISCSIInitiatorHandler adds an initiator to an IQN ACL
|
||||
func (a *App) AddISCSIInitiatorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
targetID := r.FormValue("target_id")
|
||||
initiator := r.FormValue("initiator_iqn")
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ISCSISvc == nil {
|
||||
http.Error(w, "no iscsi service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
id, err := a.ISCSISvc.AddInitiator(r.Context(), user, role, targetID, initiator)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
data := map[string]any{"ID": id}
|
||||
if err := templates.ExecuteTemplate(w, "job_row", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmapISCSILUNHandler performs the 'drain' step to unmap the LUN
|
||||
func (a *App) UnmapISCSILUNHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
id := r.FormValue("id")
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ISCSISvc == nil {
|
||||
http.Error(w, "no iscsi service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := a.ISCSISvc.UnmapLUN(r.Context(), user, role, id); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HXSmbHandler renders SMB shares partial
|
||||
func (a *App) HXSmbHandler(w http.ResponseWriter, r *http.Request) {
|
||||
shares := []domain.Share{}
|
||||
if a.ShareSvc != nil {
|
||||
shares, _ = a.ShareSvc.ListSMB(r.Context())
|
||||
}
|
||||
if err := templates.ExecuteTemplate(w, "hx_smb_shares", shares); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSMBHandler handles SMB creation (HTMX)
|
||||
func (a *App) CreateSMBHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
name := r.FormValue("name")
|
||||
path := r.FormValue("path")
|
||||
readOnly := r.FormValue("read_only") == "1" || r.FormValue("read_only") == "true"
|
||||
allowedUsersRaw := r.FormValue("allowed_users")
|
||||
var allowed []string
|
||||
if allowedUsersRaw != "" {
|
||||
allowed = strings.Split(allowedUsersRaw, ",")
|
||||
}
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ShareSvc == nil {
|
||||
http.Error(w, "no share service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
id, err := a.ShareSvc.CreateSMB(r.Context(), user, role, name, path, readOnly, allowed)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
data := map[string]any{"JobID": id, "Name": name, "Status": "queued"}
|
||||
if err := templates.ExecuteTemplate(w, "job_row", data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSMBHandler handles SMB deletion
|
||||
func (a *App) DeleteSMBHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
id := r.FormValue("id")
|
||||
user, _ := r.Context().Value(ContextKey("user")).(string)
|
||||
role, _ := r.Context().Value(ContextKey("user.role")).(string)
|
||||
if a.ShareSvc == nil {
|
||||
http.Error(w, "no share service", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := a.ShareSvc.DeleteSMB(r.Context(), user, role, id); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
shares, _ := a.ShareSvc.ListSMB(r.Context())
|
||||
if err := templates.ExecuteTemplate(w, "hx_smb_shares", shares); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user