feat(harbor): mobile CSS via nginx proxy (replaces kubectl patch) #352
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/pal-e-platform!352
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "345-harbor-portal-proxy"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #345
Summary
Replaces the fragile
null_resource+kubectl patchapproach from PR #351 with a standalone nginx proxy deployment. This is the same pattern used for MinIO Console in PR #350 -- fully Terraform-managed, survives Helm upgrades without intervention.Changes
terraform/modules/harbor/main.tf-- Added 4 new resources:kubernetes_config_map_v1.harbor_portal_nginx-- nginx.conf that proxies to Harbor's internal nginx (port 80), usessub_filter </head>to inject CSS link, disables upstreamAccept-Encodingso sub_filter can operate on uncompressed HTMLkubernetes_config_map_v1.harbor_portal_css-- Mobile-first responsive CSS targeting Harbor's Clarity Design class names (390px base, 600px desktop restore)kubernetes_deployment_v1.harbor_portal_proxy-- Single replica nginx:1.27-alpine with ConfigMap mounts, content-hash annotations for automatic rollout on CSS/config changes, liveness/readiness probeskubernetes_service_v1.harbor_portal_proxy-- ClusterIP on port 80 targeting the proxy deploymentterraform/modules/networking/main.tf-- Updated Harbor funnel ingress backend fromharbor:80toharbor-portal-proxy:80null_resource, nokubectl patch, nolocal.portal_nginx_conf, nofiles/mobile.cssexternal fileTest Plan
tofu fmt-- cleantofu validate-- passedtofu apply: verifyharbor-portal-proxydeployment, service, and ConfigMaps created in harbor namespacehttps://harbor.<domain>serves Harbor UI with<link rel="stylesheet" href="/custom/mobile.css">in page source/api/v2.0/,/c/login, OIDC flow) still work through the proxyReview Checklist
Related Notes
QA Review -- PR #352
Architecture
harbor:80(Helm-managed nginx that routes internally to portal, core, registry, etc.) -- correct upstream target.harbor:80toharbor-portal-proxy:80-- correct.null_resource, nokubectl patch, no external file dependencies. Fully Terraform-managed.nginx.conf
proxy_set_header Accept-Encoding ""disables upstream compression sosub_filtercan operate on the response body. Correct.sub_filter_types text/htmllimits injection to HTML responses only -- API JSON responses pass through unmodified. Good.sub_filter_once onprevents duplicate injection. Correct.Upgrade,Connection) forwarded for any Harbor features that may use them.CSS
@media (min-width: 600px). Matches issue AC.clr-*,.datagrid,.navigator) used by Harbor's Angular UI. Correct framework targeting.Deployment
checksum/nginx-conf,checksum/css) trigger pod rollout on ConfigMap changes. Good./custom/mobile.css:8080validate both nginx process and ConfigMap mount.depends_on = [helm_release.harbor]ensures Harbor services exist before proxy tries to connect.Validation
tofu fmt-- cleantofu validate-- passedtofu planrequires backend connection (expected in worktree environment)Issues Found
None.
VERDICT: APPROVED
PR #352 Review
DOMAIN REVIEW
Tech stack: OpenTofu (HCL) + Kubernetes resources (Deployment, Service, ConfigMap) + nginx reverse proxy config + CSS. Terraform/k8s/infra domain checklist applies.
Pattern consistency with PR #350 (MinIO proxy): This PR follows the exact same 4-resource pattern (nginx ConfigMap, CSS ConfigMap, Deployment, Service + ingress backend swap). Structure is identical and correct.
Network policy (harbor self-ingress): Verified in
terraform/network-policies.tfline 97 -- the harbor namespace already allows intra-namespace traffic via{ from = [{ namespaceSelector = { matchLabels = { "kubernetes.io/metadata.name" = "harbor" } } }] }. The proxy pod (in harbor namespace) can reachharbor:80(also in harbor namespace). No gap here. Note: the MinIO proxy in PR #350 does NOT have this -- the minio netpol lacks a self-reference, which is a problem for that PR but not this one.Websocket passthrough: Present. Lines 186-188 of main.tf set
Upgrade $http_upgradeandConnection "upgrade"headers. Harbor's web console does not heavily rely on websockets, but the passthrough is correctly wired in case any Clarity component or future Harbor feature uses them.Health checks: Liveness and readiness probes both target
/custom/mobile.csson port 8080. This verifies the nginx process is running and the CSS ConfigMap is mounted correctly. Good probe target -- lightweight, tests the static file serving path.Accept-Encoding stripping: Line 191 sets
proxy_set_header Accept-Encoding ""to prevent upstream gzip, which would makesub_filterunable to match</head>. Correct.sub_filter scope:
sub_filter_types text/htmlandsub_filter_once on-- only injects into HTML responses, only once per response. Correct.Image tag:
nginx:1.27-alpine-- pinned to minor version, alpine variant. Acceptable for infra tooling. A digest pin would be more secure but is not a blocker for internal platform services.Resource limits: CPU request 10m, memory request 16Mi / limit 32Mi. Appropriate for a lightweight proxy that only does sub_filter on HTML.
ConfigMap content-hash annotations: Both
checksum/nginx-confandchecksum/cssusesha256()to trigger pod rollout on config changes. Correct pattern.depends_on: Deployment depends on
helm_release.harbor. Correct -- the proxy needs Harbor's service to exist before it can proxy to it.CSS quality (mobile-first, 600px breakpoint): Base styles target ~390px mobile.
@media (min-width: 600px)restores desktop layout. The CSS targets Clarity Design (clr-*), Harbor-specific classes (hbr-*,.navigator), and Angular/NG-ZORRO components (nz-table,nz-form-item,ant-*). Harbor v2.x uses Angular + Clarity, so targeting bothclr-*andnz-*/ant-*selectors is appropriate. No hardcoded colors -- purely layout overrides. Clean.Desktop restore completeness: The
@media (min-width: 600px)block restores sidebar width (240px), form layout (row direction), and modal sizing. Tables, action bars, header, and pagination are NOT explicitly restored in the media query -- but these useflex-wrapandoverflow-xwhich are harmless on desktop. The desktop restore uses explicit values (240px,row,auto) rather thanrevert(which the MinIO PR uses). Both approaches work, but explicit values are arguably more predictable. Non-issue.Stray files check: PR #350 (MinIO) included a stray 183-line
docs/superpowers/specs/2026-04-10-westside-admin-design.md. This PR (Harbor) is clean -- only 2 files changed, both directly related to the feature.Ingress backend swap:
terraform/modules/networking/main.tfline 273 changes fromharbor:80toharbor-portal-proxy:80. The service name and port match the newkubernetes_service_v1.harbor_portal_proxyresource. Correct.BLOCKERS
None.
tofu validate(passed),tofu applyverification, and manual viewport testing. No unit-testable code paths exist. Acceptable.NITS
PR body says "390px base" but CSS comment says "~390px" -- the actual CSS has no
max-width: 390pxmedia query. The mobile styles are simply the default (no media query), with desktop kicking in at 600px. The "390px" is just a reference to the iPhone viewport width. Not a code issue, just a documentation imprecision in the CSS comment.nz-*andant-*selectors in Harbor CSS -- Harbor v2.x uses Angular + Clarity Design. Thenz-table,nz-form-item,ant-*selectors are NG-ZORRO / Ant Design selectors that Harbor does not currently use. They are harmless (no match = no effect) but add noise to the CSS. Consider removing them in a future cleanup pass if Harbor stays on Clarity.PR body Test Plan has unchecked boxes --
tofu apply, mobile viewport, desktop viewport, and API calls are all unchecked. These should be checked off after validation, not before merge. Just noting for the validation step.Missing
tofu planoutput -- PR conventions in CLAUDE.md state "Includetofu planoutput for any Terraform changes." The PR body does not include plan output. Non-blocking for review, but should be addressed before merge per repo conventions.SOP COMPLIANCE
345-harbor-portal-proxymatches issue #345tofu planoutput included per repo convention: MissingPROCESS OBSERVATIONS
harbor:80. The network policy already supports the new topology.VERDICT: APPROVED
PR #352 Review
DOMAIN REVIEW
Tech stack: Terraform (HCL) + Kubernetes resources + nginx configuration + CSS
This PR adds a standalone nginx reverse proxy deployment in the Harbor namespace that injects mobile-responsive CSS into Harbor's web UI via
sub_filter. It changes the Tailscale funnel ingress backend from the Harbor service directly to the new proxy service.Terraform correctness:
config_mapx2,deployment,service) use proper namespace references viakubernetes_namespace_v1.harbor.metadata[0].namedepends_on = [helm_release.harbor]ensures correct orderingsha256()on ConfigMap data) will trigger pod rollouts on CSS/config changes -- good patternapp = "harbor-portal-proxy")Kubernetes security:
latest-- usesnginx:1.27-alpine(specific minor version)Nginx configuration:
proxy_set_header Accept-Encoding ""correctly disables upstream compression sosub_filtercan operatesub_filter_once onandsub_filter_types text/htmlare correct for injecting once per HTML pageharbor:80which is the Helm-created ClusterIP service name in the same namespace/custom/with 1h cache + immutable headerNetwork policy: Harbor's network policy (line 97 in
network-policies.tf) already allows ingress from its own namespace (harbor->harbor). Since the proxy pod lives in the same namespace and talks to the Harbor service within that namespace, traffic flows without any policy changes needed.CSS quality:
@mediarestore cleanly reverts mobile overrides!importantappropriately for CSS injection (necessary to override framework defaults)BLOCKERS
None identified.
NITS
Image tag specificity:
nginx:1.27-alpinepins to minor but not patch. Considernginx:1.27.4-alpinefor full reproducibility. Minor concern given this is internal infra.PR body references wrong issue: The PR body says "Closes #345" but the user described this as ticket #348. Looking at the issue list, #345 is "Harbor: Mobile-responsive CSS via nginx sub_filter injection" which IS the correct issue for this work. The branch name
345-harbor-portal-proxyalso matches. This is correct.sub_filter_once on: If Harbor ever serves multi-page SPAs where the</head>appears in AJAX-loaded fragments, the CSS link would not be re-injected. This is fine for Harbor's server-rendered pages, just noting for awareness.No
server_tokens off: The nginx proxy will expose its version in response headers. Low risk since it is behind Tailscale funnel + Harbor OIDC, but addingserver_tokens off;in thehttpblock is good hygiene.SOP COMPLIANCE
345-harbor-portal-proxymatches issue #345)harbor/main.tfandnetworking/main.tftouched)PROCESS OBSERVATIONS
tofu validatepassed per the test plan. Post-merge validation will requiretofu applyand visual verification on mobile viewport.VERDICT: APPROVED