Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e0fe691
#270 add bookmark dialog
F3l1x1vo Jul 30, 2026
80b1716
add edit bookmarks dialog
F3l1x1vo Jul 31, 2026
b866c48
fix cancel editing bookmark
F3l1x1vo Jul 31, 2026
a663c17
fix darkmode for icons, cursor pointer over boxes
F3l1x1vo Jul 31, 2026
52340e1
update product icons to use colored versions where possible
F3l1x1vo Jul 31, 2026
1f9f85e
scaled icons down to 64x64
F3l1x1vo Jul 31, 2026
d93d2fe
Revert "scaled icons down to 64x64"
F3l1x1vo Jul 31, 2026
3684df3
revert image downscale, use svelte enhanced image component to downsc…
F3l1x1vo Jul 31, 2026
72426d6
fix unit test
F3l1x1vo Aug 4, 2026
53dd975
Add OPA client for admin rights checking (#276)
dklOrdix Aug 4, 2026
1ccbb04
#270 add admin check to pinning, add OPA tests
F3l1x1vo Aug 6, 2026
a0e5f1c
fix unit test
F3l1x1vo Aug 6, 2026
33bc633
#270 show pin for all only to admins
F3l1x1vo Aug 10, 2026
7a7d76c
fix unit test
F3l1x1vo Aug 10, 2026
edd5d5f
#271 overhaul dashboard layout design (#281)
F3l1x1vo Sep 2, 2026
fc1c717
Add bookmarks to sidebar, add pinning of bookmarks, display bookmarks…
F3l1x1vo Sep 2, 2026
dd9a256
fix styling
F3l1x1vo Sep 2, 2026
a008a2b
Merge remote-tracking branch 'origin/main' into feat/app-bookmark-dialog
F3l1x1vo Sep 10, 2026
d022e4d
#293 remove support for embedded external services
F3l1x1vo Sep 10, 2026
87137fb
#278 implement changes suggested in code review
F3l1x1vo Sep 14, 2026
e88c3d1
Merge branch 'main' into feat/app-bookmark-dialog
F3l1x1vo Sep 14, 2026
4d79ef9
#278 refactor comments
F3l1x1vo Sep 14, 2026
6e1a6d0
#278 fix unit tests
F3l1x1vo Sep 14, 2026
f8daddc
#278 open bookmarks from dashboard as well
F3l1x1vo Sep 14, 2026
16532d4
#278 implement changes suggested in code review
F3l1x1vo Sep 14, 2026
011d9c9
#278 implement changes suggested in code review
F3l1x1vo Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# A long random secret used to sign sessions and tokens (min 32 characters)
STACKABLE_COCKPIT_SESSION_SECRET=change-me-to-a-long-random-secret-min-32-chars

# The publicly accessible base URL of this application
# The publicly accessible base URL of this application.
# In development this is derived from the request host (so any free port works,
# e.g. when 5173 is already taken). In production builds this value is used
# directly for the OIDC redirect URI and must match the public origin.
STACKABLE_COCKPIT_BASE_URL=http://localhost:5173

# OIDC discovery URL (Keycloak, Entra ID, or any compliant OIDC provider)
Expand Down Expand Up @@ -41,3 +44,8 @@ STACKABLE_COCKPIT_OIDC_CLIENT_SECRET=your-client-secret
# STACKABLE_COCKPIT_COMPLETION_ENABLED=false # Disable SQL editor code completion (default: true)
# STACKABLE_COCKPIT_STORAGE_BROWSER_ENABLED=true # Enable S3/HDFS file browser (default: false)
# PUBLIC_STACKABLE_COCKPIT_UPLOAD_CONCURRENCY=3 # Maximum number of concurrent file uploads (default: 3)
Comment thread
F3l1x1vo marked this conversation as resolved.

# OPA (Open Policy Agent) — admin rights checking
# STACKABLE_COCKPIT_OPA_ENABLED=true # Enable OPA admin checks (default: false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still does not line up. Env is set to true, comment here says false and the comment in src/lib/server/feature-flags.ts says false too. Its three places that need to line up

# STACKABLE_COCKPIT_OPA_URL=http://localhost:8181 # OPA server base URL (required when enabled)
# STACKABLE_COCKPIT_OPA_TIMEOUT=5000 # OPA request timeout in milliseconds (default: 5000)
2 changes: 2 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ STACKABLE_COCKPIT_SESSION_SECRET=e2e-test-session-secret-that-is-long-enough
STACKABLE_COCKPIT_BASE_URL=http://localhost:4173
STACKABLE_COCKPIT_TRINO_URL=http://localhost:8080
STACKABLE_COCKPIT_STORAGE_BROWSER_ENABLED=true
STACKABLE_COCKPIT_OPA_ENABLED=true
STACKABLE_COCKPIT_OPA_URL=http://localhost:9191
ORIGIN=http://localhost:4173
STACKABLE_COCKPIT_TEXT_PREVIEW_BYTES=262144
STACKABLE_COCKPIT_IMAGE_PREVIEW_BYTES=5242880
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ coverage/
# Helm templates
deploy/helm/
dev/garage/
dev/opa/
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,

// Helm templates use {{ }} syntax that Prettier mangles — disable formatting.
"[yaml]": {
"editor.formatOnSave": false
},

// Route .svelte formatting through the Svelte extension so it applies the
// Svelte-aware Prettier plugin (configured in .prettierrc).
"[svelte]": {
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ The app exposes a Prometheus scrape endpoint at `/metrics` via `src/routes/metri
- Use spaces not tabs
- Run npm run format
- Use British English
- **New Features**: Do not add database migrations, data migrations, or legacy-data compatibility paths for a new feature unless persisted data or an external consumer already exists and requires it. Ask before adding one if that need is unclear.
- For the server side only: Always include logging at debug and info levels as appropriate
- **Redirects**: Never wrap `throw redirect()` in try-catch. Put redirect AFTER try-catch to avoid it being caught.

Expand Down
7 changes: 7 additions & 0 deletions dev/opa/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
apiVersion: v2
name: opa
description: Open Policy Agent for local dev and E2E testing
type: application
version: 0.1.0
appVersion: '1.16.2'
18 changes: 18 additions & 0 deletions dev/opa/policies/admin.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package stackable

default admin = false

# Admin if user ID is in the hardcoded admin list
admin if {
admin_users[input.user.id]
}

# Admin if user email ends with the admin domain
admin if {
endswith(input.user.email, "@admin.example.com")
}

admin_users := {
"admin-user-id-1": true,
"admin-user-id-2": true,
}
9 changes: 9 additions & 0 deletions dev/opa/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: opa-policies
namespace: default
data:
admin.rego: |-
{{- .Files.Get "policies/admin.rego" | nindent 4 }}
60 changes: 60 additions & 0 deletions dev/opa/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: opa
namespace: default
labels:
app: opa
spec:
replicas: 1
selector:
matchLabels:
app: opa
template:
metadata:
labels:
app: opa
spec:
containers:
- name: opa
image: '{{ .Values.image.repository }}:{{ .Values.image.tag }}'
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- run
- --server
- --addr
- 0.0.0.0:8181
- '{{ .Values.policyMountPath }}/admin.rego'
ports:
- name: http
containerPort: 8181
protocol: TCP
readinessProbe:
httpGet:
path: /health
port: 8181
initialDelaySeconds: 2
periodSeconds: 3
failureThreshold: 10
livenessProbe:
httpGet:
path: /health
port: 8181
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: policies
mountPath: '{{ .Values.policyMountPath }}'
readOnly: true
resources:
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
volumes:
- name: policies
configMap:
name: opa-policies
15 changes: 15 additions & 0 deletions dev/opa/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
apiVersion: v1
kind: Service
metadata:
name: opa
namespace: default
spec:
type: NodePort
selector:
app: opa
ports:
- name: http
port: 8181
targetPort: 8181
nodePort: {{ .Values.nodePort }}
19 changes: 19 additions & 0 deletions dev/opa/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
image:
repository: openpolicyagent/opa
tag: 1.16.2
pullPolicy: IfNotPresent

# NodePort for the OPA HTTP API.
nodePort: 30181

# Path inside the container where policies are loaded from.
policyMountPath: /policies

resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
34 changes: 31 additions & 3 deletions dev/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ if [[ "$SKIP_GARAGE" == false ]]; then
--timeout 60s
fi

# ------------------------------------------------------------------
# 5c. Deploy OPA (via Helm)
# ------------------------------------------------------------------
echo ""
echo "Deploying OPA..."
helm upgrade --install opa "$SCRIPT_DIR/opa" \
--namespace default \
--wait \
--timeout 60s

# On some local Kubernetes distributions (e.g. Rancher Desktop k3s), the node's
# InternalIP is not reachable from the host network, but NodePorts are exposed
# on localhost. Probe both and use the first reachable URL.
Expand Down Expand Up @@ -171,11 +181,12 @@ else

create_user() {
local username=$1 password=$2 first=$3 last=$4
local email=${5:-"$username@example.com"}
echo "Creating user '$username'..."
kcadm create users \
-r stackable \
-s username="$username" \
-s email="$username@example.com" \
-s email="$email" \
-s firstName="$first" \
-s lastName="$last" \
-s enabled=true
Expand All @@ -185,7 +196,7 @@ else
--new-password "$password"
}

create_user alice alicealice Alice Example
create_user alice alicealice Alice Example alice@admin.example.com
create_user bob bobbob Bob Example

echo "Fetching client secret..."
Expand Down Expand Up @@ -260,6 +271,17 @@ if [[ "$SKIP_TRINO" == false ]]; then
TRINO_BASE_URL="${TRINO_BASE_URL:-https://${NODE_IP}:${TRINO_PORT}}"
fi

# Probe OPA reachability (same pattern as Keycloak/Trino).
OPA_NODE_PORT=30181
OPA_BASE_URL=""
for base in "http://${NODE_IP}:${OPA_NODE_PORT}" "http://127.0.0.1:${OPA_NODE_PORT}" "http://localhost:${OPA_NODE_PORT}"; do
if curl -sf --max-time 2 "${base}/health" >/dev/null 2>&1; then
OPA_BASE_URL="$base"
break
fi
done
OPA_BASE_URL="${OPA_BASE_URL:-http://${NODE_IP}:${OPA_NODE_PORT}}"

if [[ "$SKIP_TRINO" == false ]]; then
cat > "$ENV_FILE" <<EOF
STACKABLE_COCKPIT_OIDC_DISCOVERY_URL=${KEYCLOAK_BASE_URL}/realms/stackable/.well-known/openid-configuration
Expand All @@ -273,6 +295,8 @@ STACKABLE_COCKPIT_TRINO_AUTH_USERNAME=stackable-cockpit
STACKABLE_COCKPIT_TRINO_AUTH_PASSWORD=stackable-cockpit-dev
STACKABLE_COCKPIT_TRINO_TLS_INSECURE=true
STACKABLE_COCKPIT_STORAGE_BROWSER_ENABLED=true
STACKABLE_COCKPIT_OPA_ENABLED=true
STACKABLE_COCKPIT_OPA_URL=${OPA_BASE_URL}
STACKABLE_COCKPIT_TEXT_PREVIEW_BYTES=262144
STACKABLE_COCKPIT_IMAGE_PREVIEW_BYTES=5242880
STACKABLE_COCKPIT_PDF_PREVIEW_BYTES=26214400
Expand All @@ -292,6 +316,8 @@ STACKABLE_COCKPIT_OIDC_CLIENT_SECRET=${SECRET}
STACKABLE_COCKPIT_SESSION_SECRET=${SESSION_SECRET}
STACKABLE_COCKPIT_BASE_URL=http://localhost:5173
STACKABLE_COCKPIT_STORAGE_BROWSER_ENABLED=true
STACKABLE_COCKPIT_OPA_ENABLED=true
STACKABLE_COCKPIT_OPA_URL=${OPA_BASE_URL}
STACKABLE_COCKPIT_TEXT_PREVIEW_BYTES=262144
STACKABLE_COCKPIT_IMAGE_PREVIEW_BYTES=5242880
STACKABLE_COCKPIT_PDF_PREVIEW_BYTES=26214400
Expand Down Expand Up @@ -339,6 +365,8 @@ echo ""
echo "Keycloak: http://${NODE_IP}:30080"
echo " Admin: admin / admin"
echo ""
echo "OPA: ${OPA_BASE_URL}"
echo ""
if [[ "$SKIP_TRINO" == false ]]; then
echo "Trino endpoint: https://${NODE_IP}:${TRINO_PORT}"
echo ""
Expand All @@ -354,5 +382,5 @@ if [[ "$SKIP_GARAGE" == false ]]; then
echo ""
fi
echo "Test users (OIDC):"
echo " alice / alicealice"
echo " alice / alicealice (admin)"
echo " bob / bobbob"
12 changes: 12 additions & 0 deletions e2e/auth/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { test as setup, expect } from '@playwright/test';
import path from 'path';
import { waitForHydration } from '../support/helpers.js';
import { ISSUER_URL, PROFILE_COOKIE } from '../support/mock-oidc-server.js';

// Each Playwright project runs its own auth setup, producing a unique
// session. The mock OIDC server issues a different `sub` per token so
Expand All @@ -10,6 +11,17 @@ import { waitForHydration } from '../support/helpers.js';
setup('authenticate via mock OIDC', async ({ page }, testInfo) => {
const authFile = path.join(import.meta.dirname, `../.auth/user-${testInfo.project.name}.json`);

// Projects whose name contains "admin" (e.g. setup-admin) log in with an
// admin profile so the mock OPA server grants them admin rights.
const isAdmin = testInfo.project.name.includes('admin');
await page.context().addCookies([
{
name: PROFILE_COOKIE,
value: isAdmin ? 'admin' : 'regular',
url: ISSUER_URL
}
]);

// Navigate to the app — auth guard redirects to /auth/login
await page.goto('/');
await expect(page).toHaveURL(/\/auth\/login/);
Expand Down
Loading