Initialisiere...
- ) : (
- <>
- {activeTab === 'dashboard' && (
-
@@ -185,6 +210,31 @@ export default function UseCasesPage() {
)}
+ {/* Pagination */}
+ {!loading && totalCount > PAGE_SIZE && (
+
+
+ {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, totalCount)} von {totalCount}
+
+
+
+
+
+
+ )}
+
{/* Empty State */}
{!loading && filtered.length === 0 && !error && (
diff --git a/admin-compliance/app/api/sdk/v1/company-profile/route.ts b/admin-compliance/app/api/sdk/v1/company-profile/route.ts
index cdbb74a..08b862a 100644
--- a/admin-compliance/app/api/sdk/v1/company-profile/route.ts
+++ b/admin-compliance/app/api/sdk/v1/company-profile/route.ts
@@ -79,3 +79,43 @@ export async function POST(request: NextRequest) {
)
}
}
+
+/**
+ * Proxy: PATCH /api/sdk/v1/company-profile → Backend PATCH /api/v1/company-profile
+ * Partial updates for individual fields
+ */
+export async function PATCH(request: NextRequest) {
+ try {
+ const body = await request.json()
+ const tenantId = body.tenant_id || 'default'
+
+ const response = await fetch(
+ `${BACKEND_URL}/api/v1/company-profile?tenant_id=${encodeURIComponent(tenantId)}`,
+ {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Tenant-ID': tenantId,
+ },
+ body: JSON.stringify(body),
+ }
+ )
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json(
+ { error: 'Backend error', details: errorText },
+ { status: response.status }
+ )
+ }
+
+ const data = await response.json()
+ return NextResponse.json(data)
+ } catch (error) {
+ console.error('Failed to patch company profile:', error)
+ return NextResponse.json(
+ { error: 'Failed to connect to backend' },
+ { status: 503 }
+ )
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/compliance-scope/route.ts b/admin-compliance/app/api/sdk/v1/compliance-scope/route.ts
new file mode 100644
index 0000000..7d426a6
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/compliance-scope/route.ts
@@ -0,0 +1,84 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+/**
+ * Proxy: GET /api/sdk/v1/compliance-scope → Backend GET /api/v1/compliance-scope
+ * Retrieves the persisted scope decision for a tenant.
+ */
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const tenantId = searchParams.get('tenant_id') || 'default'
+
+ const response = await fetch(
+ `${BACKEND_URL}/api/v1/compliance-scope?tenant_id=${encodeURIComponent(tenantId)}`,
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Tenant-ID': tenantId,
+ },
+ }
+ )
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ return NextResponse.json(null, { status: 404 })
+ }
+ const errorText = await response.text()
+ return NextResponse.json(
+ { error: 'Backend error', details: errorText },
+ { status: response.status }
+ )
+ }
+
+ const data = await response.json()
+ return NextResponse.json(data)
+ } catch (error) {
+ console.error('Failed to fetch compliance scope:', error)
+ return NextResponse.json(
+ { error: 'Failed to connect to backend' },
+ { status: 503 }
+ )
+ }
+}
+
+/**
+ * Proxy: POST /api/sdk/v1/compliance-scope → Backend POST /api/v1/compliance-scope
+ * Persists the scope decision and answers.
+ */
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+ const tenantId = body.tenant_id || 'default'
+
+ const response = await fetch(
+ `${BACKEND_URL}/api/v1/compliance-scope?tenant_id=${encodeURIComponent(tenantId)}`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Tenant-ID': tenantId,
+ },
+ body: JSON.stringify(body),
+ }
+ )
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json(
+ { error: 'Backend error', details: errorText },
+ { status: response.status }
+ )
+ }
+
+ const data = await response.json()
+ return NextResponse.json(data)
+ } catch (error) {
+ console.error('Failed to save compliance scope:', error)
+ return NextResponse.json(
+ { error: 'Failed to connect to backend' },
+ { status: 503 }
+ )
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/import/[id]/route.ts b/admin-compliance/app/api/sdk/v1/import/[id]/route.ts
new file mode 100644
index 0000000..d9561ad
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/import/[id]/route.ts
@@ -0,0 +1,41 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+/**
+ * Proxy: DELETE /api/sdk/v1/import/:id → Backend DELETE /api/v1/import/:id
+ */
+export async function DELETE(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id } = await params
+
+ const response = await fetch(`${BACKEND_URL}/api/v1/import/${encodeURIComponent(id)}`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json(
+ { error: 'Backend error', details: errorText },
+ { status: response.status }
+ )
+ }
+
+ return NextResponse.json({ success: true })
+ } catch (error) {
+ console.error('Failed to delete import:', error)
+ return NextResponse.json(
+ { error: 'Failed to connect to backend' },
+ { status: 503 }
+ )
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/modules/[moduleId]/route.ts b/admin-compliance/app/api/sdk/v1/modules/[moduleId]/route.ts
new file mode 100644
index 0000000..8c06444
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/modules/[moduleId]/route.ts
@@ -0,0 +1,45 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+/**
+ * Proxy: GET /api/sdk/v1/modules/:moduleId → Backend GET /api/modules/:moduleId
+ */
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ moduleId: string }> }
+) {
+ try {
+ const { moduleId } = await params
+
+ const response = await fetch(
+ `${BACKEND_URL}/api/modules/${encodeURIComponent(moduleId)}`,
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ }
+ )
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json(
+ { error: 'Backend error', details: errorText },
+ { status: response.status }
+ )
+ }
+
+ const data = await response.json()
+ return NextResponse.json(data)
+ } catch (error) {
+ console.error('Failed to fetch module:', error)
+ return NextResponse.json(
+ { error: 'Failed to connect to backend' },
+ { status: 503 }
+ )
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/modules/route.ts b/admin-compliance/app/api/sdk/v1/modules/route.ts
index 57c77c2..880796d 100644
--- a/admin-compliance/app/api/sdk/v1/modules/route.ts
+++ b/admin-compliance/app/api/sdk/v1/modules/route.ts
@@ -54,3 +54,41 @@ export async function GET(request: NextRequest) {
)
}
}
+
+/**
+ * Proxy: POST /api/sdk/v1/modules → Backend POST /api/modules
+ * Creates a new custom module.
+ */
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+
+ const response = await fetch(`${BACKEND_URL}/api/modules`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ body: JSON.stringify(body),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json(
+ { error: 'Backend error', details: errorText },
+ { status: response.status }
+ )
+ }
+
+ const data = await response.json()
+ return NextResponse.json(data)
+ } catch (error) {
+ console.error('Failed to create module:', error)
+ return NextResponse.json(
+ { error: 'Failed to connect to backend' },
+ { status: 503 }
+ )
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/blocked-content/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/blocked-content/route.ts
new file mode 100644
index 0000000..f8f5418
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/blocked-content/route.ts
@@ -0,0 +1,30 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const queryString = searchParams.toString()
+ const url = `${BACKEND_URL}/api/v1/admin/blocked-content${queryString ? `?${queryString}` : ''}`
+
+ const response = await fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to fetch blocked content:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/compliance-report/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/compliance-report/route.ts
new file mode 100644
index 0000000..16ba601
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/compliance-report/route.ts
@@ -0,0 +1,30 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const queryString = searchParams.toString()
+ const url = `${BACKEND_URL}/api/v1/admin/compliance-report${queryString ? `?${queryString}` : ''}`
+
+ const response = await fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to fetch compliance report:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/operations-matrix/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/operations-matrix/route.ts
new file mode 100644
index 0000000..2dfade4
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/operations-matrix/route.ts
@@ -0,0 +1,26 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function GET(request: NextRequest) {
+ try {
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/operations-matrix`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to fetch operations matrix:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/operations/[id]/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/operations/[id]/route.ts
new file mode 100644
index 0000000..3f81b45
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/operations/[id]/route.ts
@@ -0,0 +1,34 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function PUT(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id } = await params
+ const body = await request.json()
+
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/operations/${encodeURIComponent(id)}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ body: JSON.stringify(body),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to update operation:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/pii-rules/[id]/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/pii-rules/[id]/route.ts
new file mode 100644
index 0000000..b68a44c
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/pii-rules/[id]/route.ts
@@ -0,0 +1,63 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function PUT(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id } = await params
+ const body = await request.json()
+
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/pii-rules/${encodeURIComponent(id)}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ body: JSON.stringify(body),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to update PII rule:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
+
+export async function DELETE(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id } = await params
+
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/pii-rules/${encodeURIComponent(id)}`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to delete PII rule:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/pii-rules/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/pii-rules/route.ts
new file mode 100644
index 0000000..ffbe713
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/pii-rules/route.ts
@@ -0,0 +1,53 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function GET(request: NextRequest) {
+ try {
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/pii-rules`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to fetch PII rules:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/pii-rules`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ body: JSON.stringify(body),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to create PII rule:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/policy-audit/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/policy-audit/route.ts
new file mode 100644
index 0000000..2616701
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/policy-audit/route.ts
@@ -0,0 +1,30 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const queryString = searchParams.toString()
+ const url = `${BACKEND_URL}/api/v1/admin/policy-audit${queryString ? `?${queryString}` : ''}`
+
+ const response = await fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to fetch policy audit:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/policy-stats/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/policy-stats/route.ts
new file mode 100644
index 0000000..c8a589f
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/policy-stats/route.ts
@@ -0,0 +1,26 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function GET(request: NextRequest) {
+ try {
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/policy-stats`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to fetch policy stats:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/sources/[id]/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/sources/[id]/route.ts
new file mode 100644
index 0000000..0ac0652
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/sources/[id]/route.ts
@@ -0,0 +1,90 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id } = await params
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/sources/${encodeURIComponent(id)}`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to fetch source:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
+
+export async function PUT(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id } = await params
+ const body = await request.json()
+
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/sources/${encodeURIComponent(id)}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ body: JSON.stringify(body),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to update source:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
+
+export async function DELETE(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id } = await params
+
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/sources/${encodeURIComponent(id)}`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to delete source:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/source-policy/sources/route.ts b/admin-compliance/app/api/sdk/v1/source-policy/sources/route.ts
new file mode 100644
index 0000000..eaec679
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/source-policy/sources/route.ts
@@ -0,0 +1,57 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8002'
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const queryString = searchParams.toString()
+ const url = `${BACKEND_URL}/api/v1/admin/sources${queryString ? `?${queryString}` : ''}`
+
+ const response = await fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to fetch sources:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+
+ const response = await fetch(`${BACKEND_URL}/api/v1/admin/sources`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ body: JSON.stringify(body),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json({ error: 'Backend error', details: errorText }, { status: response.status })
+ }
+
+ return NextResponse.json(await response.json())
+ } catch (error) {
+ console.error('Failed to create source:', error)
+ return NextResponse.json({ error: 'Failed to connect to backend' }, { status: 503 })
+ }
+}
diff --git a/admin-compliance/app/api/sdk/v1/ucca/assessments/[id]/route.ts b/admin-compliance/app/api/sdk/v1/ucca/assessments/[id]/route.ts
index 0f64036..b516f03 100644
--- a/admin-compliance/app/api/sdk/v1/ucca/assessments/[id]/route.ts
+++ b/admin-compliance/app/api/sdk/v1/ucca/assessments/[id]/route.ts
@@ -41,6 +41,47 @@ export async function GET(
}
}
+/**
+ * Proxy: PUT /api/sdk/v1/ucca/assessments/[id] → Go Backend PUT /sdk/v1/ucca/assessments/:id
+ */
+export async function PUT(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id } = await params
+ const body = await request.json()
+
+ const response = await fetch(`${SDK_URL}/sdk/v1/ucca/assessments/${id}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(request.headers.get('X-Tenant-ID') && {
+ 'X-Tenant-ID': request.headers.get('X-Tenant-ID') as string,
+ }),
+ },
+ body: JSON.stringify(body),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json(
+ { error: 'UCCA backend error', details: errorText },
+ { status: response.status }
+ )
+ }
+
+ const data = await response.json()
+ return NextResponse.json(data)
+ } catch (error) {
+ console.error('Failed to update UCCA assessment:', error)
+ return NextResponse.json(
+ { error: 'Failed to connect to UCCA backend' },
+ { status: 503 }
+ )
+ }
+}
+
/**
* Proxy: DELETE /api/sdk/v1/ucca/assessments/[id] → Go Backend DELETE /sdk/v1/ucca/assessments/:id
*/
diff --git a/admin-compliance/components/sdk/compliance-scope/ScopeDecisionTab.tsx b/admin-compliance/components/sdk/compliance-scope/ScopeDecisionTab.tsx
index 30dfe0c..43d716c 100644
--- a/admin-compliance/components/sdk/compliance-scope/ScopeDecisionTab.tsx
+++ b/admin-compliance/components/sdk/compliance-scope/ScopeDecisionTab.tsx
@@ -5,9 +5,22 @@ import { DEPTH_LEVEL_LABELS, DEPTH_LEVEL_DESCRIPTIONS, DEPTH_LEVEL_COLORS, DOCUM
interface ScopeDecisionTabProps {
decision: ScopeDecision | null
+ answers?: unknown[]
+ onBackToWizard?: () => void
+ onGoToExport?: () => void
+ canEvaluate?: boolean
+ onEvaluate?: () => void
+ isEvaluating?: boolean
}
-export function ScopeDecisionTab({ decision }: ScopeDecisionTabProps) {
+export function ScopeDecisionTab({
+ decision,
+ onBackToWizard,
+ onGoToExport,
+ canEvaluate,
+ onEvaluate,
+ isEvaluating,
+}: ScopeDecisionTabProps) {
const [expandedTrigger, setExpandedTrigger] = useState(null)
const [showAuditTrail, setShowAuditTrail] = useState(false)
@@ -320,6 +333,35 @@ export function ScopeDecisionTab({ decision }: ScopeDecisionTabProps) {
)}
+ {/* Action Buttons */}
+
+ {onBackToWizard && (
+
+ )}
+ {canEvaluate && onEvaluate && (
+
+ )}
+ {onGoToExport && (
+
+ )}
+
+
{/* Audit Trail */}
{decision.auditTrail && decision.auditTrail.length > 0 && (
diff --git a/admin-compliance/components/sdk/compliance-scope/ScopeExportTab.tsx b/admin-compliance/components/sdk/compliance-scope/ScopeExportTab.tsx
index bdfc1fa..961fd8c 100644
--- a/admin-compliance/components/sdk/compliance-scope/ScopeExportTab.tsx
+++ b/admin-compliance/components/sdk/compliance-scope/ScopeExportTab.tsx
@@ -126,10 +126,63 @@ export function ScopeExportTab({ decision: decisionProp, answers: answersProp, s
})
}, [generateMarkdownSummary])
+ /** Simple markdown-to-HTML converter for print view */
+ const markdownToHtml = useCallback((md: string): string => {
+ let html = md
+ // Escape HTML entities
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ // Headers
+ html = html.replace(/^### (.+)$/gm, '
$1
')
+ html = html.replace(/^## (.+)$/gm, '
$1
')
+ html = html.replace(/^# (.+)$/gm, '
$1
')
+ // Bold
+ html = html.replace(/\*\*(.+?)\*\*/g, '
$1')
+ // Tables
+ const lines = html.split('\n')
+ let inTable = false
+ const result: string[] = []
+ for (const line of lines) {
+ if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
+ if (line.includes('---')) continue // separator row
+ const cells = line.split('|').filter(c => c.trim() !== '')
+ if (!inTable) {
+ result.push('
')
+ cells.forEach(c => result.push(`| ${c.trim()} | `))
+ result.push('
')
+ inTable = true
+ } else {
+ result.push('')
+ cells.forEach(c => result.push(`| ${c.trim()} | `))
+ result.push('
')
+ }
+ } else {
+ if (inTable) {
+ result.push('
')
+ inTable = false
+ }
+ // List items
+ if (line.trim().startsWith('- ')) {
+ result.push(`
${line.trim().slice(2)}`)
+ } else if (/^\d+\.\s/.test(line.trim())) {
+ result.push(`
${line.trim().replace(/^\d+\.\s/, '')}`)
+ } else if (line.trim() === '') {
+ result.push('
')
+ } else {
+ result.push(`
${line}
`)
+ }
+ }
+ }
+ if (inTable) result.push('')
+ return result.join('\n')
+ }, [])
+
const handlePrintView = useCallback(() => {
if (!decision) return
const markdown = generateMarkdownSummary()
+ const renderedHtml = markdownToHtml(markdown)
const htmlContent = `
@@ -152,6 +205,7 @@ export function ScopeExportTab({ decision: decisionProp, answers: answersProp, s
th { background-color: #f3f4f6; font-weight: 600; }
ul { list-style-type: disc; padding-left: 20px; }
li { margin: 8px 0; }
+ p { margin: 4px 0; }
@media print {
body { margin: 20px; }
h1, h2, h3 { page-break-after: avoid; }
@@ -160,7 +214,7 @@ export function ScopeExportTab({ decision: decisionProp, answers: answersProp, s
-
${markdown}
+ ${renderedHtml}