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

- +