From 800a618aaa3d49f477aaf3a471b884a997f70ffc Mon Sep 17 00:00:00 2001 From: river Date: Sun, 15 Mar 2026 12:26:04 +0000 Subject: [PATCH 1/3] feat: add role editor page with create/delete functionality - Add RoleEditorPage with role management - Add Create New Role button (admin only) - Add Delete Role button (admin only, admin role protected) - Fix useAuth import in RoleEditorPage --- Dockerfile | 2 + src/pages/RoleEditorPage.tsx | 139 ++++++++++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6fed7ee..665742e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,9 @@ COPY package.json package-lock.json* ./ RUN npm install COPY . . ARG VITE_WIZARD_PORT=18080 +ARG VITE_API_BASE ENV VITE_WIZARD_PORT=$VITE_WIZARD_PORT +ENV VITE_API_BASE=$VITE_API_BASE RUN npm run build # Production stage — lightweight static server, no nginx diff --git a/src/pages/RoleEditorPage.tsx b/src/pages/RoleEditorPage.tsx index ba483f8..a314414 100644 --- a/src/pages/RoleEditorPage.tsx +++ b/src/pages/RoleEditorPage.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react' import api from '@/services/api' +import { useAuth } from '@/hooks/useAuth' interface Permission { id: number @@ -11,18 +12,25 @@ interface Permission { interface Role { id: number name: string - description: string - is_global: boolean + description: string | null + is_global: boolean | null permission_ids: number[] } export default function RoleEditorPage() { + const { user } = useAuth() const [roles, setRoles] = useState([]) const [permissions, setPermissions] = useState([]) const [selectedRole, setSelectedRole] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [message, setMessage] = useState('') + const [showCreateForm, setShowCreateForm] = useState(false) + const [newRoleName, setNewRoleName] = useState('') + const [newRoleDesc, setNewRoleDesc] = useState('') + const [creating, setCreating] = useState(false) + + const isAdmin = user?.is_admin === true useEffect(() => { fetchData() @@ -68,6 +76,46 @@ export default function RoleEditorPage() { } } + const handleDeleteRole = async () => { + if (!selectedRole || !confirm(`Are you sure you want to delete the "${selectedRole.name}" role?`)) return + setSaving(true) + setMessage('') + try { + await api.delete(`/roles/${selectedRole.id}`) + setMessage('Role deleted successfully!') + setSelectedRole(null) + fetchData() + } catch (err: any) { + setMessage(err.response?.data?.detail || 'Failed to delete role') + } finally { + setSaving(false) + } + } + + const canDeleteRole = selectedRole && selectedRole.name !== 'admin' && isAdmin + + const handleCreateRole = async () => { + if (!newRoleName.trim()) return + setCreating(true) + setMessage('') + try { + await api.post('/roles', { + name: newRoleName.trim(), + description: newRoleDesc.trim() || null, + is_global: false + }) + setMessage('Role created successfully!') + setShowCreateForm(false) + setNewRoleName('') + setNewRoleDesc('') + fetchData() + } catch (err: any) { + setMessage(err.response?.data?.detail || 'Failed to create role') + } finally { + setCreating(false) + } + } + const groupedPermissions = permissions.reduce((acc, p) => { if (!acc[p.category]) acc[p.category] = [] acc[p.category].push(p) @@ -83,6 +131,74 @@ export default function RoleEditorPage() { Configure permissions for each role. Only admins can edit roles.

+ {isAdmin && !showCreateForm && ( + + )} + + {showCreateForm && ( +
+

Create New Role

+
+
+ + setNewRoleName(e.target.value)} + placeholder="e.g., developer, manager" + style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} + /> +
+
+ + setNewRoleDesc(e.target.value)} + placeholder="Role description" + style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} + /> +
+
+ + +
+
+
+ )} + {message && (
{saving ? 'Saving...' : 'Save Changes'} + {canDeleteRole && ( + + )} ) : (
From fb028086663602c07f149174c18dd112e0ed7e35 Mon Sep 17 00:00:00 2001 From: river Date: Sun, 15 Mar 2026 12:57:21 +0000 Subject: [PATCH 2/3] fix: set ARG before COPY for VITE_API_BASE to work in build --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 665742e..90fcc6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,11 +3,14 @@ FROM node:20-alpine AS build WORKDIR /app COPY package.json package-lock.json* ./ RUN npm install -COPY . . + +# Set build args and env vars BEFORE copying source code ARG VITE_WIZARD_PORT=18080 ARG VITE_API_BASE ENV VITE_WIZARD_PORT=$VITE_WIZARD_PORT ENV VITE_API_BASE=$VITE_API_BASE + +COPY . . RUN npm run build # Production stage — lightweight static server, no nginx From 1d749178997bb5b2279dac459dc6706741c0a942 Mon Sep 17 00:00:00 2001 From: river Date: Sun, 15 Mar 2026 13:36:27 +0000 Subject: [PATCH 3/3] fix: use backend:8000 for container network in wizard default --- .env | 2 ++ Dockerfile | 7 ------- src/pages/SetupWizardPage.tsx | 4 ++-- 3 files changed, 4 insertions(+), 9 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..c789158 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +VITE_API_BASE=http://backend:8000 +VITE_WIZARD_PORT=8080 diff --git a/Dockerfile b/Dockerfile index 90fcc6f..75ff17e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,6 @@ FROM node:20-alpine AS build WORKDIR /app COPY package.json package-lock.json* ./ RUN npm install - -# Set build args and env vars BEFORE copying source code -ARG VITE_WIZARD_PORT=18080 -ARG VITE_API_BASE -ENV VITE_WIZARD_PORT=$VITE_WIZARD_PORT -ENV VITE_API_BASE=$VITE_API_BASE - COPY . . RUN npm run build diff --git a/src/pages/SetupWizardPage.tsx b/src/pages/SetupWizardPage.tsx index 46b85bc..3e9bc47 100644 --- a/src/pages/SetupWizardPage.tsx +++ b/src/pages/SetupWizardPage.tsx @@ -38,7 +38,7 @@ export default function SetupWizardPage({ wizardBase, onComplete }: Props) { db_user: 'harborforge', db_password: 'harborforge_pass', db_database: 'harborforge', - backend_base_url: 'http://127.0.0.1:8000', + backend_base_url: 'http://backend:8000', project_name: '', project_description: '', }) @@ -182,7 +182,7 @@ export default function SetupWizardPage({ wizardBase, onComplete }: Props) {

Backend URL

Configure the HarborForge backend API URL

- +