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
-
+