diff --git a/weed/admin/dash/group_management.go b/weed/admin/dash/group_management.go new file mode 100644 index 000000000..26e519e84 --- /dev/null +++ b/weed/admin/dash/group_management.go @@ -0,0 +1,206 @@ +package dash + +import ( + "context" + "fmt" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" +) + +func (s *AdminServer) GetGroups(ctx context.Context) ([]GroupData, error) { + if s.credentialManager == nil { + return nil, fmt.Errorf("credential manager not available") + } + + groupNames, err := s.credentialManager.ListGroups(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list groups: %w", err) + } + + var groups []GroupData + for _, name := range groupNames { + g, err := s.credentialManager.GetGroup(ctx, name) + if err != nil { + glog.V(1).Infof("Failed to get group %s: %v", name, err) + continue + } + status := "enabled" + if g.Disabled { + status = "disabled" + } + groups = append(groups, GroupData{ + Name: g.Name, + MemberCount: len(g.Members), + PolicyCount: len(g.PolicyNames), + Status: status, + Members: g.Members, + PolicyNames: g.PolicyNames, + }) + } + return groups, nil +} + +func (s *AdminServer) GetGroupDetails(ctx context.Context, name string) (*GroupData, error) { + if s.credentialManager == nil { + return nil, fmt.Errorf("credential manager not available") + } + + g, err := s.credentialManager.GetGroup(ctx, name) + if err != nil { + return nil, fmt.Errorf("failed to get group: %w", err) + } + status := "enabled" + if g.Disabled { + status = "disabled" + } + return &GroupData{ + Name: g.Name, + MemberCount: len(g.Members), + PolicyCount: len(g.PolicyNames), + Status: status, + Members: g.Members, + PolicyNames: g.PolicyNames, + }, nil +} + +func (s *AdminServer) CreateGroup(ctx context.Context, name string) (*GroupData, error) { + if s.credentialManager == nil { + return nil, fmt.Errorf("credential manager not available") + } + + group := &iam_pb.Group{Name: name} + if err := s.credentialManager.CreateGroup(ctx, group); err != nil { + return nil, fmt.Errorf("failed to create group: %w", err) + } + glog.V(1).Infof("Created group %s", name) + return &GroupData{ + Name: name, + Status: "enabled", + }, nil +} + +func (s *AdminServer) DeleteGroup(ctx context.Context, name string) error { + if s.credentialManager == nil { + return fmt.Errorf("credential manager not available") + } + if err := s.credentialManager.DeleteGroup(ctx, name); err != nil { + return fmt.Errorf("failed to delete group: %w", err) + } + glog.V(1).Infof("Deleted group %s", name) + return nil +} + +func (s *AdminServer) AddGroupMember(ctx context.Context, groupName, username string) error { + if s.credentialManager == nil { + return fmt.Errorf("credential manager not available") + } + g, err := s.credentialManager.GetGroup(ctx, groupName) + if err != nil { + return fmt.Errorf("failed to get group: %w", err) + } + for _, m := range g.Members { + if m == username { + return nil // already a member + } + } + g.Members = append(g.Members, username) + if err := s.credentialManager.UpdateGroup(ctx, g); err != nil { + return fmt.Errorf("failed to update group: %w", err) + } + glog.V(1).Infof("Added user %s to group %s", username, groupName) + return nil +} + +func (s *AdminServer) RemoveGroupMember(ctx context.Context, groupName, username string) error { + if s.credentialManager == nil { + return fmt.Errorf("credential manager not available") + } + g, err := s.credentialManager.GetGroup(ctx, groupName) + if err != nil { + return fmt.Errorf("failed to get group: %w", err) + } + found := false + var newMembers []string + for _, m := range g.Members { + if m == username { + found = true + } else { + newMembers = append(newMembers, m) + } + } + if !found { + return fmt.Errorf("user %s is not a member of group %s", username, groupName) + } + g.Members = newMembers + if err := s.credentialManager.UpdateGroup(ctx, g); err != nil { + return fmt.Errorf("failed to update group: %w", err) + } + glog.V(1).Infof("Removed user %s from group %s", username, groupName) + return nil +} + +func (s *AdminServer) AttachGroupPolicy(ctx context.Context, groupName, policyName string) error { + if s.credentialManager == nil { + return fmt.Errorf("credential manager not available") + } + g, err := s.credentialManager.GetGroup(ctx, groupName) + if err != nil { + return fmt.Errorf("failed to get group: %w", err) + } + for _, p := range g.PolicyNames { + if p == policyName { + return nil // already attached + } + } + g.PolicyNames = append(g.PolicyNames, policyName) + if err := s.credentialManager.UpdateGroup(ctx, g); err != nil { + return fmt.Errorf("failed to update group: %w", err) + } + glog.V(1).Infof("Attached policy %s to group %s", policyName, groupName) + return nil +} + +func (s *AdminServer) DetachGroupPolicy(ctx context.Context, groupName, policyName string) error { + if s.credentialManager == nil { + return fmt.Errorf("credential manager not available") + } + g, err := s.credentialManager.GetGroup(ctx, groupName) + if err != nil { + return fmt.Errorf("failed to get group: %w", err) + } + found := false + var newPolicies []string + for _, p := range g.PolicyNames { + if p == policyName { + found = true + } else { + newPolicies = append(newPolicies, p) + } + } + if !found { + return fmt.Errorf("policy %s is not attached to group %s", policyName, groupName) + } + g.PolicyNames = newPolicies + if err := s.credentialManager.UpdateGroup(ctx, g); err != nil { + return fmt.Errorf("failed to update group: %w", err) + } + glog.V(1).Infof("Detached policy %s from group %s", policyName, groupName) + return nil +} + +func (s *AdminServer) SetGroupStatus(ctx context.Context, groupName string, enabled bool) error { + if s.credentialManager == nil { + return fmt.Errorf("credential manager not available") + } + g, err := s.credentialManager.GetGroup(ctx, groupName) + if err != nil { + return fmt.Errorf("failed to get group: %w", err) + } + g.Disabled = !enabled + if err := s.credentialManager.UpdateGroup(ctx, g); err != nil { + return fmt.Errorf("failed to update group: %w", err) + } + glog.V(1).Infof("Set group %s status to enabled=%v", groupName, enabled) + return nil +} diff --git a/weed/admin/dash/types.go b/weed/admin/dash/types.go index 4dbdc965c..965166de4 100644 --- a/weed/admin/dash/types.go +++ b/weed/admin/dash/types.go @@ -589,6 +589,30 @@ type UpdateServiceAccountRequest struct { Expiration string `json:"expiration,omitempty"` } +// Group management structures +type GroupData struct { + Name string `json:"name"` + MemberCount int `json:"member_count"` + PolicyCount int `json:"policy_count"` + Status string `json:"status"` // "enabled" or "disabled" + Members []string `json:"members"` + PolicyNames []string `json:"policy_names"` +} + +type GroupsPageData struct { + Username string `json:"username"` + Groups []GroupData `json:"groups"` + TotalGroups int `json:"total_groups"` + ActiveGroups int `json:"active_groups"` + AvailableUsers []string `json:"available_users"` + AvailablePolicies []string `json:"available_policies"` + LastUpdated time.Time `json:"last_updated"` +} + +type CreateGroupRequest struct { + Name string `json:"name"` +} + // STS Configuration display types type STSConfigData struct { Enabled bool `json:"enabled"` diff --git a/weed/admin/handlers/admin_handlers.go b/weed/admin/handlers/admin_handlers.go index ff0d8651a..38938c25b 100644 --- a/weed/admin/handlers/admin_handlers.go +++ b/weed/admin/handlers/admin_handlers.go @@ -28,6 +28,7 @@ type AdminHandlers struct { pluginHandlers *PluginHandlers mqHandlers *MessageQueueHandlers serviceAccountHandlers *ServiceAccountHandlers + groupHandlers *GroupHandlers } // NewAdminHandlers creates a new instance of AdminHandlers @@ -40,6 +41,7 @@ func NewAdminHandlers(adminServer *dash.AdminServer, store sessions.Store) *Admi pluginHandlers := NewPluginHandlers(adminServer) mqHandlers := NewMessageQueueHandlers(adminServer) serviceAccountHandlers := NewServiceAccountHandlers(adminServer) + groupHandlers := NewGroupHandlers(adminServer) return &AdminHandlers{ adminServer: adminServer, sessionStore: store, @@ -51,6 +53,7 @@ func NewAdminHandlers(adminServer *dash.AdminServer, store sessions.Store) *Admi pluginHandlers: pluginHandlers, mqHandlers: mqHandlers, serviceAccountHandlers: serviceAccountHandlers, + groupHandlers: groupHandlers, } } @@ -104,6 +107,7 @@ func (h *AdminHandlers) registerUIRoutes(r *mux.Router) { r.HandleFunc("/object-store/buckets/{bucket}", h.ShowBucketDetails).Methods(http.MethodGet) r.HandleFunc("/object-store/users", h.userHandlers.ShowObjectStoreUsers).Methods(http.MethodGet) r.HandleFunc("/object-store/policies", h.policyHandlers.ShowPolicies).Methods(http.MethodGet) + r.HandleFunc("/object-store/groups", h.groupHandlers.ShowGroups).Methods(http.MethodGet) r.HandleFunc("/object-store/service-accounts", h.serviceAccountHandlers.ShowServiceAccounts).Methods(http.MethodGet) r.HandleFunc("/object-store/s3tables/buckets", h.ShowS3TablesBuckets).Methods(http.MethodGet) r.HandleFunc("/object-store/s3tables/buckets/{bucket}/namespaces", h.ShowS3TablesNamespaces).Methods(http.MethodGet) @@ -185,6 +189,19 @@ func (h *AdminHandlers) registerAPIRoutes(api *mux.Router, enforceWrite bool) { saApi.Handle("/{id}", wrapWrite(h.serviceAccountHandlers.UpdateServiceAccount)).Methods(http.MethodPut) saApi.Handle("/{id}", wrapWrite(h.serviceAccountHandlers.DeleteServiceAccount)).Methods(http.MethodDelete) + groupsApi := api.PathPrefix("/groups").Subrouter() + groupsApi.HandleFunc("", h.groupHandlers.GetGroups).Methods(http.MethodGet) + groupsApi.Handle("", wrapWrite(h.groupHandlers.CreateGroup)).Methods(http.MethodPost) + groupsApi.HandleFunc("/{name}", h.groupHandlers.GetGroupDetails).Methods(http.MethodGet) + groupsApi.Handle("/{name}", wrapWrite(h.groupHandlers.DeleteGroup)).Methods(http.MethodDelete) + groupsApi.Handle("/{name}/status", wrapWrite(h.groupHandlers.SetGroupStatus)).Methods(http.MethodPut) + groupsApi.HandleFunc("/{name}/members", h.groupHandlers.GetGroupMembers).Methods(http.MethodGet) + groupsApi.Handle("/{name}/members", wrapWrite(h.groupHandlers.AddGroupMember)).Methods(http.MethodPost) + groupsApi.Handle("/{name}/members/{username}", wrapWrite(h.groupHandlers.RemoveGroupMember)).Methods(http.MethodDelete) + groupsApi.HandleFunc("/{name}/policies", h.groupHandlers.GetGroupPolicies).Methods(http.MethodGet) + groupsApi.Handle("/{name}/policies", wrapWrite(h.groupHandlers.AttachGroupPolicy)).Methods(http.MethodPost) + groupsApi.Handle("/{name}/policies/{policyName}", wrapWrite(h.groupHandlers.DetachGroupPolicy)).Methods(http.MethodDelete) + policyApi := api.PathPrefix("/object-store/policies").Subrouter() policyApi.HandleFunc("", h.policyHandlers.GetPolicies).Methods(http.MethodGet) policyApi.Handle("", wrapWrite(h.policyHandlers.CreatePolicy)).Methods(http.MethodPost) diff --git a/weed/admin/handlers/group_handlers.go b/weed/admin/handlers/group_handlers.go new file mode 100644 index 000000000..721072c53 --- /dev/null +++ b/weed/admin/handlers/group_handlers.go @@ -0,0 +1,235 @@ +package handlers + +import ( + "bytes" + "net/http" + "time" + + "github.com/gorilla/mux" + "github.com/seaweedfs/seaweedfs/weed/admin/dash" + "github.com/seaweedfs/seaweedfs/weed/admin/view/app" + "github.com/seaweedfs/seaweedfs/weed/admin/view/layout" + "github.com/seaweedfs/seaweedfs/weed/glog" +) + +type GroupHandlers struct { + adminServer *dash.AdminServer +} + +func NewGroupHandlers(adminServer *dash.AdminServer) *GroupHandlers { + return &GroupHandlers{adminServer: adminServer} +} + +func (h *GroupHandlers) ShowGroups(w http.ResponseWriter, r *http.Request) { + data := h.getGroupsPageData(r) + + var buf bytes.Buffer + component := app.Groups(data) + viewCtx := layout.NewViewContext(r, dash.UsernameFromContext(r.Context()), dash.CSRFTokenFromContext(r.Context())) + layoutComponent := layout.Layout(viewCtx, component) + if err := layoutComponent.Render(r.Context(), &buf); err != nil { + glog.Errorf("Failed to render groups template: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/html") + _, _ = w.Write(buf.Bytes()) +} + +func (h *GroupHandlers) GetGroups(w http.ResponseWriter, r *http.Request) { + groups, err := h.adminServer.GetGroups(r.Context()) + if err != nil { + glog.Errorf("Failed to get groups: %v", err) + writeJSONError(w, http.StatusInternalServerError, "Failed to get groups") + return + } + writeJSON(w, http.StatusOK, map[string]interface{}{"groups": groups}) +} + +func (h *GroupHandlers) CreateGroup(w http.ResponseWriter, r *http.Request) { + var req dash.CreateGroupRequest + if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil { + writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error()) + return + } + if req.Name == "" { + writeJSONError(w, http.StatusBadRequest, "Group name is required") + return + } + group, err := h.adminServer.CreateGroup(r.Context(), req.Name) + if err != nil { + glog.Errorf("Failed to create group: %v", err) + writeJSONError(w, http.StatusInternalServerError, "Failed to create group: "+err.Error()) + return + } + writeJSON(w, http.StatusOK, group) +} + +func (h *GroupHandlers) GetGroupDetails(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + group, err := h.adminServer.GetGroupDetails(r.Context(), name) + if err != nil { + glog.Errorf("Failed to get group details: %v", err) + writeJSONError(w, http.StatusNotFound, "Group not found") + return + } + writeJSON(w, http.StatusOK, group) +} + +func (h *GroupHandlers) DeleteGroup(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + if err := h.adminServer.DeleteGroup(r.Context(), name); err != nil { + glog.Errorf("Failed to delete group: %v", err) + writeJSONError(w, http.StatusInternalServerError, "Failed to delete group: "+err.Error()) + return + } + writeJSON(w, http.StatusOK, map[string]string{"message": "Group deleted successfully"}) +} + +func (h *GroupHandlers) GetGroupMembers(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + group, err := h.adminServer.GetGroupDetails(r.Context(), name) + if err != nil { + writeJSONError(w, http.StatusNotFound, "Group not found") + return + } + writeJSON(w, http.StatusOK, map[string]interface{}{"members": group.Members}) +} + +func (h *GroupHandlers) AddGroupMember(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + var req struct { + Username string `json:"username"` + } + if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil { + writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error()) + return + } + if req.Username == "" { + writeJSONError(w, http.StatusBadRequest, "Username is required") + return + } + if err := h.adminServer.AddGroupMember(r.Context(), name, req.Username); err != nil { + writeJSONError(w, http.StatusInternalServerError, "Failed to add member: "+err.Error()) + return + } + writeJSON(w, http.StatusOK, map[string]string{"message": "Member added successfully"}) +} + +func (h *GroupHandlers) RemoveGroupMember(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + username := mux.Vars(r)["username"] + if err := h.adminServer.RemoveGroupMember(r.Context(), name, username); err != nil { + writeJSONError(w, http.StatusInternalServerError, "Failed to remove member: "+err.Error()) + return + } + writeJSON(w, http.StatusOK, map[string]string{"message": "Member removed successfully"}) +} + +func (h *GroupHandlers) GetGroupPolicies(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + group, err := h.adminServer.GetGroupDetails(r.Context(), name) + if err != nil { + writeJSONError(w, http.StatusNotFound, "Group not found") + return + } + writeJSON(w, http.StatusOK, map[string]interface{}{"policies": group.PolicyNames}) +} + +func (h *GroupHandlers) AttachGroupPolicy(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + var req struct { + PolicyName string `json:"policy_name"` + } + if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil { + writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error()) + return + } + if req.PolicyName == "" { + writeJSONError(w, http.StatusBadRequest, "Policy name is required") + return + } + if err := h.adminServer.AttachGroupPolicy(r.Context(), name, req.PolicyName); err != nil { + writeJSONError(w, http.StatusInternalServerError, "Failed to attach policy: "+err.Error()) + return + } + writeJSON(w, http.StatusOK, map[string]string{"message": "Policy attached successfully"}) +} + +func (h *GroupHandlers) DetachGroupPolicy(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + policyName := mux.Vars(r)["policyName"] + if err := h.adminServer.DetachGroupPolicy(r.Context(), name, policyName); err != nil { + writeJSONError(w, http.StatusInternalServerError, "Failed to detach policy: "+err.Error()) + return + } + writeJSON(w, http.StatusOK, map[string]string{"message": "Policy detached successfully"}) +} + +func (h *GroupHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) { + name := mux.Vars(r)["name"] + var req struct { + Enabled bool `json:"enabled"` + } + if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil { + writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error()) + return + } + if err := h.adminServer.SetGroupStatus(r.Context(), name, req.Enabled); err != nil { + writeJSONError(w, http.StatusInternalServerError, "Failed to update group status: "+err.Error()) + return + } + writeJSON(w, http.StatusOK, map[string]string{"message": "Group status updated"}) +} + +func (h *GroupHandlers) getGroupsPageData(r *http.Request) dash.GroupsPageData { + username := dash.UsernameFromContext(r.Context()) + if username == "" { + username = "admin" + } + + groups, err := h.adminServer.GetGroups(r.Context()) + if err != nil { + glog.Errorf("Failed to get groups: %v", err) + return dash.GroupsPageData{ + Username: username, + Groups: []dash.GroupData{}, + LastUpdated: time.Now(), + } + } + + activeCount := 0 + for _, g := range groups { + if g.Status == "enabled" { + activeCount++ + } + } + + // Get available users for dropdown + var availableUsers []string + users, err := h.adminServer.GetObjectStoreUsers(r.Context()) + if err == nil { + for _, user := range users { + availableUsers = append(availableUsers, user.Username) + } + } + + // Get available policies for dropdown + var availablePolicies []string + policies, err := h.adminServer.GetPolicies() + if err == nil { + for _, p := range policies { + availablePolicies = append(availablePolicies, p.Name) + } + } + + return dash.GroupsPageData{ + Username: username, + Groups: groups, + TotalGroups: len(groups), + ActiveGroups: activeCount, + AvailableUsers: availableUsers, + AvailablePolicies: availablePolicies, + LastUpdated: time.Now(), + } +} diff --git a/weed/admin/static/js/iam-utils.js b/weed/admin/static/js/iam-utils.js index baf8ba457..1b50d54a6 100644 --- a/weed/admin/static/js/iam-utils.js +++ b/weed/admin/static/js/iam-utils.js @@ -25,6 +25,29 @@ async function deleteUser(username) { }, 'Are you sure you want to delete this user? This action cannot be undone.'); } +// Delete group function +async function deleteGroup(name) { + showDeleteConfirm(name, async function () { + try { + const encodedName = encodeURIComponent(name); + const response = await fetch(`/api/groups/${encodedName}`, { + method: 'DELETE' + }); + + if (response.ok) { + showAlert('Group deleted successfully', 'success'); + setTimeout(() => window.location.reload(), 1000); + } else { + const error = await response.json().catch(() => ({})); + showAlert('Failed to delete group: ' + (error.error || 'Unknown error'), 'error'); + } + } catch (error) { + console.error('Error deleting group:', error); + showAlert('Failed to delete group: ' + error.message, 'error'); + } + }, 'Are you sure you want to delete this group? This action cannot be undone.'); +} + // Delete access key function async function deleteAccessKey(username, accessKey) { showDeleteConfirm(accessKey, async function () { diff --git a/weed/admin/view/app/groups.templ b/weed/admin/view/app/groups.templ new file mode 100644 index 000000000..6fb8b0be9 --- /dev/null +++ b/weed/admin/view/app/groups.templ @@ -0,0 +1,396 @@ +package app + +import ( + "fmt" + "github.com/seaweedfs/seaweedfs/weed/admin/dash" +) + +templ Groups(data dash.GroupsPageData) { +
+ +
+
+

+ Groups +

+

Manage IAM groups for organizing users and policies

+
+
+ +
+
+ + +
+
+
+
+
+
+
+ Total Groups +
+
+ {fmt.Sprintf("%d", data.TotalGroups)} +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ Active Groups +
+
+ {fmt.Sprintf("%d", data.ActiveGroups)} +
+
+
+ +
+
+
+
+
+
+ + +
+
+
Groups
+
+
+ if len(data.Groups) == 0 { +
+ +

No groups found. Create a group to get started.

+
+ } else { +
+ + + + + + + + + + + + for _, group := range data.Groups { + + + + + + + + } + +
NameMembersPoliciesStatusActions
+ {group.Name} + + {fmt.Sprintf("%d", group.MemberCount)} + + {fmt.Sprintf("%d", group.PolicyCount)} + + if group.Status == "enabled" { + Enabled + } else { + Disabled + } + + + +
+
+ } +
+
+ + + + + + +
+ + + +} diff --git a/weed/admin/view/app/groups_templ.go b/weed/admin/view/app/groups_templ.go new file mode 100644 index 000000000..0b417f4bb --- /dev/null +++ b/weed/admin/view/app/groups_templ.go @@ -0,0 +1,256 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package app + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/seaweedfs/seaweedfs/weed/admin/dash" +) + +func Groups(data dash.GroupsPageData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Groups

Manage IAM groups for organizing users and policies

Total Groups
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalGroups)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/groups.templ`, Line: 38, Col: 72} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Active Groups
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.ActiveGroups)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/groups.templ`, Line: 57, Col: 73} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
Groups
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.Groups) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

No groups found. Create a group to get started.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, group := range data.Groups { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
NameMembersPoliciesStatusActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/groups.templ`, Line: 96, Col: 63} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", group.MemberCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/groups.templ`, Line: 99, Col: 109} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", group.PolicyCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/groups.templ`, Line: 102, Col: 114} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if group.Status == "enabled" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "Enabled") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "Disabled") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("viewGroup('%s')", group.Name)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("deleteGroup('%s')", group.Name)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
Create Group
Group Details
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/weed/admin/view/layout/layout.templ b/weed/admin/view/layout/layout.templ index 6e87b462a..d717548fa 100644 --- a/weed/admin/view/layout/layout.templ +++ b/weed/admin/view/layout/layout.templ @@ -168,6 +168,11 @@ templ Layout(view ViewContext, content templ.Component) { Users +
OBJECT STORE
MANAGEMENT
OBJECT STORE
MANAGEMENT