feat(groups): app permission checkboxes in create group form
This commit is contained in:
parent
9df43d8423
commit
46474981f0
7 changed files with 84 additions and 9 deletions
|
|
@ -10,6 +10,7 @@ type Group struct {
|
|||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Apps string `json:"apps"`
|
||||
UserCount int `json:"user_count"`
|
||||
}
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ func NewGroupStore(db *sql.DB) *GroupStore {
|
|||
|
||||
func (s *GroupStore) List() ([]Group, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT g.id, g.name, g.description,
|
||||
SELECT g.id, g.name, g.description, g.apps,
|
||||
(SELECT COUNT(*) FROM users WHERE ',' || groups || ',' LIKE '%,' || g.name || ',%') as user_count
|
||||
FROM groups g ORDER BY g.name
|
||||
`)
|
||||
|
|
@ -36,7 +37,7 @@ func (s *GroupStore) List() ([]Group, error) {
|
|||
var groups []Group
|
||||
for rows.Next() {
|
||||
var g Group
|
||||
if err := rows.Scan(&g.ID, &g.Name, &g.Description, &g.UserCount); err != nil {
|
||||
if err := rows.Scan(&g.ID, &g.Name, &g.Description, &g.Apps, &g.UserCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groups = append(groups, g)
|
||||
|
|
@ -46,7 +47,7 @@ func (s *GroupStore) List() ([]Group, error) {
|
|||
|
||||
func (s *GroupStore) GetByName(name string) (*Group, error) {
|
||||
var g Group
|
||||
err := s.db.QueryRow(`SELECT id, name, description FROM groups WHERE name = ?`, name).Scan(&g.ID, &g.Name, &g.Description)
|
||||
err := s.db.QueryRow(`SELECT id, name, description, apps FROM groups WHERE name = ?`, name).Scan(&g.ID, &g.Name, &g.Description, &g.Apps)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -56,8 +57,8 @@ func (s *GroupStore) GetByName(name string) (*Group, error) {
|
|||
return &g, nil
|
||||
}
|
||||
|
||||
func (s *GroupStore) Create(name, description string) error {
|
||||
_, err := s.db.Exec(`INSERT INTO groups (name, description) VALUES (?, ?)`, name, description)
|
||||
func (s *GroupStore) Create(name, description, apps string) error {
|
||||
_, err := s.db.Exec(`INSERT INTO groups (name, description, apps) VALUES (?, ?, ?)`, name, description, apps)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create group %s: %w", name, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,11 +69,27 @@ templ CreateGroupForm() {
|
|||
<label>Description</label>
|
||||
<input type="text" name="description" class="input" placeholder="Team description"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Permitted Apps</label>
|
||||
<div class="checkbox-list" id="group-apps">
|
||||
for _, a := range allApps() {
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="app" value={ a }/>
|
||||
<span>{ a }</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn" style="width:100%;margin-top:8px">Create Group</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
func allApps() []string {
|
||||
return []string{"Admin Panel", "Files", "Mail", "Calendar", "Office", "Contacts", "Tasks", "Chat"}
|
||||
}
|
||||
|
||||
|
||||
type GroupRow struct {
|
||||
Name string
|
||||
UserCount int
|
||||
|
|
|
|||
|
|
@ -192,7 +192,43 @@ func CreateGroupForm() templ.Component {
|
|||
templ_7745c5c3_Var10 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"detail-card\"><div class=\"section-header\"><h3>New Group</h3><button class=\"btn-sm\" hx-get=\"/admin/groups/cancel-form\" hx-target=\"#group-detail\" hx-swap=\"innerHTML\">Cancel</button></div><form hx-post=\"/admin/groups\" hx-target=\"#group-list-body\" hx-swap=\"innerHTML\" hx-on::after-request=\"htmx.trigger('#group-detail', 'click')\"><div class=\"form-group\"><label>Group Name *</label> <input type=\"text\" name=\"name\" class=\"input\" required placeholder=\"e.g. engineering\"></div><div class=\"form-group\"><label>Description</label> <input type=\"text\" name=\"description\" class=\"input\" placeholder=\"Team description\"></div><button type=\"submit\" class=\"btn\" style=\"width:100%;margin-top:8px\">Create Group</button></form></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"detail-card\"><div class=\"section-header\"><h3>New Group</h3><button class=\"btn-sm\" hx-get=\"/admin/groups/cancel-form\" hx-target=\"#group-detail\" hx-swap=\"innerHTML\">Cancel</button></div><form hx-post=\"/admin/groups\" hx-target=\"#group-list-body\" hx-swap=\"innerHTML\" hx-on::after-request=\"htmx.trigger('#group-detail', 'click')\"><div class=\"form-group\"><label>Group Name *</label> <input type=\"text\" name=\"name\" class=\"input\" required placeholder=\"e.g. engineering\"></div><div class=\"form-group\"><label>Description</label> <input type=\"text\" name=\"description\" class=\"input\" placeholder=\"Team description\"></div><div class=\"form-group\"><label>Permitted Apps</label><div class=\"checkbox-list\" id=\"group-apps\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, a := range allApps() {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<label class=\"checkbox-item\"><input type=\"checkbox\" name=\"app\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(a)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/groups.templ`, Line: 77, Col: 50}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\"> <span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(a)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/groups.templ`, Line: 78, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span></label>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></div><button type=\"submit\" class=\"btn\" style=\"width:100%;margin-top:8px\">Create Group</button></form></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -200,6 +236,10 @@ func CreateGroupForm() templ.Component {
|
|||
})
|
||||
}
|
||||
|
||||
func allApps() []string {
|
||||
return []string{"Admin Panel", "Files", "Mail", "Calendar", "Office", "Contacts", "Tasks", "Chat"}
|
||||
}
|
||||
|
||||
type GroupRow struct {
|
||||
Name string
|
||||
UserCount int
|
||||
|
|
|
|||
|
|
@ -143,5 +143,9 @@ templ adminStyles() {
|
|||
.group-row-left{display:flex;align-items:center;gap:10px}
|
||||
.group-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:0.8rem;font-weight:700;color:#fff}
|
||||
.group-row-right{display:flex;align-items:center;gap:8px}
|
||||
.checkbox-list{display:grid;grid-template-columns:1fr 1fr;gap:4px;max-height:120px;overflow-y:auto}
|
||||
.checkbox-item{display:flex;align-items:center;gap:6px;padding:6px 8px;border-radius:6px;cursor:pointer;font-size:0.85rem}
|
||||
.checkbox-item:hover{background:var(--surface2)}
|
||||
.checkbox-item input[type=checkbox]{accent-color:var(--blue)}
|
||||
</style>
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -332,7 +332,16 @@ func (h *Handler) createGroup(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "name required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := h.groupStore.Create(name, desc); err != nil {
|
||||
// Collect checked apps
|
||||
apps := r.Form["app"]
|
||||
appsStr := ""
|
||||
for i, a := range apps {
|
||||
if i > 0 {
|
||||
appsStr += ","
|
||||
}
|
||||
appsStr += a
|
||||
}
|
||||
if err := h.groupStore.Create(name, desc, appsStr); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,10 +67,15 @@ func (d *Database) ensureTable(name string) error {
|
|||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
apps TEXT NOT NULL DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.DB.Exec(`ALTER TABLE groups ADD COLUMN apps TEXT NOT NULL DEFAULT ''`)
|
||||
return nil
|
||||
|
||||
case "users":
|
||||
// Create table if it doesn't exist
|
||||
|
|
|
|||
Loading…
Reference in a new issue