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 6fed7ee..75ff17e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,8 +4,6 @@ WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install
COPY . .
-ARG VITE_WIZARD_PORT=18080
-ENV VITE_WIZARD_PORT=$VITE_WIZARD_PORT
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 && (
+
+ )}
>
) : (
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
-
+