F3: deleting a file-based stack now removes the stack ROOT (compose/{id}) via a
new removeStackProjectDir helper, not stack.ProjectPath (which the PR repointed to
compose/{id}/v{N}) — old version dirs + parent no longer leak. Git stacks unchanged.
F1: tests for validateRollbackTarget (rejects 0/neg/>current/hole) and the rollback
snapshot (client content ignored, target read from disk, monotonic new version, note).
F2: tests for pruneStackFileVersionDirs (deletes given dirs, swallows errors) + the
post-commit gate contract + a monotonic-version regression guard.
F4: handler tests for ?version= (negative/out-of-range -> 400, valid version served,
legacy fallback).
F5: swagger @param version on GET file; @version 2.44.0 (handler.go) + package.json
2.44.0, matching APIVersion.
F6: the version selector no longer sets rollbackTo for the current/top version and
clears it on a manual buffer edit (so edits are honored, not silently discarded);
returning to the current version restores the current content. Distinguishes real
user edits from the programmatic version-load (CodeMirror ExternalChange).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds append-only version history on disk (compose/{id}/v{N}/<files>) for
file-based (WorkflowID==0) Compose/Swarm stacks, with rollback to any past
version. Git stacks (versioned by commit) and Kubernetes are untouched.
Backend:
- Stack model: StackFileVersion, PreviousDeploymentInfo, Versions[]; new
StackFileVersionInfo type. APIVersion 2.43.0 -> 2.44.0.
- Versioned multi-file snapshot (entrypoint + AdditionalFiles) into v{N}/;
ProjectPath repointed via GetStackProjectPathByVersion each deploy. Retention
cap (20): Versions[] trimmed in-tx, old dirs deleted only AFTER the tx commits.
- Update handlers: RollbackTo (content read server-side from the target version,
never trusted from the client; validated 1..current & present in Versions).
- Create paths seed v1. stackFile reads ?version= (validated; negative -> 400).
- New GET /stacks/{id}/versions endpoint.
- Migration 2.44.0: move existing file-based stacks' files into v1/ (idempotent,
atomic pre-read of the full file set, skips git/kube/orphans).
Frontend:
- useStackVersions query + stackVersions key; StackEditorTab builds the full
history list; StackVersionSelector shows 'v{N} · date · author'; file/versions
caches invalidated (by prefix) after deploy/rollback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The auto-update/auto-heal daemon apply paths (recreate/pull/rollback/cleanup/
heal-restart) mutate production containers but had no executing tests because
they took a concrete *dockerclient.Client. Introduce minimal seam interfaces
(dockerClient + containerRecreator) and thread them through updateStandalone/
cleanupOldImage/updateStack/inspectImageID/healthGate/rollback, extract the heal
restart loop into healContainers — all behaviour-preserving (the concrete client
and ContainerService satisfy the interfaces). Add a call-recording fake and
ordering/gating integration tests: cleanup runs strictly AFTER a healthy
health-gate (rollback target never deleted early), rollback fires on gate
failure and preserves the target, the 'updated' event is held until health is
confirmed, and the heal restart respects its cooldown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The TS update-routing used the deprecated `!!stack.GitConfig` to flag a
git-backed stack, which can diverge from the canonical Go daemon routing
(`IsGit: st.WorkflowID != 0`) on the new Workflow/Source model. Derive it from
WorkflowID instead (added WorkflowID to the client Stack type). The stack-type
filter (Type === DockerCompose) was already in place and tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SERVER_TYPES only has CUSTOM and AD (OpenLDAP was retired in #5), so the
'|| ServerType === SERVER_TYPES.LDAP' clause referenced a non-existent key and
was always false. AD is the only edit-LDAP server type now.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the single container-automation webhook URL into two independently
optional URLs — UpdateWebhookURL (fired on update/rollback/update-failed) and
HealWebhookURL (fired on auto-heal restart). The notifier routes each event to
its mechanism's URL by kind; an empty URL silences only that mechanism, so a
user can enable notifications for updates without heal (or vice-versa).
Settings gain both fields (each validated http/https, {{message}} allowed), the
NotificationPanel exposes two labeled inputs, and the golden migration output is
updated. Delivery path (goroutine/recover/timeout, {{message}} GET vs POST,
per-container stack message format) is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- F3: give the <mark> search highlight the full theme-token tier set
(light / th-dark / th-highcontrast, with a legible text colour) so it reads
correctly in every theme now that the viewer is theme-aware.
- F6: restore the transient 'Copied' acknowledgement on the Copy button via
useCopy's copiedSuccessfully (Check icon + 'Copied' label).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per maintainer feedback ('сделай поддержку светлой/темной темы', 'цвета фона
карточки подтяни к стандартным', and the white gap below):
- Drop the hardcoded dark palette; style the card/header/toolbar with the
project's standard theme tokens (Card/Card.Header token set) + th-dark: /
th-highcontrast: variants, so it adapts to light/dark/high-contrast like every
other Portainer view. Reuse project Button/Input/Checkbox/Icon components; the
log body uses the themed .log_viewer class; the range picker inherits themed
form-control styling.
- Fill the available page height (root flex column, calc(100vh - nav/header),
log body flex:1 min-h:0 overflow-y) so there's no dead white space below.
Layout (single toolbar row, three toggles, gutter), real data, and props are
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- F1: cover the hook's riskiest path — a following stream that ends with an
unwritten tail fragment then resumes (tail:0 + nano-since), asserting the
fragment is dropped, resume params are correct, and the boundary line is
deduped to one; plus MAX_LOG_LINES head-trim and buffer reset on
resourceId/lineCount change.
- F2: clear the error banner on a SUCCESSFUL reconnect (via a new onOpen signal
on StreamLogsFn), not only when new lines arrive — an idle-but-healthy
reconnect no longer leaves a stuck 'unable to stream' banner.
- F4: update the stale comment in the React logs view registration (the React
logs migration is now complete).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- F1: test that clicking the badge/UpdateNowButton actually dispatches the update
(confirm->mutate) for standalone and stack, and not on dismiss.
- F2: Go test that a successful forced re-check repopulates the caches (a later
non-force read hits cache, no second registry HEAD).
- F3: throttle forced image-status re-checks against registry amplification —
coalesce concurrent forced re-checks of the same image via singleflight, plus a
5s per-image min-interval (== remoteDigestCache TTL) caching only successes. The
non-force path (daemon + background badges) is unchanged.
- F4: notifications are now per-container. Stack-member containers each emit their
own EventUpdated (not one aggregate stack event), Event carries the stack name
(from the com.docker.compose.project label), and the new image digest is fetched
best-effort by re-inspecting the container after the redeploy. Message:
'Environment | .. / Stack [<name>] / Update [<container>]: <old> -> <new>'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reproduce the maintainer-provided ContainerLogs mockup faithfully: dark card
(#0c0c0d), his header + single toolbar row styling, his custom search box /
'Filter search results' checkbox / Copy+Download buttons, his toggle-button
style (Line numbers / Timestamp / Wrap — no Auto refresh), and the dark log
area with a right-aligned line-number gutter. Palette carried inline in this one
component (deliberate dark log-viewer design).
Deviations from the mockup, by design: fonts/sizes use the project scale and
monospace (no Google Fonts / hardcoded Inter/JetBrains); real streaming data via
useLogViewer rendered as safe React span nodes (no dangerouslySetInnerHTML);
mock page chrome dropped (Portainer's page provides breadcrumb/title); the
datetime range keeps the functional react-datetimerange-picker. Live-tails by
default; selecting an upper bound in the range picker shows a bounded snapshot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close the visual gaps vs the maintainer's reference viewer:
- Replace the two separate From/To DateTimeField controls with a single combined
datetime range picker (@wojtekmaj/react-datetimerange-picker, sibling of the
react-datetime-picker / react-daterange-picker already used), from-to with time
in one control — mirrors the existing DateRangePicker wrapper.
- Add icons to the Line numbers (List), Timestamp (Clock) and Wrap lines
(WrapText) toggles (Auto refresh already had one).
- Line numbers gutter on by default.
Adds one dependency (@wojtekmaj/react-datetimerange-picker); all its transitive
deps were already present via the sibling pickers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an opt-in webhook notification for container-automation events (image
update, rollback, update-failed, auto-heal restart), plugging into the existing
Notifier seam in notify.go.
- Settings: new ContainerAutomation.Notification.WebhookURL (shared across
update + heal), persisted and validated in the settings update handler
(optional; http/https only; accepts the {{message}} placeholder).
- webhookNotifier reads the current URL from the datastore per event (UI changes
take effect without a restart). If the URL contains {{message}} it substitutes
the URL-encoded message and issues a GET; otherwise it POSTs the message as the
body. Delivery, the env/stack name lookups, and any panic run in a goroutine
under recover() with a 10s timeout — strictly best-effort, never blocks or
crashes the automation daemon. multiNotifier fans events to logNotifier +
webhook and isolates a panic in any one notifier.
- Message format (maintainer's spec):
Environment | <env>
Stack [<name>] (Container [<name>] for non-stack events)
Update [<name>]: <old> -> <new>
Auto-heal: 'Auto-heal: restarted unhealthy container'.
- New NotificationPanel in settings to configure the URL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the legacy AngularJS <log-viewer> on the container logs page with a
modern React log viewer, reusing the existing streaming (#6) and formatting/
coloring pipeline. Features: line-number gutter, zerolog level + key=value
coloring (from the existing formatter spans), from/to datetime range, Lines
limit, Line numbers / Timestamp / Wrap lines toggles, Auto refresh (live tail
on/off), Search + 'Filter search results', Copy, Download logs, and fullscreen.
The viewer is source-agnostic (StreamLogsFn), so service/task logs can adopt it
later; this PR wires container logs only. containerLogsController.js no longer
opens its own live stream (React owns fetching now), preventing a double stream.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the container image-status badge actionable, matching native Portainer:
- Clicking "Update available" opens the update confirm dialog and runs the
existing update flow (standalone recreate-with-pull / stack redeploy), gated
and disabled while in flight to avoid a double submit. The confirm+apply logic
is extracted from UpdateNowButton into a shared useApplyContainerImageUpdate
hook so the details button and the list badge share one implementation.
- Clicking "Up to date" re-queries the registry. Because the server caches image
status (statusCache 5m + remoteDigestCache 5s), a plain refetch was a no-op, so
the endpoint gains an optional ?force=true that bypasses BOTH caches for a
manual re-check while still repopulating them; the default (auto badges + the
auto-update daemon) keeps using the caches unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The stack container list reuses the shared containers datatable, whose
Quick Actions column linked to the global docker.containers.container.*
states with only {id,nodeName}. Clicking Logs/Stats/Console/Inspect/Attach
from within a stack therefore jumped to the global route and collapsed the
breadcrumb to "Containers > <name> > Logs", losing the stack trail that
PR #7 added.
Thread the current stack route params (via RowContext) down to
ContainerQuickActions so, when rendered inside a stack, its links target the
stack-scoped docker.stacks.stack.container.* sub-tab states (reusing #7's
buildStackContainerLinkParams / STACK_CONTAINER_STATE_NAME helpers). The
global containers list and service tasks pass no stack params and keep the
global links unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documents building a Portainer image from one or more feature branches and
running it, with the gotchas that bite: build-image must get ENV=production
(else it ships a dev eval-source-map client bundle that the default CSP blocks,
hanging the UI on "Loading Portainer…"), CSP is on by default, first-run admin
init needs --no-setup-token, and the admin password must be simple but >=12
chars. Referenced from CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous round crammed every control into the widget header as one flex
row, which read as cluttered. Restore the stock Portainer two-row shape:
- header bar: the "Logs" title on the left; Search + Copy + Download logs
right-aligned in the header's transclude slot;
- a single clean horizontal toolbar in the widget body: Since / Lines /
Wrap lines / Display timestamps (no longer stacked form-groups);
- the log <pre> pane below, unchanged.
Auto-refresh and the line-selection controls stay removed (already gone from
the controller). Template-only change; no controller edits.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the manual stream-and-flush loop from dockerLocalProxy.ServeHTTP
into a behaviour-preserving package-private streamResponse(w, body) helper,
and add docker_test.go regression tests for the riskiest path (it runs on
every Docker API response):
- DeliversFullBodyAndFlushesPerChunk: a >32KB body delivered as several
chunks (boundaries not aligned to the 32KB buffer), with the final Read
returning (n>0, io.EOF) simultaneously, asserts the streamed body equals
the input exactly (no loss/duplication) and that Flush ran more than once
(the per-chunk flush is the whole point of the change).
- StopsOnWriteErrorWithoutPanic: a writer that errors on first Write (and
does not implement http.Flusher, exercising the nil-flusher fallback)
breaks the loop after one write without panicking.
No production behaviour change — the loop body is identical, only moved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a container is opened from a stack, the detail tab kept the stack
trail (PR #7) but the attribute sub-tabs (Logs, Stats, Inspect, Console,
Attach) dropped it: those tabs were registered only under the global
docker.containers.container.* tree, so navigating to one left the stack
state (and its inherited params) behind, and each sub-view set a hardcoded
"Containers > ..." breadcrumb.
- Register stack-scoped child states docker.stacks.stack.container.{attach,
exec,inspect,logs,stats} mirroring the global ones, so the inherited stack
params survive and the trail can be kept.
- Centralize the breadcrumb logic in containerBreadcrumbs.ts (moved out of
ItemView, which re-exports it) and add isStackContainerState +
getContainerSubTabBreadcrumbs + buildStackContainerLinkParams.
- ActionLinksRow links sub-tabs into the stack tree (with stack+container
params) when opened from a stack, else the global states unchanged.
- InspectView + the logs/stats/console controllers render the stack-aware
trail; set up-front (no name) so it survives the load window and errors.
Covers regular/external/orphaned stacks and the non-stack fallback,
matching the existing ItemView breadcrumb behavior. New unit tests in
containerBreadcrumbs.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend (the "logs arrive every ~5s / pipe clogged" bug):
- dockerLocalProxy.ServeHTTP streamed the docker socket response via
io.Copy, which buffers ~2KB into the ResponseWriter and only flushes
when full or on handler return. Low-throughput streaming endpoints
(container logs follow=1, events, stats, attach) therefore arrived in
multi-second batches. Stream manually and Flush() after each chunk so
they are delivered live. Behaviour is otherwise identical to io.Copy
(full-write contract, EOF handling, Debug error logging); hijacked
attach/exec go through a separate websocket handler, unaffected.
- NewSingleHostReverseProxyWithHostHeader: set FlushInterval = -1 so the
remote-endpoint path streams live too.
Frontend (maintainer UI asks):
- Remove the line-selection mechanic entirely (Copy-selected-lines and
Unselect buttons, selectLine/copySelection/clearSelection, selectedLines
state, line_selected highlight): selecting/copying is mouse-native. Copy
(all visible) and Download stay.
- Rename the unclear "Fetch" since-selector label to "Since".
- Move the settings controls into the widget header (rd-widget-header
default transclude slot) so they share one row with the "Log viewer
settings" title, reclaiming vertical space for the log pane.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per maintainer request: remove the 'Auto-refresh logs' toggle entirely — logs are
now always collected (container always streams, service/task always poll). Drops
state.logCollection and its whole cascade (handleLogsCollectionChange, the
logCollectionChange binding, changeLogCollection in all three view controllers,
the log-collection-change attribute) and the now-dead manual flush-on-pause
machinery (pausedFlushCount / removeTailLines / the flush branch); pauseStream is
kept for $destroy/reconnect teardown, and the stream/poll start unconditionally.
Collapse the seven stacked settings rows into a single compact flex row
(wrap-lines, timestamps, fetch, lines, search, actions) — bindings unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After the BE indicator was removed, limitedToBE was hardcoded false and threaded
through BoxSelectorItem.onSelect -> BoxSelector.onChange/handleSelect ->
BoxSelectorAngular.handleChange, where $setValidity(name, !limitedToBE) was a
permanent no-op (always valid). Drop the parameter from the whole chain and the
no-op $setValidity. That left formCtrl/require:'^form'/IFormController dead (they
existed only for that validity call), so remove them too — the component no longer
needs a parent form. The real on-change wiring ($evalAsync -> onChange(value)) is
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The auto-heal floor fix copied durationPattern/unitSeconds/parseGoDurationSeconds
verbatim from AutoUpdatePanel into AutoHealPanel. Move them to a shared
SettingsView/parseGoDuration module imported by both panels — single source of
truth (mirrors the STALE_TIME dedup), so the two clients' floors can't drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F6: remove SERVER_TYPES.OPEN_LDAP (read nowhere after the OpenLDAP retirement).
F7: the S3 callers that passed buildUrl(subResource, action) are gone; the only
remaining caller uses buildUrl() with no args, so collapse it to return 'backup'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F10: the content-exact reconnect dedup rebuilt boundaryLines only from lines
surviving the current batch, so after one reconnect at a shared timestamp it
forgot the dropped line — a SECOND reconnect at the same nanosecond then
re-emitted both as duplicates. Seed lastTimestamp/boundaryLines from
skipUntilTimestamp/skipBoundaryContents so the boundary set accumulates all
lines ever seen at the resume ts. Regression test (fails before, passes after).
F11: extract rfc3339ToUnixNanoSince into a testable logHelper module (sinceTimestamp.ts)
and cover it (standard ns, no fraction, sub-9 pad, >9 truncate); the controller
imports the single shared function.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Maintainer pre-merge review follow-up:
F1: dedup reconnect redeliveries by EXACT boundary-line content, not just
timestamp <= resume — a new line that merely shares the boundary nanosecond
with a redelivered duplicate is no longer dropped (skipBoundaryContents +
pendingBoundary). Test proves line B survives while a real dup is dropped.
F2: flush the buffered partial line on intentional pause (not reconnect) and
strip those cosmetic lines on resume so since re-delivers the full line with
no stale-partial twin; resume point is not advanced past the partial.
F3: unify the since param to <unix>.<nanos> for initial and reconnect.
F4: fall back to 100 lines when the Lines field is cleared (avoid tail=all).
F5: memoize the API-version pin per session; warn on frame desync.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Maintainer pre-merge review follow-up:
F1: test the orphaned-stack breadcrumb branch (orphaned=true, no regular) —
href carries stackId/orphaned, not external.
F2: extract STACK_CONTAINER_STATE_NAME so code + test share one literal.
F4: type buildStackLinkParams' return as StackLinkParams (documents the real
shape; external stays boolean, serialized by ui-router — no runtime change).
F3 (legacy ?id= deep links) answered wontfix in the PR thread.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Maintainer pre-merge review follow-up (all non-blocker dead code):
F1: delete the dead S3-backup remnants (validation, query hooks, S3-only query
key, BackupS3Model/Settings types) — kept the CE file-backup path.
F2: delete the orphaned user-activity services + their registration (kept the
notifications component and routes).
F3: drop the unused buildOpenLDAPSettingsModel().
F4: drop the dead one-option ldap-options data (the selector was already collapsed).
F5: remove the dead data-edition attribute + its process.env typing; silence the
intentional hasAuthorizations unused-params; drop the dead useRolesState meta.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: cap the image-status cache TTL at 5m (was 24h) — the cache is keyed by the
LOCAL imageID, which doesn't change when upstream pushes a new image under the
same tag, so the 24h TTL hid new images from both the badge and the auto-update
daemon; a short TTL re-resolves the remote digest within the poll window.
F2: document that the update->rollback guard map is in-memory (restart implication).
F3: skip auto-update for an unnamed container when rollback is on (the endpoint+name
keyed guard can't record it, so it would loop) — pure skipUnnamedForRollback + test.
F4: wrap the pre-update ContainerInspect in context.WithTimeout(endpointTimeout).
F5: document Reload() does not interrupt an in-flight tick.
F6: floor auto-heal CheckInterval at 1s (mirrors auto-update) + test.
F7: wontfix — migration is currently correct; namespace rework is out of scope.
F8: correct the misleading SSRF/AllowList comment (no filter is applied).
F9: front auto-heal interval floor + test; dedup STALE_TIME; fix invalidation comment.
Also refresh three stale '24h/long-lived cache' comments to match the 5m TTL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TestPruneRolledBack mirrors TestPruneRetries: seeds fresh/at-boundary/expired
rolledBack entries and asserts pruneRolledBack keeps only the fresh one
(inclusive >= cooldown boundary).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F8: formatJSONLogs plain-text fallback — both arms of `withTimestamps ? rawText
: text` yield rawText (text === rawText when !withTimestamps), so use rawText.
F9: controllerLogsController comment referenced the old 'Live logs' label removed
by F7 — update it to 'Auto-refresh logs'.
F10: stripHeadersFunc has no external importers — drop the speculative export.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F6: delete AccessDatatable/columns/role.tsx — the BE-only role column lost its
only importer when useColumns stopped importing it; zero importers remain.
F7: useColumns wrapped only always-truthy helper.accessor results in _.compact,
a no-op; return the plain array and drop the now-dead lodash import (same
collapse already done in the parallel Wizard column files).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
logViewer.html is shared by the container, service and task log views, but only
the container view is a live HTTP stream — service/task still poll. Revert the
toggle wording to a mode-neutral 'Auto-refresh logs' / 'pauses log collection'
so it is accurate for both, keeping the added auto-scroll clarification.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: record rolled-back targets per service (endpointID/containerName + remote
digest) and skip auto-update during a 24h cooldown unless the remote digest
changes — breaks the infinite update→rollback loop on a persistently
unhealthy image, without blocking a genuinely new image.
F2: unit-test applyContainerUpdate dispatch/payload mapping.
F3: settings_update.go comments mention auto-heal AND auto-update.
F4: drop stale '(future M4)' TS docs; primitives are frontend-only.
F5: replace the anonymous ContainerAutomation settings struct with named
types (identical JSON tags).
F6: drop parseEnable (duplicate of boolLabel).
F7: remove the unused gitService dependency.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: drop the two HomeView edition-gate panels + their files (License/BackupFailed).
F2: delete zero-importer orphans (edition mutation, HubspotForm, HomepageFilter,
relations mutation, ActivityLogsView cluster, ExperimentalFeatures subtree).
F3: collapse single-option selectors (Backup settings, init restore, env types)
and delete the option files they orphaned.
F4: remove dead BE-teaser CSS rules and the --BE-only variable.
Also drop the orphaned .btn-warninglight BE-teaser variant.
F5 (limitedToBE) intentionally left — it is still read by BoxSelectorAngular.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: stop emitting/committing an unfinished line in onEnd/onError reconnect
paths; since-based reconnect redelivers the full line.
F2: give service/task poll rows positionally-stable ids so track by log.id
reuses DOM rows and text selection survives the 3s poll.
F3/F4: tests for CRLF stripping and reconnect-dedup across separate chunks.
F5: correct the stale refreshRate comment.
F6: unroll the side-effecting IIFE-in-ternary into if/else.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a test case driving the external-stack branch (external='true', no DB
stackId) and assert the back-link carries external=true/type and omits
stackId/regular. stackId/regular are set in the route params so the negative
assertions actually catch a fall-through-to-regular regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: tolerate up to 3 consecutive health-gate inspect failures (reset on
success) before declaring an update failed, so a transient Docker API blip no
longer triggers a false rollback.
F2: detect baseCtx cancellation during the gate and abort without rolling back
or emitting update-failed (debug log only), instead of a misleading
"rollback failed" event on every shutdown mid-gate.
F3: derive the gate deadline as start + max(RollbackTimeout, StartPeriod+buffer)
via effectiveRollbackDeadline, reading the container's healthcheck StartPeriod
so a legitimately slow-starting container is not rolled back while starting.
F4: only enable the gate when the original reference is a proper tag (new
isTagReference helper); skip with a log line for digest-pinned / bare-image-id
containers that cannot be re-tagged.
F5: document the sequential-tick delay limitation of the gate poll.
F6: emit EventUpdated only after the gate confirms healthy (or immediately when
no gate is active); the rollback path emits only EventRollback, so the event
sequence is truthful.
F7: floor RollbackTimeout at 10s in backend and frontend validation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P0 Health-gated rollback (standalone auto-update path): capture the previous
image id + reference + healthcheck before the recreate, then poll the new
container's health over a configurable window. On healthy proceed (and only
then clean up the old image); on unhealthy/exit/timeout re-tag the old image
back onto the original reference and Recreate (no pull) to restore it, reusing
Recreate's config preservation. The decision is a pure decideRollback() helper.
P1 Per-endpoint enable: ContainerAutomationDisabled flag on Endpoint (zero value
participates, no migration churn), checked by both daemons; settable via the
endpoint update API. UI control deferred (see report).
P2 Notifier seam: minimal Notifier interface + logNotifier, emitting structured
updated/rollback/update-failed/heal-restarted events from the daemon.
Settings: RollbackOnFailure + RollbackTimeout (default 120s) added to
ContainerAutomation.AutoUpdate, wired through defaults/migration/golden,
settings_update validation, the AutoUpdatePanel and the TS types.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: Stop routing git-backed stacks through a per-tick RedeployWhenChanged for
image-only updates. The git redeploy path short-circuits when the commit is
unchanged (so an upstream-digest update never applies) yet still git-fetches
every tick. Git stacks are now detect-only in the auto-apply path; their image
update lands on the next git change or via manual "Update now". File (non-git)
stacks still force-pull-redeploy immediately. The AutoUpdatePanel text no longer
promises daemon auto-update for git/externally-managed containers.
F2: Resolve registries for the file-stack redeploy the same way the established
userless/system path (RedeployWhenChanged) does, via the new
deployments.ResolveStackRegistries: scope to the stack author's endpoint access
and RefreshAndPersistECRTokens, instead of hand-passing Registry().ReadAll().
ECR-backed stacks now auto-update with fresh tokens.
F3: Add a 1m floor for the auto-update poll interval, enforced in the settings
Validate and mirrored in the frontend validation.
F4: Thread the application shutdownCtx into NewService and use it as the base
for the heal/update job operation contexts, so shutdown cancels in-flight work.
F5: Correct the updateEndpoint comment about monitor-only badge-cache warming
(only in-scope monitor-only containers are status-checked).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an optional periodic auto-update daemon that detects outdated container
images and applies updates, replacing the containrrr/watchtower sidecar. It
extends M1's containerautomation service/scheduler/labels infrastructure and
reuses the existing zlib image-detection engine, the standalone Recreate path
and the stack deployer.
Backend:
- api/containerautomation/autoupdate.go: scheduler job iterating Docker
(non-edge) endpoints -> in-scope running containers -> ContainerImageStatus;
for Outdated: standalone -> ContainerService.Recreate(pull); stack-managed ->
one stack redeploy-with-pull per stack per tick (git via RedeployWhenChanged,
file via the deployer directly); external compose -> detect only. Monitor-only
containers are status-checked (warms the badge cache) but never applied.
Overlap guard (atomic), pull/registry-auth failure -> leave running container
untouched, conservative cleanup of the dangling old image on the Cleanup flag
(non-forced ImageRemove only succeeds when truly unused).
- labels.go: update enable / monitor-only labels with watchtower aliases,
InUpdateScope, IsMonitorOnly, and pure resolveContainerUpdateRouting /
groupContainersForUpdate (Go analogue of M3's TS routing + grouping).
- service.go: run both jobs, Reload restarts/stops each per settings; NewService
also takes ContainerService, StackDeployer and GitService.
- Settings.ContainerAutomation.AutoUpdate {Enabled, PollInterval, Scope,
Cleanup} with fresh-install defaults and a 2.43.0 backfill (extends M1's
migration; golden test data updated). settings handler validates + reloads.
Frontend:
- Global AutoUpdatePanel in SettingsView (enable / poll interval / scope /
cleanup) via useUpdateSettingsMutation, plus settings TS types.
- Read-only per-container Auto-update row in the container details view
(Docker labels are immutable at runtime), surfacing monitor-only.
Tests: Go unit tests for the update label aliases, scope, monitor-only, the
routing decision and the one-redeploy-per-stack grouping; vitest for the panel
and the per-container row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: single-container "Update now" and bulk "Update" now require
PortainerStackUpdate when the resolved path is a stack, disabling the
action with a tooltip / skipping it rather than letting the click 403.
F2: resolveContainerUpdatePath only matches a Docker Compose stack; a
same-named swarm/kubernetes stack is treated as external.
F3: SecondaryActions no longer renders an empty ButtonGroup when all of
recreate/duplicate/update-now are hidden.
F4: bulk update reports an explicit no-op toast and counts containers vs
stacks honestly in the success summary.
F5: bulk toasts use trimmed container names (no leading slash).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a discoverable per-container "Update now" action, shown only when the
image status is `outdated`, plus a bulk "Update selected" action in the
containers list.
Both manual paths share ONE apply primitive (applyContainerUpdate /
useUpdateContainerImage) that also backs the future M4 auto-update job:
- standalone container -> recreate-with-pull (existing recreate endpoint)
- stack-managed -> stack redeploy-with-pull (existing git/file stack
update mutations), so the container stays in its
stack and is never recreated out-of-band
- externally-managed -> refused; the details button is disabled with an
compose explanatory tooltip and the bulk action skips it
Decision logic lives in the pure, unit-tested resolveContainerUpdatePath /
groupContainersForUpdate helpers. The bulk action filters to outdated
containers and redeploys each owning stack exactly once even when several of
its containers are selected, reporting per-item success/failure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: ContainerImageStatus now reads the 24h statusCache (keyed by imageID)
before the remote registry digest lookup, so the cache is effective on the
input side for all callers instead of being write-only. This avoids the
rate-limited registry HEAD on repeat loads.
F2: add nodeName to the imageStatus query key so cached results cannot be
reused across nodes.
F3: correct the swagger annotations to reflect that engine-level issues
degrade to a 200 skipped/error status rather than 400/404.
F4: return a generic error message to the client instead of the raw
registry/engine error; the raw error is still logged server-side.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add native CE detection of "a newer image is available" for running
containers, surfaced as a read-only HTTP endpoint and a containers-list
badge/column. No applying of updates (M3/M4), no auto-heal (M1).
Backend:
- New CE handler GET /docker/{id}/containers/{containerId}/image_status
backed by the existing zlib/CE digest engine
(images.NewClientWithRegistry + ContainerImageStatus). Honors nodeName,
authz, and routes registry calls through the credential store / SSRF
AllowList. Engine failures degrade to a 200 {Status:"error"} so the UI
stays graceful. Response shape: {Status, Message?}.
Frontend (CE-only, no isBE gating; the EE ImageStatus component is left
untouched):
- useContainerImageStatus TanStack Query hook (5min staleTime, no
refetch-on-focus; backend caches 24h) calling the non-proxied endpoint.
- UpdateStatusBadge component (own assets, neutral on skipped/error).
- "Update available" column in the containers datatable; one cached,
non-blocking query per visible row.
Tests: Go response-shape unit test; vitest for the badge (all statuses)
and the hook (url + nodeName query param via msw).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: prune retry-state by elapsed window since lastRestart instead of "not
seen this tick", so a container flapping through "starting" keeps its
cooldown/max-retries accounting (storm guard no longer defeated). Recovered
containers quiet for > window are still cleaned up.
F2: list running containers only (All:false) so stopped-unhealthy containers
are never revived.
F3: each ContainerRestart gets its own context (stop-timeout + buffer),
separate from the per-endpoint list context, so a slow/hung restart cannot
starve the others or exhaust a single shared deadline.
F4: start() is idempotent (no-op when a job is already scheduled); Reload
still stops first so it always reschedules.
F5: frontend parseBool mirrors Go strconv.ParseBool (case-insensitive
1/t/true; present-but-invalid counts as present & false).
F6: tests TestPruneRetries and TestRetryStateSurvivesStartingTick lock in
the F1 behavior; added AutoHealRow parse cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a native, CE-only auto-heal daemon that restarts Docker containers whose
healthcheck reports "unhealthy", replacing the willfarrell/autoheal sidecar.
Backend:
- New package api/containerautomation (service lifecycle + scheduler job,
per-endpoint heal pass, label/scope parsing, in-memory cooldown/retry state).
- Settings.ContainerAutomation.AutoHeal {Enabled, CheckInterval, Scope} with
fresh-install defaults and a 2.43.0 migration backfilling existing installs.
- Settings update handler reloads/stops the job via a small Reloader interface
(no import cycle); service bootstrapped from main.go after stack schedules.
Frontend:
- Global AutoHealPanel in SettingsView (enable / interval / scope) via
useUpdateSettingsMutation, plus settings TS types.
- Read-only per-container Auto-heal row in the container details view (Docker
labels are immutable at runtime; opt-in is set via Create/Edit form labels).
Tests: Go unit tests for label/scope resolution and the cooldown/retry decision;
vitest for the panel and the per-container row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Opening a container from a stack's Containers table showed
"Home > Containers > <container>" instead of keeping the stack trail,
so the user could not navigate back to the stack.
Two root causes are addressed:
1. Route param collision: docker.stacks.stack used the query param `id`
for the numeric stack DB id, while its child docker.stacks.stack.container
uses the path param `id` for the container id. Navigating into a container
overwrote the stack id. The stack id param is renamed `id` -> `stackId`
everywhere it is read or written (route url, stacks datatable link,
create-stack redirect, gitops workflow card link, stack ItemView reader).
2. Hardcoded breadcrumbs: the container details ItemView always rendered the
global "Containers" crumb. Breadcrumbs are now state-aware: when reached
via docker.stacks.stack.container the stack trail
(Stacks > <stack> > <container>) is rebuilt from the inherited stack params,
honoring external/orphaned stacks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Container log live-stream review fixes (frontend only):
- F1/F2: demux Docker's multiplexed (non-TTY) stream at the BYTE level by
frame length, decoding only payloads. Previously the stream was text-decoded
whole and cut on '\n' before stripping 8-byte headers, which desynced when a
length low-byte was 0x0a or a header byte was >= 0x80. streamContainerLogs
now hands the processor raw Uint8Array chunks; createLogStreamProcessor is
rewritten to parse frames, concatenate payloads, split lines on 0x0a, and
UTF-8-decode complete lines. formatLogs is called without stripHeaders so
headers are not stripped twice. Added explicit byte-frame tests.
- F3: request timestamps=1 internally and resume reconnects from the parsed
RFC3339 timestamp of the last line (not client wall-clock); strip the prefix
before display when the user's timestamps toggle is off; dedup the inclusive
`since` boundary lines on reconnect.
- F4: run the fetch stream URL through dockerMaxAPIVersionInterceptor so it
matches the axios getContainerLogs version pinning.
- F5: notify on stream error once per reconnect loop, not every 3s retry.
- F6: resuming Live no longer wipes the buffer (startStream(false)) and
continues from `since`.
- F7: service/task logs still poll; documented the re-render limitation
(out of scope: issue #2 is container logs).
- F8: flush the trailing partial line on the error path too (parity with onEnd).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the 3s $interval polling of container logs with a live HTTP
stream, and stop re-writing already-rendered lines (fixes selection bug).
- streamContainerLogs (containers.service.ts): fetch + ReadableStream
reader with follow=1, same-origin credentials:'include' (httpOnly JWT
cookie; CSRF only guards mutations), agent-target / manager-operation
headers replicated for Agent/Edge, AbortSignal-driven lifetime.
- containerLogsController: stream instead of poll; append parsed lines
into the buffer (push, never replace), cap at 5000 lines trimming from
the head; AbortController on pause/destroy/param-change; reconnect with
3s backoff resuming from `since` (dropping tail) on stream end/error;
Live toggle pauses/resumes the stream; tail/since/timestamps changes
restart the stream.
- log-viewer: `track by log.id` (was $index), filtering moved out of the
template into the controller (applyFilter via $watchCollection), removed
inert force-glue, decoupled auto-scroll from log collection, relabelled
"Auto-refresh logs" -> "Live logs", clearer empty states.
Backend unchanged (logs already stream transparently through the Docker
proxy). Shared task/service log views keep working via the new id'd lines.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation for append-only log rendering and HTTP log streaming.
- FormattedLine gains a stable, monotonically increasing `id` (new
lineId.ts sequence), assigned centrally in formatLogs. Internal
formatters now return id-less FormattedLineContent. This lets the
viewer use `track by log.id` so already-rendered rows are never
re-bound (fixes the text-selection collapse).
- formatJSONLine: runtime guard so a bare JSON string/array log line
falls back to plain text instead of rendering Object.keys as
`0=h 1=e ...`.
- createLogStreamProcessor: stateful demuxer that buffers streamed text,
emits only complete lines (carrying the partial remainder), and reuses
formatLogs/stripHeadersFunc for Docker 8-byte frame demux.
- Unit tests for the demuxer, stable-id assignment and the JSON guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the disabled "Installing from an OCI registry is a Portainer
Business Feature" option from the CE Helm repository selector so no
Business Feature teaser remains; CE Helm Repositories options are
unaffected.
Delete the orphaned AutomaticEdgeEnvCreation module (incl.
EnableWaitingRoomSwitch) — its render was already removed from
EdgeComputeSettingsView and nothing imports it anymore.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete the feature-flags edition machinery (isBE, init/selectShow/
isLimitedToBE, FeatureId/Edition/FeatureState enums, BEFeatureIndicator,
BEOverlay, BETeaserButton, withEdition, useLimitToBE, limitedFeatureDir)
now that all consumers are gone, drop the initFeatureService bootstrap,
and update tests/stories to assert CE-only behaviour. Mechanism B
(runtime FeatureFlag) and withHideOnExtension are left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unwrap be-feature-indicator / limited-feature directives and feature-id
attributes from LDAP/OAuth/access-management templates, delete BE-only
AngularJS views (Active Directory, OpenLDAP, RBAC access-viewer, auth
logs) and remove their registrations/routes and the r2a teaser-prop
allow-lists.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the Upgrade-to-Business banner, BE sidebar items (Licenses, Shared
Credentials, Edge Configurations, Waiting Room, Update & Rollback), BE
branding (BE logo/footer), and BE-only routed views (update-schedules,
EdgeAutoCreateScript, WaitingRoom, TimeWindowDisplay/Picker). Prune the
featureId/feature/BEFeatureID teaser props from shared components
(Switch, SwitchField, BoxSelector, TooltipWithChildren, wizard Option)
and fold isBE in useUser while preserving CE authorization semantics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse isBE/isLimitedToBE consumers and delete BE-only teaser UI in
settings, gitops, registries, custom templates, environments wizard,
access control, activity logs, registries and home/system views. The
Activity Audit view keeps its route but renders a plain CE empty state.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove always-false isBE branches, BE-only teaser controls and the
now-dead imports across the Docker, Kubernetes and Edge-stack React
views. CE behaviour is preserved; only the Business Edition branches,
teasers and BE-only (non-functional) controls are removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pnpm ignores the npm_config_frozen_lockfile env var, so the previous fix did not take effect and CI still ran a frozen install. Add an explicit 'pnpm install --no-frozen-lockfile' step before 'make build-all' to reconcile the lockfile (missing pnpmfileChecksum for configDependencies); the subsequent frozen install in 'make client-deps' then succeeds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI enables --frozen-lockfile by default, which fails with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH because the committed pnpm-lock.yaml does not record the pnpmfileChecksum for the configDependencies declared in package.json. Set npm_config_frozen_lockfile=false so the bare 'pnpm install' run by 'make client-deps' reconciles the lockfile instead of failing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds the CE fork (frontend + Go server) and publishes a drop-in compatible image to ghcr.io/vvzvlad/portainer-ce on pushes to develop, v* tags, and manual dispatch. Single-arch linux/amd64, alpine base, production frontend build (ENV=production), tags <package.json version> and latest, GHCR auth via the built-in GITHUB_TOKEN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Remove URL when rendering edge agent in list as it was displaying the server URL
* Add server and tunnel URL to information panel in environment display
- Migrate `EnvironmentList` from `GroupSortTable` to `SortableList`, removing ~1,700 lines of duplicated component code
- Move health sort ranking to the backend (`sort.go`), adding `Health` and `Id` sort keys
- Delete `GroupSortTable`, `GroupSortTableGroupRow`, `useGroupSortTableState`, and `store` — functionality absorbed by `SortableList`
- Add `useHomeViewState` hook to centralise home view URL state (`groupBy`, `groupFilter`, `order`, `page`, `search`)
- Update `useTableStateFromUrl` to support `groupBy` and `groupFilter` URL params with a `buildExtra` callback
- Rename URL param `filter` → `groupFilter` for clarity; add `search` and `order` to `/home` route definition
- Simplify `EnvironmentList` props — remove `headerFilter` / `onHeaderFilterChange`, leaving only `onClickBrowse`
- Add `computeSortDesc` pure utility to `SortableList` and cover all toggle/reset cases with unit tests
- Update `SortableListHeader` to use `activeKey` prop (renamed from `sortBy`); fix all callsites and stories
- Fix `SortableList` sort-key normalisation to be case-insensitive; update tests to reflect no-match behaviour
This commit https://github.com/docker/cli/commit/9b9d103b297cdff32e35dde771c8c392c7caabeb, introduced in docker 29, changed the behaviour of how the --tlsXXX flags are handled. Before this change leading and trailing quotes would be stripped. This meant that an invalid path that we were passing for the tls ca cert was being cleaned up to be an empty string. To preserve the old behaviour we now pass an empty string.
* fix(app/volume): make optional Container and ContainerConfig fields removed in docker 26
* fix(app/image): use image.Size instead of image.VirtualSize removed in Docker 25
* fix(cache): default cache to on for new users [EE-6293]
* clear cache to transition terminating namespace
* add rq requests back to the namespace view
---------
Co-authored-by: testa113 <testa113>
* feat(api/logout): make logout route public
* feat(app/logout): always perform API logout on /logout redirect
* fix(app): send a logout event to AngularJS when axios hits a 401
* fix(docker/container-logs): invalid string breadcrumb
* fix(docker/container): let docker select the logging driver by default on container create
* fix(docker/container-logs): information panel in container logs when logging is disabled
* fix(docker/container): dont include HostConfig.LogConfig if no driver is selected
* feat(app/home): tooltip aside edge agent version on mismatch with Portainer version
* fix(app/home): split agent and edge version display + display warning for agents before 2.15
Before asking a question, make sure it hasn't been already asked and answered. You can search our [discussions](https://github.com/orgs/portainer/discussions) and [bug reports](https://github.com/portainer/portainer/issues) in GitHub. Also, be sure to check our [knowledge base](https://portal.portainer.io/knowledge) and [documentation](https://docs.portainer.io/) first.
Before opening a new idea or feature request, make sure that we do not have any duplicates already open. You can ensure this by [searching this discussion category](https://github.com/orgs/portainer/discussions/categories/ideas). If there is a duplicate, please add a comment to the existing idea instead.
Also, be sure to check our [knowledge base](https://portal.portainer.io/knowledge) and [documentation](https://docs.portainer.io) as they may point you toward a solution.
**DO NOT FILE DUPLICATE REQUESTS.**
- type:textarea
attributes:
label:Is your feature request related to a problem? Please describe
description:Short list of what the feature request aims to address.
validations:
required:true
- type:textarea
attributes:
label:Describe the solution you'd like
description:A clear and concise description of what you want to happen.
validations:
required:true
- type:textarea
attributes:
label:Describe alternatives you've considered
description:A clear and concise description of any alternative solutions or features you've considered.
validations:
required:true
- type:textarea
attributes:
label:Additional context
description:Add any other context or screenshots about the feature request here.
The issue tracker is for reporting bugs. If you have an [idea for a new feature](https://github.com/orgs/portainer/discussions/categories/ideas) or a [general question about Portainer](https://github.com/orgs/portainer/discussions/categories/help) please post in our [GitHub Discussions](https://github.com/orgs/portainer/discussions).
You can also ask for help in our [community Slack channel](https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA).
Please note that we only provide support for current versions of Portainer. You can find a list of supported versions in our [lifecycle policy](https://docs.portainer.io/start/lifecycle).
**DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS**.
- type:checkboxes
id:terms
attributes:
label:Before you start please confirm the following.
options:
- label:Yes,I've searched similar issues on [GitHub](https://github.com/portainer/portainer/issues).
required:true
- label:Yes,I've checked whether this issue is covered in the Portainer [documentation](https://docs.portainer.io).
required:true
- type:markdown
attributes:
value:|
# About your issue
Tell us a bit about the issue you're having.
How to write a good bug report:
- Respect the issue template as much as possible.
- Summarize the issue so that we understand what is going wrong.
- Describe what you would have expected to have happened, and what actually happened instead.
- Provide easy to follow steps to reproduce the issue.
- Remain clear and concise.
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown).
- type:textarea
attributes:
label:Problem Description
description:A clear and concise description of what the bug is.
validations:
required:true
- type:textarea
attributes:
label:Expected Behavior
description:A clear and concise description of what you expected to happen.
validations:
required:true
- type:textarea
attributes:
label:Actual Behavior
description:A clear and concise description of what actually happens.
validations:
required:true
- type:textarea
attributes:
label:Steps to Reproduce
description:Please be as detailed as possible when providing steps to reproduce.
placeholder:|
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required:true
- type:textarea
attributes:
label:Portainer logs or screenshots
description:Provide Portainer container logs or any screenshots related to the issue.
validations:
required:false
- type:markdown
attributes:
value:|
# About your environment
Tell us a bit about your Portainer environment.
- type:dropdown
attributes:
label:Portainer version
description:We only provide support for current versions of Portainer as per the lifecycle policy linked above. If you are on an older version of Portainer we recommend [updating first](https://docs.portainer.io/start/upgrade) in case your bug has already been fixed.
multiple:false
options:
- '2.42.0'
- '2.41.1'
- '2.41.0'
- '2.40.0'
- '2.39.3'
- '2.39.2'
- '2.39.1'
- '2.39.0'
- '2.38.1'
- '2.38.0'
- '2.37.0'
- '2.36.0'
- '2.35.0'
- '2.34.0'
- '2.33.8'
- '2.33.7'
- '2.33.6'
- '2.33.5'
- '2.33.4'
- '2.33.3'
- '2.33.2'
- '2.33.1'
- '2.33.0'
validations:
required:true
- type:dropdown
attributes:
label:Portainer Edition
multiple:false
options:
- 'Business Edition (BE/EE) with 5NF / 3NF license'
- 'Business Edition (BE/EE) with Home & Student license'
- 'Business Edition (BE/EE) with Starter license'
- 'Business Edition (BE/EE) with Professional or Enterprise license'
- 'Community Edition (CE)'
validations:
required:true
- type:input
attributes:
label:Platform and Version
description:|
Enter your container management platform (Docker | Swarm | Kubernetes) along with the version.
stale-issue-message:'This issue has been marked as stale as it has not had recent activity, it will be closed if no further activity occurs in the next 7 days. If you believe that it has been incorrectly labelled as stale, leave a comment and the label will be removed.'
close-issue-message:'Since no further activity has appeared on this issue it will be closed. If you believe that it has been incorrectly closed, leave a comment mentioning `portainer/support` and one of our staff will then review the issue. Note - If it is an old bug report, make sure that it is reproduceable in the latest version of Portainer as it may have already been fixed.'
# Pull Request Config
days-before-pr-stale:-1# Do not stale pull request
days-before-pr-close:-1# Do not close pull request
console.error(`Unhandled ${method} request to ${url}.
This exception has been only logged in the console, however, it's strongly recommended to resolve this error as you don't want unmocked data in Storybook stories.
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses
console.error(`Unhandled ${method} request to ${url}.
This exception has been only logged in the console, however, it's strongly recommended to resolve this error as you don't want unmocked data in Storybook stories.
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses
[MSW] Uncaught exception in the request handler for "%s %s":
${parsedBody.location}
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
request.method,
request.url
);
returnrespondWithMock(clientMessage);
case'PASSTHROUGH':{
returnpassthrough();
}
}
returngetOriginalResponse();
returnpassthrough();
}
self.addEventListener('fetch',function(event){
const{request}=event;
constaccept=request.headers.get('accept')||'';
// Bypass server-sent events.
if(accept.includes('text/event-stream')){
return;
}
// Bypass navigation requests.
if(request.mode==='navigate'){
return;
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if(activeClientIds.size===0){
return;
}
constrequestId=uuidv4();
returnevent.respondWith(
handleRequest(event,requestId).catch((error)=>{
if(error.name==='NetworkError'){
console.warn('[MSW] Successfully emulated a network error for the "%s %s" request.',request.method,request.url);
return;
}
// At this point, any exception indicates an issue with the original request/response.
console.error(
`\
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
Open-source container management platform with full Docker and Kubernetes support.
## Project Structure
For a detailed breakdown of frontend and backend directory layout, feature locations, and common development tasks, see [docs/guidelines/project-structure.md](../../docs/guidelines/project-structure.md).
@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at anthony.lapenna@portainer.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contribute@portainer.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
@@ -8,9 +8,9 @@ Portainer consists of a single container that can run on any cluster. It can be
**Portainer Business Edition** builds on the open-source base and includes a range of advanced features and functions (like RBAC and Support) that are specific to the needs of business users.
- [Compare Portainer CE and Compare Portainer BE](https://portainer.io/products)
- [Compare Portainer CE and Compare Portainer BE](https://www.portainer.io/features)
- [Take3 – get 3 free nodes of Portainer Business for as long as you want them](https://www.portainer.io/take-3)
- [Portainer BE install guide](https://install.portainer.io)
- [Portainer BE install guide](https://academy.portainer.io/install/)
## Latest Version
@@ -20,22 +20,19 @@ Portainer CE is updated regularly. We aim to do an update release every couple o
@@ -47,19 +44,45 @@ You can join the Portainer Community by visiting [https://www.portainer.io/join-
- Want to report a bug or request a feature? Please open [an issue](https://github.com/portainer/portainer/issues/new).
- Want to help us build **_portainer_**? Follow our [contribution guidelines](https://docs.portainer.io/contribute/contribute) to build it locally and make a pull request.
## Generating API types
The frontend consumes a TypeScript API client (SDK functions and request/response types) that is generated from the Go API's Swagger annotations. Regenerate it after any API change — a new endpoint, a changed request/response shape, or a removed endpoint:
```bash
make generate-api
```
This runs the following pipeline:
```
Go Swagger annotations
→ dist/docs/swagger.yaml (make docs-build, via swaggo/swag)
The generator is configured in [`openapi-ts.config.ts`](./openapi-ts.config.ts), which controls the output path, plugins, and tag filters (for example, `deprecated` endpoints and `edge_agent`-tagged routes are excluded).
The generated files live in `app/react/portainer/generated-api/portainer/` and must **not** be edited by hand — your changes would be overwritten on the next run. Import the generated SDK functions and types instead of writing direct HTTP calls:
-`@api/sdk.gen` — SDK functions
-`@api/types.gen` — request/response types
See [Adding api docs](./CONTRIBUTING.md#adding-api-docs) for how to annotate handlers so they are picked up by the generator.
## Security
- Here at Portainer, we believe in [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure) of security issues. If you have found a security issue, please report it to <security@portainer.io>.
For information about reporting security vulnerabilities, please see our [Security Policy](SECURITY.md).
## Work for us
If you are a developer, and our code in this repo makes sense to you, we would love to hear from you. We are always on the hunt for awesome devs, either freelance or employed. Drop us a line to info@portainer.io with your details and/or visit our [careers page](https://portainer.io/careers).
If you are a developer, and our code in this repo makes sense to you, we would love to hear from you. We are always on the hunt for awesome devs, either freelance or employed. Drop us a line to success@portainer.io with your details and/or visit our [careers page](https://apply.workable.com/portainer/).
## Privacy
**To make sure we focus our development effort in the right places we need to know which features get used most often. To give us this information we use [Matomo Analytics](https://matomo.org/), which is hosted in Germany and is fully GDPR compliant.**
When Portainer first starts, you are given the option to DISABLE analytics. If you **don't** choose to disable it, we collect anonymous usage as per [our privacy policy](https://www.portainer.io/privacy-policy). **Please note**, there is no personally identifiable information sent or stored at any time and we only use the data to help us improve Portainer.
When Portainer first starts, you are given the option to DISABLE analytics. If you **don't** choose to disable it, we collect anonymous usage as per [our privacy policy](https://www.portainer.io/legal/privacy-policy). **Please note**, there is no personally identifiable information sent or stored at any time and we only use the data to help us improve Portainer.
Portainer maintains both Short-Term Support (STS) and Long-Term Support (LTS) versions in accordance with our official [Portainer Lifecycle Policy](https://docs.portainer.io/start/lifecycle).
| STS (Short-Term Support) | Supported until the next STS or LTS release |
| Legacy / EOL | Not supported |
For a detailed breakdown of current versions and their specific End of Life (EOL) dates,
please refer to the [Portainer Lifecycle Policy](https://docs.portainer.io/start/lifecycle).
## Reporting a Vulnerability
The Portainer team takes the security of our products seriously. If you believe you have found a security vulnerability in any Portainer-owned repository, please report it to us responsibly.
**Please do not report security vulnerabilities via public GitHub issues.**
### Disclosure Process
1.**Report**: You can report in one of two ways:
- **GitHub**: Use the **Report a vulnerability** button on the **Security** tab of this repository.
- **Email**: Send your findings to security@portainer.io.
2.**Details**: To help us verify the issue, please include:
- A description of the vulnerability and its potential impact.
- Step-by-step instructions to reproduce the issue (e.g. proof-of-concept code, scripts, or screenshots).
- The version of the software and the environment in which it was found.
3.**Acknowledge**: We will acknowledge receipt of your report and provide an initial assessment.
4.**Resolution**: We will work to resolve the issue as quickly as possible. We request that you do not disclose the vulnerability publicly until we have released a fix and notified affected users.
## Our Commitment
If you follow the responsible disclosure process, we will:
- Respond to your report in a timely manner.
- Provide an estimated timeline for remediation.
- Notify you when the vulnerability has been patched.
- Give credit for the discovery (if desired) once the fix is public.
We will make every effort to promptly address any security weaknesses. Security advisories and fixes will be published through GitHub Security Advisories and other channels as needed.
Thank you for helping keep Portainer and our community secure.
## Resources
- [Contributing to Portainer](https://docs.portainer.io/contribute/contribute#contributing-to-the-portainer-ce-codebase)
Each API environment(endpoint) has an associated access policy, it is documented in the description of each environment(endpoint).
Different access policies are available:
- Public access
- Authenticated access
- Restricted access
- Administrator access
### Public access
No authentication is required to access the environments(endpoints) with this access policy.
### Authenticated access
Authentication is required to access the environments(endpoints) with this access policy.
### Restricted access
Authentication is required to access the environments(endpoints) with this access policy.
Extra-checks might be added to ensure access to the resource is granted. Returned data might also be filtered.
### Administrator access
Authentication as well as an administrator role are required to access the environments(endpoints) with this access policy.
# Execute Docker requests
Portainer **DO NOT** expose specific environments(endpoints) to manage your Docker resources (create a container, remove a volume, etc...).
Instead, it acts as a reverse-proxy to the Docker HTTP API. This means that you can execute Docker requests **via** the Portainer HTTP API.
To do so, you can use the `/endpoints/{id}/docker` Portainer API environment(endpoint) (which is not documented below due to Swagger limitations). This environment(endpoint) has a restricted access policy so you still need to be authenticated to be able to query this environment(endpoint). Any query on this environment(endpoint) will be proxied to the Docker API of the associated environment(endpoint) (requests and responses objects are the same as documented in the Docker API).
# Private Registry
Using private registry, you will need to pass a based64 encoded JSON string ‘{"registryId":\<registryID value\>}’ inside the Request Header. The parameter name is "X-Registry-Auth".
\<registryID value\> - The registry ID where the repository was created.
Example:
```
eyJyZWdpc3RyeUlkIjoxfQ==
```
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://documentation.portainer.io/api/api-examples/).
The Portainer API is an HTTP API served by Portainer. It is used by the Portainer UI, and anything you can do in the UI can also be done via the HTTP API.
API examples are available in the [Portainer documentation](https://documentation.portainer.io/api/api-examples/)
You can find out more about Portainer [on our website](http://portainer.io) and get some support on [Slack](http://portainer.io/slack/).
# Authentication
Most of the API endpoints require authentication, as well as some level of authorization.
Portainer uses JSON Web Tokens to manage authentication. You must provide a token in the **Authorization** header of each request using the **Bearer** scheme.
Each API endpoint has an associated access policy, documented in its description.
The following policies are available:
- Public access
- Authenticated access
- Restricted access
- Administrator access
### Public access
No authentication is required.
### Authenticated access
Authentication is required.
### Restricted access
Authentication is required. Additional checks may apply to verify access to the resource, and returned data may be filtered.
### Administrator access
Authentication and an administrator role are both required.
# Execute Docker requests
Portainer does not expose dedicated endpoints for managing Docker resources (create a container, remove a volume, etc).
Instead, it acts as a reverse-proxy to the Docker HTTP API, allowing you to execute Docker requests via the Portainer HTTP API.
To do so, use the `/endpoints/{id}/docker` endpoint. Note that this endpoint is not documented below due to Swagger limitations. It has a restricted access policy, so authentication is still required. Any request made to this endpoint is proxied to the Docker API of the associated environment - request and response objects are identical to those in the [Docker official documentation](https://docs.docker.com/engine/api).
# Private Registry
When using a private registry, include a Base64-encoded JSON string in the request header. The header parameter name is `X-Registry-Auth` and the value should encode the following structure: ‘{"registryId":\<registryId\>}’ where `<registryId>` is the ID of the registry where the repository was created.
MaxBatchDelay:kingpin.Flag("max-batch-delay","Maximum delay before a batch starts").Duration(),
SecretKeyName:kingpin.Flag("secret-key-name","Secret key name for encryption and will be used as /run/secrets/<secret-key-name>.").Default(defaultSecretKeyName).String(),
LogLevel:kingpin.Flag("log-level","Set the minimum logging level to show").Default("INFO").Enum("DEBUG","INFO","WARN","ERROR"),
LogMode:kingpin.Flag("log-mode","Set the logging output mode").Default("PRETTY").Enum("PRETTY","JSON"),
LogMode:kingpin.Flag("log-mode","Set the logging output mode").Default("PRETTY").Enum("NOCOLOR","PRETTY","JSON"),
TrustedOrigins:kingpin.Flag("trusted-origins","List of trusted origins for CSRF protection. Separate multiple origins with a comma.").Envar(portainer.TrustedOriginsEnvVar).String(),
CompactDB:kingpin.Flag("compact-db","Enable database compaction on startup").Envar(portainer.CompactDBEnvVar).Default("false").Bool(),
NoSetupToken:kingpin.Flag("no-setup-token","Disable the setup token requirement for admin initialization and restore on an uninitialized instance").Envar(portainer.NoSetupTokenEnvVar).Bool(),
SetupToken:kingpin.Flag("setup-token","Set a custom setup token for admin initialization and restore on an uninitialized instance (overrides auto-generation)").Envar(portainer.SetupTokenEnvVar).String(),
}
}
// ParseFlags parse the CLI flags and return a portainer.Flags struct
log.Warn().Msg("the --no-analytics flag has been kept to allow migration of instances running a previous version of Portainer with this flag enabled, to version 2.0 where enabling this flag will have no effect")
}
if*flags.SSL{
log.Warn().Msg("SSL is enabled by default and there is no need for the --ssl flag, it has been kept to allow migration of instances running a previous version of Portainer with this flag enabled")
log.Info().Msg("using custom setup token; admin initialization and backup restore require this token in the X-Setup-Token header")
returnprovidedToken,nil
}
token,err:=setuptoken.Generate()
iferr!=nil{
return"",err
}
log.Info().
Str("setup_token",token).
Msg("no administrator account configured; admin initialization and backup restore require this setup token in the X-Setup-Token header. Start with --no-setup-token to disable.")
log.Fatal().Msg("The database schema version does not align with the server version. Please consider reverting to the previous server version or addressing the database migration issue.")
// multiNotifier fans an event out to several notifiers in order. It is how the
// service composes the always-on logNotifier with the optional webhookNotifier
// without either implementation having to know about the other. Each notifier is
// itself non-blocking, so multiNotifier stays safe on the daemon hot path.
typemultiNotifier[]Notifier
// Notify forwards the event to every wrapped notifier. Each call is isolated by
// a recover() so one misbehaving notifier can neither abort the others nor let a
// panic reach the daemon hot path; logNotifier is kept first and unchanged.
func(mmultiNotifier)Notify(eventEvent){
for_,n:=rangem{
func(){
deferfunc(){
ifr:=recover();r!=nil{
log.Warn().Interface("panic",r).Msg("container automation: recovered from panic in notifier")
}
}()
n.Notify(event)
}()
}
}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.