diff --git a/apps/web/lib/hosts/host-detail-tabs.test.mjs b/apps/web/lib/hosts/host-detail-tabs.test.mjs index ddfea88f..36bb83c0 100644 --- a/apps/web/lib/hosts/host-detail-tabs.test.mjs +++ b/apps/web/lib/hosts/host-detail-tabs.test.mjs @@ -23,12 +23,12 @@ test('container inventory is available as a top-level host tab', () => { assert.equal(inventory?.children?.includes('containers'), false) }) -test('admin host tools live under the top-level admin host tab', () => { +test('activity host tools live under the top-level activity host tab', () => { const admin = PARENT_TABS.find((tab) => tab.id === 'admin') const notes = PARENT_TABS.find((tab) => tab.id === 'notes') assert.ok(admin) - assert.equal(admin.label, 'Admin') + assert.equal(admin.label, 'Activity') assert.equal(admin.defaultTab, 'notes') assert.deepEqual(admin.children, ['notes', 'calendar']) assert.equal(notes, undefined) diff --git a/apps/web/lib/hosts/host-detail-tabs.ts b/apps/web/lib/hosts/host-detail-tabs.ts index cf464ead..9d8473b9 100644 --- a/apps/web/lib/hosts/host-detail-tabs.ts +++ b/apps/web/lib/hosts/host-detail-tabs.ts @@ -62,7 +62,7 @@ export const PARENT_TABS: Array<{ { id: 'infrastructure', label: 'Infrastructure', defaultTab: 'storage', children: ['storage', 'network', 'host-networks', 'patch-status'] }, { id: 'inventory', label: 'Inventory', defaultTab: 'packages', children: ['packages', 'vulnerabilities'] }, { id: 'containers', label: 'Containers', defaultTab: 'containers', children: null }, - { id: 'admin', label: 'Admin', defaultTab: 'notes', children: ['notes', 'calendar'] }, + { id: 'admin', label: 'Activity', defaultTab: 'notes', children: ['notes', 'calendar'] }, { id: 'management', label: 'Management', defaultTab: 'groups', children: ['users', 'groups', 'settings'] }, { id: 'tools', label: 'Tools', defaultTab: 'tasks', children: ['tasks', 'logs', 'terminal'] }, ] diff --git a/deploy/scripts/test-dev-stack.sh b/deploy/scripts/test-dev-stack.sh index 07bd52ef..d7b99c89 100755 --- a/deploy/scripts/test-dev-stack.sh +++ b/deploy/scripts/test-dev-stack.sh @@ -25,7 +25,7 @@ assert_not_contains() { } run_config_generation_test() { - local tmpdir repo_dir before_root_env private_key expected_public actual_public + local tmpdir repo_dir before_root_env private_key expected_public actual_public expected_uid expected_gid tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/ct-ops-dev-stack-test.XXXXXX")" trap 'rm -rf "'"$tmpdir"'"' RETURN @@ -62,7 +62,11 @@ EOF test -f "${repo_dir}/.dev/dev.env" test -f "${repo_dir}/apps/web/.env.local" + expected_uid="$(id -u)" + expected_gid="$(id -g)" assert_contains "${repo_dir}/.dev/dev.env" "BETTER_AUTH_URL=http://localhost:3000" + assert_contains "${repo_dir}/.dev/dev.env" "CT_OPS_DEV_UID=${expected_uid}" + assert_contains "${repo_dir}/.dev/dev.env" "CT_OPS_DEV_GID=${expected_gid}" assert_contains "${repo_dir}/.dev/dev.env" "PASSWORD_MANAGER_SESSION_COOKIE_SECURE=false" assert_contains "${repo_dir}/.dev/dev.env" "CT_OPS_TRUST_PROXY_HEADERS=true" assert_contains "${repo_dir}/apps/web/.env.local" "BETTER_AUTH_URL=http://localhost:3000" @@ -138,6 +142,10 @@ run_static_wiring_test() { assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "\${CT_OPS_DEV_BIND_ADDR}:\${CT_OPS_DEV_INGEST_GRPC_PORT}:9443" assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "127.0.0.1:\${POSTGRES_PORT}:5432" assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "host.docker.internal:host-gateway" + assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "web-permissions:" + assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "user: \"\${CT_OPS_DEV_UID}:\${CT_OPS_DEV_GID}\"" + assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "chown -R \"\${CT_OPS_DEV_UID}:\${CT_OPS_DEV_GID}\"" + assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "mkdir -p /tmp/ct-ops-dev-home" assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "CT_OPS_DEV_WEB_UPSTREAM: web-dev:3001" assert_contains "${REPO_ROOT}/docker-compose.dev-stack.yml" "CT_OPS_DEV_INGEST_UPSTREAM: ingest-dev:8080" assert_contains "${REPO_ROOT}/deploy/nginx/dev-stack.conf.template" "location /password-manager-api/" diff --git a/dev-stack.sh b/dev-stack.sh index 3ba4edc5..8166d60e 100755 --- a/dev-stack.sh +++ b/dev-stack.sh @@ -326,6 +326,8 @@ ensure_dev_env() { upsert_env_var "$DEV_ENV" CT_OPS_DEV_INGEST_GRPC_PORT "$ingest_grpc_port" upsert_env_var "$DEV_ENV" CT_OPS_DEV_APP_URL "$app_url" upsert_env_var "$DEV_ENV" CT_OPS_DEV_AGENT_INGEST_ADDRESS "${public_host:-localhost}:${ingest_grpc_port}" + upsert_env_var "$DEV_ENV" CT_OPS_DEV_UID "$(id -u)" + upsert_env_var "$DEV_ENV" CT_OPS_DEV_GID "$(id -g)" if [ -z "$(read_env_var "$DEV_ENV" CT_OPS_DEV_NEXT_FLAGS)" ]; then upsert_env_var "$DEV_ENV" CT_OPS_DEV_NEXT_FLAGS "--turbopack" fi diff --git a/docker-compose.dev-stack.yml b/docker-compose.dev-stack.yml index 8c8dc10a..d495d079 100644 --- a/docker-compose.dev-stack.yml +++ b/docker-compose.dev-stack.yml @@ -83,14 +83,36 @@ services: retries: 12 start_period: 10s + web-permissions: + image: node:24-bookworm-slim + restart: "no" + working_dir: /workspace + volumes: + - .:/workspace + - web_node_modules:/workspace/node_modules + - web_app_node_modules:/workspace/apps/web/node_modules + - pnpm_store:/pnpm/store + - web_corepack_cache:/corepack + command: + - /bin/sh + - -lc + - | + mkdir -p /workspace/apps/web/.next /workspace/node_modules /workspace/apps/web/node_modules /pnpm/store /corepack + chown -R "${CT_OPS_DEV_UID}:${CT_OPS_DEV_GID}" /workspace/apps/web/.next /workspace/node_modules /workspace/apps/web/node_modules /pnpm/store /corepack + web-migrate: image: node:24-bookworm-slim restart: "no" + user: "${CT_OPS_DEV_UID}:${CT_OPS_DEV_GID}" working_dir: /workspace depends_on: db: condition: service_healthy + web-permissions: + condition: service_completed_successfully environment: + COREPACK_HOME: /corepack + HOME: /tmp/ct-ops-dev-home DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} BETTER_AUTH_URL: ${BETTER_AUTH_URL} @@ -100,25 +122,29 @@ services: - web_node_modules:/workspace/node_modules - web_app_node_modules:/workspace/apps/web/node_modules - pnpm_store:/pnpm/store + - web_corepack_cache:/corepack command: - /bin/sh - -lc - | - corepack enable + mkdir -p /tmp/ct-ops-dev-home corepack prepare pnpm@10.6.5 --activate - pnpm config set store-dir /pnpm/store - pnpm install --frozen-lockfile + corepack pnpm config set store-dir /pnpm/store + corepack pnpm install --frozen-lockfile cd apps/web - pnpm run db:migrate + corepack pnpm run db:migrate web-dev: image: node:24-bookworm-slim restart: unless-stopped + user: "${CT_OPS_DEV_UID}:${CT_OPS_DEV_GID}" working_dir: /workspace depends_on: web-migrate: condition: service_completed_successfully environment: + COREPACK_HOME: /corepack + HOME: /tmp/ct-ops-dev-home DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} BETTER_AUTH_URL: ${BETTER_AUTH_URL} @@ -145,16 +171,17 @@ services: - web_node_modules:/workspace/node_modules - web_app_node_modules:/workspace/apps/web/node_modules - pnpm_store:/pnpm/store + - web_corepack_cache:/corepack command: - /bin/sh - -lc - | - corepack enable + mkdir -p /tmp/ct-ops-dev-home corepack prepare pnpm@10.6.5 --activate - pnpm config set store-dir /pnpm/store - pnpm install --frozen-lockfile + corepack pnpm config set store-dir /pnpm/store + corepack pnpm install --frozen-lockfile cd apps/web - pnpm exec next dev ${CT_OPS_DEV_NEXT_FLAGS} -H 0.0.0.0 -p 3001 + corepack pnpm exec next dev ${CT_OPS_DEV_NEXT_FLAGS} -H 0.0.0.0 -p 3001 ingest-dev: image: golang:1.25 @@ -226,5 +253,6 @@ volumes: web_node_modules: web_app_node_modules: pnpm_store: + web_corepack_cache: go_build_cache: go_mod_cache: