Compare commits

..

2668 Commits

Author SHA1 Message Date
agent_coder bb68acfbf6 fix(#29 review r1): stack-delete leak, rollback/retention/version tests, UX trap
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>
2026-07-02 17:28:52 +03:00
agent_coder d0d3c068ba feat(stacks): file-based stack versioning with full history + rollback (#27)
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>
2026-07-02 16:07:26 +03:00
vvzvlad 549e916c3e Merge pull request 'fix: портейнер-ишью одним PR — #17 (Go-канон роутинга), #20 (тесты демона), #21 (мёртвый isEditLDAP)' (#26) from fix/portainer-issues into develop
Reviewed-on: #26
2026-07-02 01:14:48 +03:00
claude code agent 4c76462943 Merge branch 'tmp/issue20' into fix/portainer-issues 2026-07-01 23:46:09 +03:00
claude code agent 5a4d7e557b Merge branch 'tmp/issue17' into fix/portainer-issues 2026-07-01 23:46:09 +03:00
agent_coder 32f1b601b6 test(automation): fake-docker seam + daemon orchestration tests (closes #20)
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>
2026-07-01 23:46:09 +03:00
agent_coder 3c7400ca2a fix(update): derive git-stack flag from WorkflowID to match Go daemon (closes #17)
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>
2026-07-01 23:34:10 +03:00
agent_coder cdb1f8648a fix(auth): drop dead SERVER_TYPES.LDAP branch in isEditLDAP (closes #21)
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>
2026-07-01 23:30:09 +03:00
vvzvlad 594312a777 Merge pull request 'feat(automation): native container auto-update (Watchtower-style) + auto-heal (#3)' (#19) from feat/3-auto-update into develop
Reviewed-on: #19
2026-07-01 23:25:24 +03:00
agent_coder 492d3d01b0 feat(#19): separate webhook per automation mechanism (update vs heal)
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>
2026-07-01 22:47:25 +03:00
vvzvlad 6a4242b166 Merge pull request 'feat(logs): live HTTP stream + append-only render (#2)' (#6) from feat/2-stream-logs into develop
Reviewed-on: #6
2026-07-01 22:09:47 +03:00
agent_coder 8829c8cfd2 fix(#6): theme-consistent search highlight + Copied ack on Copy button
- 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>
2026-07-01 21:46:55 +03:00
agent_coder 331319f7f1 feat(logs): theme-aware log viewer (standard colors), fill height
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>
2026-07-01 21:35:49 +03:00
agent_coder a60b7be55d fix(#6): address review — hook reconnect/trim tests, sticky error banner, stale comment
- 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>
2026-07-01 20:55:15 +03:00
agent_coder 5bb678d3ba fix(#19): address review F1-F4 (badge test, force write-back test, force throttle, per-container stack notifications)
- 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>
2026-07-01 20:50:55 +03:00
agent_coder f6752130ac feat(logs): re-skin viewer to maintainer's mockup design
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>
2026-07-01 20:41:23 +03:00
vvzvlad 394075c94c Merge pull request 'fix(stacks): сохранять стек-крошки при Quick Actions контейнера' (#24) from fix/stack-breadcrumb-quickactions into develop
Reviewed-on: #24
2026-07-01 20:23:57 +03:00
agent_coder 9a6dd0c408 feat(logs): match EE reference — combined datetime range picker, toggle icons
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>
2026-07-01 19:50:41 +03:00
agent_coder eb35e9c47f feat(automation): configurable webhook notifier for automation events
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>
2026-07-01 19:31:18 +03:00
agent_coder 7f02d20e54 feat(logs): React log viewer for container logs
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>
2026-07-01 19:05:56 +03:00
agent_coder 6aecdfbe46 feat(containers): interactive image-status badge (click to update / re-check)
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>
2026-07-01 19:04:49 +03:00
agent_coder f379e8057e fix(stacks): keep stack breadcrumb on container Quick Actions
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>
2026-07-01 18:52:44 +03:00
vvzvlad 8366f4d25c Merge pull request 'docs: гайд по локальному демо-стенду + ссылка из CLAUDE.md' (#23) from docs/dev-stand into develop
Reviewed-on: #23
2026-07-01 18:32:06 +03:00
agent_coder 4d8fb82e15 docs: add local demo stand guide + reference it from CLAUDE.md
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>
2026-07-01 18:14:49 +03:00
claude code agent 474c41ec8e Merge remote-tracking branch 'origin/develop' into feat/2-stream-logs
# Conflicts:
#	app/docker/views/containers/logs/containerLogsController.js
2026-07-01 03:53:45 +03:00
vvzvlad 0e572f4ccc Merge pull request 'fix(stacks): keep stack breadcrumb trail when opening a container from a stack (#4)' (#7) from feat/4-stack-breadcrumbs into develop
Reviewed-on: #7
2026-07-01 02:23:40 +03:00
vvzvlad 1e8f10f9cc Merge pull request 'feat(#1): remove all EE/Business-Edition features from the UI (pure-CE frontend)' (#5) from feat/1-remove-ee-ui into develop
Reviewed-on: #5
2026-07-01 02:23:07 +03:00
claude code agent 9205099f14 fix(logs): restore a stock-style log-viewer layout (drop the header cram)
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>
2026-06-30 10:41:01 +03:00
claude code agent 7257ae52d8 test(logs): cover the docker proxy stream/flush loop (F1)
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>
2026-06-30 02:58:48 +03:00
claude code agent 0bf4e71b79 fix(stacks): keep the stack breadcrumb trail on container attribute sub-tabs
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>
2026-06-30 02:25:04 +03:00
claude code agent 637e96f236 fix(logs): flush docker proxy stream per chunk; trim log-viewer settings UI
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>
2026-06-30 02:05:02 +03:00
claude code agent 343d36834a feat(logs): drop the auto-refresh toggle (always live) and compact settings to one row
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>
2026-06-30 00:14:04 +03:00
claude code agent f27e44f5f2 refactor(ce): remove vestigial limitedToBE plumbing from BoxSelector (F8)
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>
2026-06-29 21:26:10 +03:00
claude code agent f658d67ccb refactor(settings): extract shared parseGoDuration to drop the duplicated parser (F10)
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>
2026-06-29 20:49:35 +03:00
claude code agent 5f16799b4c refactor(ce): drop orphaned OPEN_LDAP type and collapse degenerate buildUrl (F6,F7)
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>
2026-06-29 20:49:32 +03:00
claude code agent 8ad42a1a45 fix(logs): seed reconnect boundary state from resume params; extract+test since parser (F10,F11)
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>
2026-06-29 20:42:32 +03:00
claude code agent 9768d7bb99 fix(logs): content-exact reconnect dedup; flush partial on pause; unify since; lines fallback (F1-F5)
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>
2026-06-29 20:05:18 +03:00
claude code agent e9fae32b43 test(stacks): cover orphaned branch; name the stack-container state; type link params (F1,F2,F4)
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>
2026-06-29 19:55:41 +03:00
claude code agent 28a06e80a8 refactor(ce): finish dead-BE removal — S3 backup, user-activity, LDAP, edition markers (F1-F5)
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>
2026-06-29 19:52:33 +03:00
claude code agent be3bfd0513 fix(automation): maintainer pre-merge review — stale detection, daemon edge cases, parity (F1-F9)
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>
2026-06-29 19:51:15 +03:00
claude code agent 6171806528 test(automation): cover pruneRolledBack boundary/retention (F8)
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>
2026-06-29 16:06:49 +03:00
claude code agent da6933c218 refactor(logs): collapse no-op ternary, drop speculative export, fix stale comment (F8,F9,F10)
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>
2026-06-29 15:37:37 +03:00
claude code agent 90f51d48bb refactor(ce): drop orphaned access role column + no-op lodash compact (F6,F7)
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>
2026-06-29 15:08:01 +03:00
claude code agent d520aec159 docs(logs): neutral auto-refresh toggle label/tooltip in shared log viewer (F7)
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>
2026-06-29 14:42:19 +03:00
claude code agent 922f506fe5 feat(automation): guard update→rollback loop; name Settings types; tests & doc fixes (F1-F7)
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>
2026-06-29 14:29:57 +03:00
claude code agent b1b09e5da0 refactor(ce): remove leftover dead BE code — gates, orphans, dead selectors/CSS (F1-F4)
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>
2026-06-29 14:13:55 +03:00
claude code agent f4f296fc05 fix(logs): drop partial line on reconnect, stabilize poll row ids, cover CRLF/dedup (F1-F6)
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>
2026-06-29 14:08:46 +03:00
claude code agent a1851417d1 test(stacks): cover external-stack breadcrumb branch in buildStackLinkParams (F1)
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>
2026-06-29 14:06:25 +03:00
claude code agent 70f7fe5e84 Merge remote-tracking branch 'origin/feat/10-update-now' into feat/3-auto-update 2026-06-29 12:48:24 +03:00
claude code agent cdf17d904d fix(automation): rollback robustness — transient inspect, start_period, digest images, shutdown, event order (#12 review)
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>
2026-06-29 10:57:54 +03:00
claude code agent 32a2b7a9ae feat(automation): health-gated rollback + per-endpoint + notify hook (#12, epic #3 M5)
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>
2026-06-29 10:41:55 +03:00
claude code agent 21b5ec3e05 fix(automation): git-stack honesty + ECR registry refresh + interval floor (#11 review)
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>
2026-06-29 10:24:58 +03:00
claude code agent b3ae5f3659 feat(automation): native auto-update daemon (#11, epic #3 M4)
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>
2026-06-29 10:04:09 +03:00
claude code agent ccd5897915 fix(automation): gate stack redeploy on PortainerStackUpdate + bulk/name polish (#10 review)
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>
2026-06-29 09:42:33 +03:00
claude code agent f7cb0f3241 feat(automation): "Update now" action (stack-aware) + bulk update (#10, epic #3 M3)
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>
2026-06-29 09:24:10 +03:00
claude code agent 7eaff4dab0 fix(automation): real status cache read + nodeName key + honest errors (#9 review)
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>
2026-06-29 09:09:18 +03:00
claude code agent f69eb3f9eb feat(automation): CE container image update detection endpoint + badge (#9, epic #3 M2)
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>
2026-06-29 08:59:54 +03:00
claude code agent b233f75ab7 fix(automation): retry-state retention + running-only heal + per-restart ctx (#8 F1-F6)
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>
2026-06-29 08:39:17 +03:00
claude code agent 51957d2f98 feat(automation): native auto-heal daemon (#8, epic #3 M1)
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>
2026-06-29 08:22:46 +03:00
claude code agent b4d10a67b2 fix(stacks): preserve stack tab on breadcrumb back-link + assert default crumb (F1,F2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 07:55:47 +03:00
claude code agent cb11b0fca4 fix(stacks): keep stack breadcrumb trail when opening a container from a stack (#4)
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>
2026-06-29 07:44:18 +03:00
claude code agent 960d43e70b fix(logs): byte-level frame demux + reconnect/since + notify throttle (F1-F8)
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>
2026-06-29 07:32:16 +03:00
claude code agent c3cdb8007e feat(logs): live HTTP stream + append-only render for container logs
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>
2026-06-29 07:03:23 +03:00
claude code agent 8ca0608b21 fix(logs): stable line ids, JSON string guard, streaming demuxer helper
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>
2026-06-29 07:03:10 +03:00
claude code agent b7df90905d feat(ce): drop OCI "Business Feature" teaser and orphaned BE edge module
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>
2026-06-29 06:47:43 +03:00
claude code agent ef47503bf8 feat(ce): tear down BE edition-gating engine
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>
2026-06-29 06:33:27 +03:00
claude code agent 76896e5916 feat(ce): strip BE teaser plumbing from AngularJS layer
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>
2026-06-29 06:33:27 +03:00
claude code agent 7dc98df2b6 feat(ce): remove BE chrome, routes, upsell banner and shared teaser props
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>
2026-06-29 06:33:15 +03:00
claude code agent cddccd2a5f feat(ce): remove BE teasers from Portainer React views
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>
2026-06-29 06:33:15 +03:00
claude code agent 003a90c235 feat(ce): collapse BE edition gating in Docker/Kubernetes/Edge views
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>
2026-06-29 06:33:03 +03:00
vvzvlad 9e9bb1bbff ci: install client deps with --no-frozen-lockfile explicitly
Build image / build (push) Has been cancelled
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>
2026-06-28 19:21:11 +03:00
vvzvlad d0a0395337 ci: disable pnpm frozen-lockfile in build workflow
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>
2026-06-28 19:18:59 +03:00
vvzvlad 88589e4cb3 ci: add GitHub Actions workflow to build and publish fork image to GHCR
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>
2026-06-28 19:14:36 +03:00
vvzvlad af74986e66 chore: add VS Code workspace file 2026-06-28 18:55:18 +03:00
andres-portainer e664bf0e19 fix(helm): add missing SSRF protections BE-13136 (#3001) 2026-06-22 20:25:10 -03:00
nickl-portainer 152c89972b chore(eslint): update eslint to latest v9 [R8S-1090] (#2954) 2026-06-23 11:04:33 +12:00
Oscar Zhou 25c69c6e9b fix(ui): update server installation timeout redirect link [BE-13124] (#2991) 2026-06-23 08:49:43 +12:00
andres-portainer a6370808ae fix(ssrf): disable HTTP/2 for some specific cases BE-13121 (#2996) 2026-06-22 16:13:43 -03:00
Chaim Lev-Ari 6bfd2360d8 docs(security): add FAQ link to setup token messages [BE-13125] (#2995)
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-22 20:30:49 +03:00
Chaim Lev-Ari 872d1e03f6 feat(gitops): add "create new source" button to GitSourceSelector [BE-13054] (#2960)
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-22 17:19:53 +03:00
Chaim Lev-Ari a5cacd712d refactor(gitops): remove manual credential entry from git form [BE-13047] (#2951)
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-22 15:42:09 +03:00
Phil Calder f596c862b3 fix(websocket): enforce environment authorization on kubernetes-shell [BE-13027] (#2774)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
2026-06-22 15:09:41 +12:00
bernard-portainer 5395dee4c6 feat(gpu-stats): add gpu stats to environments [C9S-200] (#2735) 2026-06-22 09:21:43 +12:00
Josiah Clumont 217fe870ef fix(git): use ListContext instead of List when fetching remote refs [C9S-263] (#2939) 2026-06-22 08:30:20 +12:00
andres-portainer 26334e9088 feat(ssrf): add missing transport wrappings and more checks BE-13021 (#2968) 2026-06-19 20:26:03 -03:00
RHCowan cc45af2873 fix(lint): enforce consistent golangci-lint version across CI and pre-commit [PLA-777] (#2966) 2026-06-19 11:45:12 +12:00
RHCowan 37bd8c06b5 fix(security): gate docker dashboard and edge async command routes [R8S-1057] (#2953) 2026-06-19 11:08:01 +12:00
andres-portainer c821a1c59f fix(git): avoid cloning to memory and bypassing symlinking restriction BE-13115 (#2961) 2026-06-18 16:21:09 -03:00
Dakota Walsh f5d0b3d849 feat(kubernetes): Gateway api client included in kubeclient [C9S-244] (#2884) 2026-06-18 14:37:42 +12:00
nickl-portainer 0dfd27f08c fix(pnpm): pnpm format command failing [R8S-1071] (#2932) 2026-06-18 13:27:01 +12:00
nickl-portainer 0dfa0266c7 fix(webpack): update shell-quote [R8S-1074] (#2934) 2026-06-17 10:50:48 +12:00
nickl-portainer 9b807ca314 fix(axios): update axios [R8S-1075] (#2935) 2026-06-17 10:50:34 +12:00
nickl-portainer de5d84ade4 fix(kubernetes): handling undefined responseStatus [R8S-1072] (#2933) 2026-06-17 09:32:59 +12:00
Chaim Lev-Ari 4d539a691d feat(custom-templates): reuse existing git sources in create/update [BE-13053] (#2925)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:45:35 +03:00
Chaim Lev-Ari ee8e73d7f9 feat(edge/stacks): use source ID for edge stack git creation [BE-13044] (#2926)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 17:33:19 +03:00
Chaim Lev-Ari 32c6bedb98 feat(stacks): use source for kubernetes manifest git stacks [BE-13045] (#2915)
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-16 14:35:16 +03:00
Ali cd9bb18ba1 feat(policies): reuse filter status component, give consistent styles [c9s-210] (#2723) 2026-06-16 15:58:33 +12:00
nickl-portainer f365035563 fix(git): update lint-staged to v17 [R8S-1071] (#2907) 2026-06-16 15:14:57 +12:00
Chaim Lev-Ari d9673e33ec feat(helm): reuse existing git sources in Kubernetes Helm-from-git install [BE-13046] (#2900)
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-15 22:01:31 +03:00
Chaim Lev-Ari 491df61fbf chore(hey-api): disable api validator [BE-13102] (#2918) 2026-06-15 21:35:19 +03:00
Chaim Lev-Ari ca1d9dc6a2 fix(edge/stacks): load envs by id [BE-13097] (#2917) 2026-06-15 21:23:49 +03:00
andres-portainer 16b5554f66 fix(customtemplates): add resource controls BE-13019 (#2897) 2026-06-15 14:59:07 -03:00
Chaim Lev-Ari fcdd6b4510 feat(stacks): use source id to create git stacks [BE-13043] (#2870)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 18:49:26 +03:00
Chaim Lev-Ari 04048c3818 fix(api): update environment status field to be optional [BE-13070] (#2847) 2026-06-15 13:36:12 +03:00
Chaim Lev-Ari 1afbc621a4 fix(editor): restore yaml syntax highlighting in web editor [BE-13073] (#2848)
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-15 10:50:02 +03:00
Devon Steenberg ef807950f1 fix(compose-unpacker): port swarm commands to use libstack [BE-12915] (#2890) 2026-06-15 11:43:29 +12:00
Phil Calder d37f3aa504 chore(server-ce): update Code of Conduct contact to contribute@portainer.io (#2889)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:44:41 +12:00
Oscar Zhou 39b3eb3d64 fix(registry): standard user with access permission cannot browse and delete private images [BE-13072] (#2877) 2026-06-13 19:52:03 +12:00
Devon Steenberg 8b21dfc318 feat(ssrf): add ssrf allow list to settings [BE-13021] (#2858) 2026-06-12 15:16:06 +12:00
Steven Kang f87fec6d61 fix(omni): prevent partial cluster creation on Talos/Kubernetes mismatch [R8S-1058] (#2849)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 11:50:16 +12:00
Ali 391eb22d98 fix(ui): ui consistency and bug fixes [r8s-1061] (#2880) 2026-06-12 11:49:45 +12:00
andres-portainer 0da42c01b6 feat(gitcredential): remove GitCredential BE-12919 (#2838) 2026-06-11 18:53:24 -03:00
Cara Ryan f3f0ca8e21 fix(rbac): Filter get namespace by allowed namespace list [SEC-61] (#2743) 2026-06-11 15:51:32 +12:00
RHCowan 96dc79e253 feat(alerting): add Kubernetes API server high request latency alert [R8S-1049] (#2765)
Co-authored-by: Steven Kang <skan070@gmail.com>
2026-06-11 15:44:33 +12:00
Xing ac3416c5a2 feat(policies): define ObservabilityK8s policy type with deploy-and-connect and connect-only modes [C9S-121] (#2706)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 15:09:33 +12:00
Cara Ryan ade5b2a3db feat(rbac): Add toggle for additive kubernetes RBAC policy [C9S-177] (#2814) 2026-06-11 13:44:43 +12:00
Steven Kang 1cd6017df6 fix(api): add endpoint authorization check to /api/kubernetes/{id}/* route - develop [R8S-1056] (#2829) 2026-06-11 09:49:50 +12:00
Oscar Zhou 06caea7b16 fix(security): bump golang to 1.26.4 [DEV-91] (#2866) 2026-06-11 09:31:35 +12:00
Oscar Zhou 114779d3af fix(security): bump go-git/v5 to 5.19.1 [DEV-92] (#2863) 2026-06-11 09:31:07 +12:00
nickl-portainer 96d694b66b fix(storybook): add row id for example data for datatable story [R8S-1062] (#2856) 2026-06-11 09:10:29 +12:00
andres-portainer babb4ffb37 fix(nolint): remove unnecessary nolint directives BE-13074 (#2852) 2026-06-10 15:35:08 -03:00
LP B 0c2f07988a feat(app/sources): source create view (#2680)
Co-authored-by: Chaim Lev-Ari <chaim.lev-ari@portainer.io>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:34:46 +03:00
Ali d7a1d34be7 feat(policies): docker cleanup policy [c9s-87] (#2681) 2026-06-10 16:17:23 +12:00
nickl-portainer 6a465637d4 feat(components): add new FilePicker component [R8S-1050] (#2754) 2026-06-10 10:34:14 +12:00
andres-portainer 154c19403a fix(chisel): release a lock earlier to avoid a deadlock and clean stale tunnels immediately BE-13050 (#2815) 2026-06-09 10:41:05 -03:00
bernard-portainer c9e1467244 fix(stats-items): ensure stats items have consistent widths [C92-215] (#2844) 2026-06-09 16:53:17 +12:00
andres-portainer 1765e41fd4 feat(ssrf): implement an SSRF protection mechanism BE-13021 (#2818) 2026-06-09 00:41:42 -03:00
Phil Calder d34ee82754 docs(contributing): fix reversed markdown link syntax for swag docs [DEV-89] (#2841)
Co-authored-by: ferreiraborgesaxel-design <ferreiraborgesaxel-design@users.noreply.github.com>
2026-06-09 14:25:50 +12:00
Oscar Zhou 5cdd0023d7 fix(registry): suppress ecr token pre-validation error with warning log [BE-13059] (#2827) 2026-06-09 10:58:50 +12:00
andres-portainer df7a4b5d6f feat(gitops): improve the data model BE-12919 (#2819) 2026-06-08 15:01:55 -03:00
Chaim Lev-Ari 63eb96859d fix(endpoints): parse endpoint on GET [BE-13049] (#2824) 2026-06-08 16:59:09 +03:00
Josiah Clumont e3e2a3b782 fix(environments): Environment Groups detail view environment breakdown regression [BE-13051] (#2828) 2026-06-08 16:03:32 +12:00
Steven Kang eeafa5e0a5 fix(security): bump containerd to 1.7.32 and containerd/v2 to 2.2.4 - develop [DEV-79] (#2812) 2026-06-08 11:09:34 +12:00
Chaim Lev-Ari 7e5e71ae67 chore(deps): bump patch/minor frontend dependencies [BE-13004] (#2808)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:45:58 +03:00
andres-portainer 8daf0bb2a9 feat(customtemplates): use Sources for CustomTemplates BE-12919 (#2759) 2026-06-05 01:51:18 -03:00
nickl-portainer a779c839b7 feat(kubernetes): set application list default to expanded [R8S-1040] (#2758) 2026-06-05 14:35:00 +12:00
Josiah Clumont 0da57f8747 fix(environment-groups): restore dark mode badge colors and fix empty list regression [C9S-231] (#2811) 2026-06-05 11:58:54 +12:00
Steven Kang d01d241af1 fix(security): bump golang.org/x/net to v0.55.0 - develop [DEV-77] (#2797) 2026-06-05 10:46:41 +12:00
Steven Kang dd08d09d14 fix(security): bump Go to 1.26.3 to remediate 8 stdlib CVEs - develop [DEV-78] (#2800) 2026-06-05 10:46:22 +12:00
Ali 0143393a8c chore(policies): use generic policy reconcile system so more than helm can be used [c9s-88] (#2613) 2026-06-05 07:47:25 +12:00
Chaim Lev-Ari d2b56efcb4 feat(security): require setup token for admin init and restore [BE-13029] (#2770) 2026-06-04 09:15:23 +03:00
Chaim Lev-Ari dab0cf48c6 docs(api): document CE type generation [BE-13031] (#2794)
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-04 09:13:45 +03:00
Hannah Cooper 916367dccb fix(api-docs): time.Duration bounds fix + linting fixes [C9S-223] (#2762) 2026-06-04 15:14:07 +12:00
Hannah Cooper 580a9fdfcf Add version '2.39.3' to bug report template (#2801) 2026-06-04 12:38:26 +12:00
Chaim Lev-Ari 2ba8b582e2 feat(api): use generated api client [BE-12901] (#2727) 2026-06-03 14:37:39 +03:00
Chaim Lev-Ari bc81eb7a22 feat(sources): allow user to edit source [BE-12956] (#2748) 2026-06-03 12:52:41 +03:00
Oscar Zhou a54fc041b0 fix(stacks): git polling failures caused by cancelled deployment context [BE-12980] (#2751) 2026-06-03 16:12:07 +12:00
Phil Calder 10a2b25527 chore(deps): bump vitest to 4.1.x to address CVE-2026-47429 [DEV-74] (#2779)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 15:00:21 +12:00
Steven Kang cf476953d6 fix(environment): fixed typo of kubesolo to KubeSolo (#2788) 2026-06-03 13:16:38 +12:00
Steven Kang b233453cf7 feat(kubernetes): display cached images per node [R8S-898] (#2068) 2026-06-03 10:40:14 +12:00
andres-portainer bc5136a197 fix(images): improve the image status indicator performance BE-13033 (#2781) 2026-06-02 19:25:51 -03:00
Xing e08ee08fd8 fix(policies): implement Helm release conflict detection for chart-based policies [C9S-204] (#2690) 2026-06-03 10:22:03 +12:00
Steven Kang eb5ee3bfdb fix(kubernetes): improve PVC deletion UX based on workload usage [R8S-1046] (#2766) 2026-06-03 09:43:07 +12:00
Steven Kang 86a84c3c6a fix(kubernetes): updated wrong tooltip for container restart feature-gate [R8S-1037] (#2721) 2026-06-03 09:26:04 +12:00
andres-portainer edb348c273 feat(useractivity): fix a goroutine leak BE-12969 (#2767) 2026-06-02 18:23:01 -03:00
Josiah Clumont ba91b41d36 fix(table): restore horizontal margins on table views [C9S-227] (#2785) 2026-06-03 08:14:30 +12:00
andres-portainer 99547044bc feat(boltdb): optimize encrypted connections BE-12995 (#2769) 2026-06-02 14:58:05 -03:00
andres-portainer 1fa756372e feat(gitops): general improvements BE-12919 (#2780) 2026-06-02 09:44:57 -03:00
Josiah Clumont 484af3c2c8 feat(environment group) detail view update v1 [c9s-206] (#2722)
Last system-test failure is also on dev
2026-06-02 16:59:18 +12:00
Devon Steenberg 742551e592 fix(registries): make gitlab proxy endpoint admin only [BE-13018] (#2764) 2026-06-02 15:45:57 +12:00
Steven Kang 50081cbdaa feat(environment): environment support for kubesolo [R8S-983] (#2648) 2026-06-02 09:21:08 +12:00
andres-portainer 61198a0c04 fix(otel): upgrade go.opentelemetry.io/otel to v1.43.0 to fix CVE-2026-39883 CVE-2026-39882 BE-12988 (#2713) 2026-06-01 09:46:41 -03:00
Chaim Lev-Ari 67590aa27d feat(api): auto generate typescript definition from api docs [BE-9222] (#2468) 2026-05-31 14:51:52 +03:00
Ali 6c059c41f9 chore: bump version to 2.43.0 (#2760) 2026-05-30 16:56:17 +12:00
andres-portainer f1db82934d fix(security): fix a short-circuit condition that can lead to improper access control BE-13020 (#2756) 2026-05-29 20:47:59 -03:00
Hannah Cooper 28dd6b767f fix(api-docs): API docs fixes / improvements [C9S-208] (#2717) 2026-05-29 11:33:06 +12:00
Josiah Clumont 98b1d7f585 feat(environment-groups): replace Datatable with SortableList and update list UI [R8S-827] (#2661) 2026-05-29 10:08:35 +12:00
nickl-portainer f7b8e3d84b fix(kubernetes): unify all application container actions as icon with tooltip hover [R8S-1034] (#2711) 2026-05-28 13:24:38 +12:00
bernard-portainer 4b4fa39670 fix(endpoint-summary): fix incorrect counts in CE [C9S-190] (#2744) 2026-05-28 10:38:06 +12:00
andres-portainer ab4626e7de feat(workflows): introduce Artifacts BE-12919 (#2740) 2026-05-26 16:17:32 -03:00
Xing 7164146626 fix(policies): add NoWait flag to PolicyChartBundle for externally sourced charts [C9S-207] (#2710) 2026-05-26 14:36:58 +12:00
andres-portainer 3b4f688223 feat(dataservices): reduce the allocations BE-12995 (#2733) 2026-05-25 22:57:38 -03:00
Devon Steenberg ee2706c5ee fix(swarm): service creation networks [BE-12996] (#2736) 2026-05-26 13:49:20 +12:00
Josiah Clumont 2d9fc5d8af feat(home): add environment card storybook stories with Talos permutations [C9S-216] (#2739) 2026-05-26 13:25:25 +12:00
andres-portainer 49c9a4fdd3 fix(crypto): upgrade golang.org/x/crypto to v0.52.0 to fix CVE-2026-39830 CVE-2026-39831 CVE-2026-39832 CVE-2026-39833 CVE-2026-39834 CVE-2026-42508 CVE-2026-46595 BE-12992 (#2728) 2026-05-25 19:08:14 -03:00
Chaim Lev-Ari bafdbc8313 fix(gitops): clear file path only for helm [BE-12990] (#2720) 2026-05-25 10:31:18 +03:00
bernard-portainer eca28fd4b5 fix(node-count) update docker node count for non-swarm [C9S-182] (#2702) 2026-05-25 14:19:17 +12:00
andres-portainer 3d09c70e13 feat(sources): add sources and workflows to the backend BE-12919 (#2666) 2026-05-20 20:42:10 -03:00
Hannah Cooper 4cd8c04691 Update bug report template to include 2.42.0 (#2709) 2026-05-21 10:52:09 +12:00
nickl-portainer f7764cd5cb fix(system-tests): Update changed data-cy attribute for k8s volumes page [R8S-1033] (#2703) 2026-05-21 08:30:19 +12:00
nickl-portainer afae689ea9 fix(kubernetes): PersistentVolumeClaims datatable system resource filter [R8S-1031] (#2701) 2026-05-20 12:08:53 +12:00
andres-portainer e2d7491bc9 fix(edge-stacks): fix webhook ID creation in the frontend BE-12983 (#2682) 2026-05-19 12:03:57 -03:00
RHCowan 4c55508f01 fix(alerting): remove kube-scheduler and kube-controller-manager alert rules [R8S-1030] (#2695)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 19:07:11 +12:00
Nick Wilkinson 064a4304cc chore: bump version to 2.42.0 (#2654)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:34:13 +12:00
Devon Steenberg 09c6222ecd fix(edge-environments): edge environment creation [BE-12984] (#2683) 2026-05-19 12:24:01 +12:00
Oscar Zhou cad197266d fix(ui): deployment failed progress indicator is missing [BE-12985] (#2684) 2026-05-19 12:22:06 +12:00
Steven Kang 5b9976433f feat(k8s): Refactor Volumes page (#2510)
Co-authored-by: Nicholas Loomans <nicholas.loomans@portainer.io>
Co-authored-by: Robbie Cowan <robert.cowan@portainer.io>
Co-authored-by: RHCowan <50324595+RHCowan@users.noreply.github.com>
2026-05-19 10:39:24 +12:00
RHCowan df48afff17 feat(alerting): add kube-scheduler and kube-controller-manager health alerts [R8S-992] (#2671) 2026-05-19 09:53:20 +12:00
Devon Steenberg e4e8cf4942 fix(docker): remove docker binary from ce/ee images [BE-12917] (#2674) 2026-05-19 09:37:42 +12:00
Oscar Zhou c89f34770f fix(gitops): incorrect workflow status for git-based helm edge stack [BE-12978] (#2678) 2026-05-19 09:07:35 +12:00
Chaim Lev-Ari ca5f695459 feat(gitops): introduce sources details view [BE-12911] (#2627)
Co-authored-by: andres-portainer <91705312+andres-portainer@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 18:01:36 -03:00
Devon Steenberg 10e0185c49 fix(libstack): swarm relative path env files [BE-12975] (#2662) 2026-05-19 07:48:58 +12:00
Steven Kang 8cdc2f49d8 feat(kube): backend handlers for pod delete, pod restart, and capabil… (#2491)
Co-authored-by: Nicholas Loomans <nicholas.loomans@portainer.io>
2026-05-18 19:59:01 +12:00
bernard-portainer 29db3df98d fix(url-state) sync state of list between URL and local storage [C9S-191] (#2647) 2026-05-18 16:51:49 +12:00
Devon Steenberg 52d9fbc9f2 fix(libstack): use compose service to pull images [BE-12951] (#2658) 2026-05-18 09:25:22 +12:00
Chaim Lev-Ari 7e80d88bce feat(ui): add theme selector to user menu [BE-12961] (#2625)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 13:50:51 +03:00
Oscar Zhou 6163008108 fix(auth): set Secure attribute on auth cookies based on HTTPS detection [BE-12938] (#2621) 2026-05-16 11:09:03 +12:00
andres-portainer 6945fa4496 fix(otel): upgrade go.opentelemetry.io/otel to v1.43.0 to fix CVE-2026-39883 CVE-2026-39882 BE-12967 (#2637) 2026-05-15 17:06:55 -03:00
andres-portainer 06ad0b2d78 fix(go-ntlmssp): upgrade github.com/Azure/go-ntlmssp to v0.1.1 to fix CVE-2026-32952 BE-12971 (#2651) 2026-05-15 10:42:18 -03:00
andres-portainer 2570a30a15 fix(prometheus): upgrade github.com/prometheus/prometheus to v0.311.3 to fix CVE-2026-40179 GHSA-fw8g-cg8f-9j28 CVE-2026-42151 CVE-2026-42151 BE-12972 (#2653) 2026-05-14 22:11:59 -03:00
RHCowan 93e5486db3 feat(alerting): add built-in alert for Kubernetes API server TLS certificate expiry [R8S-991] (#2559) 2026-05-15 12:11:39 +12:00
Oscar Zhou 49ef33d9f3 fix(stack): defer git metadata write until after deployment [BE-12946] (#2626) 2026-05-15 10:57:13 +12:00
andres-portainer ca8201b023 fix(in-toto-golang): upgrade github.com/in-toto/in-toto-golang to v0.11.0 to fix GHSA-pmwq-pjrm-6p5r BE-12968 (#2638) 2026-05-14 19:02:00 -03:00
andres-portainer 2cb94116a3 fix(net): upgrade golang.org/x/net to v0.54.0 to fix CVE-2026-27141 CVE-2026-33814 BE-12965 (#2631) 2026-05-14 15:46:43 -03:00
Hannah Cooper a81b66c6b0 feat(api-docs): Introduce API docs groupings [C9S-96] (#2656) 2026-05-14 15:09:22 +12:00
Devon Steenberg c9d24c3684 fix(libstack): replace filepath.Join with filesystem.JoinPaths [BE-11476] (#2655) 2026-05-14 13:57:29 +12:00
Oscar Zhou 8a22e05284 fix(stack): git stack edit validation and repo credential lookup [BE-12899] (#2594) 2026-05-14 12:27:20 +12:00
Devon Steenberg 3b0f1eca4b feat(swarm): port swarm to use libstack [BE-11476] (#2486) 2026-05-14 10:13:19 +12:00
bernard-portainer a66f114f24 fix(sidebar) override button padding to keep sidebar parent items in line [C9S-184] (#2641) 2026-05-14 08:39:06 +12:00
andres-portainer 2c00f4d40b fix(go-git): upgrade github.com/go-git/go-git/v5 to v5.19.0 to fix CVE-2026-34165 GHSA-3xc5-wrhm-f963 CVE-2026-33762 BE-12966 (#2634) 2026-05-13 13:10:19 -03:00
andres-portainer 2e88f7a245 fix(chisel): add another mechanism to ensure snapshot collection BE-12896 (#2628) 2026-05-13 10:50:58 -03:00
Chaim Lev-Ari dd68560ad0 chore(deps): upgrade prettier (#2592)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 16:39:58 +03:00
RHCowan d1b702ef37 feat(alerting): add etcd health metric and built-in alert rule [R8S-999] (#2538) 2026-05-13 18:56:09 +12:00
Oscar Zhou 7f3389d6f4 chore(version): bump develop version to 2.41.1 (#2646)
Co-authored-by: Nicholas Loomans <nicholas.loomans@portainer.io>
2026-05-13 16:23:35 +12:00
Chaim Lev-Ari d9a415f011 feat(gitops): introduce sources list view [BE-12902] (#2550) 2026-05-12 15:32:46 +03:00
Ali edff47fd41 feat(environments): offer edge connectivity test before adding edge environments [c9s-149] (#2527) 2026-05-12 16:25:39 +12:00
bernard-portainer b3a9386607 fix(edgeEnv) edge envs that haven't checked in can't be outdated [C9S-168] (#2608) 2026-05-12 15:14:58 +12:00
bernard-portainer 300a8abc97 fix(DockerDetails) replace missing icon on host panel [C9S-170] (#2612) 2026-05-12 10:41:42 +12:00
andres-portainer 2bb2b78e82 chore(csrf): remove gorilla/csrf BE-12948 (#2618) 2026-05-11 19:41:26 -03:00
andres-portainer 540c9ba6d5 fix(chisel): upgrade Chisel to v1.11.6 to avoid a panic because of a negative waitgroup counter BE-12743 (#2619) 2026-05-11 19:40:54 -03:00
Josiah Clumont 872b824dc6 feat(design-system): introduce ResourceDetailHeader [BE-12848] (#2536)
Ignored some flaky tests
2026-05-12 10:23:58 +12:00
Oscar Zhou 9ecd8d3efb fix(environment): reject TLS config for Edge Agent environment creation and update [BE-12700] (#2609) 2026-05-12 08:50:41 +12:00
andres-portainer 080d75acae chore(openamt): remove OpenAMT completely BE-12950 (#2616) 2026-05-11 15:48:39 -03:00
andres-portainer 62f4d47ee5 chore(internal): export endpoints and authorizations so they can be shared between CE and EE BE-12893 (#2464) 2026-05-11 10:44:09 -03:00
Chaim Lev-Ari c0ac6c56ac feat(ui): introduce design system primitives [DEV-52] (#2535) 2026-05-11 08:45:59 +03:00
Hannah Cooper 3e60c2306c Update bug report template to include 2.41.1 (#2611) 2026-05-11 16:34:54 +12:00
bernard-portainer 59614d31f2 fix(edgeEnvironments) update displayed edge agent URLs [C9S-167] (#2602)
* 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
2026-05-11 14:52:11 +12:00
Oscar Zhou a117e514e4 fix(stack): persist CreatedBy before deployment to prevent broken auto update [BE-12939] (#2588) 2026-05-11 12:54:04 +12:00
Josiah Clumont 8d098a2bb9 style(dropdown-menu): fix count badge alignment and uniform width [C9S-116] (#2605) 2026-05-11 12:51:18 +12:00
Josiah Clumont 899e4b6f67 refactor(dropdown-menu): update styling to align with designs [C9S-116] (#2596) 2026-05-11 10:25:15 +12:00
LP B dba86594e1 fix(app/kubernetes): kube edit app buttons (#2565) 2026-05-09 11:00:17 +02:00
Chaim Lev-Ari 8885038b7e refactor(settings/auth): migrate admin group section to react [BE-12592] (#2472) 2026-05-08 10:51:12 +03:00
bernard-portainer 76f525fd38 refactor(home): refactor Environment List to use SortableList component [C9S-131] (#2522)
- 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
2026-05-08 16:55:40 +12:00
Cara Ryan 3d741ad58d fix(users): Fix for users effective access viewer not including policies [C9S-109] (#2539) 2026-05-08 15:00:17 +12:00
RHCowan ff169ed356 feat(alerting): expand tiered rules into per-severity evaluators with state aggregation [R8S-1003] (#2586) 2026-05-08 14:50:59 +12:00
Hannah Cooper ed7f074380 Update bug report template to include 2.39.2 (#2587) 2026-05-07 16:20:36 +12:00
Ali 9eb6ebfe9b fix(wizard): ensure select renders on top of footer [c9s-169] (#2577) 2026-05-07 14:15:21 +12:00
Hannah Cooper 29cfde99ae Update bug report template to include 2.33.8 (#2583) 2026-05-07 13:11:08 +12:00
Oscar Zhou c3b0b9a2e0 fix(ecr): prevent deadlock on ECR token refresh during stack deployment [BE-12842] (#2564) 2026-05-07 08:34:19 +12:00
Devon Steenberg e7ec69708e fix(libstack): pull images sequentially and respect COMPOSE_PARALLEL_LIMIT [BE-12930] (#2556) 2026-05-06 15:16:41 +12:00
Ali ff9c10f641 feat(docker): show host disk usage in the UI [C9S-144] (#2517) 2026-05-05 22:40:16 +12:00
Ali 0eba817aab fix(environments): align Linux/Windows labels for edge agent and Docker API [c9s-157] (#2558) 2026-05-05 22:01:13 +12:00
Ali 6cb6f2e9b4 fix(change-confirmation): add git dry run and docker resize to the excluded urls [c9s-159] (#2562) 2026-05-05 18:00:03 +12:00
Devon Steenberg 6faa0939d8 fix(kubectl-shell): kubectl-shell-image flag [BE-12929] (#2542) 2026-05-05 13:50:40 +12:00
Josiah Clumont 68f93fb281 feature(storybook): Storybook usability upgrades [C9S-140] (#2482) 2026-05-05 09:25:09 +12:00
bernard-portainer 1ea8c1cb4e feat(homeView) add age sort option as default [C9S-150] (#2546) 2026-05-05 08:17:06 +12:00
andres-portainer d749d05359 fix(datastore): change EnforceEdgeID default to true BE-12925 (#2547) 2026-05-04 15:29:58 -03:00
Chaim Lev-Ari b18b4418c8 fix(kube/app): get stack only for managed stacks [BE-12927] (#2516) 2026-05-03 09:15:20 +03:00
Ali a3935ce445 feat(secrets): allow linking secrets to service accounts as imagepullsecrets [c9s-49] (#2488) 2026-05-01 22:54:33 +12:00
Oscar Zhou 92bbfb8fa3 chore(remote): add log for resolved unpacker image [BE-12884] (#2459) 2026-05-01 17:03:40 +12:00
RHCowan 6c097dcf51 feat(alerting): propagate edge annotations for meaningful Kubernetes summaries [R8S-993] (#2514) 2026-05-01 08:13:07 +12:00
LP B 0688e6bbdd fix(api/workflows): kubernetes UAC (#2508)
Co-authored-by: andres-portainer <91705312+andres-portainer@users.noreply.github.com>
2026-04-30 10:54:38 -03:00
Hannah Cooper c49e682df4 Update bug report template to include 2.41.0 (#2511) 2026-04-30 13:53:32 +12:00
RHCowan 538d57fe19 fix(agent): correct Podman container engine header in sync edge client [BE-12887] (#2498) 2026-04-30 08:47:44 +12:00
LP B 3053990411 fix(api/workflows): move filterK8SStacks outside of transaction (#2505) 2026-04-29 17:56:57 +02:00
RHCowan 49011d4d03 feat(alerting): Add built-in alert for Kubernetes nodes in NotReady state [R8S-990] (#2485) 2026-04-29 15:44:09 +12:00
Cara Ryan 6a30138b3c feat(home): environment home page ui improvements to highlight groups [C9S-23] (#2487)
Signed-off-by: Bernard Setz <bernard.setz@portainer.io>
Co-authored-by: bernard-portainer <bernard.setz@portainer.io>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
Co-authored-by: andres-portainer <91705312+andres-portainer@users.noreply.github.com>
Co-authored-by: Josiah Clumont <josiah.clumont@portainer.io>
Co-authored-by: Dakota Walsh <101994734+dakota-portainer@users.noreply.github.com>
2026-04-29 14:59:39 +12:00
Xing 6aac4f38e4 fix(test): isolate registry config in OCI client tests to fix env-dependent failures [C9S-119] (#2401) 2026-04-29 10:18:52 +12:00
LP B bc6c5da2dc feat(api/gitops): list and filter kubernetes git workflows (#2474) 2026-04-27 15:24:39 -03:00
andres-portainer 1c55555ad0 chore(tests): increase code coverage BE-12877 (#2431) 2026-04-27 12:32:44 -03:00
Chaim Lev-Ari 3f8fcb3914 fix(ui/sortable-list): remove 1 as page size option [BE-12900] (#2469) 2026-04-27 17:01:12 +03:00
andres-portainer 24a879add6 fix(docker): enforce resource controls on /containers/{id}/attach/ws BE-12891 (#2448) 2026-04-27 09:17:28 -03:00
Chaim Lev-Ari ae1b6b8a71 feat(gitops): show live git validity status in workflow overview [BE-12885] (#2447)
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 13:11:55 +03:00
Chaim Lev-Ari da36002d37 fix(gitops): align list component with current design [BE-12888] (#2443)
Co-authored-by: Bernard Setz <bernard.setz@portainer.io>
2026-04-26 16:48:45 +03:00
Chaim Lev-Ari a611e12b5c fix(kube/stacks): allow empty stack name [BE-12889] (#2444) 2026-04-26 12:14:45 +03:00
andres-portainer d4114c510d fix(factory): clear the output raw path to avoid forwarding a different path than the validated one BE-12880 (#2442) 2026-04-24 09:46:46 -03:00
nickl-portainer 5eaf145eda chore(react-query): update all deprecated withError to use withGlobalError [R8S-968] (#2461)
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2026-04-24 16:01:59 +12:00
Josiah Clumont 2c2ec6f6e6 feat(recommendations): completeness recommendations [C9S-18] (#2262) 2026-04-24 10:46:47 +12:00
Ali 39ac164890 fix(ui): use uuidv4 instead of cryptorandomuuid to support non-secure browsers [c9s-133] (#2432) 2026-04-24 08:41:51 +12:00
andres-portainer 8140c834ca fix(docker): add exec restrictions BE-12878 (#2429) 2026-04-23 15:29:03 -03:00
Ali 742523de17 feat(docker): add docker builder prune as option [C9S-128] (#2423) 2026-04-23 09:06:47 +12:00
Chaim Lev-Ari dd1c1071ce feat(gitops): introduce workflows view [BE-12807] (#2391) 2026-04-22 10:17:37 -03:00
nickl-portainer b9713f7e9e chore(version): bump version to 2.41.0 (#2421) 2026-04-22 17:11:30 +12:00
Steven Kang 9c0a13a828 fix(stacks): fix Swarm stack migration to Kubernetes hanging and empt… (#2417) 2026-04-22 13:38:06 +12:00
Robbie Cowan dc56aae7b8 fix(rebase): run go mod tidy to prepare for merge into develop 2026-04-22 10:59:12 +12:00
RHCowan ba11fe920b fix(alerting) Use prometheus scrape manager [R8S-940] (#2198) 2026-04-22 10:06:45 +12:00
RHCowan 7f2da7811c feat/r8s 900/r8s 929/ee alerting foundations (#2167) 2026-04-22 10:06:44 +12:00
RHCowan 62cf2e42d5 feat(alerting): add shared CE Prometheus foundation and alert-state contracts [R8S-927] (#2129) 2026-04-22 10:06:44 +12:00
RHCowan 64745e70d0 feat(alerting): wire K8s metrics collection and alert push transport [R8S-901] (#1993) 2026-04-22 10:06:43 +12:00
RHCowan f49cd6e932 feat(alerting): distribute enabled alert rules to edge agents via poll response [R8S-903] (#2007) 2026-04-22 10:06:43 +12:00
Steven Kang ac1e333dde feat(alerts): removal of snapshot reliance [R8S-902] (#1994) 2026-04-22 10:06:43 +12:00
RHCowan b5bc5f65ad feat(alerting): Add edge alert ingestion endpoint skeleton [R8S-895] (#1991) 2026-04-22 10:06:40 +12:00
Oscar Zhou 463d539194 refactor(stack): change stack update flow to async model [BE-12741] (#2306) 2026-04-22 10:05:17 +12:00
andres-portainer 7e544ee449 fix(docker): add more bind mount restriction checks BE-12771 (#2409) 2026-04-21 17:56:17 -03:00
Ali 1f320c976f chore(docs): update docs, skills and Claude.md to avoid repeating review comments [r8s-971] (#2400) 2026-04-22 08:21:31 +12:00
andres-portainer 825a7669a6 fix(csrf): use the proper format for trusted origins BE-12810 (#2398) 2026-04-21 11:52:58 -03:00
andres-portainer f6a72b089c fix(kubernetes): enforce admin permissions in /system BE-12862 (#2396) 2026-04-21 09:43:06 -03:00
LP B 73ea33f36c fix(app/container): handle no healthcheck logs output (#2387) 2026-04-21 13:46:36 +02:00
Chaim Lev-Ari 744a31a354 feat(stacks): allow edit of kube git stacks [BE-12671] (#2194) 2026-04-21 11:05:37 +03:00
Chaim Lev-Ari 42c7f10e79 feat(ui): introduce SortableList [BE-12806] (#2367)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:38:38 +03:00
Chaim Lev-Ari 3e57bc5aa0 fix(containers): show volume label when selected [BE-12819] (#2369) 2026-04-21 09:48:26 +03:00
Chaim Lev-Ari 4880e61e0f fix(containers): show ports in wrapping rows [BE-12709] (#2370) 2026-04-21 09:47:52 +03:00
Steven Kang 79a93cfd01 fix(security): upgrade Docker binary from v29.3.0 to v29.4.1 (#2356) 2026-04-21 10:55:14 +12:00
Steven Kang 0af7bc2004 fix(security): bump github.com/moby/spdystream to 0.5.1 (#2355) 2026-04-21 10:19:53 +12:00
Steven Kang ada103e910 fix(security): bump helm.sh/helm/v4 to 4.1.4 (#2354) 2026-04-21 10:06:22 +12:00
Steven Kang a0e964c27d fix(security): bump Go toolchain to 1.26.2 (#2352) 2026-04-21 10:05:12 +12:00
Cara Ryan a2624b7467 fix(helm): Resolve content cache must be set error when using helm dependencies [C9S-115] (#2376) 2026-04-21 09:51:02 +12:00
andres-portainer 9abd7eaeea fix(endpoints): enforce admin permissions when updating endpoint relations BE-12861 (#2394)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2026-04-20 14:19:18 -03:00
LP B 3502ed0293 fix(api): deny plugin related changes to regular users (#2284) 2026-04-20 17:07:28 +02:00
Chaim Lev-Ari 3101738adc refactor(git): ee service extends ce service [BE-12825] (#2280) 2026-04-19 10:44:23 +03:00
andres-portainer 0b390dd274 fix(tests): do all the path handling using filesystem.JoinPaths() BE-12828 (#2336) 2026-04-18 01:54:14 -03:00
andres-portainer 9d3f7b710d fix(tests): enable more parallel tests BE-12801 (#2316) 2026-04-18 01:53:10 -03:00
andres-portainer 3a8ed40943 fix(docker): enforce bind mount restrictions for Mounts field BE-12770 (#2363) 2026-04-18 01:28:24 -03:00
andres-portainer aef1d982c2 fix(docker): add missing restrictions for Swarm BE-12772 (#2226) 2026-04-18 01:27:14 -03:00
andres-portainer b287961758 fix(git): forbid the usage of symlinks BE-12768 (#2365) 2026-04-18 01:26:15 -03:00
andres-portainer 8d5675a7d7 fix(csrf): add CSRF protection from the stdlib BE-12810 (#2250) 2026-04-17 10:51:04 -03:00
Ali 544e302fe1 feat(docker): support docker image prune [c9s-91] (#2314) 2026-04-17 14:22:36 +12:00
andres-portainer b417b04a69 fix(websocket): add proper locking and avoid goroutine leakage BE-12835 (#2303) 2026-04-16 14:08:51 -03:00
Xing 6ecb99898d fix(k8s): yaml malformed document [dev-7] (#1976)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-16 13:38:34 +12:00
Xing 236c5e2415 chore(dev): use separate data dirs for CE and EE [C9S-102] (#2328) 2026-04-16 10:17:59 +12:00
Josiah Clumont 2d2b68e867 fix(tests): Fixed the breadcrumb failing tests due to update's on how the first item is rendered [C9S-98] (#2338) 2026-04-16 09:29:51 +12:00
Chaim Lev-Ari f841ea527a fix(terminal): close terminal on ctrl+d [BE-12823] (#2271) 2026-04-15 17:08:15 +12:00
Josiah Clumont 169548cc4c (feature) fix header padding [C9S-98] (#2315) 2026-04-15 14:24:15 +12:00
Chaim Lev-Ari 8f93a1a8cf chore(deps): upgrade eslint [BE-12837] (#2313) 2026-04-15 05:12:52 +03:00
nickl-portainer 8e85fa9f83 fix(policies): datatable new row behaviours [C9S-64] (#2130) 2026-04-15 14:11:11 +12:00
Chaim Lev-Ari 181a83a889 chore(deps): upgrade ts to v6 [BE-12820] (#2268) 2026-04-15 03:55:34 +03:00
andres-portainer b78504aa04 fix(websocket): remove the JWT token query string parameter BE-12833 (#2301) 2026-04-14 19:41:08 -03:00
Chaim Lev-Ari a21ec9299b feat(stacks): add redeploy git button [BE-12783] (#2278) 2026-04-14 17:49:56 +03:00
Chaim Lev-Ari 7708ace1d8 feat(gitops): add api for workflows [BE-12805] (#2273)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:25:37 +03:00
Steven Kang 218b5d5900 feat(kubernetes): add edit (yaml) and describe button [R8S-921] (#2079) 2026-04-14 14:01:41 +12:00
nickl-portainer 2983b94cf7 fix(css): add restriction on modal height [R8S-947] (#2305) 2026-04-14 13:31:21 +12:00
Josiah Clumont 25e082ea63 feat(design-system): add HeaderLayout component [C9S-95] (#2291) 2026-04-14 11:44:30 +12:00
Josiah Clumont 3313376fac feat(design-system): add StatusSummaryBar and FilterBar components [DEV-41] (#2288)
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
2026-04-14 08:28:44 +12:00
andres-portainer a96c6efcbd chore(code): add rule to mitigate the introduction of path traversal vulnerabilities BE-12828 (#2299) 2026-04-13 11:45:14 -03:00
andres-portainer 4dd6b88cdf chore(tests): simplify the code BE-12818 (#2285) 2026-04-13 11:32:07 -03:00
Ali 0d836f1e30 chore(tailwind): support tailwind class ordering in clsx functions [r8s-949] (#2292) 2026-04-13 17:13:40 +12:00
Ali ab3e0956a4 chore(tailwind): format tailwind class order [r8s-949] (#2289) 2026-04-13 16:01:10 +12:00
Josiah Clumont 615fceb4a5 feat(navigation-bar): Update the navigation bar [C9S-90] (#2263) 2026-04-13 13:54:54 +12:00
andres-portainer 68453ebcb8 chore(stackbuilders): simplify the code BE-12800 (#2230) 2026-04-09 17:45:24 -03:00
Chaim Lev-Ari 635c49d04d fix(stacks): save git credentials if required [BE-12773] (#2237) 2026-04-09 09:25:31 +03:00
RHCowan 886af7d55a feat(ci): optimise build pipeline with frontend caching and scoped validation (#1995) 2026-04-09 15:57:04 +12:00
andres-portainer 8f563220df chore(code): clean-up the code BE-12818 (#2260) 2026-04-08 20:04:27 -03:00
andres-portainer def415b6f3 chore(code): consolidate code between CE and EE BE-12818 (#2261) 2026-04-08 19:36:54 -03:00
andres-portainer c21d043183 fix(code): remove nil-pointer dereference errors BE-12817 (#2259) 2026-04-08 19:36:06 -03:00
Ali 769ea73cec feat(registries): add registry access notice to app create/edit views [c9s-39] (#2190) 2026-04-09 09:04:45 +12:00
andres-portainer d140726c46 fix(kube): use transactional code for initial detections BE-545 (#2228) 2026-04-08 16:11:23 -03:00
andres-portainer 1f42559279 fix(endpoints): fix a use-after-close data-race BE-12604 (#2214) 2026-04-08 13:04:13 -03:00
andres-portainer b6d6c7fd2a fix(containers): avoid using the request context BE-12870 (#2216) 2026-04-08 12:39:52 -03:00
andres-portainer 1298fc629e chore(tests): allow for the tests to run in parallel BE-12801 (#2231) 2026-04-07 17:38:22 -03:00
andres-portainer 30ca5e298c chore(tests): avoid initializing the DB data when not needed BE-12801 (#2233) 2026-04-07 15:49:57 -03:00
andres-portainer 2240d0516c chore(tests): speed up the time by using synctest BE-12801 (#2234) 2026-04-07 15:49:30 -03:00
Chaim Lev-Ari b87095dc7a fix(terminal): allow tui apps [BE-12674] (#2024) 2026-04-07 10:45:26 +03:00
Oscar Zhou d30503a40c feat(helm/edge): support helm repository for edge stack [BE-12480] (#2180) 2026-04-07 18:40:07 +12:00
Chaim Lev-Ari 7fbda4fe54 refactor(settings/auth): migrate group builder to react [BE-12587] (#2102) 2026-04-07 07:55:24 +03:00
Ali 24a2b29f70 fix(ui): make banner border wrap screen height [c9s-63] (#2224) 2026-04-07 16:05:52 +12:00
Oscar Zhou ca9e197d12 fix(stack): add stack creation success toast [BE-12813] (#2245) 2026-04-07 14:26:40 +12:00
Cara Ryan 51f86eb4c6 feat(api): Claude skill to validate and write api annotations and example subset run to fix helm endpoints (#2246) 2026-04-07 13:56:17 +12:00
Oscar Zhou 5aba61cc49 refactor(stack): create stack and deploy stack in async flow CE [BE-12650] (#2238) 2026-04-07 09:18:54 +12:00
andres-portainer fcf9888677 feat(git): consolidate the mocked Git service to simplify the tests BE-12799 (#544) 2026-04-06 14:24:19 -03:00
andres-portainer 9c9caeb57a chore(code): unnest some code BE-12798 (#2229) 2026-04-06 14:23:33 -03:00
Chaim Lev-Ari a58ad25533 fix(stacks): stack.env can be null [BE-12736] (#2239) 2026-04-06 16:55:06 +03:00
Oscar Zhou 11f5150190 refactor(stack): create stack and deploy stack in async flow [BE-12650] (#2048) 2026-04-05 21:18:29 +12:00
Chaim Lev-Ari 1c72dfe5ad fix(gitops): fix various gitops errors [BE-12787] (#2200) 2026-04-05 09:18:33 +03:00
Oscar Zhou b49830db8f chore: add Makefile command to host swagger ui locally [BE-12791] (#2223) 2026-04-03 12:22:52 +13:00
andres-portainer e035c490dc fix(docker): fix a data race in serviceRestore BE-12790 (#2219) 2026-04-02 11:04:02 -03:00
Phil Calder 0d8544b3ee fix(CronJobs): remove non-functional Items per page (#2166)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 10:37:09 +13:00
andres-portainer 50056bef70 fix(context): clean up context usage BE-12766 (#2164) 2026-04-01 18:02:48 -03:00
Ali e68e14787b chore(logs): add log view smoke tests [PLA-681] (#2206) 2026-04-01 16:39:24 +13:00
Chaim Lev-Ari 0ab2c5cf98 feat(react-query): suppress error when meta.error is falsy [BE-12776] (#2199) 2026-03-31 16:53:00 +03:00
Oscar Zhou 1ca56fd027 fix(git): failed git repo url returns html error page [BE-12757] (#2191) 2026-03-31 10:31:12 +13:00
Oscar Zhou c4cc9cf1c7 fix(ui): display invisible special characters in web editor [BE-12777] (#2176) 2026-03-31 10:15:47 +13:00
Chaim Lev-Ari b53684a89e chore(deps): remove unused client dependencies [BE-12749] (#2172) 2026-03-30 14:54:50 +03:00
Oscar Zhou d93508a272 fix(edge/helm): support custom namespace [BE-12678] (#2171) 2026-03-27 10:02:48 +13:00
Chaim Lev-Ari ad9b9cf5b1 fix(stacks): fix(stacks): prevent git file load before clone [BE-12764] (#2162) 2026-03-26 15:10:14 +02:00
Chaim Lev-Ari ac5fb731bc feat(motd): cache motd in server [BE-12711] (#2159) 2026-03-26 15:01:48 +02:00
Chaim Lev-Ari d36799020b refactor: remove Kubernetes ts import [BE-12730] (#2157) 2026-03-26 14:09:13 +02:00
Chaim Lev-Ari 7aa08053e0 refactor(axios): remove the need for parseAxiosError [BE-12703] (#2158) 2026-03-26 13:50:14 +02:00
andres-portainer 61b9bc248f fix(schedule): abstract simple loops with RunOnInterval() BE-12765 (#2163) 2026-03-26 07:47:54 -03:00
Chaim Lev-Ari e33f9573e8 refactor: remove Portainer ts import [BE-12732] (#2156) 2026-03-26 12:18:15 +02:00
Chaim Lev-Ari 186624d267 refactor: remove Docker ts import [BE-12731] (#2155) 2026-03-26 09:44:26 +02:00
Hannah Cooper 7c9d4cd7d8 Update bug report template to include 2.40.0 (#2168) 2026-03-26 13:52:40 +13:00
Phil Calder 541b8df735 fix(kubernetes): filter CronJob executions by namespace [DEV-19] (#2144)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 13:21:09 +13:00
andres-portainer 2900bfa1d6 chore(code): remove unused code BE-12744 (#2112) 2026-03-25 10:19:17 -03:00
andres-portainer 5ea0f682a6 fix(apikey): fix the return value of InvalidateUserKeyCache() BE-12755 (#2124) 2026-03-25 09:04:54 -03:00
andres-portainer 019cbfd972 fix(websocket): avoid leaking goroutines BE-12754 (#2123) 2026-03-25 09:04:23 -03:00
RHCowan 792c95b8bb chore: bump version to 2.40.0 and set API version support to STS (#2160) 2026-03-25 19:59:41 +13:00
Robbie Cowan 4d1f432266 Revert "chore: bump version to 2.40.0"
This reverts commit 11af66a4fce8ab98253afb2e637a946a8939747f.
2026-03-25 18:47:01 +13:00
Robbie Cowan 1e00a58b57 chore: bump version to 2.40.0 2026-03-25 18:15:54 +13:00
Phil Calder 0a26ac0279 fix(security): bump google.golang.org/grpc to v1.79.3 [DEV-22] (#2151)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 17:26:25 +13:00
Ali 63b0802ad7 fix(UI): revert global css changes targetting firefox banner fix + cleanup [C9S 71] (#2152) 2026-03-25 15:51:36 +13:00
Ali a5062dbe35 fix(ui): handle verticle alignment selectors for production build [c9s-71] (#2147) 2026-03-25 13:37:20 +13:00
Ali f84e657707 feat(registries): support service accounts with registry secrets for cluster level [C9S 37] (#2120) 2026-03-25 11:00:13 +13:00
Chaim Lev-Ari cd8a42edaf fix(settings/auth): allow dashes in ldap dn (#2141) 2026-03-24 16:00:59 -03:00
Chaim Lev-Ari e37f8a5eb9 fix(stacks): save entry point [BE-12670] (#2132) 2026-03-24 16:38:31 +02:00
Ali 7fc8d3f2b1 fix(ui): ensure when two+ angular components are in a #view, they don't all stretch to fill space [c9s-71] (#2133) 2026-03-24 23:03:24 +13:00
Ali 6f2d1a2b49 fix(ui): ensure centered pages stay centered [c9s-71] (#2131) 2026-03-24 20:02:01 +13:00
Chaim Lev-Ari d5a3e46791 feat(stacks): update git url and config path [BE-12670] (#2099)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Devon Steenberg <devon.steenberg@portainer.io>
2026-03-24 15:01:46 +13:00
andres-portainer 1f4724c537 fix(environments): fix the TLS certificate uploading BE-12719 (#2101)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2026-03-24 14:01:49 +13:00
Ali e6f8736cae fix(policies): fix page styles for firefox banner [C9S-63] (#2128) 2026-03-24 13:24:43 +13:00
Oscar Zhou 54fbe54953 fix(edge/agent): deleting k8s edge agent disconnect environment [BE-12723] (#2109) 2026-03-24 08:33:06 +13:00
Ali 3e92a2881a chore(ci): improve regular PR CI reporting for playwright [r8s-925] (#2114) 2026-03-23 22:01:47 +13:00
Devon Steenberg bd9c3c1593 feat(gitops): tidy up git auth [BE-12666] (#2026) 2026-03-23 13:53:04 +13:00
Ali f199d0882f feat(serviceaccount): service account details view [C9S-36] (#2082) 2026-03-23 09:22:56 +13:00
Chaim Lev-Ari a2fee4fc4c fix(stacks): pass prune option through the deploy pipeline [BE-12738] (#2098)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 12:37:40 +02:00
Chaim Lev-Ari 5670216d7e feat(stacks): show planned deployment info [BE-12737] (#2097)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 09:34:45 +02:00
Ali 7569266e46 fix(rbac): update namespace rbac test id to match EE [C9S-61] (#2111) 2026-03-22 20:26:19 +13:00
Hannah Cooper 23f6cb8bae Update bug report template to include 2.39.1 (#2108) 2026-03-20 13:53:32 +13:00
Ali 931c2b3ddb feat(policies): Introduce change confirmation policy template, and clear default values for custom policy [C9S-52] (#2087) 2026-03-20 09:55:41 +13:00
Oscar Zhou 8b3edb4e28 fix(docker): show correct container state for restarting and removing [BE-12707] (#2088) 2026-03-20 08:49:59 +13:00
Chaim Lev-Ari a0b03d36bd refactor(settings/auth): migrate ldap-dn-builder to react [BE-12586] (#2025) 2026-03-19 17:56:15 +02:00
andres-portainer df1cd0af2e fix(endpointrelation): add locking to RegisterUpdateStackFunction() BE-12608 (#2096) 2026-03-19 12:38:36 -03:00
Chaim Lev-Ari 5df7146828 fix(stacks): disabled edit button while submit [BE-12681] (#2094) 2026-03-19 14:56:08 +02:00
Chaim Lev-Ari bec5d829f1 refactor(settings/auth): migrate ldap security settings to react [BE-12588] (#2029) 2026-03-19 12:13:05 +02:00
Chaim Lev-Ari ee0e9f6ff8 feat(settings/auth): migrate ldap test login to react [BE-12589] (#2036) 2026-03-19 11:23:58 +02:00
Ali 9c7eef3144 feat(secrets): update secrets to show related registry [c9s-35] (#2065) 2026-03-19 15:18:35 +13:00
Chaim Lev-Ari 3110fe4e74 refactor(axios): move axios into react folder [BE-12728] (#2084) 2026-03-19 10:21:25 +13:00
Chaim Lev-Ari 565ac2c15a fix(ts): add back feature imports [BE-12733] (#2083) 2026-03-18 13:46:52 +02:00
nickl-portainer 9cba6c7475 chore(tsconfig): remove obsolete aliases (#2078) 2026-03-18 16:01:17 +13:00
nickl-portainer 07b3bdb62d chore(axios): move out axios utils into helpers and utils folder [R8S-871] (#2077) 2026-03-18 13:59:51 +13:00
nickl-portainer ac7ff0fff4 chore(axios): move axios into own folder CE [R8S-871] (#2075) 2026-03-18 13:24:44 +13:00
Chaim Lev-Ari 0d20839d5f fix(stacks): validate stacks with env vars [BE-12689] (#2050)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2026-03-17 18:53:13 -03:00
Oscar Zhou 13fb3118ee fix(edge/docker): add bind mount volume label for restarting the specific service [BE-12575] (#1821) 2026-03-18 08:44:50 +13:00
andres-portainer 364027054c fix(websocket): simplify the logout disconnection logic BE-12605 (#1868) 2026-03-17 13:21:28 -03:00
andres-portainer 31a861394f fix(otel): upgrade to v1.42.0 BE-12724 (#2072) 2026-03-17 13:02:54 -03:00
LP B 0fccc0357e fix(api/uac): panic on external stacks UAC eval (#2073) 2026-03-17 16:00:35 +01:00
andres-portainer 5550a71dea fix(docker): upgrade Docker binary to v29.3.0 to mitigate CVE-2025-68121 BE-12720 (#2064) 2026-03-16 19:02:56 -03:00
Steven Kang 0ec6f638a1 feat(kompose): support docker to kubernetes migration - [R8S-723] (#1977) 2026-03-17 09:37:18 +13:00
andres-portainer 748b4bcf19 chore(go): upgrade to v1.26.1 BE-12630 (#1855) 2026-03-16 15:52:36 -03:00
Ali 33cc29fa3c fix(sidebar): set helper anchor color to match the other items [C9S-47] (#2058) 2026-03-16 15:50:59 +13:00
Chaim Lev-Ari 5e2eb667b4 fix(kube/app): enable edit button for regular apps [BE-12690] (#2039) 2026-03-15 11:22:09 +02:00
Ali 1f9c9b082f feat(policies): banner and confirmation on change policy [C9S-20] (#1988) 2026-03-13 14:11:53 +13:00
Cara Ryan 722c1875af chore(helm): upgrade sdk to v4 [R8S-840] (#2000) 2026-03-13 11:34:28 +13:00
Ali 68471d0225 fix(stacks): use widget-tabs consistently [c9s-33] (#2038) 2026-03-13 08:30:45 +13:00
Phil Calder a6900545b0 Report a vulnerability via email or GitHub (#2037) 2026-03-12 12:30:40 +13:00
Chaim Lev-Ari 808ceba848 feat(docker): allow user to specify security-opts (#2022)
Co-authored-by: dylan <dfldylan@qq.com>
Co-authored-by: jerry-yuan <i@jerryzone.cn>
2026-03-11 08:56:42 +02:00
Oscar Zhou a796a03a15 fix(edge/helm): helm edge stack is marked as external [BE-12653] (#1974) 2026-03-11 12:51:07 +13:00
andres-portainer 5a5dc67209 fix(golang-lru): consolidate the dependencies BE-12695 (#2021) 2026-03-10 18:57:49 -03:00
andres-portainer 69ae54b523 fix(zerolog): consolidate the dependencies BE-12695 (#2030) 2026-03-10 18:30:21 -03:00
andres-portainer b405227d51 fix(jwt): consolidate the dependencies BE-12695 (#2020) 2026-03-10 15:14:21 -03:00
andres-portainer 44be39a9a4 fix(mapstructure): consolidate the dependencies BE-12695 (#2019) 2026-03-10 14:48:37 -03:00
andres-portainer 5de0cc199c fix(kingpin): consolidate dependencies BE-12695 (#2018) 2026-03-10 14:33:10 -03:00
andres-portainer 0c9e408eda fix(ldap): consolidate dependencies BE-12695 (#2017) 2026-03-10 14:18:06 -03:00
Chaim Lev-Ari 1007f1f740 feat(ui): create shared terminal component [BE-12697] (#1979) 2026-03-10 18:17:29 +02:00
Chaim Lev-Ari 774e3d5948 fix(ws): remove limit on docker console [BE-12660] (#2023) 2026-03-10 15:26:33 +02:00
andres-portainer 4d866d066a fix(uuid): consolidate dependencies BE-12695 (#2016) 2026-03-10 10:12:42 -03:00
andres-portainer da6544e981 fix(semver): consolidate dependencies BE-12695 (#2014) 2026-03-09 15:33:45 -03:00
bernard-portainer 3af9a7646d fix(ui): add getRowId to expandable storage component [R8S-538] (#2008) 2026-03-09 15:37:40 +13:00
andres-portainer 0e2cf82e3e fix(yaml): consolidate dependencies BE-12695 (#2015) 2026-03-06 18:21:12 -03:00
andres-portainer 97e69b9887 fix(GO-2026-4550): upgrade circl to v1.6.3 BE-12694 (#2011) 2026-03-06 14:29:15 -03:00
andres-portainer 692f91263b fix(GO-2026-4473): upgrade go-git to v5.17.0 BE-12693 (#2010) 2026-03-06 11:23:52 -03:00
LP B 8b61d8a9d2 fix(app/container): query env registries instead of system registries (#1996) 2026-03-06 15:03:11 +01:00
LP B 25d51f9515 fix(app): paginate nested tables (#1998) 2026-03-06 15:01:52 +01:00
LP B 20b971dc1f fix(app/stack): virtual grouping in EnvSelector for non admins (#2001) 2026-03-06 15:00:01 +01:00
andres-portainer 7a76d749e3 fix(GO-2026-4394): upgrade opentelemetry to v1.41.0 BE-12692 (#2003) 2026-03-06 09:47:20 -03:00
LP B 123afd9462 fix(api/custom_template): validate UAC when retrieving custom template file (#1980) 2026-03-04 13:22:14 +01:00
Xing ad83478b77 fix(oauth): tolerate malformed Content-Type headers from resource ept (#1969)
Co-authored-by: Mike Spook <16549186+mikespook@user.noreply.gitee.com>
Co-authored-by: Oscar Zhou <100548325+oscarzhou-portainer@users.noreply.github.com>
Co-authored-by: RHCowan <50324595+RHCowan@users.noreply.github.com>

Thanks @srikanth-karthi for the original PR.
2026-03-02 10:59:02 +13:00
nickl-portainer 2ad0a65613 feat(policies): add inline editing ability to datatable for docker RBAC policies [R8S-717] (#1955) 2026-03-02 09:12:13 +13:00
Chaim Lev-Ari 1f5762b8c8 fix(settings/auth): fix a11y labels (#1963) 2026-03-01 12:14:47 +02:00
RHCowan 0370b09ad0 fix(policy) avoid URL length limit when adding environments to large groups [R8S-893] (#1970) 2026-02-27 11:45:15 +13:00
Oscar Zhou 5869a8948d refactor(stack): change stack creation flow to save stack first [BE-12650] (#1959) 2026-02-27 10:14:17 +13:00
Chaim Lev-Ari 56a840e207 feat(settings): migrate SessionLifetimeSelect to React [BE-12583] (#1829)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-26 15:39:08 +02:00
Chaim Lev-Ari a01dd005fd refactor(settings/auth): migrate auto user provision toggle to react [BE-12585] (#1865)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-26 14:18:48 +02:00
Chaim Lev-Ari 9ad6c16d43 feat(settings): migrate authentication method selector to React [BE-12584] (#1830)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-26 10:52:39 +02:00
Hannah Cooper 9cc3e16db9 Update bug_report to include 2.39.0 (#1964) 2026-02-26 12:30:42 +13:00
andres-portainer d02bcdba29 fix(postinit): optimize PostInitMigrate() BE-12659 (#1958) 2026-02-25 16:03:26 -03:00
Steven Kang c708fe577c fix(kubernetes): local exec to fall back to SPDY - develop [R8S-873] (#1946) 2026-02-25 15:46:15 +13:00
Oscar Zhou c92161bb22 feat(edge/helm): support per device configuration [BE-12633] (#1901) 2026-02-25 10:00:37 +13:00
Ali 138aa13fdc fix(environment-groups): allow bulk selecting environments on create and edit [r8s-872] (#1954)
Merging because the failed system tests are related to helm and not environment groups
2026-02-24 17:53:16 +13:00
Steven Kang 988a795def fix(environment): collapsing More options breaking the style for podman - develop [R8S-874] (#1942) 2026-02-24 10:11:31 +13:00
Oscar Zhou 3f7a3053ff fix(stack): avoid removing running service if stack deployment fails [BE-12542] (#1940) 2026-02-24 08:41:42 +13:00
Oscar Zhou 0c8c6865be refactor(error): standardize multi errors handling [BE-12647] (#1933) 2026-02-23 09:40:01 +13:00
Chaim Lev-Ari 2bbcae39b6 feat: clean frontend test logs (#1894) 2026-02-22 09:42:49 +02:00
andres-portainer caf6b2aa0c fix(policies): fixes for async edge R8S-661 (#1917) 2026-02-20 17:45:45 -03:00
Steven Kang a00f05fe32 feat(environment): reorder options - develop [R8S-524] (#1822) 2026-02-20 14:58:01 +13:00
Chaim Lev-Ari 9fcac1ab4f chore(deps): upgrade axios [BE-12632] (#1864) 2026-02-19 15:38:08 +13:00
Josiah Clumont ae24ad4693 Bump version to 2.39.0 for LTS (#1910) 2026-02-19 15:29:08 +13:00
RHCowan 0f721b60a9 fix(policy) Improve policy status performance [R8S-710] (#1878) 2026-02-19 15:24:14 +13:00
RHCowan e8b49f53e1 fix(policy) fix policy group pagination issues [R8S-855] (#1898) 2026-02-19 13:29:01 +13:00
andres-portainer 27531a802b fix(fips): ensure custom registries cannot use HTTP without TLS BE-12511 (#1885)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2026-02-19 11:51:11 +13:00
Josiah Clumont 4bbf0ce0c0 fix(docker): Update the docker binary version that uses 1.25.6 to fix CVE-2025-61726 - for 2.39.0-LTS [R8S-818] (#1791) 2026-02-19 09:46:14 +13:00
Josiah Clumont e0c22ea3eb fix(copy): Fixed an issue with the downgrade links [R8S-832] (#1907) 2026-02-19 09:38:04 +13:00
nickl-portainer b7eb2ba068 fix(policies) convert all warnings to use PolicyOverrideAlert [R8S-837] (#1890) 2026-02-19 09:12:54 +13:00
Ali affdb69568 fix(policies): show registry policy banner, and disable registry selector when policy applies [R8S-853] (#1891) 2026-02-19 08:37:02 +13:00
LP B 763b7da65c fix(api/docker): do not rewrite HTTP code in responses of create requests (#1854) 2026-02-18 19:26:29 +01:00
Chaim Lev-Ari 42e9165347 fix(stacks): generate webhook id for stacks (#1876) 2026-02-17 10:38:18 +02:00
Ali 16dd08a359 feat(widget): update widget tab styling product wide [r8s-850] (#1881) 2026-02-17 10:33:43 +13:00
Ali 936494615c fix(select): stop react-select overlapping with footer [R8S-794] (#1880) 2026-02-17 08:53:50 +13:00
andres-portainer 5769c0b98e fix(kubernetes): add missing returns BE-12582 (#1883) 2026-02-16 12:47:27 -03:00
andres-portainer b7e1caa8c6 fix(boltdb): fix error handling BE-12582 (#1882) 2026-02-16 12:47:00 -03:00
andres-portainer e02ae6b2fb fix(archive): prevent file traversal vulnerability BE-12582 (#1875) 2026-02-16 11:26:51 -03:00
testA113 d9f131a2c5 Revert "feat(widget): update widget tab styling product wide [r8s-850]"
This reverts commit d882c3b8fa4a03bf85b4e9fb1da729fabf903cb6.
2026-02-17 00:05:24 +13:00
testA113 ad1f7dbaa5 feat(widget): update widget tab styling product wide [r8s-850] 2026-02-17 00:01:07 +13:00
Devon Steenberg aa6da0f6d3 feat(api-testing): add api testing framework [BE-12571] (#1824)
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
2026-02-16 09:35:06 +13:00
Oscar Zhou 376071e408 feat(edge/helm): add atomic and timeout options [BE-12481] (#1849) 2026-02-16 09:21:19 +13:00
Chaim Lev-Ari d3544fb9b3 refactor(tests): mock ws server (#1853) 2026-02-15 08:58:24 +02:00
Chaim Lev-Ari c8497b3944 chore(deps): upgrade html-loader (#1863) 2026-02-15 08:55:33 +02:00
andres-portainer 5aa92b8413 fix(webhooks): use transactions to check for webhook uniqueness BE-12613 (#1872) 2026-02-13 12:48:17 -03:00
Hannah Cooper bccb6694d4 Update bug_report to include 2.38.1 (#1866) 2026-02-13 12:42:08 +13:00
Hannah Cooper 506a11c658 Update bug_report to include 2.33.7 (#1836) 2026-02-13 12:28:05 +13:00
Ali bdc315a59d fix(helm): helm release not found error [r8s-842] (#1857) 2026-02-13 08:07:23 +13:00
andres-portainer ec7d3bddfc fix(endpoints): fix transaction usage BE-12612 (#1838) 2026-02-11 12:34:46 -03:00
Chaim Lev-Ari 762c1ccf28 chore(deps): upgrade vitest and msw (#1852) 2026-02-11 17:14:04 +02:00
Malcolm Lockyer 8e44c8fa06 fix(webpack): fix common cfg after webpack-dev-server upgrade [r8s-841] (#1848) 2026-02-11 18:34:14 +13:00
Chaim Lev-Ari 20db102327 chore(deps): upgrade webpack (#1802) 2026-02-10 18:01:03 +02:00
Chaim Lev-Ari 1643cb8165 fix(environments): handle unix:// urls [BE-12610] (#1837)
Co-authored-by: Nicholas Loomans <nicholas.loomans@portainer.io>
2026-02-10 15:21:25 +02:00
Ali 49e623dfeb feat(policy-RBAC): ensure RBAC policy overrides existing RBAC settings [R8S-777] (#1718) 2026-02-10 23:44:44 +13:00
Steven Kang a1208974ac fix(policy): pod security constraints - develop [R8S-808] (#1758)
Co-authored-by: Phil Calder <4473109+predlac@users.noreply.github.com>
Co-authored-by: Viktor Pettersson <viktor.pettersson@portainer.io>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
Co-authored-by: nickl-portainer <nicholas.loomans@portainer.io>
2026-02-10 08:46:02 +09:00
Chaim Lev-Ari d611087513 chore(deps): upgrade storybook 8 (#1811) 2026-02-08 09:59:08 +02:00
andres-portainer ac7cb2ee19 fix(security): fix CVE-2025-68121 by upgrading Go compiler BE-12581 (#1813) 2026-02-06 13:17:12 -03:00
Oscar Zhou f866572cbf fix(edge/helm): helm config section shows for other type [BE-12580] (#1808) 2026-02-06 09:13:06 +13:00
Chaim Lev-Ari 4c6942f60b fix(environments): update associated group [BE-12559] (#1760) 2026-02-05 18:48:02 +02:00
nickl-portainer d939897524 feat(menu) add policies to environment settings submenu [R8S-806] (#1805) 2026-02-05 14:39:41 +13:00
nickl-portainer 66c5589fd7 fix(environment-list) resize kubeconfig download modal [R8S-814] (#1786)
Co-authored-by: Phil Calder <4473109+predlac@users.noreply.github.com>
Co-authored-by: Steven Kang <skan070@gmail.com>
Co-authored-by: Viktor Pettersson <viktor.pettersson@portainer.io>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
Co-authored-by: Malcolm Lockyer <segfault88@users.noreply.github.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
Co-authored-by: RHCowan <50324595+RHCowan@users.noreply.github.com>
2026-02-05 14:39:23 +13:00
Oscar Zhou 379b1d611b feat(edge/helm): support helm chart via git repository in edge stack [BE-12448] (#1649) 2026-02-05 13:22:31 +13:00
Chaim Lev-Ari f16221f385 docs(claude): optimize memory files (#1777) 2026-02-05 04:28:36 +05:30
RHCowan 9b82560270 fix(policy) Fetch new status after policy update [R8S-711] (#1775) 2026-02-04 18:23:26 +13:00
Oscar Zhou 7271af03e6 fix(docker): dashboard api return 500 error [BE-12567] (#1784) 2026-02-04 08:32:01 +13:00
RHCowan 4d564bbce2 feat(policy): Display last attempt timestamp for policy installations [R8S-667] (#1774) 2026-02-03 12:32:22 +13:00
Oscar Zhou d7afdf214b refactor(k8s): replace kubectl delete with delete api [BE-12560] (#1768) 2026-02-03 08:36:08 +13:00
Chaim Lev-Ari 18e445ea02 refactor(environments): migrate item view to react [BE-6632] (#1747) 2026-01-31 15:05:11 +07:00
nickl-portainer cb70c705a3 fix(react): namespace selects sort alphabetically [R8S-765] (#1671) 2026-01-30 08:23:01 +13:00
Ali 9a77eb9872 chore(environment-groups): migrate environment groups to react [R8S-771] (#1741) 2026-01-29 14:17:33 +13:00
Hannah Cooper ec82f646a0 Add 2.38.0 to bug report (#1756) 2026-01-29 12:45:23 +13:00
andres-portainer 2f0e384240 fix(database): use Exists() where possible to improve performance BE-12557 (#1752) 2026-01-28 18:49:32 -03:00
Ali 19a1426869 chore(webpack): cache dependencies and use lighter sourcemap [R8S-791] (#1715) 2026-01-29 09:52:11 +13:00
andres-portainer cc5cd8db6b fix(pendingactions): clean up and optimize the code BE-12556 (#1750) 2026-01-28 15:36:54 -03:00
andres-portainer e384e2edda fix(pendingactions): fix transaction handling BE-12556 (#1749) 2026-01-28 14:11:35 -03:00
Chaim Lev-Ari dca044873f feat(environments): migrate edge form to react BE-12529 (#1676) 2026-01-28 15:35:13 +07:00
nickl-portainer 8aadddcc68 test(react): add test coverage for forms to enforce no errors showing on initial load [R8S-730] (#1696) 2026-01-28 08:12:12 +13:00
andres-portainer 2e95229c51 fix(oauth): add a timeout to GetResource() BE-12258 (#1456) 2026-01-27 10:24:45 -03:00
Phil Calder 8a1d02c23f Bump version to 2.38.0 (#1727) 2026-01-27 16:26:14 +13:00
Josiah Clumont d6bca4ea79 chore(icon): Update sidebar icon & favicon to align with branding (#1737) 2026-01-27 15:11:28 +13:00
LP B 7b567a66ed fix(app/stack): remove unauthorizedRedirect from stack details view (#1720) 2026-01-26 22:21:41 +01:00
Chaim Lev-Ari 2c8126e244 refactor(environments): migrate general environment form to react (#1706) 2026-01-26 14:40:01 -03:00
Chaim Lev-Ari 1b70fe5770 feat(registries): enable ecr registry for fips BE-12539 (#1665) 2026-01-26 14:38:57 -03:00
andres-portainer 71c000756b chore(linters): enforce error checking in CE BE-12527 (#1723) 2026-01-26 14:37:55 -03:00
Yajith Dayarathna a2a7ead82a chore(ci): updates to pnpm lint and gofmt (#1730) 2026-01-27 06:14:20 +13:00
Malcolm Lockyer ef0f1b10cc fix(database): fix encryption of existing database [r8s-537] (#1663)
Co-authored-by: Gorbasch <mbegerau@users.noreply.github.com>
2026-01-25 17:45:38 +13:00
RHCowan 42bedce9c0 feat(policy) add policy status filter to endpoint list [R8S-736] (#1682) 2026-01-23 12:03:05 +13:00
Devon Steenberg afcd44abad fix(kubectl-shell): enable kubectl shell in fips mode [BE-12422] (#1702)
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
2026-01-23 09:38:26 +13:00
Josiah Clumont 274830f533 fix(policy): Policy status bar doesn't use correct colours (#1714) 2026-01-23 08:12:45 +13:00
Ali 9cb139d190 fix(access): handle access view loading and error states [R8S-779] (#1709) 2026-01-22 13:04:43 +13:00
Josiah Clumont d681481ae9 feat(policy): rework the environment type row in the policy view [R8S-695] (#1698) 2026-01-22 09:43:55 +13:00
Oscar Zhou 5d377e602f fix(edgestack): EntryFileName not found [BE-12499] (#1578) 2026-01-22 08:44:31 +13:00
Ali f535c814d9 feat(policies): UI stepper in policy create and environment wizard [R8S-718] (#1672) 2026-01-21 09:37:39 +13:00
andres-portainer 4f5073cd9e chore(refactor): clean up the code R8S-661 (#1687) 2026-01-16 16:10:00 -03:00
LP B 9cd2340007 fix(app/home): display API error message instead of generic error when env is unreachable (#1670) 2026-01-16 14:38:28 +01:00
Chaim Lev-Ari 9ca036e393 feat(pnpm): add system-tests to workspace PLA-567 (#1664) 2026-01-15 12:45:23 +02:00
andres-portainer 5340ecb6df refactor(stackutils): consolidate validation code BE-12391 (#1667) 2026-01-14 18:00:01 -03:00
Chaim Lev-Ari 1248d52161 refactor(environment): migrate azure form to react BE-12528 (#1642)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-14 18:20:15 +02:00
andres-portainer 3e2fdb1891 fix(swarm): fix environment security checks BE-12541 (#1666) 2026-01-14 12:25:50 -03:00
andres-portainer ac8fa7672e fix(environments): improve the default environment security settings BE-12391 (#1656) 2026-01-14 10:36:42 -03:00
LP B db57716130 fix(api): remove overly verbose log on startup (#1655) 2026-01-13 19:39:35 +01:00
LP B b162814bd9 fix(uac): async SnapshotRaw data not filtered by UAC (#1540) 2026-01-13 17:17:06 +01:00
LP B a889d57013 fix(app/edge): UI form error on edge stack update (#1643) 2026-01-13 17:15:51 +01:00
Chaim Lev-Ari c6e9cdbf35 fix(stacks): save registries when creating stack BE-12526 (#1633) 2026-01-13 09:00:48 +02:00
Phil Calder 2a00d90134 chore(docs): Adds a SECURITY.md to repos (#1636)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-13 13:07:50 +13:00
andres-portainer 2676cd7219 chore(linters): add the unused, zerologlint and exptostd linters BE-12527 (#1645) 2026-01-12 10:28:17 -03:00
Chaim Lev-Ari 4f76b1fda4 refactor(environments): prepare common fields for edit env form BE-12531 (#1641)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 19:01:28 +02:00
Chaim Lev-Ari 1c56d5c59e fix(environments): fix issues in edit page (#1640) 2026-01-09 16:41:39 +02:00
Chaim Lev-Ari be44eedeb8 feat(environments): migrate KubeConfigInfo to React (PR 8 of 10) [BE-12524] (#1625)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-09 14:37:21 +02:00
Chaim Lev-Ari 36296d2f5d fix(docker/configs): delete config from item view BE-12525 (#1628) 2026-01-09 14:36:24 +02:00
andres-portainer b4db75fb55 chore(linters): add the unconvert linter BE-12527 (#1635) 2026-01-09 09:22:13 -03:00
Chaim Lev-Ari 565c36040d feat(environments): migrate edge agent deployment to React [BE-12522] (#1626)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-09 13:32:05 +02:00
Ali 36e7f821e8 fix(namespace): fix namespace user access calls and parsing [r8s-726] (#1610) 2026-01-09 13:15:57 +13:00
Ali 009e1e25f5 fix(k8s deploy): ensure namespace from deploy form/api call can be used [r8s-747] (#1632) 2026-01-09 12:57:03 +13:00
Ali 69715ed1c8 fix(helm): avoid widget title error thrown for helm edit/upgrade [r8s-746] (#1630) 2026-01-09 10:25:51 +13:00
andres-portainer e8cee12384 chore(linters): add the modernize linter BE-12527 (#1634) 2026-01-08 16:35:18 -03:00
andres-portainer f2fd2c157c chore(errcheck): ensure errcheck scans everything BE-12183 (#1094) 2026-01-08 14:41:40 -03:00
Chaim Lev-Ari 3f6cee5ded feat(portainer): migrate EdgeInformationPanel to React BE-12521 (#1624)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-08 15:27:27 +02:00
Devon Steenberg b1cb95c3b0 fix(docker): bump docker max api version [BE-12462] (#1556) 2026-01-08 14:22:48 +13:00
LP B 372bc3c97c fix(app): generate a container name when names list is empty (#1615) 2026-01-07 20:20:28 +01:00
Chaim Lev-Ari fa684f95e0 feat(portainer): migrate Environment basic config section to React BE-12520 (#1620) 2026-01-07 18:37:19 +02:00
Chaim Lev-Ari e8fb8a6f88 feat(portainer): migrate AzureEndpointConfigSection to React BE-12519 (#1619)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-07 17:36:52 +02:00
andres-portainer 93901336bb fix(git): upgrade go-git to v5.16.4 BE-12512 (#1607) 2026-01-07 09:18:21 -03:00
RHCowan 660f2095af fix(policy) Show all policy types in selector [R8S-735] (#1591) 2026-01-07 19:12:30 +13:00
Ali 13b27cf77a feat(aci): environment variable support [r8s-675] (#1445)
Merging because the playwright tests don't relate to the container instance changes in this PR
2026-01-07 15:49:54 +13:00
Oscar Zhou d1eb5a8466 fix(stack/k8s): kubectl command memory leak [BE-12455] (#1582) 2026-01-07 11:51:28 +13:00
andres-portainer 5d0aefb07a fix(registryproxy): consolidate the TLS initialization code BE-12511 (#1601) 2026-01-06 10:59:38 -03:00
andres-portainer 78a23bb722 fix(frontend): update dependencies to fix vulnerabilities BE-12506 (#1595)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: Chaim Lev-Ari <chaim.lev-ari@portainer.io>
2026-01-06 10:58:46 -03:00
Chaim Lev-Ari 38c42cb47b refactor(containers): migrate container item view to react BE-6582 (#1606)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-06 12:33:24 +02:00
Chaim Lev-Ari c9c779d5d5 refactor(containers): migrate volume section to react BE-12495 (#1605) 2026-01-06 10:18:51 +02:00
Chaim Lev-Ari dabfd4249e refactor(containers): migrate container details section to react BE-12494 (#1602) 2026-01-06 08:05:30 +02:00
Ali e62db5f1d9 chore(pre-commit hooks): allow golangci-lint to run concurrently for CE and EE for pre commit hook [R8S-737] (#1608) 2026-01-06 16:57:03 +13:00
Chaim Lev-Ari 50c01c97ee fix(proxy): add error handler to print error to user (#1593) 2026-01-05 14:40:35 +02:00
andres-portainer 68600dddf0 fix(security): fix a nil pointer dereference error in FilterEndpoints() BE-12509 (#1598) 2026-01-02 16:08:17 -03:00
andres-portainer c80464d072 fix(edgegroups): fix a nil pointer dereference BE-12487 (#1573) 2026-01-02 15:26:53 -03:00
andres-portainer 02a083fa02 fix(filesystem): fix a nil pointer dereference error in CopyPath() BE-12508 (#1597) 2026-01-02 15:18:21 -03:00
andres-portainer 36ff24c301 fix(endpointgroups): fix a nil pointer dereference error in deleteEndpointGroup BE-12510 (#1599) 2026-01-02 15:17:51 -03:00
Chaim Lev-Ari 935f3b8754 refactor(containers): migrate image section to react BE-12493 (#1594) 2026-01-01 11:12:05 +02:00
Chaim Lev-Ari eac9f649cf chore(build): introduce pnpm workspaces (#1584) 2025-12-31 18:52:58 +02:00
Chaim Lev-Ari 8bcd27e042 refactor(containers): migrate status section to react BE-12492 (#1583) 2025-12-31 10:12:37 +02:00
Chaim Lev-Ari c3dbf51a16 feat(docker): migrate ContainerActionsSection to React (PR 2 of 7) (#1576) 2025-12-30 11:41:49 +02:00
Chaim Lev-Ari 36417a0726 chore(build): migrate to pnpm (#1558) 2025-12-29 10:14:57 +02:00
Yajith Dayarathna 20b87f8bb9 fix(build): adding fixes for docker buildx build warnings in ci (#1567) 2025-12-29 10:31:51 +13:00
Chaim Lev-Ari a1bac5a133 refactor(stacks): migrate create view to react [BE-6630] (#1538) 2025-12-26 16:50:55 +02:00
Chaim Lev-Ari 177da24e47 feat(docker): migrate RestartPolicySection to React BE-12490 (#1570) 2025-12-24 18:38:52 +02:00
Chaim Lev-Ari 37ba8d17bf fix(stacks): confirm rename with modal BE-12497 (#1571) 2025-12-24 17:45:27 +02:00
andres-portainer ee8b78fd3c chore(segmentio/encoding): upgrade to v0.5.3 BE-12500 (#1575) 2025-12-24 12:09:01 -03:00
Chaim Lev-Ari 83bc685e75 fix(stacks): allow renaming stack in swarm BE-12496 (#1572) 2025-12-24 16:41:37 +02:00
andres-portainer 3781897e39 fix(compose): upgrade compose-go to v2.40.3 to fix a nil panic BE-12424 (#1550) 2025-12-23 22:26:25 -03:00
Chaim Lev-Ari 0efed6d8d3 fix(stacks): invalidate only stack cache on update BE-12476 (#1566) 2025-12-23 15:27:26 +02:00
Chaim Lev-Ari 8f2c33aec3 chore(node): upgrade node version in CI [BE-12465] (#1525) 2025-12-23 10:22:48 +02:00
Chaim Lev-Ari 433b5bc974 fix(ci): run eslint and typecheck without symlinks (#1564) 2025-12-22 17:38:42 +02:00
Chaim Lev-Ari aef27f475d feat(analytics): remove setting for collection analytics [BE-12402] (#1559) 2025-12-22 15:59:08 +02:00
Viktor Pettersson 28ccf19874 fix(docs): ensure all docs related dependencies, such as struct types are available before building swagger docs PLA-542 (#1562) 2025-12-22 15:02:56 +13:00
Yajith Dayarathna 7e54f40033 chore: ci workflow(round3) and Dockerfile update (#1542) 2025-12-22 10:54:51 +13:00
Chaim Lev-Ari bf8ccbcec6 Revert "feat(frontend): import CE code to EE" (#1557) 2025-12-18 13:45:26 +02:00
Chaim Lev-Ari 2f5b083c5c feat(frontend): import CE code to EE (#1365) 2025-12-17 13:02:19 +02:00
James Carppe 5640e8c11a Version bump for 2.33.6 (#1548) 2025-12-17 18:25:29 +13:00
Devon Steenberg c239445454 fix(swarm): stack deployments [BE-12478] (#1546)
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.
2025-12-17 14:21:49 +13:00
Chaim Lev-Ari a7b7ddbe76 fix(containers): clear mac address on edit/duplicate [BE-12436] (#1524) 2025-12-15 09:59:47 +02:00
andres-portainer d859272d43 chore(compress): upgrade klauspost/compress to v1.18.2 (#1534) 2025-12-12 12:30:00 -03:00
Oscar Zhou d59a16a9a1 fix(stack): stack start failed with private image [BE-12464] (#1523) 2025-12-12 10:55:03 +13:00
andres-portainer 79f524865f fix(yaml): switch from gopkg.in/yaml.v3 to go.yaml.in/yaml/v3 BE-12340 (#1527) 2025-12-11 16:44:56 -03:00
Chaim Lev-Ari 6d0a09402b refactor(stacks): migrate item view to react [BE-6629] (#1444) 2025-12-11 10:21:43 +02:00
Steven Kang 4bb160b281 fix(security): cve-2025-47914 and 58181 - develop [R8S-714] (#1516) 2025-12-11 15:22:22 +09:00
Hannah Cooper 24d27f421b Update bug_report to include 2.37.0 (#1518) 2025-12-11 12:41:05 +13:00
Chaim Lev-Ari 3d0b8ec5f0 feat(update): prevent the creation of updater network [BE-12441] (#1517) 2025-12-10 18:45:46 +02:00
Chaim Lev-Ari 79e6271041 refactor(docker/images): migrate list view to react [BE-6562] (#1451) 2025-12-09 15:27:20 +02:00
Chaim Lev-Ari ecac526810 feat(analytics): remove frontend analytics module (#1459) 2025-12-09 09:27:51 +02:00
Oscar Zhou ad8d5a8694 version: bump version to 2.37.0 (#1501) 2025-12-09 13:06:50 +13:00
Steven Kang 2406d67bfc feat(fcm): initial release (#1153)
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
Co-authored-by: James Player <james.player@portainer.io>
Co-authored-by: Cara Ryan <cara.ryan@portainer.io>
Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Viktor Pettersson <viktor.pettersson@portainer.io>
Co-authored-by: Viktor Pettersson <viktor.grasljunga@gmail.com>
Co-authored-by: Malcolm Lockyer <segfault88@users.noreply.github.com>
Co-authored-by: RHCowan <50324595+RHCowan@users.noreply.github.com>
Co-authored-by: Robbie Cowan <robert.cowan@portainer.io>
2025-12-09 08:05:38 +09:00
Oscar Zhou f0266e9316 fix(stack/remote): fail to pull image in stack with relative path enabled [BE-12237] (#1493) 2025-12-09 08:59:19 +13:00
Chaim Lev-Ari c08f42315e feat(docker/host): disable browse for non admin [BE-12438] (#1484) 2025-12-08 16:51:52 -03:00
Chaim Lev-Ari d2649dac90 fix(docker/services): ignore missing EndpointSpec [BE-12460] (#1494) 2025-12-08 16:51:18 -03:00
LP B 300681055e fix(api): do not give away information on error (#1496) 2025-12-08 16:50:00 -03:00
andres-portainer 712dbc9396 fix(endpointedge): reject async edge environments from the edge job logs handler BE-12372 (#1488) 2025-12-08 15:05:32 -03:00
andres-portainer f6b8e8615f fix(endpointedge): fix an incorrect documentation comment BE-12372 (#1486) 2025-12-08 11:59:53 -03:00
andres-portainer 4826c13848 fix(endpointedge): add a check for the relation of an environment and an edge job before updating the logs BE-12372 (#1487) 2025-12-08 11:59:40 -03:00
Yajith Dayarathna 80f497a185 chore(ci): minor ci workflow updates (#1491) 2025-12-08 14:12:24 +13:00
LP B d2a9adb4be fix(compose): use project in compose start options (#1477) 2025-12-05 15:22:40 +01:00
Oscar Zhou 8675086441 fix(stack): "update the stack" button is disable in stakc deployed via web editor [BE-12456] (#1473) 2025-12-05 08:56:13 +13:00
Devon Steenberg b79e784764 fix(stacks): stack updating with container_name [BE-12443] (#1453) 2025-12-02 09:32:03 +13:00
Chaim Lev-Ari 93ba3e700e fix(ui/code-editor): keep search panel in editor layer [BE-12429] (#1452) 2025-11-27 14:32:57 +02:00
Chaim Lev-Ari bf6cb8d0b8 refactor(stacks): use formik in StackRedeployGitForm [BE-12430] (#1433) 2025-11-27 08:43:51 +02:00
Hannah Cooper 7010d7bf66 Update bug_report to include 2.33.5 and 2.36.0 (#1447) 2025-11-27 10:35:38 +13:00
Oscar Zhou 1a862157a0 fix(snapshot): prevent from returning SnapshotRaw data [BE-12431] (#1441) 2025-11-26 13:07:43 +13:00
Chaim Lev-Ari 532575cab5 refactor(stacks): migrate info tab to react [BE-12383] (#1415) 2025-11-25 13:17:26 +02:00
Chaim Lev-Ari 0794d0f89f refactor(docker/configs): migrate to react [BE-6541] (#1430) 2025-11-25 12:02:50 +02:00
Chaim Lev-Ari e227ffd6d8 feat(stacks): create webhook id only if needed [BE-12392] (#1432) 2025-11-25 10:48:15 +02:00
Devon Steenberg 5058b40871 chore(version): bump to v2.36.0 (#1434) 2025-11-25 11:09:49 +13:00
Chaim Lev-Ari 5d847b59b2 feat(analytics): remove matomo dependency [BE-12404] (#1431) 2025-11-24 16:30:03 +02:00
Oscar Zhou c8d44b9416 fix(edgestack): external label on k8s application deployed by edgestack [BE-12318] (#1428) 2025-11-22 09:04:31 +13:00
Oscar Zhou 14d67d1ec7 fix(edgestack): external label on k8s application deployed by edgestack [BE-12318] (#1385) 2025-11-21 12:44:42 +13:00
Hannah Cooper 6866faf4fe Update bug_report to include 2.33.4 (#1420) 2025-11-20 13:06:07 +13:00
Viktor Pettersson 567d628a52 fix(edge-stacks): inconsistent edge stack count BE-12285 (#1382) 2025-11-20 10:56:38 +13:00
Chaim Lev-Ari a3eab75405 refactor(registries): remove superfluous useEffect in PrivateRegistryFieldset [BE-12408] (#1396) 2025-11-19 08:12:11 +02:00
Chaim Lev-Ari 566f6b067c fix(environments): fix podman auto onboarding script [BE-12327] (#1395) 2025-11-18 14:30:23 +02:00
Chaim Lev-Ari e73d07281c fix(endpoints): Change syntax for multi-line commands in Windows (#1355)
Co-authored-by: Shawn <host@shawnsg.dev>
2025-11-18 08:48:32 +02:00
Steven Kang e59d4dea77 fix: CVE-2024-25621 - develop [R8S-639] (#1412) 2025-11-18 17:34:10 +13:00
Steven Kang 4ca5370b86 fix: CVE-2025-47913 - develop [R8S-638] (#1401) 2025-11-18 16:28:14 +13:00
Devon Steenberg e831971dd1 fix(docker): bump docker max api version [BE-12399] (#1392) 2025-11-18 11:27:16 +13:00
Steven Kang 99d996dde9 fix: CVE-2025-47906 and CVE-2025-47910 - develop [R8S-618] (#1389) 2025-11-18 08:57:00 +13:00
Malcolm Lockyer 712d31b416 fix(agent): for iamra and ecr login, detect errors and retry [be-12284] (#1362) 2025-11-17 11:51:09 +13:00
Steven Kang 0394855b2f feat: reorder environment creation types (#1359) 2025-11-17 10:09:19 +13:00
Chaim Lev-Ari 9024b021ee feat(environments): deprecate openamt [BE-12359] (#1390) 2025-11-16 09:55:00 +02:00
Chaim Lev-Ari 8071641179 refactor(stacks): convert editor to tab (#1374) 2025-11-12 15:44:13 +02:00
Chaim Lev-Ari 0075374241 fix(ui/datatables): show selected filter values [BE-11301] (#1387) 2025-11-12 15:21:17 +02:00
Chaim Lev-Ari c35ddc8c76 feat(git): hide user/pass for save creds [BE-10953] (#1376) 2025-11-12 15:20:20 +02:00
Oscar Zhou 4b4aef7ef8 fix(stack): apply new stack manual redeployment filed name to regular stack [BE-12384] (#1375) 2025-11-12 09:17:57 +13:00
Copilot 6db4a62e01 Fix swagger enum issues causing duplicate constants in generated code (#1373)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: deviantony <5485061+deviantony@users.noreply.github.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
2025-11-12 08:45:08 +13:00
Chaim Lev-Ari db394b6145 feat(logs): filter activity logs by envs and users [BE-12275] (#1383) 2025-11-11 14:49:26 +02:00
Chaim Lev-Ari 53e7704724 feat(stacks): allow to rename stacks [BE-12317] (#1339) 2025-11-09 09:39:29 +02:00
Chaim Lev-Ari f607c7c271 reactor(stacks): migrate deploy git to react [BE-12382] (#1372) 2025-11-09 09:36:06 +02:00
Oscar Zhou 48c689e5d6 fix(registry): custom registry configure page doesn't reflect actual setting [BE-12385] (#1378) 2025-11-08 10:13:00 +13:00
Oscar Zhou 2f2251ff33 fix(registry): pulling private image from registry fails despite credential is valid [BE-12237] (#1303) 2025-11-08 10:12:17 +13:00
Devon Steenberg 29254d1a66 fix(proxy): replace Director with Rewrite field [BE-12328] (#1358) 2025-11-05 10:57:01 +13:00
Chaim Lev-Ari 19cbae1732 feat(registries): check dockerhub credentials [BE-12329] (#1338) 2025-11-04 18:46:37 +02:00
Chaim Lev-Ari 73ad27640c refactor(stacks): migrate duplication form to react [BE-12353] (#1357) 2025-11-04 18:44:54 +02:00
Chaim Lev-Ari 1be96e1bd1 fix(telemetry): update privacy policy url [BE-12350] (#1348) 2025-11-04 14:25:03 +02:00
Chaim Lev-Ari a9834be2ff fix(widget): remove fixed margin on button [BE-12344] (#1346) 2025-11-04 14:24:26 +02:00
Chaim Lev-Ari d8ab86d86f fix(templates): keep icon to their border size [BE-12349] (#1343) 2025-11-04 14:23:56 +02:00
Chaim Lev-Ari 3f1bd8e290 fix(ui): fix warnings in client-side tests [BE-12351] (#1342) 2025-11-04 14:23:11 +02:00
Chaim Lev-Ari 34a7d75e10 fix(edge-scripts): add podman auto onboarding script [BE-12327] (#1333) 2025-11-04 14:21:37 +02:00
Oscar Zhou ae53de42df fix(stack): stack prune service does not persist [BE-12314] (#1323) 2025-11-03 12:22:04 +13:00
Oscar Zhou b70321a0aa fix(edgestack): unify gitops update flow [BE-12184] (#1110) 2025-11-01 20:20:51 +13:00
Oscar Zhou 0ff39f9a61 refactor(stack): move stack update into transaction [BE-12244] (#1324) 2025-10-31 17:19:56 +13:00
Ali 876ba0fa0f fix: add titles to truncated text [r8s-610] (#1331)
Small behavioral change
2025-10-30 16:43:15 +13:00
Hannah Cooper c7c65d2f97 Update bug_report to include 2.33.3 (#1352) 2025-10-30 15:18:48 +13:00
andres-portainer 736f7e198f fix(CVE-2025-62725): upgrade github.com/docker/compose/v2 to v2.40.2 BE-12352 (#1345) 2025-10-29 18:17:46 -03:00
Viktor Pettersson 8cb3589fb8 chore(go.mod): pin github.com/robfig/cron/v3 to v3.0.1 due to lack of maintenance BE-12226 (#1334) 2025-10-24 10:00:09 +13:00
Chaim Lev-Ari 56530d8791 fix(sidebar): add copyright icon to CE (#1325) 2025-10-23 18:14:09 +03:00
Chaim Lev-Ari da6b0e3dcc refactor(registries): convert docker hub form to react (#1335) 2025-10-23 17:00:49 +03:00
Steven Kang eb02f99cae feat: crds support [r8s-580] (#1254)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2025-10-23 11:07:03 +13:00
Chaim Lev-Ari cb0efae81c chore(gitops): upgrade parse-duration dep [r8s-608] (#1328) 2025-10-22 13:20:20 +03:00
Viktor Pettersson e5f98e6145 test(scheduler): use synctest to cut execution time by 95% BE-12226 (#1330) 2025-10-22 10:48:12 +13:00
Devon Steenberg 8a23007ad2 fix(deps): update github.com/container/image/v5 dep [BE-12212] (#1313) 2025-10-20 15:47:46 +13:00
Oscar Zhou 592b196848 fix(registry): selecting one item checked all items in registry access table [BE-12036] (#1318) 2025-10-20 12:55:32 +13:00
Ali 8eb273e54b docs(kubernetes): update Helm install docs link to /user/kubernetes/applications/manifest/helm [R8S-601] (#1317)
Minor docs change
2025-10-20 09:33:07 +13:00
Ali 78c7e752f9 chore(build): fix relative paths for make dev [r8s-588] (#1314) 2025-10-17 10:40:23 +13:00
Hannah Cooper 7c51a3b5ff Update bug report to include 2.35.0 (#1310) 2025-10-16 12:18:34 +13:00
Viktor Pettersson 3e77db4cee chore(version): bump to v2.35.0 (#1304) 2025-10-15 15:35:33 +13:00
Steven Kang c1c831fea3 feat: gitops for Helm [r8s-343] (#1252)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2025-10-15 11:36:20 +13:00
Steven Kang 6734eab555 fix: add web socket headers for kubeconfig based access - develop [r8s-592] (#1288) 2025-10-10 13:41:07 +13:00
Viktor Pettersson 6ecfbf17c0 fix(autopatch): remove auto-patch feature flag BE-12086 (#1189) 2025-10-10 09:23:47 +13:00
Ali 42fe068db7 fix(security): fix typos in security policy [r8s-573] (#1278)
Co-authored-by: timbretimber <105982513+timbretimber@users.noreply.github.com>
2025-10-09 12:25:11 +13:00
Steven Kang 6b3db56ab2 fix: display dependency version for kubectl and helm - develop [R8S-501] (#1281) 2025-10-07 16:23:47 +13:00
Ali eee15d5ff2 chore(dev): update build scripts to support mac (darwin) [r8s-588] (#1279) 2025-10-07 13:36:17 +13:00
andres-portainer 7a618311d6 feat(boltdb): attempt to compact using a read-only database BE-12287 (#1267) 2025-09-30 19:10:20 -03:00
Oscar Zhou 7dba9ff885 fix(k8s): memory leak during k8s stack deployment [BE-12281] (#1266) 2025-10-01 08:33:01 +13:00
James Carppe 4c9c292316 Version bump for 2.33.2 (#1259) 2025-09-25 17:32:58 +12:00
James Player 00613efbd8 fix(kubernetes UI): Update ingress cache after updating (#1247) 2025-09-25 11:26:36 +12:00
andres-portainer b7384874cf feat(database): add a flag to compact on startup BE-12283 (#1255) 2025-09-24 18:44:09 -03:00
Ali c8ee2ca4a1 fix(rbac): redirect on unauthorized namespace [r8s-564] (#1244) 2025-09-24 22:09:28 +12:00
andres-portainer f97bb4a439 fix(edgestacks): add a missing webhook uniqueness check BE-12219 (#1250) 2025-09-23 17:21:13 -03:00
LP B d83b349016 fix(api/endpoints): edge stack status type filter no longer always include Pending envs (#1229) 2025-09-22 16:10:39 +02:00
Ali 657cd04af2 fix(cve): fix frontend CVEs [r8s-563] (#1239) 2025-09-22 10:15:29 +12:00
Oscar Zhou 24a092836b fix(activitylog): remove export limit and fix search function [BE-12270] (#1235) 2025-09-19 14:52:33 +12:00
andres-portainer 290374f6fc fix(kubernetes/cli): unexport a field BE-12259 (#1228) 2025-09-18 14:39:38 -03:00
andres-portainer 2e7acc73d8 fix(kubernetes/cli): fix a data-race BE-12259 (#1218) 2025-09-18 09:19:29 -03:00
Oscar Zhou 666d51482e fix(container): apply less accurate solution to calculate container status for swarm environment [BE-12256] (#1225) 2025-09-18 16:29:35 +12:00
Oscar Zhou eedf37d18a feat(edge): add option to allow always clone git repository [BE-12240] (#1215) 2025-09-17 18:25:42 +12:00
Viktor Pettersson 16f210966b fix(version): change API version support from LTS to STS (#1223) 2025-09-17 17:18:03 +12:00
andres-portainer 30e70b6327 chore(version): bump to v2.34.0 (#1216) 2025-09-15 22:13:51 -03:00
andres-portainer f91a2e3b65 fix(csp): update the Content-Security-Policy header BE-12228 (#1201) 2025-09-15 10:47:50 -03:00
Ali fdc405c912 feat(docker-networks): allow ipv6 for ipvlan networks [portainer-pr12608] (#1196)
Co-authored-by: ar0311 <arogers0311@gmail.com>
2025-09-15 11:49:06 +12:00
Phil Calder 2f2e70bb86 Fix typo (#1186) 2025-09-13 14:31:52 +12:00
andres-portainer eef54f4153 chore(golangci-lint): add forward-looking static checking rules BE-12183 (#1200) 2025-09-12 16:54:30 -03:00
LP B ad1c015f01 fix(api/custom-templates): UAC-allowed users cannot fetch custom template details (#1113) 2025-09-11 16:08:52 +02:00
LP B 326fdcf6ea refactor(api): remove duplicates of TxResponse + HandlerError detection (#1117) 2025-09-11 11:33:30 +02:00
Malcolm Lockyer 26a0c4e809 fix(encryption): set correct default secret key path [r8s-555] (#1182)
Co-authored-by: Gorbasch <57012534+mbegerau@users.noreply.github.com>
2025-09-11 16:32:43 +12:00
Ali acb465ae33 fix(node): revert table css selector, add new specific selector [r8s-331] (#1170) 2025-09-11 10:53:35 +12:00
andres-portainer 5418a0bee6 fix(mingit): remove mingit BE-12245 (#1177) 2025-09-10 15:01:12 -03:00
andres-portainer a59815264d fix(csp): add google.com to the CSP header BE-12228 (#1175) 2025-09-10 15:00:25 -03:00
Viktor Pettersson 3ac0be4e35 chore(gomod): add go mod tidy checks in the CI BE-12233 (#1151) 2025-09-10 08:28:58 +12:00
Ali feae930293 fix(node): allow switching tabs [r8s-546] (#1161) 2025-09-10 08:17:40 +12:00
LP B 7ebb52ec6d fix(api/container): standard users cannot connect or disconnect containers to networks (#1118) 2025-09-09 22:07:19 +02:00
Ali 8b73ad3b6f chore(kubernetes): node view react migration [r8s-331] (#746) 2025-09-08 22:51:32 +12:00
Ali 6fc2a8234d fix(registry): allow trusted tls custom registries [r8s-489] (#1116) 2025-09-08 09:28:40 +12:00
Ali e2c2724e36 fix(helm): update helm repo validation to match helm cli [r8s-531] (#1141) 2025-09-08 08:58:04 +12:00
Malcolm Lockyer 6abfbe8553 fix(fips): encrypt the chisel private key file for fips [be-12132] (#1143) 2025-09-05 13:17:30 +12:00
andres-portainer 54f6add45d fix(compose): fix a data race in a test BE-12231 (#1148) 2025-09-04 17:31:57 -03:00
andres-portainer f8ae5368bf fix(git): add a minimum interval validation BE-12220 (#1144) 2025-09-04 15:11:12 -03:00
andres-portainer 2ba348551d fix(scheduler): fix a data race in the job scheduler BE-12229 (#1146) 2025-09-04 15:09:52 -03:00
andres-portainer 110f88f22d chore(endpointutils): remove unnecessary field BE-10415 (#1136) 2025-09-04 11:22:46 -03:00
James Player c90a15dd0f refactor(app/repository): migrate edit repository view to React [R8S-332] (#768) 2025-09-04 16:27:39 +12:00
andres-portainer f4335e1e72 fix(registries): clear sensitive fields in the update handler BE-12215 (#1128) 2025-09-02 15:44:09 -03:00
andres-portainer 8d9e1a0ad5 fix(csp): add object-src to the CSP header BE-12217 (#1126) 2025-09-02 11:39:46 -03:00
andres-portainer 48dcfcb08f fix(forbidigo): add more rules to avoid skipping TLS verifications BE-11973 (#1123) 2025-09-01 16:57:22 -03:00
andres-portainer def19be230 fix(depguard): mitigate improper usage of openpgp BE-11977 (#1122) 2025-09-01 14:44:45 -03:00
andres-portainer 36154e9d33 fix(depguard): add a rule against golang.org/x/crypto BE-11978 (#1119) 2025-09-01 10:54:24 -03:00
Oscar Zhou 7cf6bb78d6 fix(container): inaccurate healthy container count [BE-2290] (#1114) 2025-09-01 17:01:13 +12:00
Cara Ryan 541f281b29 fix(kubernetes): Namespace resource limits and requests display consistent value (#1055) 2025-09-01 10:25:53 +12:00
Viktor Pettersson 965ef5246b feat(autopatch): implement OCI registry patch finder BE-12111 (#1044) 2025-08-27 19:04:41 +12:00
James Carppe 9c88057bd1 Updates for release 2.33.1 (#1109) 2025-08-27 16:56:01 +12:00
andres-portainer 8c52e92705 chore(bbolt): upgrade bbolt to v1.4.3 BE-12193 (#1103) 2025-08-25 15:51:56 -03:00
Devon Steenberg 3a727d24ce fix(sslflags): Deprecate ssl flags [BE-12168] (#1075) 2025-08-25 14:35:55 +12:00
Malcolm Lockyer 185558a642 fix(standard): manual endpoint refresh fails to save new status [be-12188] (#1092) 2025-08-25 13:49:17 +12:00
Ali 35aa525bd2 fix(environments): create k8s specific edge agent before connecting [r8s-438] (#1088)
Merging because this change is unrelated to the failing kubernetes/tests/helm-oci.spec.ts tests
2025-08-25 09:32:10 +12:00
Oscar Zhou 2ce8788487 fix(autoupdate): update tooltips in edge stack gitops update [BE-12177] (#1084) 2025-08-23 10:56:04 +12:00
andres-portainer ec0e98a64b chore(linters): enable testifylint BE-12183 (#1091) 2025-08-22 15:31:10 -03:00
Steven Kang 121e9f03a4 fix: GHSA-2464-8j7c-4cjm - develop [R8S-495] (#1087) 2025-08-22 14:03:13 +12:00
andres-portainer a0295b1a39 chore(go): upgrade Go to v1.25.0 BE-12181 (#1071) 2025-08-20 12:55:06 -03:00
andres-portainer 30aba86380 chore(benchmarks): use b.Loop() BE-12182 (#1072) 2025-08-20 12:54:26 -03:00
James Carppe 89f5a20786 Updates for release 2.33.0 (#1067) 2025-08-20 15:35:58 +12:00
James Player ef7caa260b fix(UI): add experimental features back in [r8s-483] (#1061) 2025-08-19 16:55:24 +12:00
Steven Kang 39d50ef70e fix: cve-2025-55198 and cve-2025-55199 - develop [R8S-482] (#1057) 2025-08-19 16:22:52 +12:00
James Player 58a1392480 fix(helm): support http and custom tls helm registries, give help when misconfigured - develop [r8s-472] (#1050)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2025-08-19 13:32:32 +12:00
James Player 06f6bcc340 fix(ui): Fixed react-select TooManyResultsSelector filter and improved scrolling (#1024) 2025-08-19 09:35:00 +12:00
LP B c9d18b614b fix(api/edge-stacks): avoid overriding updates with old values (#1047) 2025-08-16 03:52:13 +02:00
andres-portainer 2035c42c3c fix(migrator): rewrite a migration so it is idempotent BE-12053 (#1042) 2025-08-15 09:26:10 -03:00
Malcolm Lockyer a760426b87 fix(fips): use standard lib pbkdf2 [be-12164] (#1038) 2025-08-15 11:44:35 +12:00
andres-portainer 10b129a02e fix(crypto): replace fips140 calls with fips calls BE-11979 (#1033) 2025-08-14 19:36:15 -03:00
Cara Ryan 129b9d5db9 fix(pending-actions): Small improvements to pending actions (R8S-350) (#949) 2025-08-15 10:07:51 +12:00
andres-portainer 2c08becf6c feat(openai): remove OpenAI BE-12018 (#873) 2025-08-14 10:42:21 -03:00
Ali a3bfe7cb0c fix(logs): improve log rendering performance [r8s-437] (#993) 2025-08-14 13:55:37 +12:00
andres-portainer 7049a8a2bb fix(linters): add many linters BE-12112 (#1009) 2025-08-13 19:42:24 -03:00
LP B 1197b1dd8d feat(api): Permissions-Policy header deny all (#1021) 2025-08-13 22:07:55 +02:00
andres-portainer 7f167ff2fc fix(auth): remove a nil pointer dereference BE-12149 (#1014) 2025-08-13 13:20:56 -03:00
Andrew Amesbury 3ade5cdf19 bump version to 2.33.0-rc1 (#1019) 2025-08-13 14:40:34 +12:00
LP B 5f6fa4d79f fix(app/update_schedule): create schedule performance issues at scale (#1002) 2025-08-12 16:50:11 +02:00
Ali 3ee20863d6 fix(editor): remove yaml specific highlighting [r8s-441] (#1010) 2025-08-12 11:53:31 +12:00
Steven Kang 8fe5eaee29 feat(ui): Kubernetes - Create from Manifest - tidy up [R8S-67] (#971) 2025-08-12 11:49:33 +12:00
Cara Ryan 208534c9d9 fix(helm): helm apps do not combine in applications view if different namespace [R8S-420] (#988) 2025-08-12 10:23:27 +12:00
Steven Kang 3f030394c6 fix(security): remediation of cve-2025-54338 and cve-2025-8556 (#989) 2025-08-12 09:08:29 +12:00
Devon Steenberg 6ca0085ec8 fix(stackbuilders): swarm and k8s deploys [BE-12138] (#1003) 2025-08-11 15:44:36 +12:00
Malcolm Lockyer 2cf1649c67 fix(encryption): in fips mode, use pbkdf2 for db password [be-11933] (#985)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2025-08-11 12:03:38 +12:00
andres-portainer 64ed988169 fix(linters): upgrade golangci-lint to v2.3.1 BE-12136 (#997) 2025-08-08 21:39:21 -03:00
LP B 85b7e881eb docs(api/dashboard): docker/{envId}/dashboard incorrectly marked as POST instead of GET (#996) 2025-08-08 09:31:34 +02:00
andres-portainer 9325cb2872 fix(all): avoid using pointers to zero sized structs BE-12129 (#986) 2025-08-07 09:47:42 -03:00
Steven Kang e39dcc458b fix(security): ghsa-fv92-fjc5-jj9h [R8S-449] (#979) 2025-08-07 12:21:31 +12:00
Devon Steenberg 84b4b30f21 fix(rand): Use crypto/rand instead of math/rand in FIPS mode [BE-12071] (#961)
Co-authored-by: codecov-ai[bot] <156709835+codecov-ai[bot]@users.noreply.github.com>
2025-08-06 10:19:15 +12:00
andres-portainer 6c47598cd9 fix(apikey): use HMAC-SHA256 for FIPS mode API keys BE-11936 (#980) 2025-08-05 13:09:35 -03:00
andres-portainer d00d71ecbf fix(linter): add linter rules to reduce the chance for invalid FIPS settings BE-11979 (#975) 2025-08-05 09:23:07 -03:00
Ali dc273b2d63 fix(helm): don't block install with dry-run errors [r8s-454] (#976)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-05 18:53:41 +12:00
James Carppe 497b16e942 Chore update readme graphic (#963)
Co-authored-by: Phil Calder <4473109+predlac@users.noreply.github.com>
2025-08-05 17:14:54 +12:00
LP B a472de1919 fix(app/edge-jobs): edge job results page crash at scale (#954) 2025-08-04 17:10:46 +02:00
Malcolm Lockyer d306d7a983 fix(encryption): replace encryption related methods for fips mode [be-11933] (#919)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2025-08-04 17:04:03 +12:00
andres-portainer 163aa57e5c fix(tls): centralize the TLS configuration to ensure FIPS compliance BE-11979 (#960) 2025-08-01 22:23:59 -03:00
andres-portainer 3eab294908 fix(linters): add the bodyclose linter BE-12112 (#959) 2025-07-30 11:35:30 -03:00
Viktor Pettersson da30780ac2 feat(autopatch): implement patch finder for retrieving latest patches from GitHub (#957) BE-12085 2025-07-30 15:57:32 +12:00
Ali ef53354193 fix(snapshot): show snapshot stats [r8s-432] (#952) 2025-07-29 22:51:05 +12:00
andres-portainer e9ce3d2213 fix(endpointedge): optimize buildSchedules() BE-12099 (#955) 2025-07-28 19:19:07 -03:00
andres-portainer a46db61c4c fix(endpointrelation): optimize updateEdgeStacksAfterRelationChange() BE-12092 (#941) 2025-07-28 13:19:05 -03:00
Steven Kang 5e271fd4a4 feat(ui): reordered kubernetes create from code options [R8S-429] (#951) 2025-07-28 15:41:12 +12:00
James Player 6481483074 fix(app/sidebar): Custom logo UI issue [r8s-435] (#939) 2025-07-25 15:29:06 +12:00
James Player 7bcb37c761 feat(app/kubernetes): Popout kubectl shell into new window [r8s-307] (#922) 2025-07-25 15:24:32 +12:00
LP B e7d97d7a2b fix(app/edge-configs): high numbers UI overlap (#931) 2025-07-24 16:37:07 +02:00
James Carppe 1afae99345 Updates for release 2.32.0 (#936) 2025-07-24 14:30:37 +12:00
Steven Kang bdb2e2f417 fix(transport): portainer generated kubeconfig causes kubectl exec fail [R8S-430] (#929) 2025-07-24 13:11:13 +12:00
andres-portainer bba3751268 fix(roar): return empty slices instead of nil for easier API compatibility BE-12053 (#932) 2025-07-23 14:06:20 -03:00
Ali 60bc04bc33 feat(helm): show manifest previews/changes when installing and upgrading a helm chart [r8s-405] (#898) 2025-07-23 10:52:58 +12:00
andres-portainer a4cff13531 fix(bouncer): add missing domain to CSP header BE-12067 (#916) 2025-07-21 21:32:50 -03:00
andres-portainer 937456596a fix(edgegroups): convert the related endpoint IDs to roaring bitmaps to increase performance BE-12053 (#903) 2025-07-21 21:31:13 -03:00
Devon Steenberg caf382b64c feat(git): support bearer token auth for git [BE-11770] (#879) 2025-07-22 08:36:08 +12:00
Ali 55cc250d2e fix(pods): represent pod container statuses correctly [r8s-416] (#910) 2025-07-21 15:05:08 +12:00
Ali eaa2be017d fix(helm): ensure the form is not 'dirty', when the values are unchanged [r8s-421] (#901) 2025-07-17 12:07:11 +12:00
James Player 4e4c5ffdb6 fix(app/kubernetes): Fix listing of secrets and configmaps with same name [r8s-288] (#897) 2025-07-16 16:37:59 +12:00
James Player 383bcc4113 fix(docker/images): Fix image detail actions icon colours [be-12044] (#892) 2025-07-15 13:57:43 +12:00
James Player 9f906b7417 refactor(app/tests): Make createMockUsers more deterministic [r8s-406] (#887) 2025-07-14 17:16:33 +12:00
Cara Ryan db2e168540 chore: bump version to 2.32.0 (#884) 2025-07-14 10:23:05 +12:00
Ali 2697d6c5d7 feat(oci): oci helm support [r8s-361] (#787) 2025-07-13 10:37:43 +12:00
andres-portainer b6a6ce9aaf fix(endpointedge): fix a deadlock in createAsyncEdgeAgentEndpoint() BE-12039 (#883) 2025-07-11 18:54:05 -03:00
Ali 89f6a94bd8 chore(select): show data-cy react select [r8s-402] (#881) 2025-07-11 20:06:41 +12:00
Steven Kang 96f2d69ae5 feat(observability): alerting experimental feature (#801)
Co-authored-by: JamesPlayer <james.player@portainer.io>
2025-07-11 16:55:23 +12:00
Cara Ryan b7e906701a fix(kubernetes): Namespace access permission changes role bindings not created [R8S-366] (#826) 2025-07-11 14:55:48 +12:00
Steven Kang 150d986179 fix: CVE-2025-53547 (#880) 2025-07-11 13:57:21 +12:00
James Player ef10ea2a7d fix(ui): Fixed TagsDatatable name column link (#847) 2025-07-11 11:01:37 +12:00
Viktor Pettersson 3bf84e8b0c fix(tags): reconcile edge relations prior to deletion [BE-11969] (#867) 2025-07-10 10:52:12 +12:00
andres-portainer ea4b334c7e feat(csp): enable CSP by default BE-11961 (#872) 2025-07-09 16:15:43 -03:00
Oscar Zhou 4d11aa8655 fix(tag): ignore "environment not found" when deleting tag [BE-11944] (#869) 2025-07-09 09:55:59 -03:00
andres-portainer 302deb8299 chore(dataservices): enhance ReadAll() so it takes predicates for filtering results BE-12016 (#866) 2025-07-07 14:29:56 -03:00
Viktor Pettersson 0c80b1067d fix(styles): update datetime picker styles for improved dark mode support [BE-11672] (#863) 2025-07-07 20:54:44 +12:00
Steven Kang 0a36d4fbfd fix: kubectl sdk - capture fatal error and return instead of exiting 1 [r7s-371] (#841) 2025-07-07 11:29:29 +12:00
Oscar Zhou c20a8b5a68 fix(template): app template v3 error [BE-11998] (#854) 2025-07-04 11:49:33 -03:00
Devon Steenberg 8ffe4e284a fix(tls): set insecureSkipVerify to false in FIPS mode [BE-11932] (#849) 2025-07-04 10:48:54 +12:00
Steven Kang 1332f718ae feat: add warning events count next to the status badge (#828) 2025-07-04 10:07:57 +12:00
James Player f4df51884c fix(tests): Fix ServicesDatatable tests - r8s-395 (#860) 2025-07-03 16:01:08 +12:00
James Carppe ce86129478 Updates for release 2.31.3 (#859) 2025-07-03 15:17:50 +12:00
andres-portainer 097b125e3a fix(boltdb): change some options to increase performance BE-12002 (#848) 2025-07-02 18:17:19 -03:00
andres-portainer 5c6b53922a feat(go): upgrade to Go v1.24.4 BE-11774 (#855) 2025-07-02 18:14:29 -03:00
James Carppe e1b9f23f73 Updates for release 2.27.9 (#853) 2025-07-02 17:45:59 +12:00
LP B e1c480d3c3 feat(app/edge-stacks): summarize the edge stack statuses in the backend (#818) 2025-07-01 15:04:10 +02:00
Steven Kang 363a62d885 fix: bump the docker binary version to v28.3.0 [r8s-390] (#837) 2025-07-01 20:10:39 +12:00
James Player c6ee9a5a52 feat(ui): Rebranding - r8s-374 (#840) 2025-07-01 12:58:31 +12:00
andres-portainer cf5990ccba fix(edgestackstatus): improve error handling BE-11963 (#844) 2025-06-30 20:54:16 -03:00
Oscar Zhou b6f3682a62 refactor(edge): init endpoint relation when endpoint is created [BE-11928] (#814) 2025-06-30 15:15:56 -03:00
LP B b43f864511 fix(api/endpoints): filter out waiting room environments for non admins (#810) 2025-06-30 15:35:51 +02:00
Oscar Zhou 0556ffb4a1 feat(csrf): add trusted origins cli flags [BE-11972] (#836) 2025-06-27 17:41:10 -03:00
Ali 303047656e fix(k8s-services): avoid rerendering services table [r8s-387] (#832) 2025-06-27 22:48:40 +12:00
Steven Kang 8d29b5ae71 fix: kubeconfig download button inconsistency between http and https (#829) 2025-06-27 09:38:04 +12:00
James Carppe 7d7ae24351 Updates for release 2.31.2 (#834) 2025-06-26 15:41:23 +12:00
James Carppe 97838e614d Updates for release 2.27.8 (#827) 2025-06-25 17:11:58 +12:00
Steven Kang c897baad20 fix: fetching values from both install and upgrade views - develop [R8S-368] (#820) 2025-06-24 15:46:10 +12:00
andres-portainer d51e9205d9 fix(endpointrelation): use a read-write transaction for mutations BE-11964 (#819) 2025-06-20 20:03:35 -03:00
James Carppe e051c86bb5 Updates for release 2.31.1 (#816) 2025-06-19 14:07:18 +12:00
Steven Kang c2b48cd003 feat(k8s): CloudNativePG in applications list and details - [R8S-357] (#777) 2025-06-19 09:03:52 +12:00
James Carppe a7009eb8d5 Update bug report template for 2.27.7 (#805) 2025-06-17 12:52:12 +12:00
andres-portainer 036b87b649 fix(middlewares): fix data race in WithEndpoint() BE-11949 (#803) 2025-06-16 12:56:51 -03:00
Steven Kang f07a3b1875 security: cve-2025-22874 & cve-2025-22871 bump go to 1.23.10 (#798) 2025-06-12 17:30:53 +12:00
Yajith Dayarathna 6e89ccc0ae fix(api-documentation): swagger document genration error (#795) 2025-06-12 13:39:34 +12:00
James Carppe cc67612432 Update bug report template for 2.31.0 (#793) 2025-06-12 13:26:25 +12:00
Malcolm Lockyer 17ebe221bb chore: bump version to 2.31.0 (#789) 2025-06-10 16:47:17 +12:00
Ali 1963edda66 feat(helm): add registry dropdown [r8s-340] (#779) 2025-06-09 20:08:50 +12:00
Cara Ryan c9e3717ce3 fix(kubernetes): Display more than 10 workloads under Helm expandable in the Applications view [R8S-339] (#781) 2025-06-09 15:12:24 +12:00
Oscar Zhou 9a85246631 fix(edgestack): display deploying status by default after creating edgestack [BE-11924] (#783) 2025-06-07 09:06:57 +12:00
andres-portainer 75f165d1ff feat(edgestackstatus): optimize the Edge Stack structures BE-11740 (#756) 2025-06-05 19:46:10 -03:00
Viktor Pettersson eaf0deb2f6 feat(update-schedules): new update schedules view [BE-11754, BE-11887] (#686) 2025-06-05 17:03:43 +12:00
Ali a9061e5258 feat(helm): enhance helm chart install [r8s-341] (#766) 2025-06-05 13:13:45 +12:00
James Player caac45b834 feat(UI): Add repository url to Helm chart installation list items (#769) 2025-06-05 10:14:39 +12:00
LP B 24ff7a7911 chore(deps): upgrade docker/cli to v28.2.1 | docker/docker to v28.2.1 | docker/compose to v2.36.2 (#758) 2025-05-30 09:12:27 +02:00
Devon Steenberg b767dcb27e fix(proxy): whitelist headers for proxy to forward [BE-11819] (#665) 2025-05-30 11:49:23 +12:00
Cara Ryan 731afbee46 feat(helm): filter on chart versions at API level [R8S-324] (#754) 2025-05-27 15:20:28 +12:00
Cara Ryan 07dfd981a2 fix(kubernetes): events api to call the backend [R8S-243] (#563) 2025-05-27 13:55:31 +12:00
Cara Ryan 32ef208278 Revert "feat(helm): filter on chart versions at API level [R8S-324]" (#753) 2025-05-26 16:58:53 +12:00
Cara Ryan a80b185e10 feat(helm): filter on chart versions at API level [R8S-324] (#747) 2025-05-26 14:10:38 +12:00
Malcolm Lockyer b96328e098 fix(async-perf): In async poll snapshot handling, reduce redundant json marshal [be-11861] (#726) 2025-05-23 12:42:45 +12:00
Devon Steenberg 45471ce86d fix(docker): check len of device capabilities [BE-11898] (#750) 2025-05-22 14:27:14 +12:00
Viktor Pettersson 1bc91d0c7c fix(edge-update): set edge stack status to EdgeStackStatusError to avoid redeployment of portainer-updater [BE-11855] (#714) 2025-05-20 08:28:40 +02:00
James Carppe 799325d9f8 Update bug report template for 2.30.1 (#749) 2025-05-20 14:40:43 +12:00
James Carppe b540709e03 Update bug report template for 2.30.0 (#737) 2025-05-15 12:09:28 +12:00
Oscar Zhou 44daab04ac fix(libclient): option to disable external http request [BE-11696] (#719) 2025-05-15 09:54:35 +12:00
Ali ee65223ee7 chore: bump version to 2.30.0 (#735) 2025-05-14 17:35:05 +12:00
Ali d49fcd8f3e feat(helm): make the atomic flag optional [r8s-314] (#733) 2025-05-14 16:31:42 +12:00
Ali 4ee349bd6b feat(helm): helm actions [r8s-259] (#715)
Co-authored-by: James Player <james.player@portainer.io>
Co-authored-by: Cara Ryan <cara.ryan@portainer.io>
Co-authored-by: stevensbkang <skan070@gmail.com>
2025-05-13 22:15:04 +12:00
Steven Kang dfa32b6755 chore: add KaaS deprecation notice (#727)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2025-05-13 16:33:14 +12:00
Ali 0b69729173 chrore(microk8s): add deprecation notice [r8s-320] (#728) 2025-05-13 14:28:42 +12:00
Steven Kang 3b313b9308 fix(kubectl): rollout restart [r8s-322] (#729) 2025-05-13 11:35:44 +12:00
Devon Steenberg 1abdf42f99 feat(libstack): expose env vars with PORTAINER_ prefix [BE-11661] (#687) 2025-05-12 11:18:04 +12:00
andres-portainer 9fdc535d6b fix(csrf): skip the trusted origins check for plain-text HTTP requests BE-11832 (#710)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
2025-05-09 14:39:29 +12:00
James Carppe b9b734ceda Update bug report template for 2.27.6 (#721) 2025-05-09 14:39:15 +12:00
Viktor Pettersson 3b05505527 fix(update-schedules): display enriched error logs for agent updates [BE-11756] (#693) 2025-05-08 10:24:20 +02:00
Steven Kang bc29419c17 refactor: replace the kubectl binary with the upstream sdk (#524) 2025-05-07 20:40:38 +12:00
James Carppe 4d4360b86b Update bug report template for 2.27.5 (#705) 2025-05-02 13:14:39 +12:00
James Carppe 8cc28761d7 Update bug report template for 2.29.2 (#692) 2025-04-24 16:47:31 +12:00
Viktor Pettersson 24b3499c70 fix(dependencies): downgrade gorilla/csrf to v1.7.2 (#684) 2025-04-24 12:13:45 +12:00
Devon Steenberg 4e4fd5a4b4 fix(validate): refactor validate functions [BE-11574] (#683) 2025-04-24 08:59:44 +12:00
Devon Steenberg 1a3df54c04 fix(govalidator): replace govalidator dependency [BE-11574] (#673) 2025-04-23 13:59:51 +12:00
James Carppe 3edacee59b Update bug report template for 2.29.1 (#682) 2025-04-23 13:35:20 +12:00
andres-portainer f25d31b92b fix(code): remove dead code and reduce duplication BE-11826 (#680) 2025-04-22 18:09:36 -03:00
Ali c91c8a6467 feat(helm): rollback helm chart [r8s-287] (#660) 2025-04-23 08:58:34 +12:00
Ali 61d6ac035d feat(helm): auto refresh helm resources [r8s-298] (#672) 2025-04-23 08:58:21 +12:00
Oscar Zhou 9a9373dd0f fix: cve-2025-22871 [BE-11825] (#678) 2025-04-22 21:29:39 +12:00
andres-portainer e319a7a5ae fix(linter): enable ineffassign BE-10204 (#669) 2025-04-21 19:27:14 -03:00
andres-portainer 342549b546 fix(validate): remove dead code BE-11824 (#671) 2025-04-21 18:59:51 -03:00
Ali bbe94f55b6 feat(helm): uninstall helm app from details view [r8s-285] (#648) 2025-04-22 09:52:52 +12:00
andres-portainer 6fcf1893d3 fix(code): remove duplicated code BE-11821 (#667) 2025-04-18 17:34:34 -03:00
Ali 01afe34df7 fix(namespaces): fix service not found error [r8s-296] (#664) 2025-04-17 12:29:37 +12:00
Devon Steenberg be3e8e3332 fix(proxy): don't forward sensitive headers [BE-11819] (#654) 2025-04-16 15:30:56 +12:00
James Carppe cf31700903 Update bug report template for 2.29.0 (#655) 2025-04-16 13:34:38 +12:00
andres-portainer 66dee6fd06 fix(codemirror): optimize the autocompletion performance R8S-294 (#650)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2025-04-16 12:27:30 +12:00
andres-portainer bfa55f8c67 fix(logs): remove duplicated code BE-11821 (#653) 2025-04-15 17:16:04 -03:00
James Carppe 5a2318d01f Update bug report template for 2.27.4 (#646) 2025-04-15 13:50:14 +12:00
Steven Kang 7de037029f security: cve-2025-30204 and other low ones - develop [BE-11781] (#638) 2025-04-15 09:58:55 +12:00
andres-portainer 730c1115ce fix(proxy): remove code duplication BE-11627 (#644) 2025-04-14 17:46:40 -03:00
Oscar Zhou 2c37f32fa6 version: bump version to 2.29.0 (#637) 2025-04-14 13:13:38 +12:00
LP B 7aa9f8b1c3 Revert "feat(app): 1s staleTime to avoid sending repeated requests" (#639) 2025-04-14 11:12:11 +12:00
LP B c331ada086 feat(app): 1s staleTime to avoid sending repeated requests (#607) 2025-04-14 09:05:48 +12:00
Oscar Zhou ebc25e45d3 fix(edge): redeploy edge stack doesn't apply to std agents [BE-11766] (#633) 2025-04-12 10:24:23 +12:00
andres-portainer f82921d2a1 fix(edgestacks): fix edge stack update when using Git BE-11766 (#629) 2025-04-10 20:12:27 -03:00
Ali d68fe42918 fix(apps): better align sub tables [r8s-255] (#617) 2025-04-11 08:39:39 +12:00
Oscar Zhou 823f2a7991 fix(edge): missing env var in async agent docker snapshot [BE-11709] (#625) 2025-04-11 08:26:11 +12:00
Ali 0ca9321db1 feat(helm): update helm view [r8s-256] (#582)
Co-authored-by: Cara Ryan <cara.ryan@portainer.io>
Co-authored-by: James Player <james.player@portainer.io>
Co-authored-by: stevensbkang <skan070@gmail.com>
2025-04-10 16:08:24 +12:00
James Player 46eddbe7b9 fix(UI): Make sure localStorage.getUserId actually returns user id R8S-290 (#623) 2025-04-09 09:09:07 +12:00
James Player 64c796a8c3 fix(kubernetes): Config maps and secrets show as unused BE-11684 (#596)
Co-authored-by: stevensbkang <skan070@gmail.com>
2025-04-08 12:52:21 +12:00
James Player 264ff5457b chore(kubernetes): Migrate Helm Templates View to React R8S-239 (#587) 2025-04-08 12:51:36 +12:00
LP B ad89df4d0d refactor(app): reword docker security features (#608) 2025-04-07 17:14:51 +02:00
Anthony Lapenna 0f10b8ba2b api: update TeamInspect doc (#618) 2025-04-07 11:25:23 +12:00
Oscar Zhou 940bf990f9 fix(edgeconfig): add edge config file interpolation info message on edge stack page [BE-11741] (#606) 2025-04-04 11:56:42 +13:00
Devon Steenberg 1b8fbbe7d7 fix(libstack): compose project working directory [BE-11751] (#600) 2025-04-04 09:07:35 +13:00
James Player f6f07f4690 improvement(kubernetes): right align tags in datatables R8S-250 (#601)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2025-04-03 14:18:31 +13:00
Anthony Lapenna 3800249921 api: use response code 200 (#604) 2025-04-03 11:12:24 +13:00
Oscar Zhou a5d857d5e7 feat(docker): add --pull-limit-check-disabled cli flag [BE-11739] (#581) 2025-04-03 09:13:01 +13:00
Devon Steenberg 4c1e80ff58 fix(axios): correctly encode urls [BE-11648] (#517)
fix(edgegroup): nil pointer defer
2025-04-02 08:51:58 +13:00
Oscar Zhou 7e5db1f55e refactor(edgegroup): optimize edge group search performance [BE-11716] (#579) 2025-04-01 14:05:56 +13:00
Anthony Lapenna 1edc56c0ce api: remove name from edgegroupupdate payload validation (#588) 2025-04-01 13:25:09 +13:00
Anthony Lapenna 4066a70ea5 api: fix typo in operation name (#585) 2025-04-01 13:24:55 +13:00
andres-portainer a0d36cf87a fix(server): add panic logging middleware BE-11750 (#599) 2025-03-31 18:58:20 -03:00
Viktor Pettersson 1d12011eb5 fix(edge groups): make large edge groups editable [BE-11720] (#558) 2025-03-28 15:16:05 +01:00
Steven Kang 7c01f84a5c fix: improve the node view for detecting roles - develop (#354) 2025-03-28 10:52:59 +13:00
Ali 81c5f4acc3 feat(editor): provide yaml validation for docker compose in the portainer web editor [BE-11697] (#526) 2025-03-27 17:11:55 +13:00
Ali 0ebfe047d1 feat(helm): use helm upgrade for install [r8s-258] (#568) 2025-03-26 11:32:26 +13:00
samdulam e68bd53e30 Update bug_report template with 2.27.3 (#572) 2025-03-25 08:40:15 +05:30
andres-portainer cdd9851f72 fix(stubs): clean up the stubs and mocks BE-11722 (#557) 2025-03-24 19:56:08 -03:00
andres-portainer 995c3ef81b feat(snapshots): avoid parsing raw snapshots when possible BE-11724 (#560) 2025-03-24 19:33:05 -03:00
James Player 0dfde1374d fix(kubernetes): Cluster reservation CPU not showing R8S-268 (#569) 2025-03-25 10:59:28 +13:00
Devon Steenberg 34235199dd fix(libstack): correctly load COMPOSE_* env vars [BE-11474] (#536) 2025-03-25 08:57:23 +13:00
Anthony Lapenna 5d1cd670e9 docs: review TeamMembershipCreate API operation (#565) 2025-03-24 09:55:33 +13:00
Anthony Lapenna 1d8ea7b0ee docs: review TeamUpdate API operation (#564) 2025-03-21 16:45:43 +13:00
Oscar Zhou 4b218553c3 fix(libstack): data loss for stack with relative path [FR-437] (#548) 2025-03-21 09:19:25 +13:00
Viktor Pettersson a61c1004d3 fix(agent-updates): fix remote agent updates cannot be scheduled properly for large edge groups [BE-11691] (#528) 2025-03-20 10:05:15 +01:00
James Carppe 5d1b42b314 Update bug report template for 2.28.1 (#549) 2025-03-20 15:54:53 +13:00
Oscar Zhou 4b992c6f3e fix(k8s/config): force insecure-skip-tls-verify option for internal use [BE-11706] (#537) 2025-03-20 08:49:27 +13:00
Viktor Pettersson 38562f9560 fix(api): remove duplicated /users/me route [BE-11689] (#516) 2025-03-19 13:08:03 +01:00
James Carppe c01f0271fe Update bug report template for 2.27.2 (#539) 2025-03-19 17:41:36 +13:00
andres-portainer 0296998fae fix(users): optimize the /users/me API endpoint BE-11688 (#515)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
Co-authored-by: JamesPlayer <james.player@portainer.io>
2025-03-18 17:55:53 -03:00
James Player a67b917bdd Bump version to 2.28.0 (#523) 2025-03-17 16:00:33 +13:00
Steven Kang 2791bd123c fix: cve-2025-22869 develop (#511) 2025-03-17 12:24:39 +13:00
andres-portainer e1f9b69cd5 feat(edgestack): improve the structure to make JSON operations faster BE-11668 (#475) 2025-03-15 10:10:17 -03:00
andres-portainer 2c05496962 feat(edgeconfigs): parse .env config files for interpolation BE-11673 (#514) 2025-03-15 10:09:22 -03:00
Oscar Zhou 66bcf9223a fix(k8s/config): avoid hardcoded "insecure-skip-tls-verify" in kubeconfig [BE-11651] (#500) 2025-03-14 11:20:41 +13:00
James Player 993f69db37 chore(app): Migrate helm templates list to react (#492) 2025-03-14 10:37:14 +13:00
Ali 58317edb6d fix(namespaces): only show namespaces with access [r8s-251] (#501) 2025-03-14 07:57:06 +13:00
Steven Kang 417891675d fix: ensure no non-admin users have access to system namespaces (#499) 2025-03-13 16:43:56 +13:00
Steven Kang 8b7aef883a fix: display unscheduled applications (#496)
Co-authored-by: JamesPlayer <james.player@portainer.io>
2025-03-13 14:13:18 +13:00
Ali b5961d79f8 refactor(helm): helm binary to sdk refactor [r8s-229] (#463)
Co-authored-by: stevensbkang <skan070@gmail.com>
2025-03-13 12:20:16 +13:00
LP B 0d25f3f430 fix(app): restore gitops update options (#419) 2025-03-12 14:00:31 +01:00
Steven Kang 798fa2396a feat: kubernets service - display external hostname (#486) 2025-03-12 22:34:00 +13:00
James Player 28b222fffa fix(app): Make sure empty tables don't have select all rows checkbox checked (#489) 2025-03-12 10:34:07 +13:00
James Player b57855f20d fix(app): datatable global checkbox doesn't reflect the selected state (#470) 2025-03-10 09:21:20 +13:00
Cara Ryan 438b1f9815 fix(helm): Remove duplicate helm instructions in CE [BE-11670] (#482) 2025-03-06 09:35:31 +13:00
LP B 2bccb3589e fix(app/images): nodeName on images list links (#484) 2025-03-05 16:04:16 +01:00
James Player 52bb06eb7b chore(helm): Convert helm details view to react (#476) 2025-03-03 11:29:58 +13:00
Malcolm Lockyer 8e6d0e7d42 perf(endpointrelation): Part 2 of fixing endpointrelation perf [be-11616] (#471) 2025-02-28 14:41:54 +13:00
Steven Kang 5526fd8296 chore: bump 2.27.1 - develop (#468) 2025-02-27 11:02:25 +13:00
Anthony Lapenna a554a8c49f api: remove server-ce swagger.json (#467) 2025-02-26 16:10:02 +13:00
James Player 7759d762ab chore(react): Convert cluster details to react CE (#466) 2025-02-26 14:13:50 +13:00
Oscar Zhou dd98097897 fix(libstack): miss to read default .env file [BE-11638] (#458) 2025-02-26 13:00:25 +13:00
Steven Kang cc73b7831f fix: cve-2024-50338 - develop (#461) 2025-02-25 12:55:44 +13:00
James Carppe 9c243cc8dd Update bug report template for 2.27.0 (#450) 2025-02-20 13:38:26 +13:00
Oscar Zhou 5d568a3f32 fix(edge): edge stack pending when yaml file is under same root folder of edge configs [BE-11620] (#447) 2025-02-20 12:09:26 +13:00
Steven Kang 1b83542d41 chore: bump version to 2.27.0 - develop (#445) 2025-02-20 09:42:52 +13:00
LP B cf95d91db3 fix(swarm): keep swarm stack stop command attached (#444) 2025-02-19 19:25:28 +01:00
Viktor Pettersson 41c1d88615 fix(edge): configure persisted mTLS certificates on start-up [BE-11622] (#437)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
Co-authored-by: Oscar Zhou <100548325+oscarzhou-portainer@users.noreply.github.com>
2025-02-19 14:46:39 +13:00
Steven Kang df8673ba40 version: bump version to 2.27.0-rc3 - develop (#426) 2025-02-14 08:39:02 +13:00
andres-portainer 96b1869a0c fix(swarm): fix the Host field when listing images BE-10827 (#352)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2025-02-12 00:47:45 +01:00
Oscar Zhou e45b852c09 fix(platform): remove error log when local env is not found [BE-11353] (#364) 2025-02-12 09:23:52 +13:00
Steven Kang 2d3e5c3499 workaround: leave the globally set helm repo to empty and add disclaimer - develop (#409) 2025-02-11 15:36:29 +13:00
Oscar Zhou b25bf1e341 fix(podman): missing filter in homepage [BE-11502] (#404) 2025-02-10 21:08:27 +13:00
Oscar Zhou 4bb80d3e3a fix(setting): failed to persist edge computer setting [BE-11403] (#395) 2025-02-10 21:05:15 +13:00
Steven Kang 03575186a7 remove deprecated api endpoints - develop [BE-11510] (#399) 2025-02-10 10:46:36 +13:00
Steven Kang 935c7dd496 feat: improve diagnostics stability - develop (#355) 2025-02-10 10:45:47 +13:00
Steven Kang 1b2dc6a133 version: bump version to 2.27.0-rc2 - develop (#402) 2025-02-07 14:47:49 +13:00
Steven Kang d4e2b2188e chore: bump go version to 1.23.5 develop (#392) 2025-02-07 08:48:19 +13:00
viktigpetterr 9658f757c2 fix(endpoints): use the post method for batch delete API operations [BE-11573] (#394) 2025-02-06 18:14:43 +01:00
Ali 371e84d9a5 fix(podman): create new image from a container in podman [r8s-90] (#347) 2025-02-05 20:22:33 +13:00
Steven Kang 5423a2f1b9 security: cve-2025-21613 develop (#390) 2025-02-05 15:56:30 +13:00
Oscar Zhou 7001f8e088 fix(edge): check all endpoint_relation db query logic [BE-11602] (#378) 2025-02-05 15:20:20 +13:00
Steven Kang 678cd54553 security: cve-2024-45338 develop (#386) 2025-02-05 15:03:39 +13:00
Oscar Zhou bc19d6592f fix(libstack): cannot open std edge stack log page [BE-11603] (#384) 2025-02-05 12:17:51 +13:00
James Player 5af0859f67 fix(datatables): "Select all" should select only elements of the current page (#376) 2025-02-04 15:34:33 +13:00
Oscar Zhou 379711951c fix(edgegroup): failed to associate env to static edge group [BE-11599] (#368) 2025-02-04 09:41:24 +13:00
LP B a50a9c5617 fix(app/edge): edge stacks webhooks cannot be disabled once created (#372) 2025-02-03 20:50:24 +01:00
LP B c0d30a455f fix(api/edge): backend panic on edge stack removal (#371) 2025-02-03 20:25:25 +01:00
LP B 9a3f6b21d2 feat(app/service-details): hide view while loading data (#348) 2025-02-03 14:20:35 +01:00
Steven Kang 9ea41f68bc version: bump version to 2.27.0-rc1 (#363)
Co-authored-by: steven <steven@stevens-Mini.hub>
2025-02-03 11:38:38 +13:00
James Player e943aa8f03 feat(documentation): change docs to use LTS/STS instead of version number (#357) 2025-02-03 11:17:36 +13:00
James Player 17a4750d8e fix(kubernetes): Resource reservation wasn't displaying properly in business edition and remove leader status (#362) 2025-02-03 11:02:23 +13:00
Malcolm Lockyer 7d18c22aa1 fix(ui): bring back k8s applications page row expand published urls [r8s-145] (#356) 2025-01-31 13:16:18 +13:00
Ali c80cc6e268 chore(automation): give unique selectors [r8s-168] (#345)
Co-authored-by: JamesPlayer <james.player@portainer.io>
2025-01-30 15:42:32 +13:00
andres-portainer b30a1b5250 fix(edgestacks): avoid repeated statuses BE-11561 (#351) 2025-01-27 16:00:05 -03:00
LP B b753371700 fix(app/edge-stack): edge stack create form validation (#343) 2025-01-24 17:02:52 +01:00
andres-portainer 3ca5ab180f fix(system): optimize the memory usage when counting nodes BE-11575 (#342) 2025-01-23 20:41:09 -03:00
Ali 4971f5510c fix(app): edit app with configmap [r8s-95] (#341) 2025-01-24 11:35:47 +13:00
andres-portainer 20fa7e508d fix(edgestacks): decouple the EdgeStackStatusUpdateCoordinator so it can be used by other packages BE-11572 (#340) 2025-01-23 17:10:46 -03:00
James Player ebffc340d9 fix(k8s): Changed 'Deploy from file' button text to 'Deploy from code' (#338) 2025-01-23 16:47:52 +13:00
andres-portainer 9a86737caa fix(edgestacks): add a status update coordinator to increase performance BE-11572 (#337) 2025-01-22 20:24:54 -03:00
Steven Kang d35d8a7307 feat(oauth): fix mapping (#330) 2025-01-23 09:03:51 +13:00
andres-portainer 701ff5d6bc refactor(edgestacks): move handlerDBErr() out of the handler BE-11572 (#336) 2025-01-22 16:35:06 -03:00
LP B 9044b25a23 fix(app): remove passwords from registries list response (#334) 2025-01-22 17:40:21 +01:00
Ali 7f089fab86 fix(apps): use replicas from application spec [r8s-142] (#335) 2025-01-22 12:31:27 +13:00
James Carppe a259c28678 Update bug report template for 2.26.1 (#329) 2025-01-21 16:19:03 +13:00
LP B db48da185a fix(app/editor): reduce editor slowness by debouncing onChange calls (#326) 2025-01-17 22:41:06 +01:00
LP B cab667c23b fix(app/edge-stack): UI notification on creation error (#325) 2025-01-17 20:33:01 +01:00
andres-portainer 154ca9f1b1 fix(edge): return proper error from context BE-11564 (#323) 2025-01-16 20:18:51 -03:00
Oscar Zhou 2abe40b786 fix(edgestack): remove project folder after deleting edgestack [BE-11559] (#320) 2025-01-16 09:16:09 +13:00
James Carppe 6be2420b32 Update bug report template for 2.26.0 (#319) 2025-01-15 14:38:59 +13:00
Ali 9405cc0e04 chore(portainer): bump version to 2.26.0 (#302) 2025-01-14 07:20:11 +13:00
Yajith Dayarathna 55c98912ed feat(omni): support for omni [R8S-75] (#105)
Co-authored-by: stevensbkang <skan070@gmail.com>
Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Malcolm Lockyer <segfault88@users.noreply.github.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2025-01-13 17:06:10 +13:00
Ali 45bd7984b0 fit(jobs): remove redundant checkboxes in executions datatable [r8s-182] (#295) 2025-01-12 18:24:22 +13:00
andres-portainer 1ed9a0106e feat(edge): optimize Edge Stack retrieval BE-11555 (#294) 2025-01-10 16:44:19 -03:00
LP B f8b2ee8c0d fix(app/edge-stack): local filesystem path is not retained (#292) 2025-01-10 18:20:44 +01:00
Steven Kang d32b0f8b7e feat(kubernetes): support for jobs and cron jobs - r8s-182 (#260)
Co-authored-by: James Carppe <85850129+jamescarppe@users.noreply.github.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: andres-portainer <91705312+andres-portainer@users.noreply.github.com>
Co-authored-by: Oscar Zhou <100548325+oscarzhou-portainer@users.noreply.github.com>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2025-01-10 13:21:27 +13:00
andres-portainer 24fdb1f600 fix(libstack): redirect the Docker and Compose logging to zerolog BE-11518 (#289) 2025-01-08 16:26:04 -03:00
Oscar Zhou 4010174f66 fix(docker/volume): failed to list volume before snapshot is created [BE-11544] (#286) 2025-01-08 09:45:13 +13:00
andres-portainer e2b812a611 fix(edgestacks): check the version of the edge stack before updating the status BE-11488 (#287) 2025-01-07 17:31:57 -03:00
andres-portainer d72b3a9ba2 feat(edgestacks): optimize the Edge Stack status update endpoint BE-11539 (#279) 2025-01-06 15:39:24 -03:00
LP B 85f52d2574 feat(app/stack): ability to prune volumes on stack/edge stack delete (#232)
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
2025-01-01 10:44:49 +13:00
andres-portainer 33ea22c0a9 feat(ssl): improve caching behavior BE-11527 (#273) 2024-12-30 11:10:13 -03:00
andres-portainer 0d52f9dd0e feat(async): avoid sending CSRF token for async edge polling requests BE-1152 (#272) 2024-12-30 10:58:44 -03:00
andres-portainer 3caffe1e85 feat(async): filter out Docker snapshot diffs without meaningful changes BE-11527 (#265) 2024-12-26 18:45:20 -03:00
Oscar Zhou 87b8dd61c3 fix: replace strings.ToLower with strings.EqualFold [BE-11524] (#263) 2024-12-24 11:15:16 +13:00
andres-portainer ad77cd195c fix(docker): fix a data race in the Docker transport BE-10873 (#255) 2024-12-23 09:54:11 -03:00
James Carppe eb2a754580 Update bug report template for 2.21.5 / 2.25.1 (#261) 2024-12-20 14:39:33 +13:00
Steven Kang 9258db58db feat(auth): add 30m session timeout - r8s-178 (#259) 2024-12-20 10:49:13 +13:00
andres-portainer 8d1c90f912 fix(platform): fix a data race in GetPlatform() BE-11522 (#253) 2024-12-19 09:37:50 -03:00
Steven Kang 1c62bd6ca5 fix: security - CVE-2024-45337 - portainer-suite develop (#247) 2024-12-19 10:55:34 +13:00
andres-portainer 13317ec43c feat(stacks): simplify WaitForStatus() BE-11505 (#241) 2024-12-17 16:25:49 -03:00
James Carppe 35dcb5ca46 Update bug report template for 2.25.0 (#245) 2024-12-16 13:53:15 +13:00
AndrewHucklesby 4454b6b890 bump version to 2.25.0 (#240) 2024-12-12 16:42:55 +13:00
Ali 117e3500ae fix(edge-stack): revert useEffect, to call matchRegistry less often [BE-11501] (#239) 2024-12-12 15:22:19 +13:00
andres-portainer 94fda6a720 fix(offlinegate): avoid leaking an RLock when the handler panics BE-11495 (#234) 2024-12-11 16:38:03 -03:00
Ali e1388eff84 fix(annotations): parse annotation keys in angular forms [r8s-170] (#233) 2024-12-11 17:50:08 +13:00
Ali 94d2e32b49 fix(apps): simplify helm status [r8s-155] (#230) 2024-12-11 13:18:34 +13:00
Ali 069f22afa4 fix(services): separate table state [BE-11401] (#152) 2024-12-11 11:58:43 +13:00
LP B 52c90d4d0a feat(app/edge-stack): ability to prune containers on edge stack update (#216) 2024-12-10 22:54:02 +01:00
Ali ce7e0d8d60 refactor(namespace): migrate namespace edit to react [r8s-125] (#38) 2024-12-11 10:15:46 +13:00
Oscar Zhou 40c7742e46 fix(edgestack): validate edge stack name for api [BE-11365] (#222) 2024-12-11 08:21:46 +13:00
Malcolm Lockyer 05e872337a feat(support): add db and activity db file size to support bundle [r8s-169] (#221) 2024-12-10 09:35:30 +13:00
Ali aac9d001f7 feat(askai): hide askAI for CE [BE-11409] (#220) 2024-12-10 09:11:51 +13:00
andres-portainer d295968948 feat(libstack): update Compose to v2.31.0 BE-11416 (#223) 2024-12-09 16:36:57 -03:00
Ali 97e7a3c5e2 fix(edge-stacks): various custom template issues [BE-11414] (#189) 2024-12-09 17:48:34 +13:00
Ali 16a1825990 feat(version): remove brackets for sts/lts [BE-11409] (#215) 2024-12-06 22:52:47 +13:00
Ali 441afead10 feat(ask-ai): integrate kapa-ai page [BE-11409] (#214) 2024-12-06 18:41:32 +13:00
Malcolm Lockyer 783ab253af feat(support): collect system info bundle to assist support troubleshooting [r8s-157] (#154) 2024-12-06 15:38:10 +13:00
Yajith Dayarathna 17648d12fe codecov integration with portainer-suite [PLA-119] (#210) 2024-12-06 12:09:09 +13:00
andres-portainer 2f4f1be99c feat(performance): increase HTTP compression performance BE-11417 (#211) 2024-12-05 19:10:56 -03:00
Ali 5d4d3888b8 fix(rbac): use team ids to get namespace access [r8s-154] (#209) 2024-12-05 17:29:45 +13:00
andres-portainer 473084e915 fix(edgestacks): remove edge stacks even after a system crash or power-off BE-10822 (#208) 2024-12-04 19:52:53 -03:00
Anthony Lapenna a8147b9713 build: tidy up packages by removing unused scripts and files (#207) 2024-12-05 11:18:49 +13:00
Yajith Dayarathna 3c3dc547b2 fix(app/edge-stack): hide non-working BE fields from CE (#205)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2024-12-04 19:00:40 +01:00
James Carppe c5accd0f16 Update bug report template for 2.24.1 (#191) 2024-12-04 08:34:59 +13:00
Oscar Zhou cb949e443e fix(volume): unable to inspect and browse volume [BE-11216] (#186) 2024-12-03 09:10:10 +13:00
Anthony Lapenna bb6815f681 build: introduce central Makefile and live-reload for Go (#184) 2024-12-03 08:49:03 +13:00
Anthony Lapenna a261f60764 version: display dependencies versions (#188)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2024-12-03 08:45:44 +13:00
LP B d393529026 fix(app): passing an initial table state overrides the default global filter state (#180) 2024-11-29 21:06:11 +01:00
Oscar Zhou 219c9593e0 fix(container): binding ip disappear after duplicate container [BE-11413] (#177) 2024-11-29 08:56:44 +13:00
andres-portainer faa6b2b790 fix(libstack): add the build step for Compose BE-11448 (#173) 2024-11-27 18:43:25 -03:00
Oscar Zhou 4046bf7b31 feat(image): build image with file [BE-11372] (#171) 2024-11-27 18:33:35 -03:00
Ali 4f708309af fix(activity logs): decode base64 [BE-11418] (#172) 2024-11-28 08:54:32 +13:00
andres-portainer f2e7680bf3 fix(compose): fix path resolution for env files BE-11428 (#167) 2024-11-26 22:09:58 -03:00
andres-portainer 5d2689b139 fix(compose): avoid creating a default network unnecessarily BE-11427 (#169) 2024-11-26 19:48:49 -03:00
andres-portainer 145ffeea40 fix(libstack): resolve env vars correctly in Compose BE-11420 (#166) 2024-11-26 18:09:12 -03:00
andres-portainer 13143bc7ea fix(libstack): fix environment variable handling in compose BE- (#165) 2024-11-26 17:37:22 -03:00
Oscar Zhou ee0dbf2d22 feat(init): allow to customize kubectl-shell image by cli flag [BE-11419] (#162) 2024-11-26 10:17:46 +13:00
andres-portainer 4265ae4dae feat(offlinegate): improve error message BE-11402 (#163) 2024-11-25 17:40:17 -03:00
andres-portainer 821c1fdbef feat(swarm): do not prevent server startup when Swarm config.json file is invalid BE-11402 (#160) 2024-11-25 17:40:10 -03:00
andres-portainer fe29d6aee3 feat(backup): reduce the locking time of the offline gate BE-11402 (#157) 2024-11-25 10:10:11 -03:00
Ali c0c7144539 fix(app templates): load app template for deployment [BE-11382] (#141) 2024-11-25 17:41:09 +13:00
Anthony Lapenna 20e3d3a15b fix: review snapshot and post init migration logic (#158) 2024-11-25 11:03:12 +13:00
James Carppe 07d1eedae3 Update template to include lifecycle policy link (#156) 2024-11-21 17:11:20 +13:00
James Carppe 4ad3d70739 Update bug report template for 2.24.0 (#153) 2024-11-20 13:15:56 +13:00
andres-portainer e6a1c29655 fix(compose): fix support for ECR BE-11392 (#151) 2024-11-18 16:42:53 -03:00
Yajith Dayarathna 333dfe1ebf refactor(edge/update): choose images from registry [BE-10964] (#6)
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
2024-11-18 14:11:26 +13:00
andres-portainer c59872553a fix(stacks): pass the registry credentials to Compose stacks BE-11388 (#147)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2024-11-18 08:39:13 +13:00
andres-portainer 1a39370f5b fix(libstack): add missing private registry credentials BE-11388 (#143) 2024-11-15 17:38:55 -03:00
Oscar Zhou bc44056815 fix(swarm): failed to deploy app template [BE-11385] (#138) 2024-11-15 11:53:22 +13:00
andres-portainer 17c92343e0 fix(compose): avoid leftovers in Run() BE-11381 (#129) 2024-11-13 20:24:20 -03:00
andres-portainer cd6935b07a feat(edgestacks): add a retry period to edge stack deployments BE-11155 (#109)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2024-11-13 20:13:30 -03:00
andres-portainer 47d428f3eb fix(libstack): fix compose run BE-11381 (#126) 2024-11-13 14:38:53 -03:00
LP B 2baae7072f fix(edge/stacks): use default namespace when none is specified in manifest (#124) 2024-11-13 16:30:08 +13:00
andres-portainer 2e9e459aa3 fix(libstack): add a different timeout for WaitForStatus BE-11376 (#120) 2024-11-12 19:31:44 -03:00
andres-portainer 7444e2c1c7 fix(compose): provide the project name for proper validation BE-11375 (#118) 2024-11-12 17:18:40 -03:00
Oscar Zhou d6469eb33d fix(libstack): empty project name [BE-11375] (#116) 2024-11-12 10:20:45 -03:00
Ali a2da6f1827 fix(configmap): create portainer configmap if it doesn't exist [r8s-141] (#113) 2024-11-12 18:23:00 +13:00
Oscar Zhou e6508140f8 version: bump version to 2.24.0 (#102) 2024-11-12 12:13:27 +13:00
andres-portainer a7127bc74f feat(libstack): remove the docker-compose binary BE-10801 (#111)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
2024-11-11 19:05:56 -03:00
Malcolm Lockyer 55aa0c0c5d fix(ui): kubernetes create from file page - fix template load failed mistake in ce (#112) 2024-11-12 10:46:37 +13:00
Ali d25de4f459 fix(more-resources): address CE review comments [r8s-103] (#110) 2024-11-12 10:41:43 +13:00
Yajith Dayarathna 6d31f4876a fix(more resources): fix porting and functionality [r8s-103] (#8)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2024-11-12 09:55:30 +13:00
Steven Kang e6577ca269 kubernetes: improved the node view [r8s-47] (#108) 2024-11-12 09:42:14 +13:00
Ali 08d77b4333 fix(namespace): handle no accesses found [r8s-141] (#106) 2024-11-12 09:29:55 +13:00
Ali 1ead121c9b fix(apps): for helm uninstall, ignore manual associated resource deletion [r8s-124] (#103) 2024-11-12 09:03:22 +13:00
LP B ad19b4a421 fix(app): relocate Skip TLS switch next to git repo URL field (#107) 2024-11-11 17:16:37 +01:00
LP B 6bc52dd39c feat(edge): kubernetes WaitForStatus support (#85) 2024-11-11 14:02:20 +01:00
Malcolm Lockyer fd2b00bf3b fix(ui): kubernetes create from file page - fix template load failed message style [R8S-68] (#95) 2024-11-11 12:06:56 +13:00
Ali cd8c6d1ce0 fix(apps): don't delete the 'kubernetes' service or duplicate service names [r8s-124] (#90) 2024-11-11 08:26:56 +13:00
Ali e9fc6d5598 refactor(namespace): migrate namespace access view to react [r8s-141] (#87) 2024-11-11 08:17:20 +13:00
Steven Kang 8ed7cd80cb feat(ui): improve Kubernetes node view [r8s-47] (#84) 2024-11-07 14:10:19 +13:00
Malcolm Lockyer 81322664ea fix(ui): kubernetes create from manifest page misalignments and incorrect loading icon [R8S-68] (#88) 2024-11-07 09:04:24 +13:00
Ali 458d722d47 fix(ui): consistent widget padding [r8s-136] (#82) 2024-11-05 14:25:40 +13:00
Malcolm Lockyer 3c0d25f3bd fix(ui): rename create from manifest to create from file [BE-11335] (#86) 2024-11-05 14:10:08 +13:00
Oscar Zhou ca7e4dd66e fix(edge/async): onboarding agent without predefined group cannot be associated [BE-11281] (#83) 2024-11-05 09:32:25 +13:00
Ali c1316532eb fix(apps): update associated resources on deletion [r8s-124] (#75) 2024-11-01 21:03:49 +13:00
Ali d418784346 fix(rbac): revert rbac detection logic [r8s-137] (#81) 2024-11-01 19:28:23 +13:00
andres-portainer 1061601714 feat(activity-log): set descending timestamps as the default sorting order BE-11343 (#66) 2024-10-31 18:07:26 -03:00
andres-portainer 2f3d4a5511 fix(activity-log): fix broken sorting BE-11342 (#65) 2024-10-31 17:25:38 -03:00
LP B 9ea62bda28 fix(app/image-details): export images to tar (#40) 2024-10-31 17:40:01 +01:00
Steven Kang 94b1d446c0 fix(ingresses): load cluster wide ingresses [r8s-78] (#78) 2024-10-31 13:08:09 +13:00
Ali 6c57a00a65 fix(cluster): UI RBAC alert fix [r8s-138] (#72) 2024-10-31 10:12:56 +13:00
Yajith Dayarathna 8808531cd5 update ci trigger paths for portainer-ee - develop (#68) 2024-10-29 12:23:31 +13:00
andres-portainer 966fca950b fix(oauth): add a timeout to getOAuthToken() BE-11283 (#63) 2024-10-28 17:28:22 -03:00
Yajith Dayarathna e528cff615 bump golang version to 1.23.2 (#60) 2024-10-29 09:02:18 +13:00
andres-portainer 1d037f2f1f feat(websocket): improve websocket code sharing BE-11340 (#61) 2024-10-25 11:21:49 -03:00
James Carppe b2d67795b3 Update bug report template for 2.21.4 (#62) 2024-10-25 15:49:31 +13:00
Ali 959c527be7 refactor(apps): migrate applications view to react [r8s-124] (#28) 2024-10-25 12:28:05 +13:00
andres-portainer cc75167437 fix(swarm): fix service updates BE-11219 (#57) 2024-10-23 18:23:24 -03:00
andres-portainer 3114d4b5c5 fix(security): add initial support for HSTS and CSP BE-11311 (#47) 2024-10-21 13:52:11 -03:00
andres-portainer ac293cda1c feat(database): share more database code between CE and EE BE-11303 (#43) 2024-10-18 10:33:10 -03:00
Ali 7b88975bcb fix(applications): scale resource usage by pod count [r8s-127] (#33) 2024-10-16 14:33:45 +13:00
James Carppe da4b2e3a56 Updated bug report template for 2.23.0 (#32) 2024-10-16 09:23:02 +13:00
andres-portainer 369598bc96 Bump version to v2.23.0 (#29) 2024-10-14 13:55:11 -03:00
andres-portainer 61c5269353 fix(edgejobs): decouple the Edge Jobs from the reverse tunnel service BE-10866 (#11) 2024-10-14 10:37:13 -03:00
LP B 7a35b5b0e4 refactor(ui/code-editor): accept enum type (#22)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2024-10-14 13:52:51 +02:00
Yajith Dayarathna 20e9423390 chore: standalone repository workflow cleanup (#26) 2024-10-14 18:34:08 +13:00
Ali cf230a1cbc fix(k8s-volumes): add missing json labels tag [r8s-108] (#27) 2024-10-14 13:37:59 +13:00
Ali a06a09afcf fix(app): use standard resource request units [r8s-122] (#15) 2024-10-14 11:27:22 +13:00
Yajith Dayarathna c88382ec1f fix(apps): persist table settings [r8s-120] (#10)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2024-10-14 11:27:04 +13:00
Ali fd0bc652a9 fix(volumes): update external labels CE [r8s-108] (#7) 2024-10-14 10:48:13 +13:00
Ali 57e10dc911 fix(apps): group helm apps together [r8s-102] (#24) 2024-10-14 10:28:56 +13:00
Yajith Dayarathna 1110f745e1 fix(volumes): allow standard users to select volumes [r8s-109] (#9)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2024-10-12 13:01:27 +13:00
Oscar Zhou 811d03a419 chore: rm old .vscode.example folders in sub-repo [BE-11287] (#17)
Co-authored-by: deviantony <anthony.lapenna@portainer.io>
2024-10-11 16:10:16 +02:00
andres-portainer 666c031821 fix(git): optimize the git cloning process in terms of space BE-11286 (#20) 2024-10-10 18:49:50 -03:00
andres-portainer 4e457d97ad fix(linters): add back removed linters and extend them to CE BE-11294 2024-10-10 17:05:03 -03:00
andres-portainer 364e4f1b4e fix(linters): add back removed linters and extend them to CE BE-11294 2024-10-10 12:06:20 -03:00
andres-portainer 8aae557266 fix(stacks): run webhooks in background to avoid GitHub timeouts BE-11260 2024-10-09 17:28:19 -03:00
Yajith Dayarathna 2bd880ec29 required changes to enable monorepo.
Co-authored-by: deviantony <anthony.lapenna@portainer.io>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
2024-10-09 08:37:23 +13:00
Oscar Zhou b14438fd99 fix(edge): add agent id/name into edge api response [BE-10988] (#12256) 2024-10-08 19:17:09 +13:00
James Carppe ba96d8a5fb Update bug report template for 2.21.3 (#12309) 2024-10-08 16:24:16 +13:00
Ali db4b1dd024 fix(app): fix cpu type for decimals [r8s-107] (#12306) 2024-10-08 11:44:22 +13:00
Ali 469a4e94c2 fix(volumes): update the external, unused badges and used by col [r8s-105] (#12302) 2024-10-08 11:41:47 +13:00
Ali 44d6c0885e fix(node): call node usage [r8s-106] (#12304) 2024-10-08 11:39:05 +13:00
andres-portainer 9ce4ac9c9e fix(oauth): change the logging level from Debug to Error BE-4583 (#12305) 2024-10-07 18:21:05 -03:00
James Carppe b40d22dc74 Update bug report template for 2.22.0 (#12283) 2024-10-03 14:53:37 +13:00
Steven Kang a257696c25 fix access conditions when the restrict default namespace is enabled (#12280) 2024-10-02 15:55:05 +13:00
andres-portainer f742937359 fix(endpoints): optimize the search performance BE-11267 (#12262) 2024-10-01 15:13:54 -03:00
Steven Kang c0db48b29d fix ingress creation for none class (#12273) 2024-10-01 14:43:46 +13:00
Steven Kang ea228c3d6d refactor(k8s): namespace core logic (#12142)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: James Carppe <85850129+jamescarppe@users.noreply.github.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2024-10-01 14:15:51 +13:00
Ali da010f3d08 fix(podman): ensure initial env type matches container runtime [r8s-98] (#12259) 2024-09-30 09:16:24 +13:00
Ali 32e94d4e4e feat(podman): support add podman envs in the wizard [r8s-20] (#12056) 2024-09-25 11:55:07 +12:00
Ali db616bc8a5 fix(wizard): update nodeport placeholder [r8s-62] (#12255) 2024-09-25 11:36:50 +12:00
James Carppe b8b46ec129 Update bug report template for 2.21.2 (#12251) 2024-09-24 11:42:15 +12:00
LP B 7d0b79a546 fix(app/images): export images to tar (#12223) 2024-09-23 21:55:45 +02:00
LP B fd26565b14 fix(app/templates): non admins cannot load templates list (#12235) 2024-09-23 17:54:32 +02:00
Nik Wakelin e0b6f2283a chore(branding): Changes Linode to Akamai Connected Cloud (#12221) 2024-09-23 09:21:02 +12:00
Oscar Zhou d3d3d50569 fix(version): add specific version for updater image [BE-11153] (#12227) 2024-09-21 14:54:08 +12:00
andres-portainer cee997e0b3 fix(edgestacks): reorder operations to properly update the endpoint relations BE-11233 (#12239) 2024-09-20 19:10:28 -03:00
LP B 80f53ed6ec fix(api): skip guessing env when there is no env in DB (#12238) 2024-09-20 17:56:41 -03:00
Chaim Lev-Ari 6f84317e7a feat(system): upgrade on swarm [EE-5848] (#11728)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2024-09-20 18:00:38 +02:00
LP B 3cb484f06a fix(app/users): password validation hint + missing message on empty teams list (#12231) 2024-09-20 16:33:13 +02:00
LP B 61353cbe8a fix(app/edge): race between redirects when selecting a template (#12230) 2024-09-20 16:00:40 +02:00
Yajith Dayarathna d647980c3a updating attest params (#12228) 2024-09-20 11:48:32 +12:00
Oscar Zhou 5740abe31b fix(authorization): add registry button disappear for admin [BE-11228] (#12213) 2024-09-20 08:18:51 +12:00
andres-portainer 5fd4f52e35 fix(jwt): fix handling of non-expiring JWT tokens BE-11242 (#12220) 2024-09-17 18:23:33 -03:00
Yajith Dayarathna dbe7cd16d4 2024-09-CVE (#12189) 2024-09-11 11:08:46 +12:00
Yajith Dayarathna 2b630ca2dd enabling build attestations (#12211) 2024-09-11 10:57:52 +12:00
Oscar Zhou 2ede22646b fix(version): add specific version for updater image [BE-11153] (#12202) 2024-09-11 08:29:23 +12:00
James Carppe 994b6bb471 Update bug report template for 2.21.1 (#12207) 2024-09-10 14:33:32 +12:00
andres-portainer 92f338e0cd fix(users): fix data-race in userCreate() BE-11209 (#12193) 2024-09-05 22:28:04 -03:00
andres-portainer 7a176cf284 fix(teams): fix data-race in teamCreate() BE-11210 (#12195) 2024-09-05 21:36:13 -03:00
Oscar Zhou 80e607ab30 fix(stack): env placeholder as host path [BE-11187] (#12192) 2024-09-06 08:43:12 +12:00
Anthony Lapenna 6cff21477e service: update stop grace period description (#12173) 2024-09-05 08:47:06 +02:00
Yajith Dayarathna 4bb5a7f480 updating ci workflow (#12183) 2024-09-05 09:19:36 +12:00
andres-portainer 9a88511d00 fix(docker): avoid specifying the MAC address of container for Docker API < v1.44 BE-10880 (#12179) 2024-09-03 10:31:24 -03:00
Yajith Dayarathna 48cd614948 CVE 2024 43798 (#12171) 2024-09-03 09:27:24 +12:00
andres-portainer 2fe252d62b fix(jwt): generate JWT IDs BE-11179 (#12175) 2024-09-02 12:06:39 -03:00
LP B 8fae7f8438 feat(app/wizard): info panel telling to add env only once per swarm cluster (#11954) 2024-09-02 14:22:07 +02:00
andres-portainer e4e55157e8 fix(bouncer): add support for JWT revocation BE-11179 (#12164) 2024-08-30 20:24:05 -03:00
Yajith Dayarathna a5e246cc16 testing go directive change (#12124) 2024-08-30 08:27:42 +02:00
andres-portainer d28dc59584 fix(git): optimize listFiles() BE-11184 (#12160) 2024-08-29 19:01:51 -03:00
andres-portainer 5353570721 task(code): remove unnecessary uses of govalidator BE-11181 (#12156) 2024-08-28 19:37:20 -03:00
andres-portainer eb3e367ba8 fix(edgestacks): change the level of a logged line EE-6874 (#11396) 2024-08-28 18:16:34 -03:00
Chaim Lev-Ari 3c1441d462 refactor(users): migrate list view to react [EE-2202] (#11914) 2024-08-28 17:04:32 -03:00
Chaim Lev-Ari 33ce841040 refactor(docker/events): migrate list view to react [EE-2228] (#11581) 2024-08-28 16:41:15 -03:00
Chaim Lev-Ari 9797201c2a feat(docker): label gpu as nvidia only [EE-6999] (#11729) 2024-08-28 16:38:27 -03:00
Chaim Lev-Ari 6e14ac583b fix(access-control): fix dt column header typo [EE-7113] (#11853) 2024-08-28 16:37:12 -03:00
Anthony Lapenna 0b37b677c1 refactor: fix linting issues across the codebase (#12152) 2024-08-28 15:03:15 +02:00
Oscar Zhou f59dd34154 fix(swarm/service): list task when filtering service [BE-11029] (#12146) 2024-08-28 18:28:38 +12:00
James Carppe e8ec648886 Update bug report template for 2.21.0 (#12145) 2024-08-27 16:42:49 +12:00
Ali 10767a06df fix(invalidate): keep invalidate default behaviour [BE-11064] (#12080) 2024-08-27 09:48:50 +12:00
James Carppe 59b3375b59 Update bug report template for 2.21.0-rc2 (#12128) 2024-08-23 10:55:43 +12:00
andres-portainer 4408fd0cd3 chore(polling): simplify the polling logic BE-4585 (#12121) 2024-08-22 10:54:34 -03:00
Yajith Dayarathna 975a9517b9 undo change to go directive 2024-08-22 16:21:13 +12:00
Yajith Dayarathna 89c92b7834 updating go directive 2024-08-22 16:17:28 +12:00
Anthony Lapenna 747cea8084 security: bump dependencies to address CVEs (#12119) 2024-08-21 20:08:25 +12:00
Ali f016b31388 fix(docker-desktop): support auth cookies [BE-11134] (#12108) 2024-08-21 18:21:51 +12:00
Oscar Zhou 8cd53a4b7a fix(registry): non admin can see add registry button [BE-10834] (#12112) 2024-08-21 11:00:00 +12:00
LP B a39abe61c2 fix(api/edge_stacks): ensure edge stacks related endpoints list generation returns unique elements (#12101) 2024-08-20 10:20:03 +02:00
James Carppe 054898f821 Update bug report template for 2.21.0-rc1 (#12104) 2024-08-15 19:27:24 +12:00
Oscar Zhou 13d9b12a2e fix(group): create group twice when associating devices [EE-7418] (#12092) 2024-08-12 17:09:49 +12:00
LP B aaec856282 fix(app/registries): enforce user accesses on registries (#12087) 2024-08-10 11:53:16 +02:00
andres-portainer 009eec9475 fix(compose): avoid the need to pass the file to remove the stack BE-11057 (#12065)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
2024-08-09 10:22:31 -03:00
Yajith Dayarathna 8d14535fd5 updating github workflow 2024-08-09 14:58:20 +12:00
Oscar Zhou cc7f14951c fix(stack/remote): pass forceRecreate setting [EE-7374] (#12051) 2024-08-06 09:02:21 +12:00
Yajith Dayarathna b67ff87f35 Installing docker-compose during test-server step (#12075) 2024-08-05 11:28:47 +12:00
andres-portainer f55ef6e691 fix(pendingactions): remove excessive logging BE-11094 (#12071) 2024-08-02 16:35:14 -03:00
andres-portainer 560a1a00ca fix(scheduler): remove jobs that won't be used anymore BE-11045 (#12058) 2024-08-01 10:59:29 -03:00
andres-portainer 3b5ce1b053 fix(scheduler): remove unnecessary goroutines BE-11044 (#12059) 2024-08-01 10:58:53 -03:00
andres-portainer 03e8d05f18 fix(scheduler): fix a data race in a unit test BE-11084 (#12057) 2024-08-01 10:58:08 -03:00
Oscar Zhou bedb7fb255 fix(swarm): auto multi-select volume with same name [EE-7240] (#11955) 2024-07-31 12:12:26 +12:00
Oscar Zhou 4d586f7a85 fix(docker): missing browse volume option [EE-7179] (#11901) 2024-07-30 08:53:17 +12:00
Oscar Zhou 6486a5d971 fix(edgestack): broken parallel setting in create view [EE-7245] (#11945) 2024-07-29 09:42:05 +12:00
andres-portainer e3364457c4 fix(security): update github.com/containers/image/v5 to fix GO-2024-2842 BE-11055 (#12046) 2024-07-23 18:56:17 -03:00
andres-portainer 66119a8b57 fix(snapshots): remove the attempt to snapshot untrusted environments EE-7407 (#12044) 2024-07-23 18:43:31 -03:00
Ali 6eb9e906af fix(placements) filter out empty items in the required node affinity array [BE-11022] (#12034)
Co-authored-by: testa113 <testa113>
2024-07-23 09:31:08 +12:00
LP B 1900fb695d fix(docker/container): use nodeName to build links to networks used by containers (#12002) 2024-07-17 14:40:05 +02:00
Oscar Zhou a62aac296b fix(host): show clear host info message [EE-7075] (#12011) 2024-07-12 08:45:50 +12:00
Oscar Zhou 5294aa2810 fix(stack): excessive alias count error [EE-7305] (#11990) 2024-07-11 14:09:25 +12:00
andres-portainer 31bdb948a8 chore(code): use int ranges in loops BE-10990 (#12028) 2024-07-10 19:22:47 -03:00
andres-portainer 468c12c75b chore(bbolt): update to v1.3.10 EE-7298 (#12006) 2024-07-09 15:19:27 -03:00
andres-portainer 220fe28830 fix(snapshots): fix background snapshots on environment creation EE-7273 (#12021) 2024-07-09 15:18:13 -03:00
andres-portainer 7fd1a644a6 chore(loops): remove loop var copy EE-7342 (#12023) 2024-07-09 12:47:34 -03:00
andres-portainer 6e7a42727a chore(kompose): remove the code EE-4917 (#12003) 2024-07-08 17:19:07 -03:00
LP B ac4b129195 fix(docker/network): send target nodeName when removing a network on swarm (#12001) 2024-07-08 17:31:18 +02:00
Steven Kang 85bc14e470 fix(cve): remediate cves detected in docker scout (#12018) 2024-07-08 10:24:39 +12:00
Yajith Dayarathna 6e791a2cfe (fix)nightly code security scan (#12017) 2024-07-06 10:54:41 +12:00
andres-portainer 340830d121 chore(docker): clean up the code EE-7325 (#11997) 2024-07-05 16:19:46 -03:00
andres-portainer faca64442f chore(code): use cmp.Or() EE-7333 (#12009) 2024-07-04 19:23:53 -03:00
andres-portainer 854474478c chore(go): update to Go v1.22.5 EE-7297 (#12005) 2024-07-03 22:29:20 -03:00
andres-portainer 4adce14485 fix(errors): improve error handling EE-4430 (#11987) 2024-06-28 17:35:26 -03:00
andres-portainer dc62604ed8 chore(code): remove unused third-party code EE-7306 (#11988) 2024-06-28 17:33:53 -03:00
andres-portainer f0d43f941f chore(code): replace interface{} with any EE-6513 (#11986) 2024-06-28 14:59:28 -03:00
andres-portainer 9c4935286f chore(jsoniter): remove leftover code EE-6702 (#11984) 2024-06-28 09:46:49 -03:00
andres-portainer e1648425ea chore(nomad): remove Nomad code EE-7234 (#11980) 2024-06-28 08:54:44 -03:00
andres-portainer 19fa40286a chore(fdo): remove FDO code EE-7235 (#11981) 2024-06-28 08:42:16 -03:00
andres-portainer 1a3db327c7 fix(kube): improve error handling EE-7196 (#11976) 2024-06-27 10:45:11 -03:00
andres-portainer 1170004097 fix(kube): improve error handling EE-7199 (#11974) 2024-06-27 10:43:44 -03:00
Ali d2b0eacbf5 chore(deps): upgrade typescript to 5.5 [EE-7294] (#11970)
Co-authored-by: testa113 <testa113>
2024-06-27 13:54:10 +12:00
andres-portainer ca9f85a1ff fix(snapshots): enable the background snapshotter EE-7273 (#11973) 2024-06-26 18:27:39 -03:00
andres-portainer 9ee092aa5e chore(code): reduce the code duplication EE-7278 (#11969) 2024-06-26 18:14:22 -03:00
Oscar Zhou 39bdfa4512 fix(edgestack): gitops auto update issue [EE-7260] (#11965) 2024-06-27 08:48:03 +12:00
cmeng e828615467 fix(host-info) host info improvement EE-7075 (#11884) 2024-06-26 12:18:22 -03:00
Ali ba4526985a fix(app): show services explanation in app form [EE-7284] (#11967)
Co-authored-by: testa113 <testa113>
2024-06-26 10:44:20 +12:00
Chaim Lev-Ari 607feb183e refactor(ui/button): remove duplicate data-cy [EE-7242] (#11934) 2024-06-20 15:39:03 +02:00
LP B 9994ed157a fix(app): properly update the app state when losing connectivity to a remote environment while browsing it (#11942) 2024-06-19 13:44:57 +02:00
andres-portainer bfa27d9103 chore(code): clean up the code EE-7251 (#11948) 2024-06-18 15:59:12 -03:00
Ali be9d3285e1 fix(custom-templates): add stack validation, remove custom template validation [EE-7102] (#11938)
Co-authored-by: testa113 <testa113>
2024-06-17 09:24:54 +12:00
Chaim Lev-Ari 0f5988af49 fix(edge/stacks): load template [EE-7109] (#11848) 2024-06-16 07:54:00 +03:00
Chaim Lev-Ari a28bd349ae fix(edge/update): show environment count when more than 100 [EE-6424] (#11917) 2024-06-14 18:37:45 -03:00
Chaim Lev-Ari 51f9977885 fix(endpoints): show toaster on delete [EE-7170] (#11889) 2024-06-13 18:32:17 -03:00
Ali 27865981df fix(namespace): sanitize owner label [EE-7122] (#11935)
Co-authored-by: testa113 <testa113>
2024-06-13 11:06:17 +12:00
James Carppe ac3f1cd5c3 Add support for specifying the NFS server address in the mount point EE-7019 (#11921) 2024-06-12 11:23:08 -03:00
Dakota Walsh 7549b6cf3f fix(kubernetes): cluster setup screen text on own line EE-7112 (#11905) 2024-06-12 08:43:17 +12:00
Oscar Zhou dd372ee122 fix(customtemplate): duplicated error handling [EE-7197] (#11913) 2024-06-11 22:11:15 +12:00
LP B 6a8e6734f3 feat(app): limit the docker API version supported by the frontend (#11855) 2024-06-10 20:54:31 +02:00
andres-portainer 4ba16f1b04 chore(errors): remove superfluous error handling EE-7192 (#11909) 2024-06-10 09:57:02 -03:00
andres-portainer 90a19cec5c chore(code): remove unnecessary type conversions EE-7191 (#11908) 2024-06-10 09:32:52 -03:00
Chaim Lev-Ari 8e480c9fab fix(ui): add accessibility labels to access control fieldset (#11439) 2024-06-09 14:34:22 +03:00
Chaim Lev-Ari b0e3afa0b6 feat(edge/stacks): default refresh rate to 10s [EE-7155] (#11891) 2024-06-09 14:17:21 +03:00
Chaim Lev-Ari eb6d251a73 feat(edge/jobs): migrate item view to react [EE-2220] (#11887) 2024-06-06 21:07:39 +03:00
Matt Hook 62c2bf86aa fix(db): fix missing portainer.edb in backups when encrypted portainer db is used [EE-6417] (#11885) 2024-06-06 12:36:27 +12:00
Oscar Zhou 4a7f96caf6 fix(stack): unable to delete invalid stack [EE-5753] (#11813) 2024-06-04 11:34:02 +12:00
Chaim Lev-Ari 9c70a43ac3 refactor(edge/groups): migrate view to react [EE-2219] (#11758) 2024-06-02 15:43:37 +03:00
Chaim Lev-Ari b7cde35c3d fix(ui/datatables): make empty table label consistent [EE-6499] (#11612) 2024-06-02 12:29:20 +03:00
Chaim Lev-Ari 02fbdfec36 feat(edge/jobs): migrate create view to react [EE-2221] (#11867) 2024-06-02 11:10:38 +03:00
Chaim Lev-Ari 94c91035a7 refactor(custom-templates): migrate list view to react [EE-2256] (#11611) 2024-05-30 12:04:28 +03:00
Matt Hook 5c6c66f010 ix(pendingactions): fix deadlock and reduce needless debug logging [EE-7049] (#11869) 2024-05-30 14:55:16 +12:00
Oscar Zhou 0c870bf37b fix(compose): add project directory option to compose command [EE-7093] (#11870) 2024-05-30 08:47:07 +12:00
matias-portainer 9e0e0a12fa fix(waiting-room): add support for bulk deletion in waiting room EE-7136 (#11879) 2024-05-28 17:18:23 -03:00
andres-portainer c5a1d7e051 fix(tunnels): make the tunnels more robust EE-7042 (#11877) 2024-05-28 16:42:56 -03:00
andres-portainer aaab2fa9d8 fix(tls): add support for more cipher suites EE-7150 (#11874) 2024-05-28 15:49:31 -03:00
andres-portainer ef4beef2ea task(endpoints): change the definition of /endpoints/remove EE-7126 (#11873) 2024-05-28 09:05:35 -03:00
Chaim Lev-Ari 1261887c9e fix(stacks): store filter state [EE-5159] (#11637) 2024-05-28 08:14:12 +03:00
cmeng 84fe3cf2a2 fix(stack): remove tailing slash of git url EE-6664 (#11773) 2024-05-28 09:24:29 +12:00
Chaim Lev-Ari 50fd7c6286 feat(docker/containers): limit items on volume selector [EE-7077] (#11845) 2024-05-23 13:15:36 +03:00
cmeng d7b412eccc fix(container): replace container using correct node name EE-7066 (#11847) 2024-05-23 09:13:49 +12:00
Oscar Zhou d283c63a33 fix(api/docker): no authorized user can call restricted api [EE-6808] (#11480) 2024-05-22 09:09:06 +12:00
James Carppe d15e2cdc0c Update bug report template for 2.20.3 (#11846) 2024-05-21 12:50:29 +12:00
Matt Hook 9cef912c44 feat(dashboard): dashboard api [EE-7111] (#11843) 2024-05-21 11:09:29 +12:00
Oscar Zhou 659abe553d fix(edge/stack): edge stack env table pagination and action [EE-6836] (#11837) 2024-05-21 09:40:11 +12:00
Chaim Lev-Ari 014a590704 refactor(docker): migrate dashboard to react [EE-2191] (#11574) 2024-05-20 09:34:51 +03:00
cmeng 2669a44d79 fix(react-query): set react-query networkMode to offlineFirst EE-7081 (#11812) 2024-05-20 15:29:56 +12:00
Matt Hook db8f9c6f6c fix(console): fix command not found [EE-6982] (#11825) 2024-05-20 14:35:29 +12:00
andres-portainer 2b01136d03 feat(demo): remove demo mode EE-6769 (#11841) 2024-05-17 20:00:01 -03:00
andres-portainer fbbf550730 fix(endpoints): remove all the endpoints in the same transaction EE-7095 (#11839) 2024-05-17 16:45:06 -03:00
cmeng 3924d0f081 fix(deletion): delete objects batch by batch EE-7084 (#11833) 2024-05-16 14:34:50 +12:00
Matt Hook 00ab9e949a fix(pending-actions): correctly detect unreachable/down cluster [EE-7049] (#11809) 2024-05-16 09:03:10 +12:00
Chaim Lev-Ari 42d9dfba36 fix(docker/volumes): return 409 on volume conflict [EE-6748] (#11691) 2024-05-15 08:27:44 +03:00
Chaim Lev-Ari a808f83e7d fix(ui): use expand button in sidebar and tables [EE-6844] (#11608) 2024-05-15 08:26:23 +03:00
Matt Hook 413b9c3b04 fix(terminal): don't close terminal on websocket close [EE-6631] (#11824) 2024-05-15 16:17:32 +12:00
Matt Hook 7edce528d6 fix(console): remove deprecated httputil and update console [EE-6468] (#10848) 2024-05-15 10:28:21 +12:00
Chaim Lev-Ari 836df78181 fix(templates): remove console.log [EE-7092] (#11815) 2024-05-14 09:11:05 +03:00
Ali a80aa2b45c fix(app): ensure placement errors surface per node [EE-7065] (#11820)
Co-authored-by: testa113 <testa113>
2024-05-14 13:39:53 +12:00
Ali 9dd9ffdb3b fix(app): redirect to app after edit [EE-6385] (#11772)
Co-authored-by: testa113 <testa113>
2024-05-14 13:34:28 +12:00
Ali b6daee2850 fix(app): surface placement rules from form [EE-6553] (#11816) 2024-05-14 13:34:06 +12:00
Ali 1ba4b590f4 fix(app): statefulset pvc summary [EE-6760] (#11802) 2024-05-14 13:33:25 +12:00
Ali e73b1aa49c fix(docker): log cleanup errors during endpointforceupdate [EE-7055] (#11762) 2024-05-13 15:34:13 +12:00
Ali 6b5a402962 fix(errors): surface react docker errors to front end [EE-7053] (#11726)
Co-authored-by: testa113 <testa113>
2024-05-13 15:34:00 +12:00
Ali 55667a878a fix(gitops): manifest validation warning [EE-6859] (#11664) 2024-05-13 15:09:25 +12:00
Ali a0ab82b866 fix(LDAP): skip pw validation on edit [EE-616] (#11666)
Co-authored-by: testa113 <testa113>
2024-05-13 15:08:48 +12:00
Matt Hook 6a51b6b41e fix(pending-actions): further refactoring [EE-7011] (#11806) 2024-05-10 11:59:58 +12:00
matias-portainer b4e829e8c6 fix(waiting-room): add icon in list title EE-6687 (#11092) 2024-05-09 19:24:04 -03:00
Oscar Zhou 06ef12d0ff fix(image): github registry image truncated [EE-7021] (#11769) 2024-05-10 09:01:54 +12:00
Chaim Lev-Ari cd5f342da0 refactor(edge/stacks): migrate edit view to react [EE-2222] (#11648) 2024-05-09 18:02:20 +03:00
Oscar Zhou 27e309754e fix(api): list docker volume performance [EE-6896] (#11541) 2024-05-09 13:02:56 +12:00
Ali 6ae0a972d4 fix(docker): surface node details docker error [EE-7054] (#11752)
Co-authored-by: testa113 <testa113>
2024-05-09 12:01:13 +12:00
Dakota Walsh 014c491205 fix(sidebar): environment names on hover EE-6854 (#11755) 2024-05-08 17:08:07 -04:00
Dakota Walsh 4ef71f4aca fix(account): enable add access token button EE-7059 (#11745) 2024-05-08 17:07:44 -04:00
Matt Hook 5a5a10821d fix(pendingactions): refactor pending actions [EE-7011] (#11780) 2024-05-09 08:10:10 +12:00
cmeng 9685e260ea fix(docker): keep /docker url prefix for DockerHandler EE-7073 (#11801) 2024-05-08 14:26:53 +12:00
Ali f8871fcd2a fix(auth logs): fix typo in search keyword [EE-6742] (#11790)
Co-authored-by: testa113 <testa113>
2024-05-08 09:15:56 +12:00
Ali 6d17d8bc64 fix(be-overlay): consistency overlay with variants [EE-6742] (#11774)
Co-authored-by: testa113 <testa113>
2024-05-07 16:16:49 +12:00
Ali 46c6a0700f fix(app): show one tooltip to describe rollback feature [EE-6825] (#11777)
Co-authored-by: testa113 <testa113>
2024-05-07 15:27:22 +12:00
cmeng 5f8fd99fe8 fix(container): specify node name when get a container EE-6981 (#11748) 2024-05-07 11:34:46 +12:00
Chaim Lev-Ari 8a81d95253 refactor(edge/stacks): migrate create view to react [EE-2223] (#11575) 2024-05-06 08:08:03 +03:00
Prabhat Khera f22aed34b5 fix(pending-action): pending action data format [EE-7064] (#11766) 2024-05-06 15:46:51 +12:00
Steven Kang e75e6cb7f7 fix: windows container capability [EE-5814] (#11764) 2024-05-03 10:56:34 +12:00
Ali 14a365045d fix(configs): update unused badge logic [EE-6608] (#11500)
Co-authored-by: testa113 <testa113>
2024-05-03 09:13:33 +12:00
Prabhat Khera 9b6779515e fix(kubernetes): namespace yaml [EE-6701] (#11747) 2024-05-03 09:12:37 +12:00
Matt Hook 88ee1b5d19 fix(kube): correctly extract namespace from namespace manifest [EE-6555] (#11676)
Co-authored-by: Prabhat Khera <prabhat.khera@portainer.io>
2024-05-02 14:28:11 +12:00
Matt Hook a45ec9a7b4 fix(kube): fix text in activity and authentication logs teasers [EE-6742] (#11683)
Co-authored-by: testa113 <testa113>
2024-05-02 14:23:56 +12:00
Ali 51605c6442 fix(app): explain rollback tooltip [EE-6825] (#11698)
Co-authored-by: testa113 <testa113>
2024-05-02 14:10:36 +12:00
Dakota Walsh 2fe213d864 fix(metadata): add mutli endpoint delete api EE-6872 (#11550) 2024-04-30 21:32:20 -04:00
Dakota Walsh 439f13af19 fix(migration): improper version EE-7048 (#11712) 2024-04-30 21:30:40 -04:00
James Carppe 2b5ecd3a57 Add 2.20.2 to bug report template (#11751) 2024-05-01 12:55:14 +12:00
cmeng a9ead542b3 fix(edge-stack): add completed status EE-6210 (#11632) 2024-04-30 13:44:08 +12:00
Ali 7479302043 fix(jwt): handle kubeconfig with no expiry [EE-7044] (#11710)
Co-authored-by: testa113 <testa113>
2024-04-30 09:22:45 +12:00
Ali 10d20e5963 fix(version): reduce github requests [EE-7017] (#11677) 2024-04-26 08:46:02 +12:00
Ali 5a2e6d0e50 fix(app): avoid 'no label' error when deleting external app [EE-6019] (#11671) 2024-04-26 08:42:10 +12:00
andres-portainer 9068cfd892 chore(code): remove superfluous checks EE-7040 (#11692) 2024-04-25 11:25:23 -03:00
Chaim Lev-Ari 5560a444e5 fix(users): return json from create token [EE-6856] (#11577) 2024-04-25 10:10:42 +03:00
Matt Hook 505a2d5523 fix(jwt): upgrade jwt to remove deprecated jwt.StandardClaims [EE-6469] (#10850) 2024-04-23 17:33:36 +12:00
Ali 2463648161 fix(node): check more node role labels [EE-6968] (#11658)
Co-authored-by: testa113 <testa113>
2024-04-23 16:16:41 +12:00
Ali 48cf27a3b8 fix(migration): run post init migrations for edge after server starts [EE-6905] (#11546)
Co-authored-by: testa113 <testa113>
2024-04-23 16:15:28 +12:00
Matt Hook 39fce3e29b fix(published-ports): fix published port link and into a new component [EE-6592] (#11656) 2024-04-23 13:47:37 +12:00
Matt Hook 4f4c685085 fix(settings): fix crash during settings update when not using oauth [EE-7031] (#11662) 2024-04-23 12:58:28 +12:00
Prabhat Khera d177a70c54 fix(stack): correct documentation link for stack ENV variables [EE-6902] (#11654) 2024-04-23 08:35:34 +12:00
James Carppe cf8ec631dd Add 2.19.5 to bug report template (#11652) 2024-04-22 13:44:10 +12:00
Ali ea61f36e5d fix(app): fix app stuck in loading [EE-7014] (#11651)
Co-authored-by: testa113 <testa113>
2024-04-22 13:11:41 +12:00
Oscar Zhou ffc66647f8 feat(setting/oauth): add authstyle option [EE-6038] (#11610) 2024-04-22 10:35:19 +12:00
Oscar Zhou 6623475035 fix(stack/git): option to overwrite target path during dir move [EE-6871] (#11628) 2024-04-22 10:34:32 +12:00
cmeng 0dd12a218b fix(docker-client): explicitly set docker client scheme EE-6935 (#11520) 2024-04-22 09:00:45 +12:00
Chaim Lev-Ari 5f89d70fd8 refactor(datatables): remove angular table utilities [EE-4700] (#11634) 2024-04-21 04:47:09 +03:00
Ali 3ccbd40232 fix(stacks): conditionally hide node and namespace stacks [EE-6949] (#11527)
Co-authored-by: testa113 <testa113>
2024-04-19 17:33:22 +12:00
Prabhat Khera 7e9dd01265 fix(swagger): swagger docs for http status code 409 [EE-5767] (#11535) 2024-04-19 15:19:13 +12:00
Matt Hook 0fb3555a70 chore(kubectl): update kubectl to latest point release [EE-7018] (#11620) 2024-04-19 11:46:44 +12:00
andres-portainer 73ce754316 fix(workflows): upgrade Go to v1.21.9 EE-6939 (#11641) 2024-04-18 19:03:13 -03:00
Prabhat Khera d304f330e8 fix(stack): fix stack env variable link [EE-6902] (#11624) 2024-04-19 07:00:22 +12:00
andres-portainer 7333598dba fix(mingit): upgrade to v2.44.0.1 EE-7023 (#11638) 2024-04-18 15:22:05 -03:00
Ali bb61e73464 refactor(kube): events datatable react migration [EE-6450] (#11583)
Co-authored-by: testa113 <testa113>
2024-04-18 19:14:09 +12:00
Prabhat Khera c15789eb73 fix(images): consider stopped containers for unused label [EE-6983] (#11629) 2024-04-18 17:14:39 +12:00
andres-portainer e7a2b6268e fix(docker): upgrade to v24.0.9 EE-7016 (#11617) 2024-04-17 19:37:57 -03:00
andres-portainer 688fa3aa78 fix(go): upgrade Go to v1.21.9 in the nightly security scan EE-6939 (#11614) 2024-04-17 18:09:53 -03:00
Matt Hook 48bc7d0d92 fix(auth): prevent user enumeration attack [EE-6832] (#11589) 2024-04-17 16:08:27 +12:00
Prabhat Khera d9df58e93a fix(pending-actions): clean pending actions for deleted environment [EE-6545] (#11598) 2024-04-16 15:09:10 +12:00
Oscar Zhou 37bba18c81 fix(api/endpoint): filter status for async devices [EE-6958] (#11509) 2024-04-16 13:37:04 +12:00
Matt Hook 40498d8ddd chore(docker): bump docker client to 26.0.1 [EE-6941] (#11592) 2024-04-16 08:27:58 +12:00
Prabhat Khera b265810b95 fix(stacks): update info text for stack environment variables [EE-6902] (#11551) 2024-04-16 08:03:40 +12:00
Prabhat Khera 09837769d7 fix(pending-actions): fix create kubeclient to check endpoint status [EE-6545] (#11584) 2024-04-16 07:40:41 +12:00
Matt Hook cf1fd17626 chore(api): bump docker and protobuf pkgs [EE-6941] (#11566) 2024-04-15 10:53:15 +12:00
Matt Hook 785f021898 chore(unpacker): use APIVersion as unpacker image tag [EE-6974] (#10955)
Co-authored-by: Prabhat Khera <91852476+prabhat-portainer@users.noreply.github.com>
2024-04-15 10:29:52 +12:00
Prabhat Khera 80cc9f18b5 chore(unpacker): use APIVersion as unpacker image tag [EE-6974] (#11506) 2024-04-15 10:29:24 +12:00
Matt Hook 5e7e91dd6d bump helm version (#11562) 2024-04-15 09:18:04 +12:00
Chaim Lev-Ari 1032b462b4 chore(deps): upgrade react-query to v4 [EE-6638] (#11041) 2024-04-14 17:54:25 +03:00
andres-portainer 104307b2b2 fix(protobuf): upgrade protobuf to v1.33 EE-6945 (#11570) 2024-04-12 17:52:35 -03:00
andres-portainer f8c66a31d9 fix(go): upgrade Go to v1.21.9 EE-6939 (#11554) 2024-04-12 17:08:07 -03:00
Chaim Lev-Ari 2100155ab5 refactor(docker/containers): migrate inspect view to react [EE-2190] (#11005) 2024-04-11 19:07:58 +03:00
Chaim Lev-Ari de473fc10e refactor(docker): remove EndpointProvider from exec [EE-6462] (#10840) 2024-04-11 19:04:58 +03:00
Chaim Lev-Ari 76e49ed9a8 refactor(kube/apps): migrate table to react [EE-4685] (#11028) 2024-04-11 10:11:17 +03:00
Chaim Lev-Ari e9ebef15a0 refactor(rbac): migrate access table to react [EE-4710] (#10823) 2024-04-11 09:49:38 +03:00
Chaim Lev-Ari 6ff4fd3db2 refactor(templates): migrate list view to react [EE-2296] (#10999) 2024-04-11 09:29:30 +03:00
Ali d38085a560 chore(data-cy): require data-cy attributes [EE-6880] (#11453) 2024-04-11 12:11:38 +12:00
Chaim Lev-Ari 3cad13388c refactor(ui): use external/system badge where applicable [EE-6952] (#11475) 2024-04-10 08:56:02 +03:00
Matt Hook 0b62456236 fix(backups): improved archive encryption [EE-6764] (#11489) 2024-04-10 10:45:49 +12:00
Chaim Lev-Ari c22d280491 refactor(activity-logs): migrate activity logs table to react [EE-4714] (#10891) 2024-04-09 08:53:23 +03:00
Chaim Lev-Ari 960d18998f refactor(registries): migrate gitlab projects table to react [EE-4709] (#10792) 2024-04-09 08:52:44 +03:00
Chaim Lev-Ari 3f3db75d85 refactor(account): migrate access tokens table to react [EE-4701] (#10669) 2024-04-09 08:17:43 +03:00
Chaim Lev-Ari 48aab77058 refactor(rbac): migrate roles table to react [EE-4711] (#10772) 2024-04-09 08:11:29 +03:00
Chaim Lev-Ari 7e53d01d0f refactor(activity-logs): migrate auth logs table to react [EE-4715] (#10890) 2024-04-09 08:10:25 +03:00
Chaim Lev-Ari bd271ec5a1 refactor(registries): migrate tags table to react [EE-6452] (#10990) 2024-04-09 08:08:14 +03:00
Matt Hook 8913e75484 fix(services): speed up service count on the kubernetes dashboard [EE-6967] (#11526) 2024-04-09 15:50:48 +12:00
Chaim Lev-Ari c95ffa9e2d refactor(rbac): migrate access viewer table to react [EE-6447] (#11498) 2024-04-08 17:25:38 +03:00
Chaim Lev-Ari ddb89f71b4 refactor(settings/auth): migrate ldap tables to react [EE-4712] (#10822) 2024-04-08 17:24:45 +03:00
Chaim Lev-Ari 45be6c2b45 refactor(tags): migrate tags to react [EE-4707] (#10771) 2024-04-08 17:23:49 +03:00
Chaim Lev-Ari a00cb951bc refactor(kube/registries): migrate access table to react [EE-4706] (#10688) 2024-04-08 17:23:12 +03:00
Chaim Lev-Ari f584bf3830 refactor(registries): migrate list view to react [EE-4704] (#10687) 2024-04-08 17:22:43 +03:00
Chaim Lev-Ari 9600eb6fa1 refactor(tables): use add and delete buttons [EE-6297] (#10668)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portaienr.io>
2024-04-08 17:21:41 +03:00
Chaim Lev-Ari d88ef03ddb refactor(edge/jobs): migrate results table to react [EE-4679] (#10663) 2024-04-08 13:18:59 +03:00
Matt Hook dc9d7ae3f1 fix(apikey): don't authenticate api key for external auth [EE-6932] (#11460) 2024-04-08 11:03:52 +12:00
James Carppe a3c7eb0ce0 Update bug report template for 2.20.1 (#11505) 2024-04-05 14:56:19 +13:00
Chaim Lev-Ari d1ba484be1 refactor(env/groups): migrate list view to react [EE-4703] (#10671) 2024-04-04 18:54:57 +03:00
Chaim Lev-Ari 521eb5f114 refactor(edge): use native progress tag for deployment counter [EE-6075] (#10936) 2024-04-04 18:12:27 +03:00
Chaim Lev-Ari 66770bebd4 refactor(edge/jobs): migrate view to react [EE-2236] (#10661) 2024-04-04 16:25:32 +03:00
Matt Hook 86c4b3059e fix(kube): use https when port is 443 in various tables [EE-6592] (#11443) 2024-04-04 14:36:38 +13:00
Ali e3a8853212 fix(app): port namespace limit refresh from EE to CE [EE-6835] (#11483)
Co-authored-by: testa113 <testa113>
2024-04-04 08:19:04 +13:00
Ali 194b6e491d fix(namespace): wait for system ns setting to load before selecting existing ns [EE-6917] (#11481)
Co-authored-by: testa113 <testa113>
2024-04-04 08:18:13 +13:00
Chaim Lev-Ari a439695248 refactor(users): migrate users table to react [EE-4708] (#10759) 2024-04-03 17:38:32 +03:00
Chaim Lev-Ari 86f1b8df6e refactor(kube/volumes): migrate storage table to react [EE-4697] (#11030) 2024-04-02 23:27:20 +03:00
Chaim Lev-Ari a5faddc56c refactor(kube/cluster): migrate node apps table to react [EE-4691] (#11016) 2024-04-02 23:12:34 +03:00
Chaim Lev-Ari 9c68c6c9f3 refactor(kube/namespaces): migrate item apps table to react [EE-4693] (#11012) 2024-04-02 22:55:34 +03:00
Chaim Lev-Ari d99486ee72 refactor(kube/namespaces): remove unused ingresses table [EE-6448] (#11029) 2024-04-02 22:41:45 +03:00
Chaim Lev-Ari 946166319f refactor(kube/apps): migrate integrated apps table to react [EE-4690] (#11025) 2024-04-02 22:37:47 +03:00
Chaim Lev-Ari 26bb028ace refactor(kube/namespaces): migrate table to react [EE-4694] (#10988) 2024-04-02 22:26:22 +03:00
Chaim Lev-Ari da615afc92 refactor(kube/volumes): migrate to react [EE-4695] (#10987) 2024-04-02 22:10:22 +03:00
LP B 2b53bebcb3 fix(app): replace fields removed by Docker 25 and 26 (#11468)
* 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
2024-03-29 13:57:14 +01:00
Chaim Lev-Ari d336a14e50 feat(docker/services): show port ranges [EE-4012] (#10657) 2024-03-27 09:56:00 +02:00
cmeng 4ca6292805 fix(edge-stack): avoid reference of undefined EE-6914 (#11463) 2024-03-27 16:08:08 +13:00
andres-portainer 44ef5bb12a fix(kubernetes): avoid a deadlock EE-6901 (#11442) 2024-03-25 14:19:38 -03:00
andres-portainer bf600f8b11 fix(kubernetes): sync with EE EE-6906 (#11448) 2024-03-22 16:14:00 -03:00
Prabhat Khera d6d7afddbc chore(version): version bump to 2.22.0 [EE-6897] (#11438) 2024-03-22 14:37:27 +13:00
James Carppe 61642b8df6 Added 2.20.0 to bug report version option dropdown (#11430) 2024-03-19 16:34:03 +13:00
Matt Hook 07de1b2c06 fix(doclinks): fix help link paths [EE-6861] (#11418) 2024-03-19 11:46:41 +13:00
andres-portainer bd3440bf3c fix(tests): fix data races EE-6867 (#11387) 2024-03-18 10:56:22 -03:00
Matt Hook 573f003226 fix(docs): fix all remaining webhook app links [EE-6861] (#11393) 2024-03-18 16:28:58 +13:00
Matt Hook 6e169662c2 fix(kube): fix edit application webhook link [EE-6861] (#11391) 2024-03-18 10:21:09 +13:00
cmeng 31658d4028 fix(stack): prepopulate when creating template from stack EE-6853 (#11380) 2024-03-18 09:36:06 +13:00
Oscar Zhou bb02c69d14 chore(template/git): sync frontend code from ee (#11344) 2024-03-18 08:55:16 +13:00
Matt Hook 73307e164b fix(docs): make all doc links versioned [EE-6861] (#11382) 2024-03-15 16:57:51 +13:00
Matt Hook 9ea5efb6ba fix(stacks): update swagger stacks doc description [EE-6860] (#11384) 2024-03-15 16:47:14 +13:00
cmeng 3cd58cac54 fix(container): make blank string as valid value EE-6852 (#11373) 2024-03-15 09:01:47 +13:00
Prabhat Khera 1303a08f5a fix(auth): make createAccessToken api backward compatible [EE-6818] (#11326)
* fix(auth): make createAccessToken api backward compatible [EE-6818]

* fix(api): api error message [EE-6818]

* fix messages
2024-03-14 09:02:28 +13:00
Ali 3b1d853090 fix(app): only show special message when limits change for existing app resource limit [EE-6837] (#11367)
Co-authored-by: testa113 <testa113>
2024-03-14 08:45:48 +13:00
cmeng a2a4c85f2d fix(csrf): disable csrf secure cookie EE-6787 (#11300) 2024-03-13 11:22:11 +13:00
LP B 506ee389e3 fix(app): views not loading when quickly navigating in app (#11278) 2024-03-12 15:16:14 +01:00
Chaim Lev-Ari 8635bc9b9c fix(docker): apply private uac to edge admin [EE-6788] (#11285) 2024-03-12 09:59:36 +02:00
cmeng 447f497506 fix(edge-stack): deploy button is disabled EE-6819 (#11355) 2024-03-12 17:19:42 +13:00
Prabhat Khera 71292a60b1 address review commets (#11360) 2024-03-12 11:32:06 +13:00
Ali 51449490fa fix(app): on create don't mention previous values [EE-6837] (#11350)
Co-authored-by: testa113 <testa113>
2024-03-11 16:43:41 +13:00
Prabhat Khera ae4970f0ed fix(container): autocomplete off for create container form [EE-6761] (#11336)
* autocomplete off doe create container form

* address review commets

* remove auto complete off from forms
2024-03-11 13:39:04 +13:00
Prabhat Khera e96d5c245d fix(kube): stackname in daemonsets and statefulsets app [EE-6670] (#11352) 2024-03-11 10:04:51 +13:00
Chaim Lev-Ari f8e3d75797 refactor(tests): wrap tests explicitly with provider [EE-6686] (#11090) 2024-03-10 14:22:01 +02:00
Chaim Lev-Ari 27aaf322b2 fix(kube/config): validate change window start [EE-6830] (#11329) 2024-03-10 09:42:33 +02:00
Matt Hook b77132dbb1 fix(exec): improve alignment of help icon [EE-6816] (#11339) 2024-03-08 14:03:09 +13:00
Prabhat Khera c35473f308 fix(kube-stacks): change wordings [EE-6670] (#11334) 2024-03-08 12:15:31 +13:00
Ali a570073d12 fix(matomo): stop oauth link event [EE-6779] (#11332) 2024-03-08 10:17:29 +13:00
Oscar Zhou 0ad4826fab fix(stack): filter out orphan stacks that have same name as normal stacks [EE-6791] (#11291) 2024-03-08 09:56:10 +13:00
Matt Hook 6db7d31554 fix(exec): fix alignment and text size and alignment [EE-6816] (#11325) 2024-03-07 12:58:05 +13:00
cmeng 21d67a971d fix(menu): edge compute menu not clickable EE-6804 (#11319) 2024-03-07 12:11:58 +13:00
Prabhat Khera 8dfa5efa71 fix(stacks): make stackName kube stack specific field [EE-6670] (#11315)
* fix(stacks): make stackName kube stack specific field [EE-6670]

* fix wordings
2024-03-07 11:31:21 +13:00
Prabhat Khera 529750fa21 fix(UI): axios progress bar loading issue [EE-6781] (#11289) 2024-03-07 11:30:27 +13:00
Ali 96b1d36280 fix(time window): show errors for component [EE-6800] (#11317)
Co-authored-by: testa113 <testa113>
2024-03-07 09:03:22 +13:00
Chaim Lev-Ari 31c5a82749 fix(kube/setup): add a11y labels [EE-6747] (#11307) 2024-03-06 14:57:00 +02:00
Matt Hook 82516620e7 fix(contexthelp): remove extra slash from contexthelp docs link [EE-6780] (#11311) 2024-03-06 16:38:06 +13:00
Matt Hook d26d5840f1 fix(helm): remove helm insights from the stack datatable [EE-6803] (#11314) 2024-03-06 16:36:58 +13:00
Dakota Walsh ebd26316bf fix(datatable): title size EE-6774 (#11272) 2024-03-06 08:01:51 +13:00
Chaim Lev-Ari 18dbad232e fix(docker/images): export image [EE-6807] (#11306) 2024-03-05 19:30:48 +02:00
matias-portainer ebcc98d5c5 fix(edge/templates): get correct default value for selectType env vars EE-6796 (#11294) 2024-03-04 10:35:24 -03:00
Matt Hook e919da3771 fix(kube): update doc links to match new menu structure [EE-6759] (#11267) 2024-03-01 15:37:21 +13:00
Matt Hook eda2dd20ee fix(help): add versioned doc links to support LTS/STS docs [EE-6780] (#11281) 2024-03-01 15:36:09 +13:00
cmeng 385fd95779 fix(edge-stacks): take not-found stack as removed EE-6758 (#11248) 2024-03-01 11:50:20 +13:00
cmeng 88185d7f6d fix(container): get old container info correctly EE-6716 (#11216) 2024-03-01 09:14:19 +13:00
cmeng 253cda8cef fix(stack): more space for add button EE-6773 (#11259) 2024-03-01 09:11:41 +13:00
Chaim Lev-Ari b34afba7cd fix(auth): prevent unauthorized redirect on page load [EE-6777] (#11264) 2024-02-29 09:41:26 +02:00
Chaim Lev-Ari 6c70049ecc feat(kube): add a11y props for smoke tests [EE-6747] (#11263) 2024-02-29 09:26:13 +02:00
Chaim Lev-Ari 42c2a52a6b fix(ci): prevent tests from running twice [EE-6728] (#11197) 2024-02-29 08:11:49 +02:00
Chaim Lev-Ari 19a6a5c608 fix(docker): hide write buttons for non authorized [EE-6775] (#11260) 2024-02-27 12:36:44 +02:00
Prabhat Khera d8e374fb76 fix(ui): autocomplete on edge custom template and stacks [EE-6761] (#11268) 2024-02-27 20:15:52 +13:00
Matt Hook 84ca6185dc fix(kube): make app autorefresh and show system settings stay [EE-6771] (#11257) 2024-02-27 11:18:44 +13:00
Prabhat Khera 5088634a41 fix(stack): auto complete dropdown in docker stacks [EE-6761] (#11253) 2024-02-26 11:43:15 +13:00
Ali f6beedf0d5 fix(app): parse nan in validation check [EE-6714] (#11246) 2024-02-26 09:20:54 +13:00
Oscar Zhou 3caf1ddb7d fix(edge/template): validate app template env vars [EE-6743] (#11235) 2024-02-26 09:00:12 +13:00
Chaim Lev-Ari c622f6da4e fix(docker): prevent non admins from passing security settings [EE-6765] (#11240) 2024-02-25 11:57:22 +02:00
cmeng 9ec7394124 fix(stack): make web editor readonly for git template EE-6706 (#11182) 2024-02-23 13:28:27 +13:00
Matt Hook af8fde66b0 fix(dependancies): update compose and runc [EE-6744] (#11245) 2024-02-23 11:49:09 +13:00
Prabhat Khera 709315dde5 fix(ui): turn autocomplete off for git deployment [EE-6761] (#11242) 2024-02-23 08:44:03 +13:00
Ali 8856bae5c6 fix(app): NaN validation for autoscaling [EE-6714] (#11237) 2024-02-22 17:36:44 +13:00
Matt Hook 90451bfd47 fix(helm) tighten up helm requests [EE-6722] (#11236) 2024-02-22 11:35:33 +13:00
Ali 0c05539dee fix(input): allow clearing number inputs [EE-6714] (#11186) 2024-02-21 10:43:35 +13:00
Ali a2a2c6cf3e fix(inputlist): update warning style [EE-6737] (#11221) 2024-02-21 08:29:10 +13:00
Matt Hook 76aa086d79 fix(libhttp): capitalize http error responses for better display [EE-6698] (#11114) 2024-02-21 07:51:46 +13:00
Chaim Lev-Ari 76fdfeaafc fix(ui): check for authorization [EE-6733] (#11208) 2024-02-20 11:06:09 +02:00
Chaim Lev-Ari 5932c78b88 fix(kube/apps): move namespace selector in apps view [EE-6612] (#11024) 2024-02-20 10:14:11 +02:00
Ali 68f5ca249f fix(app): remove insight from helm [EE-6693] (#11213)
Co-authored-by: testa113 <testa113>
2024-02-20 17:25:19 +13:00
Ali 2d87a8d8c3 fix(app): set values in react autoscaling form section [EE-6740] (#11219) 2024-02-20 09:35:27 +13:00
Prabhat Khera 988d4103d4 fix(git): update stack name for git stacks [EE-6670] (#11217) 2024-02-20 09:23:46 +13:00
Chaim Lev-Ari ce3a1b8ba5 feat(a11y): add labels and roles [EE-6717] (#11181) 2024-02-19 16:37:26 +02:00
Oscar Zhou 6c89d3c0c9 fix(edge/template): custom template git fields not pre-filled [EE-6695] (#11112) 2024-02-19 08:39:05 +13:00
Ali 6b91fbf7f4 fix(app): update app type when changing data access policy [EE-6719] (#11211)
Co-authored-by: testa113 <testa113>
2024-02-19 08:08:22 +13:00
Ali 4f3f5e57b6 fix(app): avoid duplicate env requests [EE-6727] (#11194)
Co-authored-by: testa113 <testa113>
2024-02-16 14:02:05 +13:00
Prabhat Khera 6b3f30e32f fix(ui): update search placeholder [EE-6667] (#11190)
* update search placeholder

* remove box selector description
2024-02-16 12:34:06 +13:00
Matt Hook bdeedb4018 fix(namespace): fix default namespace quota [EE-6700] (#11185) 2024-02-16 08:20:24 +13:00
Chaim Lev-Ari 50946e087c chore(eslint): add rule to check imports [EE-6730] (#11201) 2024-02-15 17:46:03 +02:00
Chaim Lev-Ari 7b89b04667 fix(auth): export hasAuthorizations [EE-6595] (#11199) 2024-02-15 14:05:52 +02:00
Chaim Lev-Ari f5f84c5fa4 feat(ui): restrict views by role [EE-6595] (#11010) 2024-02-15 13:29:55 +02:00
Chaim Lev-Ari 437831fa80 feat(edge/stacks): add app templates to deploy types [EE-6632] (#11040) 2024-02-15 09:01:01 +02:00
Chaim Lev-Ari 31f5b42962 feat(auth): add useIsEdgeAdmin hook [EE-6627] (#11057) 2024-02-14 19:50:20 -03:00
Ali 7a6c872948 fix(insight): split insight from input [EE-6693] (#11176)
Co-authored-by: testa113 <testa113>
2024-02-15 10:45:59 +13:00
Chaim Lev-Ari 4bf18b1d65 feat(ui): write tests [EE-6685] (#11081) 2024-02-14 17:25:37 +02:00
Ali 2d25bf4afa fix(configs): correct 'external' display in tables [EE-6649] (#11110)
Co-authored-by: testa113 <testa113>
2024-02-14 11:48:09 +13:00
Ali 56ae19c5ab fix(stacks): add app form stacks input [EE-6693] (#11104) 2024-02-14 09:00:51 +13:00
Matt Hook cdf9197274 fix(logs): add NOCOLOR option for use when exporting to greylog etc [EE-6696] (#11106) 2024-02-14 07:55:00 +13:00
Ali 901549e8dd fix(kube-owner): owner labels from resources created via manifest [EE-6647] (#11102)
Co-authored-by: testa113 <testa113>
2024-02-12 15:30:49 +13:00
Dakota Walsh 80b1cd19cb fix(restore): add S3 teaser EE-6675 (#11095) 2024-02-12 13:12:45 +13:00
Prabhat Khera c4942de89b fix(ui): stackname auto fill on create from manifest screen [EE-6688] (#11099)
* fix(ui): stackname auto fill on create from manifest screen [EE-6688]

* address review comment
2024-02-12 10:54:29 +13:00
Ali 80d02f9cd1 fix(auth): isAdmin redirect for wizard [EE-6669] (#11074) 2024-02-12 08:04:39 +13:00
Prabhat Khera 671b22b5d6 fix(ui): scroll issue [EE-6667] (#11084)
* Fix scroll issue

* fix minorissue

* address review comments

* add comment
2024-02-09 15:35:34 +13:00
Steven Kang 43e56bf1c0 fix: pre-release build only after merging (#11097) 2024-02-09 15:26:43 +13:00
Matt Hook a175619623 fix(docs): fix swagger docs for webhook params [EE-6668] (#11088) 2024-02-09 14:44:14 +13:00
Prabhat Khera 63c11d9310 fix(kube): ingress path duplication issue [EE-6649] (#11086) 2024-02-09 07:49:48 +13:00
Prabhat Khera 4c00b72ae3 fix stack name update issue (#11064) 2024-02-08 13:51:01 +13:00
Matt Hook f4db09a534 fix(kube-apps): add helm insights, remove namespace insights panel [EE-6671] (#11077) 2024-02-08 11:38:04 +13:00
Prabhat Khera 01cd64037f fix(UI): some minor fixes [EE-6667] (#11061)
* minor tweeks for kubernetes settings

* address review comments
2024-02-06 12:17:38 +13:00
Steven Kang a93344386c Pre-release as part of the CI (#11066)
* feat: add pre-release
* feat: add extension
* feat: fix typo
2024-02-05 18:24:16 +13:00
Prabhat Khera a2195caa10 keep labels on edit ingress, configmaps and secrets (#11050) 2024-02-05 16:30:36 +13:00
Ali 9ad78753bc fix(r2a): don't set errors to undefined [EE-6665] (#11059)
Co-authored-by: testa113 <testa113>
2024-02-05 14:24:11 +13:00
Prabhat Khera 517190e28b chore(version): bump to 2.21.0 [EE-6652] (#11047)
* chore(version): bump to 2.21.0 [EE-6652]

* address review comments
2024-02-02 15:17:52 +13:00
Dakota Walsh 5ee6efb145 fix(backup): restore over network share EE-6578 (#11044) 2024-02-01 11:41:32 +13:00
Matt Hook a618ee78e4 fix(helm): minor helm screen page corrections [EE-6642] (#11045) 2024-02-01 11:34:33 +13:00
Ali 9a1604e775 fix(kubeclient): cache kubeclient by user token [EE-6610] (#11039) 2024-01-31 14:50:41 +13:00
Prabhat Khera 9615e678e6 chore(golang): version upgrade to 1.21.6 [EE-6634] (#11036) 2024-01-31 06:28:53 +13:00
Dakota Walsh e39c19bcca fix(console): export LANG and LC_ALL for kube app console EE-6593 (#11037) 2024-01-30 15:19:53 +13:00
Matt Hook 16ae4f8681 fix(kube): change pod security policy teaser screen wording [EE-6629] (#11035) 2024-01-30 13:03:54 +13:00
Matt Hook 70deba50ba fix(kube): clear kube cache on login/logout [EE-6620] (#11026) 2024-01-30 10:39:12 +13:00
Dakota Walsh 89359dae8c ix(console): docker console UTF-8 EE-6593 (#11034) 2024-01-30 09:34:10 +13:00
Chaim Lev-Ari 97d227be2a fix(swarm/services): convert webhooks API filters to JSON on list request [EE-6621] (#11031)
Co-authored-by: matias-portainer <matias.spinarolli@portainer.io>
2024-01-29 18:08:25 +02:00
Matt Hook 8a98704111 fix(helm): increase default helm timeouts [EE-6617] 2024-01-29 13:03:11 +13:00
Prabhat Khera 46b2175729 fix(kubernetes): placement rules calculations [EE-6552] (#11013) 2024-01-29 08:00:15 +13:00
Chaim Lev-Ari 1561814fe5 feat(gitops): add autocomplete to ref selector [EE-6245] (#10935) 2024-01-28 15:55:10 +02:00
Chaim Lev-Ari 2826a4ce39 feat(custom-templates): filter templates by edge [EE-6565] (#10979) 2024-01-28 15:54:34 +02:00
Matt Hook 441a8bbbbf fix(helm): add clarifying text and new badge to helm user repo settings table [EE-6609] (#11018) 2024-01-26 12:37:13 +13:00
Ali 2248ce0173 fix(secret): update hide secret tooltip [EE-6568] (#11020)
Co-authored-by: testa113 <testa113>
2024-01-26 11:21:34 +13:00
Dakota Walsh b640b58371 fix(console): use writeUtf8 instead of environment variables EE-6593 (#11019) 2024-01-26 11:21:00 +13:00
Ali 249b6bc628 fix(secrets): teaser wording updates [EE-6568] (#11017) 2024-01-26 10:28:57 +13:00
Chaim Lev-Ari 4a10c2bb07 feat(version): show git commit and env [EE-6021] (#10748) 2024-01-25 07:41:33 +02:00
Chaim Lev-Ari 52db4cba0e fix(storybook): fix msw stories [EE-6503] (#10985) 2024-01-24 10:06:38 +02:00
Chaim Lev-Ari 079bade139 refactor(kube/app): use structuredClone to copy object [EE-6581] (#11004) 2024-01-24 09:31:33 +02:00
Ali 26e52a0f00 fix(pods): don't add labels to old pod that has none [EE-6587] (#11009) 2024-01-24 14:44:15 +13:00
Ali 3ccc764d40 fix(images): update up to date teaser wording [EE-6537] (#11008)
Co-authored-by: testa113 <testa113>
2024-01-24 14:22:15 +13:00
Dakota Walsh dd068473d2 fix(console): minor typo in tooltip EE-1976 (#11007) 2024-01-24 12:02:56 +13:00
Dakota Walsh fe47318e26 fix(terminal): display os specific copy/paste tooltip EE-1976 (#10835) 2024-01-24 09:45:40 +13:00
Dakota Walsh fc7d9ca2cd fix(secrets): add CE teaser EE-6568 (#11001) 2024-01-24 09:44:50 +13:00
Ali 7bf346bd2d fix(app): no summary for existing pvc on edit [EE-6569] (#11003) 2024-01-24 08:09:59 +13:00
Chaim Lev-Ari 8f0f9d7aaa fix(ui): stub unused modules [EE-6583] (#11006) 2024-01-23 15:22:56 +02:00
Chaim Lev-Ari 69c06bc756 feat(ci): replace jest with vitest [EE-6504] (#10997) 2024-01-23 08:42:52 +02:00
Ali 4a19871fcc fix(app): fix capitalisation typos and match EE codebase [EE-6480] (#11002)
Co-authored-by: testa113 <testa113>
2024-01-23 16:28:00 +13:00
Ali d5080b6884 fix(r2a): fix layout shifting from errors showing as undefined [EE-6570] (#11000) 2024-01-23 14:16:34 +13:00
Prabhat Khera f7840e0407 fix(ui): mark resources system correctly [EE-6558] (#10996)
* fix(ui): mark resources system correctly [EE-6558]

* address review comments
2024-01-23 13:49:25 +13:00
andres-portainer 85ae705833 fix(gitops): add singleflight behavior to RedeployWhenChanged calls EE-6377 (#10734) 2024-01-22 19:41:48 -03:00
Ali 77c38306b2 fix(app): get min resource limits [EE-6567] (#10994)
Co-authored-by: testa113 <testa113>
2024-01-23 11:20:24 +13:00
Ali b81babe682 fix(app): no summary for existing pvc [EE-6569] (#10995) 2024-01-23 11:19:52 +13:00
Ali 4c0049edbe fix(app): allow editing pod services [EE-6480] (#10875)
* fix(app): allow editing pod services [EE-6480]
* address review comment

---------

Co-authored-by: testa113 <testa113>
Co-authored-by: prabhat khera <prabhat.khera@portainer.io>
2024-01-23 10:10:16 +13:00
Oscar Zhou 7cba02226e fix(container): duplicate/edit button causes empty container screen [EE-6566] (#10982) 2024-01-22 10:28:16 +13:00
Ali a15b7cf39a fix(app): fix namespace validation message for admin [EE-6561] (#10992)
Co-authored-by: testa113 <testa113>
2024-01-22 09:30:11 +13:00
Dakota Walsh 36ab4dfb1a Revert "fix(docs): add APIKey Digest example EE-6199 (#10980)" (#10981)
This reverts commit 7b6e106606.
2024-01-19 14:02:19 +13:00
Dakota Walsh 7b6e106606 fix(docs): add APIKey Digest example EE-6199 (#10980) 2024-01-19 13:16:56 +13:00
Ali 5f040bf788 fix(app): namespace selector fixes [EE-6561] (#10977) 2024-01-19 12:20:44 +13:00
Prabhat Khera a4739f1701 fix messaging for resourse over commit (#10974) 2024-01-19 12:14:47 +13:00
Ali 59f642ea56 fix(app): persisted volume fixes [EE-6554] (#10975)
Co-authored-by: testa113 <testa113>
2024-01-19 12:14:19 +13:00
Oscar Zhou fa63432695 fix(stack/template): web editor error shows for changing between same mustache templates [EE-6563] (#10976) 2024-01-19 09:28:09 +13:00
Dakota Walsh 1676fefd97 fix(backup): calculate file size correctly EE-6439 (#10919) 2024-01-18 09:00:01 +13:00
Prabhat Khera bf66b6c5f3 fix(ui): reset auto-scaling formvalues if needed [EE-6544] (#10969) 2024-01-18 07:59:00 +13:00
Chaim Lev-Ari 115b01cee3 fix(docker): include healthy containers in running [EE-6264] (#10746) 2024-01-17 22:30:12 +07:00
Chaim Lev-Ari a305fe9e4c feat(stacks): hide redeploy for orphaned stacks [EE-5784] (#10841) 2024-01-17 21:45:08 +07:00
Ali a58b4f479b fix(app): remove duplicate validation messages [EE-5933] (#10967) 2024-01-17 16:30:30 +13:00
Prabhat Khera 93593e1379 fix(ui): update button disabled when manisfest reverted to the orignial content [EE-6544] (#10968) 2024-01-17 13:56:10 +13:00
Prabhat Khera 51ae2198f6 fix typo in app name (#10965) 2024-01-17 12:15:58 +13:00
Prabhat Khera ccc97e6f78 fix(ui): app summary [EE-6515] (#10966) 2024-01-17 12:15:22 +13:00
Dakota Walsh 3f28d56bfc fix(teams): show add user notification EE-4899 (#10873) 2024-01-17 12:14:05 +13:00
Matt Hook 3103d498cf fix(docs): fix minor swagger issue and upgrade swag [EE-6548] 2024-01-17 11:27:57 +13:00
Oscar Zhou 47f29002f0 fix(edgestack): repull image not work in git autoupdate [EE-6430] (#10952) 2024-01-17 10:20:59 +13:00
Ali 787c7ec4cc fix(app): remove canUndo function from environment variables [EE-6232] (#10961)
Co-authored-by: testa113 <testa113>
2024-01-17 10:13:53 +13:00
Ali a8e53a4510 fix(app): hide placement form section [EE-6386] (#10964)
Co-authored-by: testa113 <testa113>
2024-01-17 09:34:29 +13:00
Ali 752be47fcc fix(app): get utilization percentage in payload [EE-6387] (#10962)
Co-authored-by: testa113 <testa113>
2024-01-17 08:33:40 +13:00
Ali 95474b7dc5 fix(app): various persisted folder fixes [EE-6235] (#10963)
Co-authored-by: testa113 <testa113>
2024-01-17 08:31:22 +13:00
Prabhat Khera 7a04d1d4ea fix input cursor moving to the end on edit (#10959) 2024-01-16 16:03:01 +13:00
Prabhat Khera 211fff5ed4 update metrics help text (#10960) 2024-01-16 16:02:26 +13:00
Prabhat Khera 2f2cfad722 fix high contrast theme colors (#10872) 2024-01-16 14:32:24 +13:00
Prabhat Khera 380c16c8dd increase the font size for search panel (#10838) 2024-01-16 14:31:39 +13:00
Prabhat Khera bbf1900677 Disable update application button on load of edit screen (#10957) 2024-01-16 09:31:44 +13:00
Ali fcc5736d61 fix(app): use isAdmin check on CE [EE-6231] (#10956)
Co-authored-by: testa113 <testa113>
2024-01-15 15:34:21 +13:00
Ali ae6333bf7c fix(app): remove duplicate values for multinode cluster [EE-6386] (#10947) 2024-01-15 14:34:54 +13:00
Ali 3a959208a8 fix(app): autoscaling min validation [EE-6387] (#10945) 2024-01-15 14:34:16 +13:00
Prabhat Khera b3b7cfa77f fix(kube): patching stateful service [EE-6523] (#10948) 2024-01-15 13:30:45 +13:00
Ali 6d71a28584 fix(app): improve resource quota error handling [EE-5933] (#10951) 2024-01-15 13:29:35 +13:00
Dakota Walsh 488fcc7cc5 fix(docs): convert APIKey to string EE-6199 (#10943) 2024-01-15 11:59:39 +13:00
Ali d750389c67 fix(app): fix exhaused error message [EE-6231] (#10949)
Co-authored-by: testa113 <testa113>
2024-01-15 11:03:38 +13:00
Ali cb7efd8601 fix(app): fix wording and 2 key validation [EE-6233] (#10944)
Co-authored-by: testa113 <testa113>
2024-01-15 11:01:48 +13:00
Ali 55f66f161e fix(app): fix env var state and validation [EE-6232] (#10941)
Co-authored-by: testa113 <testa113>
2024-01-15 10:56:53 +13:00
Prabhat Khera 067a7d148f update endpoint angular state (#10950) 2024-01-12 16:44:49 +13:00
Prabhat Khera cf88570c39 update validation for storage classes (#10940) 2024-01-12 09:40:55 +13:00
Prabhat Khera 0e6a175bf6 fix metrics text wordings (#10939) 2024-01-12 08:31:29 +13:00
Oscar Zhou bb680ef20a fix(git): incorrect git commit url for bitbucket [EE-6446] (#10855) 2024-01-12 08:22:50 +13:00
Oscar Zhou c6505a6647 fix(docker/container): show exit code in status column if needs [EE-5760] (#10916) 2024-01-12 08:21:38 +13:00
Ali 4e7d1c7088 refactor(app): migrate remaining form sections [EE-6231] (#10938) 2024-01-11 15:13:28 +13:00
Prabhat Khera 0b9cebc685 fix(caching): integrate with axios cache interceptor [EE-6505] (#10922)
* integrate with axios-cache-interceptor
* remove extra headers as not needed
2024-01-11 11:12:53 +13:00
Prabhat Khera d0b9e3a732 fix(UI): app summary on forvalues update [EE-6515] (#10932)
* app summary on forvalues update

* comment added
2024-01-11 10:14:23 +13:00
Prabhat Khera b7635feff0 fix rbac message when not enabled (#10933) 2024-01-11 08:28:01 +13:00
Matt Hook 7528cabf5a deep upgrade dependencies, follow-redirects, @babel/traverse, postcss (#10931) 2024-01-10 15:40:05 +13:00
Matt Hook 39eb37d5e5 upgrade circl => v1.3.7 (#10925) 2024-01-10 13:08:26 +13:00
Matt Hook dbd2e609d7 fix(api-key): add password requirement to generate api key [EE-6140] (#10617) 2024-01-09 11:14:24 +13:00
Chaim Lev-Ari 236e669332 refactor(templates): migrate edit view to react [EE-6412] (#10774) 2024-01-08 14:32:32 +07:00
Chaim Lev-Ari e142939929 fix(ui): apply controlled input to field [EE-6411] (#10738) 2024-01-08 12:11:31 +07:00
Prabhat Khera 98157350b6 fix(UI): add resourse quota warning is consumed 100% [EE-6508] (#10914)
* add resourse quota warning is consumed 100%

* address review comments
2024-01-08 13:49:57 +13:00
Prabhat Khera 317eec2790 allow kube app to scale 0 (#10909) 2024-01-08 08:31:31 +13:00
Prabhat Khera 7a1893f864 fix showing env var values (#10908) 2024-01-08 08:26:20 +13:00
Chaim Lev-Ari c7125266f6 fix(registries): retag image [EE-6456] (#10836) 2024-01-05 18:02:09 -03:00
matias-portainer 69271c9d59 fix(docker/images): check for empty tags EE-6256 (#10531) 2024-01-05 17:33:42 -03:00
andres-portainer 717f0978d9 fix(tls): set the correct scheme for Docker clients EE-6514 (#10917) 2024-01-05 15:24:29 -03:00
Ali abf517de28 refactor(app): migrate app summary section [EE-6239] (#10910) 2024-01-05 15:42:36 +13:00
matias-portainer 7a4314032a fix(docker/console): avoid resizing console when inactive EE-5370 (#10292) 2024-01-04 13:01:52 -03:00
andres-portainer 791c21f643 fix(swarm): retrieve the node names for the image list EE-6401 (#10879) 2024-01-04 10:28:24 -03:00
Chaim Lev-Ari eb5975a400 docs(dashboard): update link for swarm node [EE-6318] (#10833)
Co-authored-by: holysoles <holysoles97@gmail.com>
2024-01-04 17:02:36 +07:00
Chaim Lev-Ari 400a80c07d chore(deps): upgrade to msw v2 [EE-6489] (#10911) 2024-01-04 16:57:21 +07:00
Matt Hook ecd603db8c fix(docker-networks): use Network icon for networks [EE-6507] (#10913) 2024-01-04 18:54:04 +13:00
Chaim Lev-Ari 95358c204b chore(deps): upgrade docker-types [EE-6491] (#10905) 2024-01-03 16:55:45 +07:00
Ali 9fc7187e24 refactor(app): placement form section [EE-6386] (#10818)
Co-authored-by: testa113 <testa113>
2024-01-03 11:00:50 +13:00
Ali 2d77e71085 refactor(app): migrate-autoscaling [EE-6387] (#10709)
* refactor(app): migrate-autoscaling [EE-6387]
2024-01-03 10:42:39 +13:00
Ali 6da71661d5 refactor(app): migrate replicas form section [EE-6238] (#10705)
Co-authored-by: testa113 <testa113>
2024-01-03 10:27:38 +13:00
Ali 58da51f767 refactor(app): migrate deployment type section [EE-6237] (#10704)
Co-authored-by: testa113 <testa113>
2024-01-03 10:04:08 +13:00
Ali 947ba4940b refactor(app): migrate resource reservations [EE-6236] (#10695)
* refactor(app): migrate resource reservations [EE-6236]
2024-01-03 10:03:33 +13:00
Ali e07ee05ee7 refactor(app): persisted folders form section [EE-6235] (#10693)
* refactor(app): persisted folder section [EE-6235]
2024-01-03 09:46:26 +13:00
Ali 7a2412b1be refactor(app): migrate configmap and secret form sections [EE-6233] (#10528)
* refactor(app): migrate configmap and secret form sections [EE-6233]
2024-01-03 09:07:11 +13:00
Matt Hook 391b85da41 fix(lib): update binaries and modules for CVEs [EE-6457] 2024-01-03 08:58:13 +13:00
Prabhat Khera e412958dcc chore(build): exclude draft PRs [EE-5872] (#9987)
* exclude draft PRs
2024-01-03 08:25:35 +13:00
Ali 488393007f refactor(app): migrate env var form section [EE-6232] (#10499)
* refactor(app): migrate env var form section [EE-6232]

* allow undoing delete in inputlist
2024-01-03 08:17:54 +13:00
matias-portainer 6228314e3c fix(oauth): show asterisks placeholder in secret key input field EE-5664 (#10761) 2024-01-02 12:19:15 -03:00
Chaim Lev-Ari ba19aab8dc refactor(registries): migrate repos table to react [EE-6451] (#10830) 2024-01-02 14:04:15 +07:00
Chaim Lev-Ari 3ae430bdd8 chore(build): remove eslint plugin [EE-6432] (#10773) 2024-01-02 13:42:48 +07:00
Chaim Lev-Ari faa7180536 docs(api): default to pascal case for property name [EE-6471] (#10860) 2024-01-02 13:30:02 +07:00
Chaim Lev-Ari a1519ba737 chore(deps): upgrade axios [EE-6488] (#10885)
Co-authored-by: Matt Hook <hookenz@gmail.com>
2024-01-02 13:26:54 +07:00
Chaim Lev-Ari 4c226d7a17 fix(templates): separate template views filters [EE-6397] (#10711) 2024-01-02 13:25:26 +07:00
Chaim Lev-Ari 82951093b5 chore(ci): run lint and test on all pkgs [EE-6201] (#10481) 2024-01-02 10:59:49 +07:00
Matt Hook 2e15cad048 fix(postcss): update postcss to 8.4.32 [EE-6490] 2023-12-29 06:39:53 +13:00
Matt Hook 27e997fe0d update go-get and x/crypto (#10893) 2023-12-28 07:54:41 +13:00
Matt Hook 6a4cfc8d7c chore(libs): update go libs and hide passwords/keys [EE-6496] (#10889) 2023-12-28 05:23:25 +13:00
Matt Hook ebac0b9da2 upgrade golang and other dependant binaries (#10888) 2023-12-27 10:42:35 +13:00
andres-portainer e3c5cd063b fix(chisel): fix a nil pointer dereference EE-6481 (#10871) 2023-12-22 11:36:01 -03:00
Chaim Lev-Ari 2b73116284 fix(templates): add host file entry [EE-6461] (#10839) 2023-12-21 15:56:02 +07:00
Prabhat Khera d2ccb10972 add border to tooltip and modal in high contrast theme (#10834) 2023-12-20 08:55:00 +13:00
Prabhat Khera 6ede9f8cc3 disable html5 validation (#10844) 2023-12-20 08:54:00 +13:00
Prabhat Khera 6b07c874fc revert #10765 (#10870) 2023-12-19 14:19:24 +13:00
Ali e84dd27e88 feat(cache): default to off [EE-6293] (#10867)
Co-authored-by: testa113 <testa113>
2023-12-19 12:13:44 +13:00
Matt Hook 5f1f797281 remove deprecated random seed and other minor staticcheck errors (#10851) 2023-12-18 11:48:41 +13:00
Ali 52fe09d0b1 fix(stacks): remove deployed version column [EE-6346] (#10859)
Co-authored-by: testa113 <testa113>
2023-12-18 11:39:38 +13:00
Matt Hook e687cee608 ignore, remove or comment out unused code. Enable unused linter (#10743) 2023-12-18 10:28:15 +13:00
Matt Hook 8396ff068d enable gosimple linter (#10744) 2023-12-18 10:27:24 +13:00
Ali d98fc1238e fix(git): stacks deployed version [EE-6346] (#10852)
Co-authored-by: testa113 <testa113>
2023-12-15 16:55:39 +13:00
Dakota Walsh 0ddf84638f fix(kubernetes): deprecate old configurations api EE-5571 (#10837)
* fix(kubernetes): deprecate old configurations api EE-5571

* fix doc variable type
2023-12-15 09:04:08 +13:00
Matt Hook 0b9407f0a6 close db before restore. fix log (#10826) 2023-12-14 12:01:05 +13:00
Oscar Zhou e4d71d858d fix(setting/ssl): cert files are optional to upload [EE-6139] (#10776) 2023-12-13 23:20:19 +13:00
Chaim Lev-Ari 25741e8c4c feat(edge): sort waiting room table [EE-6259] (#10577) 2023-12-13 11:10:29 +02:00
Prabhat Khera 32d8dc311b fix cpu parsing logic (#10808) 2023-12-12 15:35:36 +13:00
Dakota Walsh 6ff6fd7f75 fix(swagger): custom template create docs EE-6428 (#10807) 2023-12-11 10:04:23 +13:00
Matt Hook 41b73fe2ae close the db before backup for windows shares and better error handling (#10810) 2023-12-08 15:24:23 +13:00
Prabhat Khera fb3b00de41 fix(UI): remember backup settings tab selection [EE-6347] (#10765)
* remember backup settings tab selection

* address review comments
2023-12-08 15:17:27 +13:00
Prabhat Khera 0f9b91a15f disable create access btn if there is no team or user (#10766) 2023-12-08 14:19:43 +13:00
Dakota Walsh 79f3e1b04b fix(backup ui): minor typo on backup page EE-6348 (#10716) 2023-12-08 13:22:41 +13:00
matias-portainer 56022ab7b1 fix(stacks): allow editing custom templates before stack deployment EE-6380 (#10712) 2023-12-07 09:42:18 -03:00
Ali 4e8b371fb7 fix(gitops): clean trailing slash [EE-6346] (#10777)
Co-authored-by: testa113 <testa113>
2023-12-07 13:43:01 +13:00
Ali a2d6d6002c fix(app): update sliders when limits are known [EE-5933] (#10768)
Co-authored-by: testa113 <testa113>
2023-12-07 12:11:44 +13:00
Chaim Lev-Ari dabcf4f7db feat(custom-templates): migrate create view to react [EE-6400] (#10715) 2023-12-06 14:11:02 +01:00
Prabhat Khera bd5ba7b5d0 fix(kube): configmaps and secrets from envFrom in the app detail screen [EE-6282] (#10742)
* fix configmaps and secrets from envFrom

* adress review comments
2023-12-06 16:02:26 +13:00
James Carppe 1d279428a7 Update bug template for 2.19.4 (#10763) 2023-12-06 12:05:53 +13:00
Chaim Lev-Ari 8ee0c0cf27 fix(images): sort by tags [EE-6410] (#10740) 2023-12-04 08:47:28 +02:00
Chaim Lev-Ari 2a18c9f215 fix(edge/templates): fix issues with git templates [EE-6357] (#10679) 2023-12-04 08:46:44 +02:00
Ali 974378c9b5 fix(gitops): correct commit hash link [EE-6346] (#10723) 2023-12-04 11:18:15 +13:00
Matt Hook eb23818f83 fix(rollback): reimplement rollback feature [EE-6367] (#10721) 2023-12-04 09:12:41 +13:00
Ali 8f4d6e7e27 fix(app): shift external to the top [EE-6392] (#10719)
Co-authored-by: testa113 <testa113>
2023-12-04 07:43:53 +13:00
Oscar Zhou 5c7f6aab66 fix(docker/image): swarm image list issue [EE-6374] (#10710) 2023-12-01 16:49:31 +13:00
Chaim Lev-Ari 3cf36b0e93 fix(app/templates): show default url in settings [EE-6393] (#10706) 2023-11-30 07:18:15 +02:00
Chaim Lev-Ari 7a9436dad7 fix(edge/stacks): clear templates values on change method [EE-6390] (#10707) 2023-11-30 07:13:01 +02:00
cmeng 5c59c53e91 fix(password): force change password EE-6382 (#10708) 2023-11-30 17:46:57 +13:00
Ali e3a995d515 fix(pvc): show access modes [EE-5581] (#10554)
Co-authored-by: testa113 <testa113>
2023-11-30 09:48:55 +13:00
Ali 87b486b798 fix(PVC): access modes match storage class capability [EE-5580] (#10550) 2023-11-30 09:48:14 +13:00
cmeng 92c18843b2 fix(wizard): count swarm agent as local environment EE-6215 (#10684) 2023-11-30 08:53:56 +13:00
Ali 450c167461 fix(cache): exclude reqs that accept yaml [EE-6381] (#10696)
Co-authored-by: testa113 <testa113>
2023-11-29 11:45:10 +13:00
Ali bdcb003a32 fix(app): dont validate stack name [EE-6379] (#10701)
Co-authored-by: testa113 <testa113>
2023-11-29 11:44:33 +13:00
Chaim Lev-Ari c40931b31c fix(templates): show type selector [EE-6370] (#10694) 2023-11-28 15:40:22 +02:00
Matt Hook db46dc553f fix(backups): fix rollback feature [EE-6367] (#10691) 2023-11-28 11:12:17 +13:00
Chaim Lev-Ari 76bcdfa2b8 fix(edge/templates): fix issues [EE-6328] (#10656) 2023-11-27 09:56:15 +02:00
cmeng 140ac5d17c fix(logout): clean user data when logout EE-6368 (#10690) 2023-11-27 17:21:55 +13:00
Ali 2fe965942a fix(kubeClient): get standard client [EE-6376] (#10692)
Co-authored-by: testa113 <testa113>
2023-11-27 16:48:47 +13:00
cmeng dc574af734 fix(container): runtime and resources issues EE-6306 (#10611) 2023-11-27 11:56:44 +13:00
Ali 1bcbfb8213 fix(cache): set as true for a new admin [EE-6293] (#10689)
Co-authored-by: testa113 <testa113>
2023-11-27 10:19:08 +13:00
Oscar Zhou 6bec4cdecc fix(edgestack): set retry deployment (#10676) 2023-11-24 13:45:10 +13:00
Ali 04c1c7d8fb fix(cache): cache fixes [EE-6293] (#10681)
* 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>
2023-11-24 11:27:52 +13:00
Ali 2f91315ac7 fix(namespace): handle undefined registry options [EE-6366] (#10683)
Co-authored-by: testa113 <testa113>
2023-11-24 10:58:32 +13:00
andres-portainer a4b17d2548 fix(gitops): change the condition that checks if the environment is online EE-6321 (#10665) 2023-11-23 11:54:50 -03:00
Chaim Lev-Ari 26953d0b15 fix(templates): change default url [EE-6363] (#10680) 2023-11-23 03:41:22 +02:00
cmeng 13d1fc63ff fix(stack): sync ee code to ce EE-5960 (#10642) 2023-11-23 09:17:12 +13:00
Ali a4926e5237 fix(namespace): create page layout [EE-6385] (#10675) 2023-11-22 15:37:11 +13:00
James Carppe 936a71ee00 Update bug template for 2.19.3 (#10674) 2023-11-22 14:25:22 +13:00
Ali 4096bb562d feat(cache): introduce cache option [EE-6293] (#10672)
Co-authored-by: testa113 <testa113>
2023-11-22 14:21:07 +13:00
cmeng 57ed6ae6a6 fix(edge-stack): parse docker compose multi lines json output EE-6317 (#10627) 2023-11-20 22:54:28 +13:00
Chaim Lev-Ari ad5a17ac34 feat(edge/updates): schedule time changes [EE-5975] (#10458) 2023-11-20 10:24:54 +02:00
Chaim Lev-Ari 436da01bce feat(auth): save jwt in cookie [EE-5864] (#10527) 2023-11-20 09:35:03 +02:00
Ali ecce501cf3 Revert "feat(cache): introduce cache option [EE-6293] (#10641)" (#10658)
This reverts commit 2c032f1739.
2023-11-20 15:08:19 +13:00
Ali 2c032f1739 feat(cache): introduce cache option [EE-6293] (#10641) 2023-11-20 10:22:48 +13:00
cmeng fffc7b364e fix(container): Unable to create container with webhook EE-6313 (#10619) 2023-11-17 14:35:47 +13:00
andres-portainer 0b5b8971b1 fix(gitops): handle the local environment in isEnvironmentOnline() EE-6321 (#10631) 2023-11-16 09:40:42 -03:00
cmeng be09c5e346 fix(volumes): Volumes browse button spacing issue EE-6323 (#10633) 2023-11-16 16:25:17 +13:00
cmeng d089dfbca0 fix(container): fix various creating container issues EE-6287 (#10595)
* fix(container): show placeholder for image field EE-6287

* fix(container): correct query params for search button field EE-6287

* fix(container): use btoa to encode registry credential EE-6287

* fix(container): allow creating non-existing option EE-6287

* fix(ui/forms): typeahead component

* fix(container): select the default registry EE-6287

* fix(container): always enable deploy button when always pull is off EE-6287

* fix(container): reset command fields outside current event to avoid validation on broken values EE-6287

* fix(container): query registry with endpoint ID param EE-6287

---------

Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2023-11-16 08:50:23 +13:00
Chaim Lev-Ari e43d076269 feat(edge/templates): introduce edge specific settings [EE-6276] (#10609) 2023-11-15 14:43:18 +02:00
Chaim Lev-Ari 68950fbb24 feat(edge/templates): introduce custom templates [EE-6208] (#10561) 2023-11-15 10:45:07 +02:00
Chaim Lev-Ari a0f583a17d fix(containers): align switches [EE-6314] (#10616) 2023-11-15 09:34:08 +02:00
Chaim Lev-Ari 51474262eb fix(access-control): show only environment users [EE-6315] (#10614) 2023-11-15 09:33:29 +02:00
Matt Hook 3525a1af77 fix(kube): change advanced deployment label [EE-6310] (#10626)
* change namespace label to deploy to

* fix var typo
2023-11-15 11:35:39 +13:00
Chaim Lev-Ari e1e90c9c1d feat(edge/templates): introduce edge app templates [EE-6209] (#10480) 2023-11-14 14:54:44 +02:00
Chaim Lev-Ari 95d96e1164 fix(ui): parse slider value correctly [EE-6225] (#10484) 2023-11-14 13:17:25 +02:00
Chaim Lev-Ari 99b39da03d refactor(edge/groups): migrate view to react [EE-4683] (#10592) 2023-11-14 12:57:27 +02:00
Chaim Lev-Ari 1f2f4525e3 feat(ui/buttons): introduce Add and Delete buttons [EE-6296] (#10585) 2023-11-14 12:36:15 +02:00
James Carppe 66635ba6b1 Updated versions in bug report template (#10620)
LGTM
2023-11-13 07:07:44 +05:30
yi-portainer 3630aab820 * remove line break 2023-11-13 14:18:52 +13:00
Matt Hook 3c8c2118d4 update namespace section for helm (#10610) 2023-11-13 09:37:52 +13:00
Chaim Lev-Ari d6ac29b498 fix(edge/stacks): remove parentheses [EE-6277] (#10560)
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:linux]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
Test / test-server (map[arch:arm64 platform:linux]) (push) Has been cancelled
2023-11-09 09:55:54 +02:00
Prabhat Khera e73b7fe0fd fix(kubernetes): clear user token from kube token cache on logout + update cluster rolebindings for user on change of team/user authorization [EE-6298] (#10598)
* clear user token from kube token cache on logoug + updates cluster role bindings for service accounts on change user/teams authorizations
2023-11-09 14:33:23 +13:00
Prabhat Khera e761a00098 fix(kubernetes): URL form validation for advance deployment [EE-6280] (#10607) 2023-11-09 13:26:42 +13:00
Oscar Zhou 9041880bdb fix(container): assign container domain name (#10605) 2023-11-09 10:19:28 +13:00
Ali e4ddd8048a fix(app): disable deploy when there are no namespaces [EE-6295] (#10606)
Co-authored-by: testa113 <testa113>
2023-11-08 03:22:41 +00:00
Oscar Zhou e6ef913bb1 fix(docker/swarm): suppress no such container logs (#10604) 2023-11-08 11:43:42 +13:00
Matt Hook 3fd696d6b5 switch to filter after upgrading helm binary (#10596) 2023-11-06 13:32:14 +13:00
Ali 24c9959ca4 fix(app): hide services section when there are no namespaces [EE-6295] (#10588)
Co-authored-by: testa113 <testa113>
2023-11-05 17:37:48 +00:00
Chaim Lev-Ari e72671e4ab fix(edge/updates): hide sidebar item when disabled [EE-6294] (#10582) 2023-11-05 12:45:56 +02:00
Prabhat Khera 47c9e498f9 fix validation fro custom template (#10587) 2023-11-03 11:39:44 +13:00
Ali 0c323b48e7 fix(nodes): restrict nodes details from standard user [EE-6125] (#10586)
Co-authored-by: testa113 <testa113>
2023-11-02 19:02:19 +00:00
Prabhat Khera 103d908e63 fix(users): hide admin users for non admins from user list API [EE-6290] (#10580)
* hide admin users for non admins from user list API

* address review comments
2023-11-02 16:08:17 +13:00
cmeng 2972022523 fix(version): show build info EE-6278 (#10578) 2023-11-02 11:04:11 +13:00
Oscar Zhou 4ffeefd267 feat(security): add docker scout pr github action flow (#10557) 2023-11-02 09:34:24 +13:00
Prabhat Khera c8bdf21d07 fix(kubernetes): validation for advance deployments [EE-6280] (#10574) 2023-11-02 08:50:12 +13:00
Prabhat Khera b6f9777bbf fix custom template id on select (#10573) 2023-11-02 08:46:50 +13:00
Ali f6b78312f4 fix(nodes): allow standard users to get kube endpoints [EE-6125] (#10572)
Co-authored-by: testa113 <testa113>
2023-11-01 19:08:38 +00:00
Chaim Lev-Ari 948486df77 fix(services): update service [EE-6275] (#10559) 2023-11-01 12:27:38 +02:00
cmeng 600c8a3025 fix(volumes): fix broken volume browse button EE-6274 (#10566) 2023-11-01 14:15:54 +13:00
cmeng 8daf77c3b6 fix(log-viewer): unable to view container logs EE-6273 (#10555) 2023-11-01 12:00:05 +13:00
matias-portainer 8bb5129be0 feat(nomad): remove nomad from UI EE-6060 (#10509) 2023-10-31 15:27:20 -03:00
Ali 1140804fe9 fix(app): sync showSystem between stacks and apps tables [EE-6216] (#10532) 2023-10-30 19:41:41 +00:00
Prabhat Khera 7d868d1dc9 hide stacks tab if stack feature is disabled (#10551) 2023-10-30 14:13:36 +13:00
andres-portainer 247f358b94 fix(code): revert omitempty optimization EE-6269 (#10548) 2023-10-27 17:33:04 -03:00
matias-portainer f10356641a fix(edge/aeec): make edge id generator field mandatory EE-6010 (#10545) 2023-10-27 10:35:10 -03:00
LP B 9e60723e4d fix(app/logout): always perform API logout + make API logout route public [EE-6198] (#10448)
* 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
2023-10-27 14:44:05 +02:00
Ali 47fa1626c6 fix(app): don't attach all ingresses to app [EE-5686] (#10537) 2023-10-27 16:59:45 +13:00
Prabhat Khera 26036c05f2 fix(kubernetes): remove unique check from kubernetes stacks [EE-6170] (#10542) 2023-10-27 15:41:02 +13:00
Matt Hook 8ee718f808 chore(binaries): upgrade binaries [EE-6253] (#10529) 2023-10-27 15:40:06 +13:00
cmeng 30e4b3e68c fix(edge-stack): remove text info for relative path EE-6228 (#10541) 2023-10-27 14:53:20 +13:00
cmeng 0d56504268 fix(settings): disable save button when revert changes EE-6263 (#10543) 2023-10-27 13:47:08 +13:00
Ali 6a5f44b5ba fix(errors): display kube confgimap and secret errors [EE-5558] (#10539) 2023-10-27 10:56:03 +13:00
cmeng 3964852fda fix(container): hide capabilities tab EE-6258 (#10540) 2023-10-26 15:44:31 +13:00
Ali 403fdf7ce3 fix(nodes): disable select [EE-4692] (#10538)
Co-authored-by: testa113 <testa113>
2023-10-25 23:59:01 +01:00
Ali afa3e7477b fix(toggle): update cursor style and color transition speed [EE-6229] (#10534)
Co-authored-by: testa113 <testa113>
2023-10-25 23:17:18 +01:00
Ali d9effb3597 fix(nodes): fix nodes datatable width [EE-4962] (#10533)
Co-authored-by: testa113 <testa113>
2023-10-25 20:42:36 +01:00
andres-portainer ee80e3d252 fix(edge): fix frontend issues with omitempty EE-6260 (#10536) 2023-10-25 15:51:39 -03:00
matias-portainer 824706e4e9 fix(ui): label GPU functionality as NVIDIA only EE-6204 (#10522) 2023-10-25 14:48:22 -03:00
Chaim Lev-Ari 09f9c09706 chore(ci): separate tests from CI (#10524) 2023-10-25 07:52:04 +03:00
Prabhat Khera 577eef5de0 fix stack name placeholder and some css styling (#10523) 2023-10-25 08:59:10 +13:00
andres-portainer ae1726cece feat(performance): performance optimizations EE-6042 (#10520) 2023-10-24 13:55:11 -03:00
andres-portainer e4e66dac9c fix(gitops): only attempt to redeploy when the environment appears to be online EE-6182 (#10464) 2023-10-24 11:20:45 -03:00
Steven Kang 08fdebfbd9 feat(ci): introduce GH Actions for Portainer CE (#10419)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2023-10-24 13:30:33 +03:00
Matt Hook 860890046d fix(registry): remove k8s registry secrets when registries are removed [EE-5768] (#10369) 2023-10-24 09:24:09 +13:00
Ali 96ead31a8d fix(kubeapi): fix ts api error handling [EE-5558] (#10488)
* fix(kubeapi): fix ts api error handling [EE-5558]

* use portainer errors for mapped functions

* don't parse long patch responses

* allow nested kube error that's thrown to bubble up

---------

Co-authored-by: testa113 <testa113>
2023-10-23 20:52:40 +01:00
andres-portainer 6c55cac52a feat(code): equalize the code with EE EE-6218 (#10518) 2023-10-23 15:52:37 -03:00
Ali e110856003 fix(namespace): remove duplicate 'no registry' text [EE-2226] (#10519)
Co-authored-by: testa113 <testa113>
2023-10-23 19:49:24 +01:00
Chaim Lev-Ari 10c3ed42f0 refactor(custom-templates): migrate list component to react [EE-6206] (#10440) 2023-10-23 20:00:50 +03:00
Chaim Lev-Ari 14129632a3 refactor(app-templates): convert list to react [EE-6205] (#10439) 2023-10-23 19:04:18 +03:00
Chaim Lev-Ari 1fa63f6ab7 refactor(docker/services): migrate service tasks to react [EE-4676] (#10328) 2023-10-23 13:52:49 +03:00
Chaim Lev-Ari 70455320be fix(docker/volumes): Add volume typo [EE-6226] (#10483) 2023-10-23 13:31:59 +03:00
Chaim Lev-Ari b933bee95e feat(docker/networks): migrate networks datatable to React [EE-4670] (#10351)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2023-10-22 12:35:22 +03:00
Chaim Lev-Ari 0dc1805881 refactor(docker/services): convert services table to react [EE-4675] (#10289) 2023-10-22 12:32:05 +03:00
Chaim Lev-Ari 6b5c24faff refactor(custom-templates): migrate common-fields to react [EE-6207] (#10445) 2023-10-22 12:19:19 +03:00
Chaim Lev-Ari 1ad9488ca7 refactor(templates): migrate template item to react [EE-6203] (#10429) 2023-10-19 21:09:15 +02:00
Chaim Lev-Ari d970f0e2bc refactor(containers): migrate create view to react [EE-2307] (#9175) 2023-10-19 13:45:50 +02:00
cmeng bc0050a7b4 fix(user-token): prevent admin read tokens of other admins EE-5858 (#10489) 2023-10-19 16:23:14 +13:00
Prabhat Khera 03155685ab fix toggle colors (#10503) 2023-10-19 15:01:31 +13:00
Prabhat Khera 7e4d113fda fix libhelm error message (#10502) 2023-10-19 13:20:49 +13:00
Oscar Zhou 7c91780eb7 fix(edge): introduce pause and rollback status [EE-5992] (#10465) 2023-10-19 11:26:02 +13:00
Ali 877dc1e236 fix(namespace): update no registries text [EE-2226] (#10501)
Co-authored-by: testa113 <testa113>
2023-10-18 22:57:29 +01:00
Prabhat Khera 56f3bd8417 add name field for helm install in advance deployments (#10493) 2023-10-18 11:37:03 +13:00
Ali 776be2e022 fix(sidebar): high contrast styles, single option link [EE-5666] (#10485) 2023-10-16 21:23:23 +01:00
Ali 0e47f22c0a refactor(cluster): migrate nodes datatable to react [EE-4962] (#10459)
Co-authored-by: testa113 <testa113>
2023-10-16 21:19:08 +01:00
andres-portainer b346fd7f39 fix(store): fix StoreIsUpdating() to properly set the state EE-6227 (#10486) 2023-10-16 16:32:30 -03:00
Prabhat Khera 35448c7f48 fix helm install (#10479) 2023-10-17 07:50:13 +13:00
Ali 07ec2ffe5e fix(namespace): create ns qa feedback [EE-2226] (#10474) 2023-10-16 19:15:44 +01:00
Prabhat Khera bcb3f918d1 some minor UI fixes (#10475) 2023-10-16 14:08:55 +13:00
Prabhat Khera 7840e0bfe1 feature(kubernetes): stack name made optional & add toggle to disable stack in kubernetes [EE-6170] (#10436) 2023-10-16 14:08:06 +13:00
Chaim Lev-Ari 44d66cc633 fix(docker/secrets): allow navigating to secret item page [EE-6164] (#10382) 2023-10-15 09:33:27 +03:00
Matt Hook 148bd4d997 chore:(kubeclient): refactor kubeclient middleware and endpoints [EE-5028] (#10423) 2023-10-13 13:43:36 +13:00
Matt Hook 7c4c985247 upgrade some badge components to match EE (#10451) 2023-10-13 03:10:16 +13:00
Chaim Lev-Ari 57c45838d5 fix(edge/updates): allow group search [EE-6179] (#10408) 2023-10-12 08:30:23 +03:00
Ali 5a73605df2 fix(sidebar): consistent font weight [EE-5666] (#10461) 2023-10-12 01:59:46 +01:00
Prabhat Khera ff5b311eee fix(helm): fix helm move to advance deployments issues [EE-5999] (#10453)
* fix helm move to adv deployments
2023-10-12 11:02:09 +13:00
Ali 7218eb0892 feat(namespace): migrate create ns to react [EE-2226] (#10377) 2023-10-11 20:32:02 +01:00
Prabhat Khera 31bcba96c6 feature(UI): toggle styling changes [EE-4602] (#10373) 2023-10-12 07:34:38 +13:00
Ali 6a5f5aa424 fix(sidebar): qa feedback [EE-5666] (#10452) 2023-10-11 19:32:52 +01:00
matias-portainer da5a4d6714 fix(swarm/services): avoid sending credSpec object when empty EE-6178 (#10441) 2023-10-11 11:30:23 -03:00
Chaim Lev-Ari 35dfde70de refactor(ui/page-header): make docs url explicit [EE-5966] (#10411) 2023-10-11 10:38:57 +03:00
Chaim Lev-Ari 9e57530bde fix(build): handle warning about userId [EE-5612] (#10444) 2023-10-11 10:30:14 +03:00
Chaim Lev-Ari 5c37ed328f refactor(docker/volumes): migrate table to react [EE-4677] (#10312) 2023-10-11 10:27:42 +03:00
Chaim Lev-Ari 8e1417b4e9 refactor(docker/containers): remove EndpointProvider from container service [EE-6180] (#10392) 2023-10-11 10:26:44 +03:00
Chaim Lev-Ari b80fcb0467 fix(docker/stacks): show orphaned stacks option [EE-6149] (#10346) 2023-10-11 10:24:35 +03:00
cmeng 66ca73f98b fix(edge-stack): sync CE code with EE EE-6163 (#10437) 2023-10-11 18:11:12 +13:00
Ali a0dbabcc5f feat(sidebar): update menu structure [EE-5666] (#10418) 2023-10-09 19:23:12 +01:00
Prabhat Khera b468070945 feature(helm): move helm charts inside advance deployments (create from manifest) [EE-5999] (#10395) 2023-10-09 11:20:44 +13:00
Oscar Zhou 9885694df6 fix(filesys): update stack version methods [EE-6190] (#10406) 2023-10-06 09:08:22 +13:00
Chaim Lev-Ari 95f3cf6e5b refactor(server): use httperror.NewError instead of struct [EE-6189] (#10398) 2023-10-05 11:26:24 +03:00
Chaim Lev-Ari da346cba60 chore(deps): update ts and more deps [EE-5756] (#10409) 2023-10-05 11:25:35 +03:00
Chaim Lev-Ari 5f9687a361 fix(edge/waitingroom): hide sidebar when disabled [EE-6003] (#10343) 2023-10-05 10:31:08 +03:00
Chaim Lev-Ari 20823a7f27 chore(deps): upgrade golangci [EE-5685] (#10410) 2023-10-04 08:50:59 +03:00
Chaim Lev-Ari 9bf2957ea7 feat(docker/images): show used tag correctly [EE-5396] (#10305) 2023-10-03 15:55:23 +03:00
Ali b895e88075 fix(teasers): add teaser message full stops [EE-6035] (#10401) 2023-10-02 21:23:00 +01:00
matias-portainer 671f74ce0d fix(edge/groups): include only user trusted endpoints in endpoint count EE-5964 (#10378) 2023-10-02 11:37:39 -03:00
cmeng 56ab19433a fix(websocket): abort websocket when logout EE-6058 (#10372) 2023-09-29 12:13:09 +13:00
Matt Hook 9440aa733d support proxy for helm repo validation (#10358) 2023-09-29 10:55:49 +13:00
LP B ada6b31f69 fix(docker/container): container logs viewer error when logging is disabled (#10384)
* 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
2023-09-28 15:53:52 +02:00
Ali d678b155ba fix(teasers): updated muted styles from qa feedback [EE-6035] (#10390)
* fix(teasers): updated muted styles from qa feedback [EE-6035]
2023-09-28 11:32:58 +01:00
Prabhat Khera 99625cd35f fix team lead access to view user names (#10388) 2023-09-28 12:40:54 +13:00
Chaim Lev-Ari 95ca1d396b fix(docker/services): show cred spec configs [EE-5276] (#10083) 2023-09-27 07:57:47 +03:00
Chaim Lev-Ari e28322459a fix(stacks): mark stack as start after autoupdate [EE-6165] (#10376) 2023-09-27 07:53:33 +03:00
Prabhat Khera 3ff2f64930 fix(authorization): disable user list api call if not authorised [EE-5825] (#10379)
* disable user list api call if not authorised

* fix tests

* fix lint issues
2023-09-27 10:12:30 +13:00
Ali 702391cf88 remove apostrophe from tooltip (#10386)
Co-authored-by: testa113 <testa113>
2023-09-26 21:25:08 +01:00
LP B d437cde046 fix(docker/container): missing return statement when preparing container config (#10383) 2023-09-26 14:09:38 +02:00
Chaim Lev-Ari 7acde18930 feat(containers): migrate labels tab to react [EE-5212] (#10348) 2023-09-26 13:54:45 +03:00
cmeng b4b44e6fa4 fix(edge-config): allow empty filter type EE-5962 (#10381) 2023-09-26 13:49:25 +13:00
Chaim Lev-Ari 2dfa4a7c45 refactor(containers): migrate restart policy tab to react [EE-5213] (#10347) 2023-09-25 20:40:26 +03:00
Chaim Lev-Ari 3d19c46326 style(kubernetes): disable autoFocus warning [EE-5752] (#10368) 2023-09-25 20:13:31 +03:00
Chaim Lev-Ari 57e04c3544 refactor(containers): migrate caps tab to react [EE-5215] (#10366) 2023-09-25 19:36:50 +03:00
Chaim Lev-Ari 9dde610da3 fix(docker/containers): create container with bridge network [EE-6160] (#10365) 2023-09-25 19:35:54 +03:00
LP B 26cb75def9 feat(app/home): tooltip aside edge agent version on mismatch with Portainer version (#10287)
* 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
2023-09-25 11:56:08 +02:00
Prabhat Khera 3c4660bbf3 fix(permissions): non admin access to view users [EE-5825] (#10352)
* fix non admin access to view users

* review comments and fix tests
2023-09-25 09:08:26 +13:00
Ali 13c48ab961 fix(be-teaser): mute styles [EE-6035] (#10349) 2023-09-24 19:56:09 +01:00
Chaim Lev-Ari ffac83864d refactor(containers): migrate resources tab to react [EE-5214] (#10355) 2023-09-24 15:31:06 +03:00
Prabhat Khera ec091efe3b fix deadlock situation (#10360) 2023-09-22 16:06:20 +12:00
cmeng fb7a2fbbe6 fix(stack): fix edit git stack validation EE-5855 (#10339) 2023-09-22 10:09:24 +12:00
matias-portainer dfce48cd5e fix(stacks): check properly if endpoint id is defined in the stacks object EE-6118 (#10302) 2023-09-21 10:12:43 -03:00
Chaim Lev-Ari 2b47b84e5e feat(docker/containers): migrate network tab to react [EE-5210] (#10344) 2023-09-21 14:02:02 +03:00
Chaim Lev-Ari e92f067e42 refactor(containers): migrate volumes tab to react [EE-5209] (#10284) 2023-09-21 05:31:00 +03:00
Chaim Lev-Ari 16ccf5871e refactor(docker/containers): migrate env vars to react [EE-5211] (#10345) 2023-09-21 04:11:18 +03:00
cmeng 54112b56f2 feat(edge-config): support edge config for group EE-5962 (#10329) 2023-09-21 11:22:44 +12:00
LP B a66942aa5a fix(app/stacks): swarm stacks incorrectly marked as orphaned (#10319) 2023-09-20 12:40:08 +02:00
Ali c18504d6f1 fix(cluster): make angular refresh env [EE-5524] (#10315)
Co-authored-by: testa113 <testa113>
2023-09-20 19:33:43 +12:00
Chaim Lev-Ari 25d5e62f5c refactor(kube/apps): migrate stacks table to react [EE-4661] (#10091) 2023-09-20 09:04:26 +03:00
James Carppe a5f60c64ef Added 2.19.1 to list of versions in bug report template (#10338) 2023-09-20 07:48:35 +05:30
Matt Hook d6d532473e allow libhelm to use forward proxy (#10331) 2023-09-19 18:07:51 +12:00
Chaim Lev-Ari af7834174a fix(api): restore deleted apis [EE-6090] (#10267) 2023-09-19 13:44:48 +12:00
Prabhat Khera 14853f6da0 fix(kubernetes): kube env permissions when down [EE-5427] (#10327) 2023-09-19 08:57:27 +12:00
Oscar Zhou cc37ccfe4d fix(db/migration): avoid fatal error from being overwritten (#10316) 2023-09-18 14:33:04 +12:00
Matt Hook e3a4b7ad17 improved user update validation (#10321) 2023-09-18 12:29:04 +12:00
Dakota Walsh 0a02f6b02e fix(kubernetes): add prefix only when needed EE-6068 (#3915) (#10310) 2023-09-15 09:25:56 +12:00
Chaim Lev-Ari dcdf5e1837 fix(edge/jobs): clear logs [EE-5923] (#10291) 2023-09-13 22:11:42 +01:00
Chaim Lev-Ari bf85a8861d refactor(docker/swarm): migrate nodes table to react [EE-4672] (#10184) 2023-09-13 10:51:33 +01:00
Chaim Lev-Ari fbdbd277f7 fix(docker/container): pass empty command and entrypoint [EE-6106] (#10285) 2023-09-13 10:47:13 +01:00
cmeng 0a80f4dc51 fix(backup): add chisel key to backup EE-6105 (#10283) 2023-09-13 09:01:27 +12:00
andres-portainer 5a0cb4d0e8 fix(gitops): avoid cancelling the auto updates for any error EE-5604 (#10294) 2023-09-12 17:53:01 -03:00
Oscar Zhou f17da30d31 fix(db/init): check server version and db schema version (#10300) 2023-09-12 15:55:09 +12:00
Matt Hook 291625959b update logic to purge the cache, update the message when the environment can't be reached (#10298) 2023-09-12 13:52:09 +12:00
Prabhat Khera 4c16594a25 fix(security): added restrictions to see user names [EE-5825] (#10296)
* fix(security): added restrictions to see user names [EE-5825]

* use pluralize method
2023-09-12 13:15:34 +12:00
Chaim Lev-Ari 60477ae287 refactor(docker/networks): migrate macvlan nodes selector to react [EE-4669] (#10183) 2023-09-11 15:27:04 +01:00
Chaim Lev-Ari 09aa1d35a8 refactor(ui): remove unused tables [EE-4698] (#10215) 2023-09-11 15:26:22 +01:00
cmeng 7669a3c8c6 fix(settings): misaligned poll frequency selector EE-6081 (#10286) 2023-09-11 15:35:44 +12:00
Ali dde4b95426 fix(cluster): faster submitting load times [EE-5524] (#10280)
* faster submitting load times

* scroll to selected tz option

---------

Co-authored-by: testa113 <testa113>
2023-09-11 10:52:00 +12:00
LP B dfd415c62e fix(app/stacks): stacks incorrectly marked as orphaned (#10273) 2023-09-08 22:22:26 +02:00
Matt Hook b40b305e63 fix(styles): improve styling of form-section-title [EE-5366] (#10250) 2023-09-08 13:40:09 +12:00
Chaim Lev-Ari c8a1f0fa77 refactor(docker/stacks): migrate table to react [EE-4705] (#9956) 2023-09-07 15:59:59 +01:00
Chaim Lev-Ari c3d266931f refactor(docker/services): convert service tasks table to react [EE-4674] (#10188) 2023-09-07 15:19:03 +01:00
Chaim Lev-Ari c47a804c97 refactor(docker/secrets): migrate table to react [EE-4673] (#10185) 2023-09-07 15:15:22 +01:00
Chaim Lev-Ari b15812a74d refactor(docker/containers): migrate networks table to react [EE-4665] (#10069) 2023-09-07 15:14:03 +01:00
matias-portainer 776f6a62c3 fix(authentication): allow nested whitespaces on AD OU names EE-5206 (#10260) 2023-09-07 11:02:57 -03:00
Chaim Lev-Ari ae3e612a24 feat(docker/stacks): fold env vars by default [EE-5575] (#9957) 2023-09-07 14:45:59 +01:00
Ali 6a8ff7c076 fix(yaml): remove create message on edit views [EE-5356] (#10254)
Co-authored-by: testa113 <testa113>
2023-09-07 09:29:25 +12:00
andres-portainer 4a39122415 fix(code): remove code that is no longer necessary EE-6078 (#10256) 2023-09-05 22:35:16 -03:00
andres-portainer c748385879 feat(transactions): remove the feature flag EE-6080 (#10257) 2023-09-05 20:27:20 -03:00
Oscar Zhou e83aa4d88d fix(gomod): update golang version (#10255) 2023-09-06 10:29:58 +12:00
Matt Hook 91d2132264 prevent regular users changing their username (#10247) 2023-09-06 09:17:04 +12:00
Matt Hook e5f7641e46 non-admins must supply existing passwd when changing passwd (#10249) 2023-09-06 08:26:32 +12:00
Ali 515b02813b feat(k8sconfigure): migrate configure to react [EE-5524] (#10218) 2023-09-06 04:06:36 +12:00
Oscar Zhou 0f1e77a6d5 fix(security): update dependency and binary version [EE-5798] (#10192) 2023-09-05 17:23:12 +12:00
Prabhat Khera a02f9f1f07 fix(kubernetes): run group permission when endpoint is up [EE-5427] (#10121)
* update group access when env is down

* fix tests
2023-09-05 11:03:43 +12:00
Dakota Walsh d75a8027a5 fix(security): block user access policies for non admins EE-5826 (#10243) 2023-09-05 09:17:55 +12:00
Dakota Walsh 6a08bbe7e9 fix(security): block non-admins from user info listing EE-5825 (#10241) 2023-09-05 09:17:05 +12:00
Chaim Lev-Ari e82b34b775 refactor(docker/services): migrate scale form to react [EE-6057] (#10208) 2023-09-04 16:24:41 -03:00
Chaim Lev-Ari f7366d9788 refactor(docker/containers): migrate commands tab to react [EE-5208] (#10085) 2023-09-04 19:07:29 +01:00
Chaim Lev-Ari 46e73ee524 refactor(docker/containers): migrate processes table to react [EE-4666] (#10081) 2023-09-04 17:05:01 +01:00
Chaim Lev-Ari e5880b3e34 fix(edge): add background to table icons [EE-6020] (#10187) 2023-09-04 16:52:51 +01:00
Chaim Lev-Ari 0e2eb17220 chore(deps): upgrade tailwind and prettier [EE-5218] (#10068) 2023-09-04 16:20:36 +01:00
Chaim Lev-Ari cb7377ead6 refactor(ui/datatables): allow datatable to globally filter on object value [EE-5824] (#9955) 2023-09-04 10:33:07 +01:00
Oscar Zhou 440f4e8dda fix(edge): stack associated no dynamic group being deployed [EE-5531] (#10224) 2023-09-04 17:04:45 +12:00
James Carppe 490e4ec655 Add 2.19.0 to bug report template (#10239) 2023-09-04 10:20:55 +05:30
Dakota Walsh 7be8619ab7 fix(search): Add noindex meta tag EE-5371 (#10220) 2023-09-04 07:45:44 +12:00
Chaim Lev-Ari 4a6b7e2654 fix(ui/switch): reduce label size [EE-3803] (#10019) 2023-09-03 10:26:38 +01:00
andres-portainer 8cc5e0796c feat(libhttp): move into the Portainer repository EE-5475 (#10231) 2023-09-01 19:27:02 -03:00
andres-portainer 090fa4aeb3 feat(libcrypto): move into the Portainer repository EE-5476 (#10230) 2023-09-01 17:27:19 -03:00
andres-portainer 9a234204fa chore(go): move go.mod up one level to simplify dependencies EE-5726 (#10228) 2023-09-01 13:39:13 -03:00
Prabhat Khera 4560a53317 add tls options to the tls dropdown (#10221) 2023-09-01 10:42:22 +12:00
Chaim Lev-Ari 1b0fd60115 refactor(docker/configs): remove EndpointProvider [EE-5746] (#9198) 2023-08-31 22:11:57 +02:00
Ali cd3c6e3089 fix(k8sconfigure): make ingress restrict be only [EE-6062] (#10216)
Co-authored-by: testa113 <testa113>
2023-09-01 06:11:48 +12:00
Oscar Zhou 4654978567 fix(api/system): support to display server edition via api (#10211) 2023-08-31 13:39:02 +12:00
Prabhat Khera 6d203033c1 fix showing default ns for ingresses on edi (#10197) 2023-08-29 15:12:49 +12:00
cmeng 4ca45e89c5 fix(relative-path): not deploy git stack via unpacker EE-6043 (#10195) 2023-08-29 11:49:00 +12:00
Prabhat Khera a8c6bd8082 fix ECR registry token refresh (#10191) 2023-08-29 10:32:41 +12:00
Ali 841ca1ebd4 feat(app): migrate app parent view to react [EE-5361] (#10086)
Co-authored-by: testa113 <testa113>
2023-08-28 09:01:35 +12:00
Chaim Lev-Ari 531f88b947 chore(tests): clean tests output [EE-5758] (#9215) 2023-08-27 12:30:45 +02:00
Dakota Walsh 2953848b9a feat(gpu): remove GPU lightbubble EE-5254 (#10096) 2023-08-25 15:32:08 +12:00
Dakota Walsh c0ba221021 fix(registry): ecr secret fix [EE-5673] (#10107) 2023-08-25 13:12:41 +12:00
andres-portainer be85d34c4b fix(logging): enable colored logging EE-5512 (#10097) 2023-08-24 18:40:52 -03:00
cmeng 7125ef81f3 fix(stack): pass registries to unpacker to start stack EE-4797 (#10095) 2023-08-24 13:01:49 +12:00
cmeng 1aae2e27f4 chore(chisel): bump chisel to 1.9.0 EE-5976 (#10093) 2023-08-24 11:06:33 +12:00
cmeng 3237e1990c fix(waiting-room): search endpoints by dynamic edge group name EE-5965 (#10090) 2023-08-24 09:18:59 +12:00
Ali 1e61f7e305 fix(ingress): handle system resources [EE-4775] (#9972)
* fix(ingress): handle system resources [EE-4775]
2023-08-23 09:13:35 +12:00
Chaim Lev-Ari 5586910e9d fix(ui/datatables): sync page count with filtering [EE-5890] (#10010) 2023-08-22 09:36:31 +03:00
Prabhat Khera bb646162d1 fix wrong error message for secrets (#10073) 2023-08-21 08:05:57 +12:00
Chaim Lev-Ari cfe0d3092d feat(ui): add confirmation to delete actions [EE-4612] (#10003) 2023-08-19 19:19:02 +03:00
cmeng 6fde4195f8 fix(migrator): prevent duplicated migration EE-5777 (#10077) 2023-08-18 21:40:48 +12:00
Chaim Lev-Ari 36b8c849b3 feat(edge/stacks): reload edge stacks from server [EE-5970] (#10061) 2023-08-17 14:09:41 +03:00
Ali 0f6607e703 refactor(app): migrate the yaml inspector to react [EE-5356] (#10058)
Co-authored-by: testa113 <testa113>
2023-08-17 22:01:10 +12:00
Ali 23295d2736 feat(app): migrate app containers to react [EE-5353] (#9992) 2023-08-17 22:00:25 +12:00
cmeng 6290e9facc fix(waiting-room): search endpoints by edge group name EE-5965 (#10072) 2023-08-17 14:47:09 +12:00
cmeng 95424c322d fix(datatable): image page not loading image list EE-5978 (#10071) 2023-08-17 09:53:28 +12:00
Chaim Lev-Ari a1e610a39a fix(edge/groups): filter selected environments [EE-5891] (#10050) 2023-08-16 12:24:37 +03:00
Chaim Lev-Ari a27cc6c0e5 fix(edge/stacks): show pending envs [EE-5913] (#10052) 2023-08-16 10:22:41 +03:00
Ali 2b4cb1b7b4 fix(ingress): empty initial selection + fixes [EE-5852] (#10066)
Co-authored-by: testa113 <testa113>
2023-08-16 18:07:46 +12:00
Ali 26074437ca fix(environments): fix env table [EE-5971] (#10059)
Co-authored-by: testa113 <testa113>
2023-08-16 13:21:23 +12:00
Prabhat Khera 665a25e448 fix edit namespace resource quota issue (#10064) 2023-08-16 10:25:01 +12:00
Chaim Lev-Ari 4a91e947ed feat(edge/configs): add context help [EE-5963] (#10055) 2023-08-15 18:46:58 +03:00
Chaim Lev-Ari d514eeec86 fix(edge/devices): search waiting room devices [EE-5895] (#10014) 2023-08-15 06:05:10 +03:00
matias-portainer 0ef4aad79a fix(authentication): allow whitespaces when loading AD OU name EE-5206 (#9977) 2023-08-14 12:18:07 -03:00
matias-portainer 8355d449c5 fix(edge/stacks): add pagination to environments list EE-5908 (#10042) 2023-08-14 12:17:00 -03:00
Chaim Lev-Ari fd7e8a629e feat(edge/stacks): info for old agent status [EE-5792] (#10013) 2023-08-14 16:04:24 +03:00
Ali 7757bf7a84 fix(r2a): remove withUserProvider [EE-5355] (#10048)
Co-authored-by: testa113 <testa113>
2023-08-14 19:01:31 +12:00
Ali 5862aa5dd8 fix(app): use correct withCurrentUser wrapper [EE-5928] (#10040)
Co-authored-by: testa113 <testa113>
2023-08-14 16:53:28 +12:00
cmeng 925a0d0a9a fix(stack): fail to start swarm stack with private image EE-4797 (#10047) 2023-08-14 16:13:12 +12:00
Ali 2a7a96f498 fix(microk8s): PO ui fixes [EE-5900] (#10031)
Co-authored-by: testa113 <testa113>
2023-08-14 12:34:58 +12:00
Ali c472fe9c18 refactor(app): app events datatable [EE-5355] (#10024) 2023-08-14 05:09:40 +12:00
andres-portainer 0eaf296e1b fix(unpacker): implement unpacker error parsing EE-5779 (#10005) 2023-08-10 10:25:59 -03:00
Oscar Zhou 598b8d0f28 fix(stagger): introduce stack version into DeploymentInfo struct (#10011) 2023-08-10 11:58:40 +12:00
matias-portainer e1a3010bc7 fix(edge/stacks): fix UI issues EE-5844 (#10021) 2023-08-09 10:09:08 -03:00
cmeng 2de4863532 fix(edge-stack): detaching swarm stack from git repository EE-5812 (#9998) 2023-08-07 10:33:04 +12:00
Oscar Zhou 8cf54cd0df fix(react/datatable): override getColumnCanGlobalFilter method (#9990) 2023-08-07 10:30:38 +12:00
cmeng 1ef1953d7d fix(edge-stack): detaching from git repository EE-5812 (#9989) 2023-08-04 15:17:46 +12:00
cmeng 5b033abaa4 fix(registry): registry login failure for regular stack EE-5832 (#9986) 2023-08-04 15:16:55 +12:00
Ali 5865f1ca77 fix(app): update summary with ingresses [EE-5847] (#9973)
Co-authored-by: testa113 <testa113>
2023-08-04 13:48:21 +12:00
Chaim Lev-Ari f59573f306 fix(home): empty default sort [EE-5822] (#9951) 2023-08-03 16:21:09 -03:00
Chaim Lev-Ari 1cecbd7177 fix(docker/images): show empty size cell [EE-5823] (#9954) 2023-08-03 16:19:58 -03:00
Ali acf9203580 fix(ingress): ingress ui feedback [EE-5852] (#9982)
Co-authored-by: testa113 <testa113>
2023-08-03 23:03:09 +12:00
cmeng 9845518aa9 fix(edge-stack): unable to edit edge stack EE-5845 (#9981) 2023-08-03 17:21:01 +12:00
matias-portainer d7e83aad26 fix(endpoints): fix nil pointer dereference EE-5843 (#9969) 2023-08-02 11:06:34 -03:00
Matt Hook df47f3d8a8 show kube icon for custom template (#9968) 2023-08-02 09:43:54 +12:00
Ali d0ecf6c16b fix(ingress): loading and ui fixes [EE-5132] (#9959) 2023-08-01 19:31:35 +12:00
Matt Hook e400c4dfc6 bump compose to 2.20.2 (#9964) 2023-08-01 12:27:21 +12:00
Matt Hook 721457b71d bump version to 2.20 (#9963) 2023-08-01 09:20:51 +12:00
Ali b19800681f fix(app): improve perceived ingress load time [EE-5805] (#9946)
Co-authored-by: testa113 <testa113>
2023-07-31 20:18:45 +12:00
cmeng 6a4e44ee0a fix(stack): update gitops updates tooltip EE-5827 (#9962) 2023-07-31 18:46:00 +12:00
Chaim Lev-Ari 37ece734f0 refactor(kube/apps): convert placement table to react [EE-4662] (#8938) 2023-07-29 17:08:41 +02:00
Prabhat Khera bf79ef7d89 fix(security): upgrade helm binary to v3.12.2 [EE-5801] (#9263) 2023-07-28 15:08:45 +12:00
James Carppe 883ef2578f fix indentation in bug report template (#9944) 2023-07-28 13:05:43 +12:00
Matt Hook a585f34106 workding change (#9266) 2023-07-28 07:53:33 +12:00
Ali b128139b69 fix(UI): PO review tweaks [EE-5776] (#9245)
Co-authored-by: testa113 <testa113>
2023-07-28 07:50:53 +12:00
James Carppe 4c425a7af8 Discussions updates (#9730)
* Update bug template: versions to dropdown, add license types to editions, set render on command used

* Update docs URL in help template
2023-07-27 10:27:32 +05:30
Dakota Walsh 400d95c1a5 fix(metrics): node chart race condition EE-5447 (#9249) 2023-07-27 11:46:38 +12:00
Dakota Walsh ca617e2ac9 fix(jwt): replace deprecated gorilla/securecookie [EE-5153] (#9247) 2023-07-27 09:34:16 +12:00
samdulam 4a90b8a3f7 Fix links in Discussions and Issues Templates (#9258)
* Fix Links

* Fix links for discussions
2023-07-26 12:34:15 +05:30
samdulam 43ad3face2 Fix Links (#9257) 2023-07-26 12:11:06 +05:30
samdulam 69e61be474 file type changes (#9256) 2023-07-26 12:07:06 +05:30
samdulam a4ea7a3709 Changes to issues templates now that Discussions are enabled (#9255)
* Discussions Enabled and Templates

* Discussions - Ideas Template
2023-07-26 12:02:38 +05:30
samdulam c5ecf8a66d Change Issues so we can move to discussions 2023-07-26 12:00:41 +05:30
samdulam c2c0631495 Add Discussion Templates (#9254)
* Discussions Enabled and Templates

* Discussions - Ideas Template
2023-07-26 11:57:59 +05:30
samdulam 4ff3cee72e Add workflow_dispatch so we can run manually (#9253) 2023-07-26 09:33:54 +05:30
Matt Hook c4e8251e52 post po review changes (#9244) 2023-07-26 11:36:02 +12:00
andres-portainer 21b00c267d fix(docker): use version negotiation for the Docker client EE-5797 (#9250) 2023-07-25 19:00:21 -03:00
samdulam 86ec058347 Change stabot action version as it stopped working (#9246) 2023-07-25 14:47:07 +05:30
Prabhat Khera 9fac997300 warning message placement and persisted folders heading (#9233) 2023-07-24 12:16:58 +12:00
Dakota Walsh 704d70c99b fix(gke): port metrics to the backend EE-5447 (#9041) 2023-07-24 12:16:29 +12:00
Oscar Zhou e996d29d52 feat(edgestack/async): add Rollbackto field in stackPayload [EE-5684] (#9238) 2023-07-24 10:48:02 +12:00
cmeng 634326b5cd fix(container): column selection UI issue EE-5780 (#9242) 2023-07-24 10:12:43 +12:00
cmeng 94379763f8 fix(edge-stack): edfix(image): force remove button color EE-5787ge stack git authentication issues EE-5595 (#9237) 2023-07-21 16:37:30 +12:00
Ali bb61723ba1 apply changes from EE (#9232)
Co-authored-by: testa113 <testa113>
2023-07-20 13:48:48 +12:00
cmeng ff1f549590 fix(edge-stack): edge stack git authentication issues EE-5595 (#9228) 2023-07-20 09:11:38 +12:00
matias-portainer b8f20a4f05 fix(waiting-room): remove breadcrumb EE-5781 (#9231) 2023-07-19 16:44:37 -03:00
matias-portainer b5c5df798a chore(edgegroups): rename decoratedEdgeGroup property EE-5501 (#9212) 2023-07-19 12:28:17 -03:00
matias-portainer 88da28694c refactor(docker/events): fix null dataset EE-4667 (#9208) 2023-07-19 12:28:07 -03:00
Chaim Lev-Ari 4f0f53b9aa feat(edge/stacks): ui for status [EE-5593] (#9214) 2023-07-19 12:26:18 -03:00
Chaim Lev-Ari 03b9a9b65d fix(registry): find registry for image [EE-5660] (#9181) 2023-07-19 12:23:38 -03:00
Chaim Lev-Ari fa755ffbca fix(edge/stacks): show registry field for git [EE-5742] (#9217) 2023-07-19 12:22:29 -03:00
Chaim Lev-Ari 5ad83d0adb fix(endpoints): filter by all edge stack states (#9218) 2023-07-19 12:21:47 -03:00
andres-portainer 9fa097d45f fix(endpointgroups): add transactions support to the User model to avoid a nil pointer dereference EE-5328 (#9221) 2023-07-17 21:23:35 -03:00
cmeng 7acd1080ad fix(edge-stack) make protainer compatible with previous agent EE-5614 (#9220) 2023-07-18 09:25:29 +12:00
andres-portainer 8c533bee67 feat(transactions): migrate some parts to use transactional code EE-5494 (#9213) 2023-07-17 17:36:00 -03:00
Chaim Lev-Ari fbec123595 style(edge/stacks): remove duplicate component [EE-5554] (#9209) 2023-07-17 09:52:43 +03:00
Chaim Lev-Ari 09f60c3277 feat(docker): migrate files table to react [EE-4663] (#8916) 2023-07-16 10:59:58 +03:00
andres-portainer 146681e1c7 fix(snapshots): increase the chance of taking a snapshot for edge environments EE-4795 (#9211) 2023-07-14 12:34:50 -03:00
Chaim Lev-Ari 615af4fdee refactor(docker/configs): implement table in react [EE-4664] (#8912) 2023-07-14 08:48:08 +03:00
Chaim Lev-Ari 0bcb57568c feat(edge/stacks): increase status transparency [EE-5554] (#9094) 2023-07-13 23:55:52 +03:00
cmeng db61fb149b feat(edge-stack): per-device-configs-for-edge-stack EE-5461 (#9203) 2023-07-13 15:41:47 -03:00
Chaim Lev-Ari 76b871d8a0 chore(deps): upgrade webpack loaders [EE-5126] (#9206) 2023-07-13 21:36:23 +03:00
Chaim Lev-Ari a725883cbc refactor(docker/events): convert table to react [EE-4667] (#8937) 2023-07-13 12:55:22 +03:00
Chaim Lev-Ari ecd54ab929 refactor(docker/images): convert table to react [EE-4668] (#8910) 2023-07-13 10:47:20 +03:00
Chaim Lev-Ari 0e9902fee9 refactor(settings): migrate view to react [EE-5509] (#9179) 2023-07-13 10:46:12 +03:00
cmeng b93624fa1f fix(chisel): convert seed to private key file EE-5099 (#9149) 2023-07-13 15:19:40 +12:00
Matt Hook 91cfd2d0f2 fix(templates): show correct icon for tempate [EE-5426] (#9202)
* show correct os icon for swarm stacks

* add compose edge stack doc comment
2023-07-13 14:22:53 +12:00
Matt Hook 2d94f020d0 fix react table filter placement (#9200) 2023-07-13 11:47:53 +12:00
James Carppe 01b9c64216 Update 5NF references to 3NF, updated documentation links (#9201) 2023-07-13 11:46:37 +12:00
Oscar Zhou b93aced176 feat(stack): introduce versioning for stack file [EE-5674] (#9184) 2023-07-13 11:06:24 +12:00
Chaim Lev-Ari a216a1e960 refactor(edge/stacks): migrate list view to react [EE-2237] (#9186) 2023-07-12 17:26:52 +03:00
Chaim Lev-Ari 020ecb740a chore(ci): run test in github action [EE-3014] (#9187) 2023-07-12 09:27:33 +03:00
Chaim Lev-Ari de5c959e24 fix(kube): deploy app with image [EE-5738] (#9194) 2023-07-12 09:26:50 +03:00
Dakota Walsh a9c6fa5ac2 fix(docs): update kubeconfig import doc link EE-5478 (#9193) 2023-07-12 16:07:23 +12:00
Matt Hook 39c431392e indent submenus (#9192) 2023-07-12 15:32:59 +12:00
Matt Hook cbe23dc753 more robust deletion strategy when removing endpoints (#9191) 2023-07-12 13:20:13 +12:00
Chaim Lev-Ari afaeddb887 fix(edge/groups): skip count untrusted endpoints [EE-5672] (#9176) 2023-07-11 20:31:32 +03:00
LP B 39eed67fd7 fix(app): dark theme be teaser colors [EE-5621] (#9124)
* fix(app): dark theme be teaser colors

* fix(app): review comments and prettier format

* fix(app): revert changes on arbitrary class

* feat(app/teaser): blue lock svg on dark theme
2023-07-11 17:35:43 +02:00
andres-portainer 64b227b2e1 chore(code): clean up the code EE-5719 (#9183) 2023-07-10 23:26:54 -03:00
Matt Hook 979af5301e correctly identify master nodes (#9064) 2023-07-11 10:22:06 +12:00
Chaim Lev-Ari 10014ae171 refactor(ui/image-config): create react component [EE-5342] (#8856) 2023-07-10 18:56:12 +03:00
andres-portainer bf51f1b6c9 chore(filenames): fix filenames EE-5717 (#9171) 2023-07-10 12:22:24 -03:00
Chaim Lev-Ari 60ae6a63fc refactor(settings): migrate ssl panel to react [EE-5506] (#9163) 2023-07-10 14:03:58 +03:00
Ali c752b98120 fix(app): fix app ingress edge cases [EE-5663] (#9150)
Co-authored-by: testa113 <testa113>
2023-07-10 16:20:22 +12:00
Chaim Lev-Ari 8b11e1678e refactor(settings): migrate hidden containers panel to react [EE-5507] (#9119) 2023-07-10 03:39:11 +07:00
andres-portainer eefb4c4287 fix(polling): clean up the logic for correctness and performance EE-5700 (#9169) 2023-07-07 18:00:20 -03:00
matias-portainer 29c1862754 fix(api): add missing public access middleware to routes EE-5191 (#9086) 2023-07-07 17:37:42 -03:00
Oscar Zhou 801b7d43ee refactor(edgestack): rename version to fileVersion (#9157) 2023-07-07 15:48:45 +12:00
Prabhat Khera 14d84c8025 fix(UI): message styling on ingress form EE-5665 (#9161) 2023-07-07 09:37:04 +12:00
Prabhat Khera d8c4dcbe72 fix secret warning message styling (#9160) 2023-07-06 15:23:41 +12:00
andres-portainer 31d68f8091 fix(snapshots): avoid a last-write-wins situation EE-5701 (#9156) 2023-07-05 17:27:05 -03:00
andres-portainer 91088a5e0f fix(polling): reorder operations to avoid updating untrusted environments EE-5700 (#9155) 2023-07-05 17:26:52 -03:00
andres-portainer e4ae4d5312 fix(edgegroups): fix updateEndpointStacks() EE-5699 (#9154) 2023-07-05 14:03:48 -03:00
andres-portainer 74515f102d fix(docker-proxy): reduce DB writes to optimize the proxy calls EE-5516 (#9148) 2023-07-05 09:25:05 -03:00
andres-portainer b37120802e fix(edgegroups): avoid doing extra operations EE-5627 (#9144) 2023-07-04 16:57:20 -03:00
Chaim Lev-Ari f5e09618f0 feat(edge): add EnvVar to stack details [EE-5463] (#9036) 2023-07-04 11:14:35 +07:00
Oscar Zhou 1a9a564553 fix/EE-5636/edge-stack-git-update-button-status (#9135) 2023-07-03 10:18:50 +12:00
andres-portainer 8a432ebbf8 fix(edgegroups): remove duplicated endpoints when updating an edge group EE-5679 (#9138) 2023-06-30 18:39:56 -03:00
Oscar Zhou bc47061624 feat(edgestack): git stack versioning [EE-5458] (#9126) 2023-06-30 16:49:38 +12:00
Chaim Lev-Ari ceabb2884b fix(edge/stacks): ignore missing file [EE-5649] (#9127) 2023-06-29 13:32:04 +07:00
Chaim Lev-Ari f293ea41d3 refactor(settings): migrate helm cert panel to react [EE-5505] (#9132) 2023-06-29 13:31:17 +07:00
Chaim Lev-Ari c452de82b7 refactor(libstack): use libstack [EE-5474] (#9122) 2023-06-28 08:03:52 +07:00
Chaim Lev-Ari 599d214e50 refactor(docker): remove EndpointProvider from commit [EE-5641] (#9123) 2023-06-28 08:02:43 +07:00
Oscar Zhou f02ede00b3 fix(docker/tls): update tls certs for Docker API env [EE-4286] (#9112) 2023-06-28 08:51:58 +12:00
andres-portainer f1f46f4da1 fix(boltdb): remove undefined behavior when deleting objects while iterating EE-5643 (#9129) 2023-06-27 16:42:52 -03:00
Oscar Zhou c96e076871 feat(edge/stack): add stack deployment info struct [EE-5523] (#9042) 2023-06-26 18:12:15 +12:00
Ali 89c1d0e337 feat(app): add ingress to app service form [EE-5569] (#9106) 2023-06-26 16:21:19 +12:00
Chaim Lev-Ari 8c16fbb8aa refactor(libstack): move library to portainer [EE-5474] (#9120) 2023-06-26 08:11:05 +07:00
Chaim Lev-Ari 11571fd6ea refactor(edge/stacks): migrate envs table to react [EE-5613] (#9093) 2023-06-25 12:38:43 +07:00
Chaim Lev-Ari dfc1a7b1d7 refactor(docker/images): remove EndpointProvider from build [EE-5551] (#9020) 2023-06-25 08:02:54 +07:00
cmeng 7cb6e3f66a feat(edge-stack): relative path support for edge stack EE-5521 (#9103) 2023-06-23 09:41:50 +12:00
andres-portainer 4cc96b4b30 feat(dataservices): unify access methods and abstract away redundant code [EE-5628] (#9115) 2023-06-22 18:28:07 -03:00
cmeng 4c6bbe9a2f fix(registry) undefined error on image tag view EE-4836 (#8885) 2023-06-23 09:07:52 +12:00
Chaim Lev-Ari ea2f752a4f fix(edge/updates): validate amount of environments [EE-5053] (#9014) 2023-06-22 11:13:57 -03:00
Chaim Lev-Ari 4c8af378af fix(access-control): set user id when private (#8839) 2023-06-22 11:12:49 -03:00
Chaim Lev-Ari e91b4f5c83 refactor(groups): migrate groups selectors to react [EE-3842] (#8936) 2023-06-22 11:11:10 -03:00
Chaim Lev-Ari 2018529add fix(kube/setup): toggle section on click [EE-4799] (#9107) 2023-06-22 11:10:04 -03:00
Chaim Lev-Ari 58651810bd fix(custom-templates): update template from git [EE-5534] (#9053) 2023-06-22 11:08:47 -03:00
Chaim Lev-Ari 2363d23de0 fix(stacks): fix stop and start [EE-5572] (#9050) 2023-06-22 11:08:24 -03:00
Chaim Lev-Ari 2cd5d55b00 fix(edge/stacks): fix ui issues [EE-5578] (#9070) 2023-06-22 11:08:04 -03:00
Prabhat Khera 3d22cde096 fix fallback rule (#9114)
Test / test-client (push) Has been cancelled
2023-06-22 09:33:22 +12:00
Oscar Zhou cd89487c41 fix(environment): blank environments list page [EE-5615] (#9113) 2023-06-22 09:05:58 +12:00
andres-portainer b12e1aade4 fix(boltdb): remove extra allocation and copy from GetObject() EE-5622 (#9111) 2023-06-20 18:29:33 -03:00
andres-portainer 716c196682 feat(dataservices): abstract away some redundant code EE-5620 (#9110) 2023-06-20 17:51:34 -03:00
Chaim Lev-Ari 7dc6a1559f refactor(settings): kube settings panel [EE-5504] (#9079) 2023-06-20 11:02:39 +07:00
matias-portainer 806e1fdffa fix(environments): fix Add Environments button link EE-5616 (#9105) 2023-06-19 17:56:51 -03:00
LP B 2eca5e05d4 fix(edge-stack): URI too large error for edge stacks with a large amount of environments [EE-5583] (#9085)
* refactor(edge-stacks): filter endpoints by edgeStack

* feat(api/endpoints): edge stack filter support filtering on status in stack

* refactor(endpoints): use separate query params and not JSON query param when querying for an edge stack

* feat(api/endpoints): handle stack filter on dynamic groups + unique list with multiple groups sharing environments

* fix(app/endpoints): edge stack related query params type definition

* fix(api/endpoints): rebase conflicts on imports
2023-06-19 11:55:33 +02:00
Prabhat Khera 223dfe89dd fix(ingress): remove path from ingress host when added EE-5406 (#9099)
* remove path from ingress host when added

* add icon to message
2023-06-19 14:11:50 +12:00
Oscar Zhou 9f9cdf7d43 refactor(settings/backup): migrate backup setting module [EE-5508] (#9076) 2023-06-19 09:57:33 +12:00
Chaim Lev-Ari caf87bb0b5 refactor(environments): migrate table to react [EE-4702] (#8882) 2023-06-18 12:18:55 +07:00
andres-portainer f7dd73b0f7 feat(unit-testing): add a mock for the RequestBouncer EE-5610 (#9089) 2023-06-16 10:44:22 -03:00
andres-portainer 933e764a13 fix(endpoints): document an undocumented parameter EE-5556 (#9088) 2023-06-16 10:43:37 -03:00
andres-portainer e43973da1a fix(logging): skip caller frames to give better context EE-5227 (#9087) 2023-06-15 18:34:11 -03:00
Ali a2388226ad fix(app): path override validation [EE-5078] (#9077)
Co-authored-by: testa113 <testa113>
2023-06-15 09:05:19 +12:00
Oscar Zhou 0074bcc2ee fix(node): update minimum node version (#9078) 2023-06-14 21:44:12 +12:00
Ali a4dfeda4ae fix(app): handle no options and volume mounts [EE-5078] (#9075)
* fix(app): handle no options and vol mounts EE-5078

* rm comment

---------

Co-authored-by: testa113 <testa113>
2023-06-14 16:22:44 +12:00
Prabhat Khera 90759182db fix fallback rule and wordings (#9074) 2023-06-14 13:45:25 +12:00
Chaim Lev-Ari 79822e1d3b refactor(edge): move stack response to a shared config [EE-5564] (#9033) 2023-06-13 13:20:02 +07:00
Ali 9d3f13ac92 fix(app): single delete config or secret [EE-5078] (#9069)
Co-authored-by: testa113 <testa113>
2023-06-13 17:03:55 +12:00
Oscar Zhou 2ac70b1eb6 feat(gitops): reword automatic update to gitops update for ui and docs (#9067) 2023-06-13 15:32:10 +12:00
Prabhat Khera 57fa044f2e feat(kubernetes): remove path and service from mandatory fields EE-5406 (#9054) 2023-06-13 12:38:00 +12:00
Ali 3721c1478e fix(app): fix app env var update issues [EE-5078] (#9066)
Co-authored-by: testa113 <testa113>
2023-06-13 09:14:10 +12:00
andres-portainer 424c98e256 fix(http): log HTTP server errors as DEBUG level EE-5225 (#9060) 2023-06-12 09:54:28 -03:00
Ali 2d69e93efa feat(app): rearrange app form services [EE-5566] (#9056) 2023-06-12 11:50:13 +12:00
Ali d7fc2046d7 feat(config): separate configmaps and secrets [EE-5078] (#9029) 2023-06-12 09:46:48 +12:00
Ali 4a331b71e1 refactor(r2aform): remove validationData [EE-5559] (#9045)
* refactor(r2aform): remove validationData [EE-5559]

* update doc

---------

Co-authored-by: testa113 <testa113>
2023-06-12 08:48:10 +12:00
Chaim Lev-Ari 834ab7c158 fix(docker/images): show image tag [EE-5495] (#9051) 2023-06-11 08:55:19 +07:00
Chaim Lev-Ari f799dd86c3 chore(deps): upgrade babel [EE-5219] (#9034) 2023-06-11 08:44:20 +07:00
Prabhat Khera 3233987a21 feat(kubernetes): add note teaser and styled application note on details EE-5364 (#9016) 2023-06-09 08:35:29 +12:00
Chaim Lev-Ari 58c1a60fee chore(edge): add test ids [EE-5323] (#9048) 2023-06-08 06:03:04 +07:00
Chaim Lev-Ari 8129e7590b feat(waiting-room): add beta alert to assignment [EE-5384] (#9028) 2023-06-08 06:02:36 +07:00
LP B 73950f3603 fix(app/stacks): swarm stack duplicate and migrate errors [EE-5520] (#9039)
* fix(dev): dev container script

* fix(app/stacks): make swarm stack migrate effectively target the target env and not the current env

* fix(app/stacks): make stack duplicate save the target swarm id on duplicated swarm stack
2023-06-07 14:28:40 +02:00
Chaim Lev-Ari c7756f3018 refactor(settings): move app settings to panel [EE-5503] (#9043) 2023-06-07 12:16:47 +07:00
cmeng 4f04fe54a7 fix(edge-stack): transmit dot env file to agent [EE-4533] (#8664) 2023-06-06 09:39:08 +12:00
matias-portainer c90a1be0e5 fix(edgegroups): allow edge groups with no environments or tags EE-4927 (#8439) 2023-06-05 10:18:34 -03:00
Matt Hook 0c5a0eb3a0 fix golint version (#9030) 2023-06-02 17:10:21 +12:00
Prabhat Khera ecf7f7ec14 update docker go mod to 23.0.3 (#9024) 2023-06-02 11:55:37 +12:00
cmeng e8e8329aab fix(registry): disable tls for azure [EE-3726] (#8605) 2023-06-02 11:53:46 +12:00
cmeng 4c2906e89d fix(edge) inconsistent heartbeat EE-5533 (#9011) 2023-06-02 10:36:14 +12:00
Matt Hook fb2646b70c port changes from EE (#9003) 2023-06-02 08:35:15 +12:00
Matt Hook 3cd0409184 fix(build) cleanup build process [EE-5555] (#9026)
* improve makefile and related files

* update wording for build-all target
2023-06-01 13:50:34 +12:00
Matt Hook 1b041a029e increase timeout (#9023) 2023-05-31 18:11:34 +12:00
Ali 69776b4863 refactor(app): app service form to react [EE-5415] (#8994) 2023-05-31 17:58:41 +12:00
Chaim Lev-Ari 2d05103fed refactor(ui): migrate env var field to react [EE-4853] (#8451) 2023-05-31 10:08:41 +07:00
Matt Hook 6b5940e00e add card component (#9022) 2023-05-31 13:18:05 +12:00
Matt Hook 3a49dbf803 add copy to clipboard to web editor (#9009) 2023-05-31 12:28:11 +12:00
Chaim Lev-Ari 1cda08ca11 chore(deps): upgrade css tools [EE-5116] (#8990) 2023-05-31 01:35:25 +07:00
Chaim Lev-Ari 93bf630105 feat(edge/stacks): sync EE codechanges [EE-498] (#8580) 2023-05-31 01:33:22 +07:00
Chaim Lev-Ari 0ec7dfce69 chore(git): ignore go.work.sum [EE-5550] (#9019)
closes [EE-5550]
2023-05-30 21:35:46 +07:00
andres-portainer eda07614ce chore(unit-test): simplify teardown EE-5536 (#9015) 2023-05-30 11:02:22 -03:00
Chaim Lev-Ari b498cd657f chore(docs): replace cloudinovasi with portainer [EE-5547] (#9013) 2023-05-30 10:48:21 +07:00
Chaim Lev-Ari 61b568a738 fix(registry): sync config on change [EE-5460] (#8955) 2023-05-30 10:47:44 +07:00
Chaim Lev-Ari d803d5f821 docs(build): update contrib guidelines to use makefile [EE-5519] (#8998) 2023-05-30 10:46:51 +07:00
Chaim Lev-Ari 2347133438 docs(build): update contrib guidelines to use makefile [EE-5519] (#8997) 2023-05-30 10:46:47 +07:00
Oscar Zhou 96de026eba fix(container/network): recreate container changes static IP [EE-5448] (#8960)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2023-05-30 09:36:10 +12:00
LP B d340c4ea96 fix(app/gitform): check if authentication is enabled before using form credentials (#8722) 2023-05-29 18:12:07 +02:00
Chaim Lev-Ari 9567072ce0 chore(deps): upgrade husky and lint-staged [EE-4842] (#8989) 2023-05-29 11:17:42 +07:00
Chaim Lev-Ari d18b276e30 fix(settings): full width form fields [EE-4954] (#8867) 2023-05-29 10:16:58 +07:00
Ali af77e33993 refactor(app): details widget migration [EE-5352] (#8886) 2023-05-29 15:06:14 +12:00
cmeng fdd79cece8 fix(container): delete Mounts field from HostConfig object EE-5387 (#9001) 2023-05-29 09:01:42 +12:00
andres-portainer ac94d344df fix(customtemplates): set TLSSkipVerify on update EE-5336 (#9007) 2023-05-26 00:29:09 -03:00
andres-portainer bcbdb01785 fix(kubernetes): fix manifestFilePaths slice creation EE-4554 (#8023) 2023-05-25 11:38:14 -03:00
Chamhaw a2f734051c fix(service): service related UI issues [EE-4062] (#7943) 2023-05-25 15:59:32 +12:00
cmeng 93866644c6 fix(GPU): EE-4331 error when enabling gpu on existing container (#8316) 2023-05-24 09:21:21 +12:00
Chaim Lev-Ari 6242952141 docs(stacks): require endpoint id [EE-5286] (#8988) 2023-05-23 10:25:32 +07:00
Chaim Lev-Ari b4dd5c5989 docs(http): sort tags [EE-3697] (#8974) 2023-05-23 10:07:06 +07:00
Chaim Lev-Ari ef00350922 docs(webhooks): document required endpoint and webhook type [EE-5286] (#8973) 2023-05-23 10:05:55 +07:00
matias-portainer 8acea44ee8 fix(edgejobs): sort logs result in UI EE-5304 (#8746) 2023-05-22 12:25:43 -03:00
Matt Hook c193360741 cleanup docs (#8949) 2023-05-22 10:50:12 +12:00
Prabhat Khera 4f34a78f7f fix(ui): fix beta alert EE-5498 #8968 2023-05-22 10:17:11 +12:00
Chaim Lev-Ari f96e7ff434 fix(ui): confirm deletion [EE-4612] (#8868) 2023-05-21 17:16:15 +07:00
Chaim Lev-Ari e37e87971d fix(stacks): confirm enable tls verification [EE-5410] (#8896) 2023-05-21 12:27:29 +07:00
matias-portainer 5daef54456 fix(stacks): normalize stack name before performing actions EE-4839 (#8539) 2023-05-18 17:58:42 -03:00
andres-portainer db93e5880f feat(endpointedge): add support for transactions EE-5327 (#8961) 2023-05-18 14:58:33 -03:00
Chaim Lev-Ari 881fa01eb2 fix(docker/networks): load containers from target node [EE-5446] (#8928) 2023-05-18 12:53:34 +07:00
Prabhat Khera 14fa60f6e6 fix(docker): fix extension spelling EE-5277 (#8956) 2023-05-18 10:21:07 +12:00
Prabhat Khera b58cd1e87e fix(UI): update icons for beta and experimental features EE-5435 (#8940) 2023-05-18 10:19:44 +12:00
andres-portainer 395d86dcd1 feat(settings): add support for transactions EE-5331 (#8957) 2023-05-17 15:00:22 -03:00
andres-portainer dbd476008b feat(snapshots): add support for transactions EE-5329 (#8947) 2023-05-17 11:57:05 -03:00
LP B 5a04338087 feat(api/stacks): use compose-unpacker to deploy stacks from git [EE-4758] (#8725)
* feat(api/stacks): use compose-unpacker to deploy stacks from git

* refactor(api/stacks): move stack operation as unpacker builder parameter + check builder func existence

* fix(api/stacks): defer removal of unpacker container after error check

* refactor(api/unpacker-builder): clearer code around client creation for standalone and swarm manager

* refactor(api/stacks): extract git stack check to utility function

* fix(api/stacks): apply skip tls when deploying with unpcker - ref EE-5023

* fix(api/stacks): defer close of docker client
2023-05-17 14:52:39 +02:00
Chaim Lev-Ari dc5f866a24 feat(stacks): add ref to stack.env [EE-5145] (#8872) 2023-05-17 10:30:56 +07:00
Prabhat Khera 83551201fb fix(docker): add docker desktop extension flag in settings and add migration EE-5277 (#8948) 2023-05-17 14:31:46 +12:00
cmeng e156243e43 fix(code-editor): highlight syntax web editor EE-5405 (#8871) 2023-05-17 14:07:21 +12:00
andres-portainer 1473cc208b feat(edgegroups): add support for transactions EE-5323 (#8946) 2023-05-16 16:07:03 -03:00
andres-portainer d29b688eb9 feat(endpointgroups): implement support for transactions EE-5328 (#8944) 2023-05-16 14:47:31 -03:00
Chaim Lev-Ari 077046030d chore(deps): upgrade build-tools [EE-5117] (#8577) 2023-05-16 12:22:50 +07:00
Chaim Lev-Ari 5f3c0ff835 fix(ui/form): expandable form section [EE-4799] (#8866) 2023-05-16 10:39:52 +07:00
Chaim Lev-Ari 23e3cdb193 fix(stacks): show containers table [EE-5487] (#8935) 2023-05-16 10:30:34 +07:00
cmeng e6984c5787 fix(icon) update ecr icon EE-4143 (#8880) 2023-05-16 14:08:23 +12:00
Matt Hook 0743f26ab8 fix(kube): updated kube terminology for configmaps/secrets [EE-4816] (#8770) 2023-05-16 09:21:50 +12:00
Dakota Walsh 8fa49d47f4 fix(docker): search published ports EE-4856 (#8939) 2023-05-15 12:26:42 +12:00
Chaim Lev-Ari 6ef53f0598 chore(deps): upgrade typescript [EE-4841] (#8247) 2023-05-14 16:24:37 +07:00
Chaim Lev-Ari 365316971b feat(waiting-room): choose relations when associated endpoint [EE-5187] (#8720) 2023-05-14 09:26:11 +07:00
andres-portainer 511adabce2 fix(http): drain and close response bodies EE-5486 (#8933) 2023-05-12 17:55:27 -03:00
andres-portainer 5b96136dd2 fix(customtemplates): set TLSSkipVerify properly EE-5336 (#8742) 2023-05-12 09:59:28 -03:00
Ali 42fce1ec57 fix(kube-tables): update table accessor fns [EE-5464] (#8920)
* fix(services): update accessor fns [EE-5464]

* small fixes

---------

Co-authored-by: testa113 <testa113>
2023-05-11 12:55:15 +12:00
Ali 22f4c5d650 separate internal configs with isInternal (#8690)
client-key: /Users/aliharris/.minikube/profiles/minikube/client.key

Co-authored-by: testa113 <testa113>
2023-05-11 08:13:54 +12:00
Prabhat Khera 945798a662 fix(kubernetes): fix light bulb panel for non docker envs EE-5418 2023-05-10 10:43:58 +12:00
Prabhat Khera 6a29198c5c fix note patching for Pod (#8915) 2023-05-10 10:42:56 +12:00
andres-portainer 7197ca435a fix(tls): add missing cipher suites EE-5465 (#8924) 2023-05-09 16:23:27 -03:00
Matt Hook c3c2221437 fix(docs): fixing missing kube api endpoint docs [EE-5204] (#8843) 2023-05-09 16:42:26 +12:00
Matt Hook d8fcce4c31 sync makefile with ee (#8918) 2023-05-09 15:19:38 +12:00
Chaim Lev-Ari c86b76261a fix(gitops): make polling mechanism static button [EE-5420] (#8893) 2023-05-09 08:00:14 +07:00
Chaim Lev-Ari acc340b324 fix(ui/code-editor): disable multi select [EE-5383] (#8861) 2023-05-09 07:59:34 +07:00
Chaim Lev-Ari e0609e3d93 docs(teams): fix swagger [EE-5414] (#8890) 2023-05-08 16:00:00 +07:00
Prabhat Khera 926ca19a1b feat(UI): migrate console view to react EE-2276 (#8767) 2023-05-08 14:07:46 +12:00
hungdoo c03b2ebbc1 wrap response rewrite operation with validation check (#7727) 2023-05-07 13:52:03 +12:00
andres-portainer e82c88317e feat(edgestacks): add support for transactions EE-5326 (#8908) 2023-05-05 20:39:22 -03:00
cmeng 59f543f442 fix(web-editor) update web editor button color EE-5404 (#8892) 2023-05-05 16:49:11 +12:00
Matt Hook f092b85f55 feat(makefile): improvements to the makefile and use gotestsum [EE-5439] (#8906)
* makefile improvements. use gotestsum

* increase timeout
2023-05-05 14:35:32 +12:00
Chaim Lev-Ari cfed481d6e feat(license): remove untrusted devices from node count [EE-5357] (#8817) 2023-05-05 09:02:31 +07:00
Chaim Lev-Ari 5f6ddc2fad fix(edge/stacks): validate deployment type [EE-4580] (#8875) 2023-05-05 09:01:43 +07:00
Matt Hook 334eee0c8c fix(errors): wrap db errors, improve error handling (#8859)
* use error check func, wrap db object not found

* add errorlint and fix all the linting errors

* add exportloopref linter and fix errors

* fix incorrect error details returned on an api

* fix new errors

* increase linter timeout

* increase timeout to 10minutes

* increase timeout to 10minutes

* rebase and fix new lint errors

* make CE match EE

* fix govet issue
2023-05-05 12:19:47 +12:00
Oscar Zhou 550e235d59 fix(admin): infinite loop when initializaing admin user (#8905) 2023-05-05 11:45:03 +12:00
Matt Hook 9970fb3940 fix git options for kube (#8889) 2023-05-05 09:20:20 +12:00
pibica 5d2723f4b9 #8546 fix(logging): manage time in seconds or milliseconds (#8547) 2023-05-05 07:41:11 +12:00
andres-portainer a062a0bfbe feat(resourcecontrol): add support for transactions EE-5431 (#8901) 2023-05-04 13:24:04 -03:00
andres-portainer 706d66a76e feat(teammemberships): add support for transactions EE-5412 (#8900) 2023-05-04 11:51:30 -03:00
Chaim Lev-Ari 2d22c4ff7d docs(stacks): require endpointId for delete [EE-4334] (#8897) 2023-05-04 21:32:46 +07:00
Chaim Lev-Ari d77a0887a7 docs(endpoints): deprecate EdgeCheckinInterval [EE-5281] (#8864) 2023-05-04 21:31:57 +07:00
Chaim Lev-Ari 2383d243d5 docs(custom-templates): add missing parameters [EE-5233] (#8865) 2023-05-04 21:31:06 +07:00
Chaim Lev-Ari 426c132f97 refactor(edge/stacks): separate create by method [EE-4947] (#8898) 2023-05-04 21:11:19 +07:00
LP B 1ff19f8604 fix(app/home): env tile hover style [EE-5299] (#8765)
* fix(app/home): environment item hover

* fix(app/home): remove white border above env list footer

* fix(app/home): icon color on edit buttons hover in high contrast theme
2023-05-04 16:00:56 +02:00
Ali 14a581e86b fix(dialog): dialog migration issues [EE-5385] (#8849)
* fix(dialog): dialog migration issues [EE-5385]

* don't highlight slider tooltip text

---------

Co-authored-by: testa113 <testa113>
2023-05-04 16:23:27 +12:00
Oscar Zhou ed279ba65b fix(edgestack): incorrect response code (#8873) 2023-05-04 10:01:33 +12:00
Oscar Zhou 19eceaf37f fix(restore/swarm): init primary endpoint after admin user is created (#8854) 2023-05-04 09:44:11 +12:00
Oscar Zhou 1963d064a3 fix(swarm/ui): keep stack detail page on the top [EE-4931] (#8858) 2023-05-04 09:29:47 +12:00
Matt Hook 58d130ee37 fix(buildscripts): make build process more closely resemble EE (#8881) 2023-05-03 12:13:28 +07:00
Ali 98e6393274 refactor(app): summary widget migration [EE-5351] (#8796)
* refactor(app): summary widget migration [EE-5351]

* update converter and limit display

---------

Co-authored-by: testa113 <testa113>
2023-05-03 15:55:25 +12:00
andres-portainer 745bbb7d79 feat(roles): add transactions support EE-5390 (#8878) 2023-05-02 19:05:18 -03:00
Chaim Lev-Ari 757461d58b chore(deps): upgrade react-table to v8 [EE-4837] (#8245) 2023-05-02 13:42:16 +07:00
Chaim Lev-Ari f20d3e72b9 chore(build): remove grunt and add makefile [EE-4824] (#8125) 2023-05-02 10:13:37 +07:00
Prabhat Khera 731f3959c7 fix(UI): update application deploy/update messages EE-4005 (#8819) 2023-05-01 09:14:30 +12:00
cmeng 0f9a0e25f2 fix(login) hide password in console EE-5279 (#8774) 2023-04-29 07:24:33 +12:00
cmeng ae339a0047 fix(stack) add skip TLS toggle for edit stack EE-5391 (#8851) 2023-04-28 13:35:33 +12:00
Chaim Lev-Ari 77f8b9333a refactor(stacks): break swagger docs by type [EE-5381] (#8820) 2023-04-27 11:03:55 +07:00
Chaim Lev-Ari bbea0bc8a5 feat(edge): hide envs from waiting room [EE-5185] (#8688) 2023-04-27 09:23:10 +07:00
Chaim Lev-Ari 4b9c857d85 feat(waiting-room): show and filter by check in [EE-5186] (#8701) 2023-04-27 09:22:05 +07:00
Dakota Walsh b5771df6a8 fix(image): allow dot in image names [EE-4595] (#8619) 2023-04-27 09:44:08 +12:00
matias-portainer 7ed8e9e167 fix(images): avoid returning null on registryId default value EE-5394 (#8841) 2023-04-26 10:24:45 -03:00
Prabhat Khera 80a3a5f16e feat(kubernetes): fix annotation validation EE-5021 (#8818) 2023-04-26 16:48:55 +12:00
Ali 3e654ff9b2 fix(deploy): return to referring view [EE-5345] (#8763)
* fix(deploy): return to referring view [EE-5345]

* no-underline -> no-decoration

---------

Co-authored-by: testa113 <testa113>
2023-04-26 11:23:15 +12:00
LP B 9b287f3020 fix(api/registry): encode X-Registry-Auth header using base64url instead of base64 [EE-4726] (#8492) 2023-04-24 13:57:39 +02:00
Oscar Zhou a7404e00d1 fix(ci/security): intepret matrix summary as string not shell command (#8836) 2023-04-24 13:21:35 +12:00
Dakota Walsh 3654109332 fix(slider): update rc-slider [EE-5011] (#8611)
* fix(slider): update rc-slider [EE-5011]

* fix PasswordLengthSlider tooltip

* fix unnecessarily bulky className for SliderTooltip

* remove SliderTooltip inner div

* center slider handle value

* relative tooltip

* update z index

---------

Co-authored-by: testa113 <testa113>
2023-04-21 16:52:05 +12:00
Oscar Zhou bf9dc8c2d0 feat(ci/security): remove deprecated github action command alert [EE-3059] (#8795) 2023-04-21 10:57:38 +12:00
cmeng 67f8e8f3c2 fix(webhook) remove NaN fom webhook url EE-5373 (#8816) 2023-04-21 10:56:53 +12:00
andres-portainer 56d6dfe02e feat(transactions): add transaction support for Registries EE-5382 (#8825) 2023-04-20 18:42:52 -03:00
Ali 861a9a5bbb fix(templates): update name validation [EE-5344] (#8760)
Co-authored-by: testa113 <testa113>
2023-04-21 09:39:55 +12:00
Matt Hook 1b470845b8 better logging during critical migration error (#8576) 2023-04-21 09:30:12 +12:00
Matt Hook 3c26aa8f34 feat(packages): upgrade packages [EE-5147] (#8658)
* upgrade packages

* update eksctl to match ee

* update helm to latest
2023-04-20 13:31:29 +12:00
Ali de953da5a4 fix(editor): fix styles [EE-5369] (#8809)
* fix(editor): fix styles [EE-5369]

* rm hash

---------

Co-authored-by: testa113 <testa113>
2023-04-20 08:27:25 +12:00
Chaim Lev-Ari 5356d1feeb fix(edge/updates): add padding for edge groups [EE-5349] (#8772) 2023-04-18 13:40:12 +12:00
Matt Hook 7a8a20e0cc feat(libhelm): allow passing optional env and http client [EE-5252] (#8758) 2023-04-14 14:50:37 +12:00
andres-portainer a7474188b9 chore(deps): unify dependencies EE-5360 (#8778) 2023-04-13 18:07:32 -03:00
cmeng 6fe56f89c6 fix(backup) add description text to backup EE-5283 (#8775) 2023-04-13 16:05:12 +12:00
Oscar Zhou a98f480974 fix(swagger): correct endpoint api annotations [EE-5333] (#8761) 2023-04-13 15:31:27 +12:00
cmeng 8ccac7c98f fix(stack): upgrade docker-compose EE-5334 (#8757) 2023-04-11 17:56:00 +12:00
andres-portainer e0ce3671e8 fix(tags): migrate to transactional code EE-5330 (#8755) 2023-04-10 19:03:51 -03:00
andres-portainer 62128d1069 fix(edgejobs): migrate to transactional code EE-5324 (#8747) 2023-04-10 15:59:34 -03:00
Oscar Zhou a65ffe519a fix(k8s/gitops): missing git auth toggle in k8s app edit page [EE-5320] (#8741) 2023-04-10 20:14:13 +12:00
Ali 5ac1ea3df8 fix(ns): add selection caching back [EE-5273] (#8738)
Co-authored-by: testa113 <testa113>
2023-04-06 14:28:01 +12:00
Matt Hook bf56bdb8f6 search for correct source directory when doing a restore (#8676) 2023-04-06 10:39:10 +12:00
cmeng b00aa68c2b fix(homepage) move heartbeat logic to backend EE-5317 (#8737) 2023-04-06 09:09:22 +12:00
Matt Hook 8c5edd2c97 fix(docs): add missing swagger docs for upload file [EE-4886] (#8708)
* add docs for uploading files via host management features

* fix other doc issues
2023-04-04 16:59:34 +12:00
Oscar Zhou c650868fe9 feat(templates): allow managing git based templates [EE-2600] (#7855)
Co-authored-by: itsconquest <william.conquest@portainer.io>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
2023-04-04 12:44:42 +12:00
cmeng 30a2bb0495 fix(security): potential vulnerability of path traversal attacks EE-5303 (#8728) 2023-04-04 09:00:17 +12:00
andres-portainer 1a451823d9 fix(edgestacks): fix a deadlock in UpdateEdgeStackFunc() (#8735) 2023-04-03 14:24:27 -03:00
Chaim Lev-Ari feab2a757e feat(gitops): allow to skip tls verification [EE-5023] (#8668) 2023-04-03 09:19:17 +03:00
andres-portainer 17839aa473 fix(endpointrelation): change a callback so it is transactional EE-5312 (#8729) 2023-03-30 23:16:56 -03:00
Prabhat Khera fc1aec3bb8 fix(ui): namespace caching issue EE-5273 (#8709)
* fix namespace caching issue

* fix(apps): add loading state [EE-5273]

* rm endpoint provider

* fix(namespace): remove caching [EE-5273]

* variable typo

---------

Co-authored-by: testa113 <testa113>
2023-03-31 13:24:57 +13:00
Chaim Lev-Ari d64e7eacfc fix(ui/code-editor): stretch code editor content full height [EE-5202] (#8673) 2023-03-30 12:26:32 +03:00
Ali 7f805ac5be fix(ns): save filter to local storage [EE-5287] (#8723)
* fix(ns): save filter to local storage [EE-5287]

* allow system ns and save per user

---------

Co-authored-by: testa113 <testa113>
2023-03-30 11:21:05 +13:00
Chaim Lev-Ari 308a78db21 refactor(edge): deprecate IsEdgeDevice [EE-5046] (#8534) 2023-03-28 09:19:22 +03:00
andres-portainer 814fc9dfc0 fix(http): drain and close HTTP response bodies EE-5280 (#8716) 2023-03-27 15:14:16 -03:00
andres-portainer 3635df89dc fix(snapshots): change the snapshot object to maintain backwards compatibility EE-5240 (#8705) 2023-03-23 13:30:32 -03:00
Ali 30248eabb4 fix(apps) UI release fixes [EE-5197] (#8702)
* fix(apps) searchbar flex resizing and insights

* UI fixes

* update stacks datatable

---------

Co-authored-by: testa113 <testa113>
2023-03-23 08:20:30 +13:00
Ali 3636ac5c26 fix(dashboard): use faster proxy request [EE-5160] (#8693)
Co-authored-by: testa113 <testa113>
2023-03-22 15:34:44 +13:00
Prabhat Khera f6e8b25cf3 fix Gpus null issue (#8692) 2023-03-21 16:06:01 +13:00
Oscar Zhou 124e0bf9b9 fix(stack/git): unexpected cursor movement in git text fields [EE-5143] (#8655) 2023-03-20 10:00:49 +13:00
Chaim Lev-Ari 45def82156 fix(ui/box-selector): BE link and use icons standard size [EE-5133] (#8607) 2023-03-19 13:37:35 +01:00
andres-portainer 76bdf6f220 fix(websocket): use the read part of the buffer instead of everything EE-5235 (#8685) 2023-03-17 17:23:24 -03:00
Ali e142be399d fix(kubeconfig): fix download checkbox [EE-5199] (#8674)
Co-authored-by: testa113 <testa113>
2023-03-17 10:33:56 +13:00
Ali 13ba72ee07 fix(wizard): Capitalise Kubernetes [EE-5178] (#8662)
Co-authored-by: testa113 <testa113>
2023-03-16 18:50:54 +13:00
Dakota Walsh f17a608dc7 fix(kubernetes): Prevent rerunning initial cluster detection [EE-5170] (#8666) 2023-03-16 15:39:26 +13:00
Prabhat Khera 6ee5cc6a56 fix(ui): namespace cache refresh on reload EE-5155 (#8644) 2023-03-16 10:10:37 +13:00
andres-portainer 44582732bb fix(home): exclude snapshots from the home page to improve the loading times EE-5154 (#8626) 2023-03-15 15:16:41 -03:00
andres-portainer ea03024fbc fix(edgegroup): fix data race in edge group update EE-4441 (#8523) 2023-03-15 14:53:38 -03:00
Oscar Zhou 795e6a5b3c fix(stack/git): unable to move git repository error [EE-5144] (#8618) 2023-03-15 12:54:09 +13:00
andres-portainer 2b17cb9104 fix(kubernetes): fix data-race in GetKubeClient() EE-4436 (#8498) 2023-03-14 20:11:28 -03:00
andres-portainer 347f66b1f1 fix(edge): fix status inspect error message EE-5190 (#8661) 2023-03-14 13:28:20 -03:00
Ali 40c387f4f4 fix(annotations) ingress tip to match ee [EE-5158] (#8653)
Co-authored-by: testa113 <testa113>
2023-03-14 10:41:35 +13:00
andres-portainer 15cbdb8af9 chore(portainer): clean up the code EE-5188 (#8660) 2023-03-13 13:18:28 -03:00
matias-portainer 621a01ba3b fix(upgrade): remove yellow upgrade banner EE-5141 (#8640) 2023-03-13 09:01:27 -03:00
Ali 37f382d286 fix(kube): check for ns on enter [EE-5160] (#8647)
Co-authored-by: testa113 <testa113>
2023-03-13 13:57:11 +13:00
Prabhat Khera 77b49ae9c5 fix typo in delete image modal dialog (#8621) 2023-03-13 11:05:51 +13:00
Matt Hook 29648f517b reduce throttling in the kube client (#8630) 2023-03-13 09:44:27 +13:00
Ali 8f42af49e8 fix(annotation): update wording/styling [EE-5158] (#8642)
Co-authored-by: testa113 <testa113>
2023-03-10 16:52:09 +13:00
cmeng 0ab7987684 fix(edge-stack) always show edge group selector [EE-5157] (#8639) 2023-03-10 10:48:44 +13:00
Ali 31d956dbcb fix(app): restrict ns fix create app [EE-5123] (#8597)
* fix(app): restrict ns fix create app [EE-5123]

* fix node limits race condition

---------

Co-authored-by: testa113 <testa113>
2023-03-10 10:24:23 +13:00
Ali 2cc80e5e5d refactor(GPU): refactor to colocate and simplify UI work [EE-5127] (#8593)
* refactor to colocate and simplify

* fix(insights): text size to match portainer views

---------

Co-authored-by: testa113 <testa113>
2023-03-09 22:06:57 +13:00
matias-portainer fb6e26a302 fix(stacks): pass WorkingDir to deployer command EE-5142 (#8615) 2023-03-08 19:34:57 -03:00
Matt Hook 9cca299833 fix(gotest): fix go tests as part of version bump to 2.19.0 (#8623)
* bump version to 2.19

* fix broken go tests
2023-03-08 17:23:34 +13:00
Chaim Lev-Ari 4c86be725d feat(system): upgrade portainer on kubernetes [EE-4625] (#8448) 2023-03-07 23:34:55 -03:00
Chaim Lev-Ari 0669ad77d3 fix(home): disable live connect for async [EE-5000] (#8462) 2023-03-07 21:27:34 -03:00
Matt Hook 2bfc956f58 bump version to 2.19 (#8617) 2023-03-08 13:24:59 +13:00
Ali 89194405ee fix(kube): improve dashboard load speed [EE-4941] (#8572)
* apply changes from EE

* clear query cache when logging out

* Text transitions in smoother
2023-03-08 11:22:08 +13:00
Chaim Lev-Ari 5f0af62521 fix(git): fix more files form error [EE-5125] (#8601) 2023-03-07 18:44:26 -03:00
Dakota Walsh e3299eddd5 fix(docker): container health table [EE-5008] (#8591) 2023-03-08 10:33:15 +13:00
Prabhat Khera bdde278139 feat(applications): application page performance improvements EE-4956 (#8569) 2023-03-08 10:27:42 +13:00
Chaim Lev-Ari 01ea9afe33 feat(edge): hide FDO features behind a flag [EE-5128] (#8600) 2023-03-07 13:45:39 -03:00
matias-portainer 8345d1471e fix(stacks): remove old dependency from stacks redeploy controller EE-5138 (#8609) 2023-03-07 13:35:43 -03:00
Prabhat Khera 2a55d20eff fix(annotations): minor issues in Annotations EE-4089 (#8612) 2023-03-07 21:09:41 +13:00
Ali 7dca784ec6 fix(env): get edited environment info [EE-5129] (#8603)
Co-authored-by: testa113 <testa113>
2023-03-07 14:44:19 +13:00
Oscar Zhou 37484566eb fix(ui/wizard): edge agent ui issue [EE-5126] (#8598) 2023-03-07 10:27:40 +13:00
Chaim Lev-Ari 70710cfeb7 feat(edge): associate edge env to meta fields [EE-3209] (#8551)
* refactor(edge/groups): load edge groups in selector

fix(edge/stacks): remove double groups title

* feat(edge): supply meta fields to edge script [EE-5043]

* feat(edge): auto assign aeec envs to groups and tags [EE-5043]

fix [EE-5043]

fix(envs): fix global key test

* fix(edge/groups): save group type

* refactor(edge/devices): move loading of devices to table

* refactor(tags): select paramter for query

* feat(edge/devices): show meta fields

* refactor(home): simplify filter

* feat(edge/devices): filter by meta fields

* refactor(edge/devices): break filter and loading hook
2023-03-07 09:25:04 +13:00
Chaim Lev-Ari 03712966e4 fix(ui/code-editor): apply theme colors [EE-5104] (#8558)
* fix(ui/code-editor): apply theme colors [EE-5104]

fix [EE-5104]

* fix(kube/yaml): expand yaml inspector

* fix(ui/code-editor): have default height

* fix(ui/code-editor): boolean color

* fix(ui/code-editor): style search bar
2023-03-06 09:13:42 +02:00
Chaim Lev-Ari 07100258cd fix(git): file path background [EE-5114] (#8573)
* fix(git): file path background [EE-5114]

also disabled url check on CE because
the http handler wasn't available and raised errors

* fix(git): highlight hovered path option

* feat(git): hide path options when choosing
2023-03-06 09:13:22 +02:00
cmeng 4c6f5f961e fix(wizard): fix tunnel add validation [EE-5124] (#8595) 2023-03-06 10:43:08 +13:00
Chaim Lev-Ari 77e1f5aa34 fix(oauth): danger confirm for hiding prompt [EE-4576] (#8574) 2023-03-05 15:22:49 +02:00
Chaim Lev-Ari 3baab6d695 fix(ui): import components for envs and registries [EE-5120] (#8583) 2023-03-05 15:22:00 +02:00
Chaim Lev-Ari d546ff269b fix(ui/input-group): remove z-index [EE-5121] (#8582) 2023-03-05 12:14:08 +02:00
cmeng 60275dd31c feat(edge): EE-4621 support high latency for tunnel (#8302) 2023-03-04 09:13:37 +13:00
Ali 07df4b1591 fix(notifications): limit only in header [EE-4815] (#8579)
Co-authored-by: testa113 <testa113>
2023-03-03 15:08:15 +13:00
Ali fd916bc8a2 feat(gpu): rework docker GPU for UI performance [EE-4918] (#8518) 2023-03-03 14:47:10 +13:00
Ali 769c8372fb feat(microk8s): BE teasers in wizard [EE-4772] (#8449)
* feat(microk8s): separate existing/new envs EE-4772

update icons

fix be teaser box selector style

* match environment wizard box selectors

* revert title back

* updated kaas description in wizard

---------

Co-authored-by: testa113 <testa113>
Co-authored-by: Prabhat Khera <prabhat.khera@portainer.io>
2023-03-03 12:27:24 +13:00
James Carppe d032119ebc Update docs URL and add --log-level docs (#8511) 2023-03-03 11:38:19 +13:00
Matt Hook ac47649631 feat(kubernetes): list all kube services screen [EE-1571] (#8524)
* port services from ee

* fix external link

* post review improvements

* remove applications-ports-datatable

* minor post review updates

* add services help url

* post review update

* more post review updates

* post review updates

* rename index to component

* fix external ip display and sorting

* fix external apps tag

* fix ingress screen time format

* use uid for row id. Prevent blank link

* fix some missing bits ported from EE

* match ee

* fix display of show system resources

* remove icon next to service type
2023-03-03 08:45:19 +13:00
Chaim Lev-Ari 8d6797dc9f fix(edge): show edge settings [EE-4959] (#8581)
* fix(edge): show edge settings [EE-4959]

fix [EE-4959]

* fix(edge/settings): validate ce
2023-03-02 19:32:43 +02:00
andres-portainer 197b0bcbde fix(code): add missing returns after sending HTTP errors EE-4442 (#7868) 2023-03-02 12:52:10 -03:00
Chaim Lev-Ari 6918da2414 refactor(stacks): extract auto update logic [EE-4945] (#8545) 2023-03-02 17:07:50 +02:00
cmeng 085381e6fc fix(logs-viewer): fail to search json logs [EE-4857] (#8482) 2023-03-02 23:55:13 +13:00
Chaim Lev-Ari 6074d1fcb5 fix(docker): clone config [EE-5115] (#8575) 2023-03-02 11:38:21 +02:00
Ali 96e5d44cc2 fix(teaser): add up-to-date teaser [EE-5014] (#8548)
Co-authored-by: testa113 <testa113>
2023-03-02 10:14:50 +13:00
matias-portainer a45ef3d72e fix(azure): ensure azure client HTTPS verification EE-4444 (#8137) 2023-03-01 17:52:29 -03:00
Chaim Lev-Ari c819d4e7f7 feat(environments): create async edge [EE-4480] (#8527) 2023-03-01 20:33:05 +02:00
LP B bc6a667a6b feat(api/snapshot): extend docker container snapshot type (#8537) 2023-03-01 17:33:40 +01:00
matias-portainer 7dcd6f9b9e fix(ui): fix search in associated endpoints selector EE-4532 (#8454) 2023-03-01 10:34:07 -03:00
Chaim Lev-Ari c8d334e603 fix(server): skip file deletion errors [EE-4916] (#8443) 2023-03-01 13:44:34 +02:00
Chaim Lev-Ari ab9b0c2147 feat(docker/containers): show name in log view [EE-5108] (#8557) 2023-03-01 09:37:23 +02:00
cmeng 6d659b4a2c fix(edge-stack): add validation for edge stack name [EE-4283] (#8504) 2023-03-01 19:51:08 +13:00
Prabhat Khera defce0cf6d feat(kuberenetes): add annotations to kube objects EE-4089 (#8499)
* add annotations BE teaser
* fix settings icon click on home screen for kube env
* add debouce to namespace validation
* ingress button tooltip fixed
* fix tooltip text
2023-03-01 13:11:12 +13:00
Dakota Walsh 5f66020e42 fix(docker): container health alignment EE-5008 (#8553) 2023-03-01 12:48:41 +13:00
Chaim Lev-Ari b3e72ecaa0 fix(gitops): load correct auth creds [EE-4849] (#8550)
close [EE-4849]
2023-02-28 17:55:54 +02:00
Chaim Lev-Ari b98c71f1ab refactor(ui): move react components to react codebase [EE-3354] (#8258)
* refactor(ui): move react components to react codebase [EE-3354]

* refactor(app): move bocx selector options

* refactor(react): spearate portainer components

* fix(app): fix imports
2023-02-28 17:32:29 +02:00
matias-portainer f9a09301a8 fix(edgejobs): fix data race in edge jobs tasks collect EE-4766 (#8542) 2023-02-28 12:14:09 -03:00
Chaim Lev-Ari 2c247efd0f fix(settings/oauth): show internal auth prompt by default [EE-4576] (#8481)
* fix(settings/oauth): show internal auth prompt by default [EE-4576]

fix [EE-4576]

* fix(oauth): use new confirm modal
2023-02-28 16:49:46 +02:00
Chaim Lev-Ari 86d0e30eb7 fix(build): ignore source maps for 3rd party [EE-5106] (#8549) 2023-02-28 14:54:52 +02:00
Chaim Lev-Ari 69a91ff90a fix(ui/box-selectors): make multi use square [EE-3856] (#8470) 2023-02-27 12:47:29 +02:00
Chaim Lev-Ari e0481f69b1 chore(deps): replace semver compare with local solutions [EE-5018] (#8491) 2023-02-26 15:25:24 +02:00
matias-portainer 088262b6dc fix(edgejobs): fix data race on task logs clear EE-4767 (#8236) 2023-02-23 16:21:17 -03:00
Oscar Zhou 1b12ee9f01 fix(security): update dependency and binary version [EE-4863] (#8394) 2023-02-23 22:29:48 +13:00
Chaim Lev-Ari 5507b1e8c9 refactor(ui/editor): migrate code-editor to react [EE-4848] (#8257) 2023-02-23 09:10:31 +04:00
Chaim Lev-Ari 273a3f9a10 refactor(gitops): migrate git form to react [EE-4849] (#8268) 2023-02-23 01:43:33 +05:30
Oscar Zhou afe6cd6df0 fix(code/security): vcs status error [EE-5062] (#8510) 2023-02-22 10:40:09 +13:00
andres-portainer 95ac2cc4c3 chore(edge): add transaction support for common objects EE-5079 (#8522) 2023-02-20 16:11:18 -03:00
Chaim Lev-Ari 9a8e95d017 feat(upgrade): show subtle banner [EE-5017] (#8489) 2023-02-19 09:47:50 +05:30
Oscar Zhou 631503fc1b fix(upgrade): add label to upgrade mustache template [EE-5029] (#8505) 2023-02-17 13:56:01 +13:00
Ali 23f3008500 chore(notifications):improve performance [EE-4815] (#8475)
* chore(notifications):improve performance [EE-4815]

Co-authored-by: testa113 <testa113>
2023-02-17 12:51:00 +13:00
matias-portainer 89dd72b4ac fix(registry): correct words in registry page and other places EE-4894 (#8356) 2023-02-16 10:31:08 -03:00
Chaim Lev-Ari 5a375ff055 refactor(environment/edge): show copy token button [EE-3691] (#8497) 2023-02-16 09:13:30 +05:30
andres-portainer f081631808 fix(edgegroups): fix data-race in edgeGroupCreate EE-4435 (#8477) 2023-02-14 15:18:07 -03:00
Chaim Lev-Ari e66dea44e3 refactor(ui/modals): replace bootbox with react solution [EE-4541] (#8010) 2023-02-14 13:49:41 +05:30
cmeng 392c7f74b8 fix(backup): reword region placeholder [EE-5012 (#8496) 2023-02-14 16:13:53 +13:00
Matt Hook 1dba5e464b minor fix (#8485) 2023-02-14 10:35:21 +13:00
Matt Hook 56dc2d1000 fix typo (#8486) 2023-02-13 23:23:44 +13:00
Ali 5c05ec489e fix(nameField): imperatively call setDebouncedValue when the value is changed by setFieldValue [EE-5002] (#8468)
Co-authored-by: testa113 <testa113>
2023-02-13 15:06:14 +13:00
Oscar Zhou cef9255161 fix(snapshot): prevent snapshot containers from fast failing in Swarm mode (#8308) 2023-02-13 14:42:10 +13:00
Ali 0befdacc0e chore(prettier): ignore tailwind changes [EE-4809] (#8483)
Co-authored-by: testa113 <testa113>
2023-02-13 11:38:06 +13:00
Matt Hook b2105f3614 feat(dockerfile): layered images [EE-4879] (#8301)
* multistage build to target production and storybook

* missing slash

* add storybook for windows too and build targets

* feature flag the storybook api

* remove kompose and prep for new FF lib

* todo comment for FF

* update to new feature flags library

* simplify logic

* fix compilation error

* simplified it
2023-02-13 11:28:32 +13:00
Ali 58d66d3142 chore(prettier): add tailwind prettier plugin [EE-4809] (#8221)
* add prettier plugin

* apply tailwind prettier formatting
2023-02-13 10:04:24 +13:00
Ali 9f6702d0b8 feat(resourcequotas): reduce resource quota requests [EE-4757] (#8420) 2023-02-10 18:28:53 +13:00
Matt Hook 44d69f3a3f hide the password in the response (#8437) 2023-02-10 18:26:18 +13:00
Matt Hook e255bd710f chore(golang) remove api pkg and go mod tidy (#8474)
* remove this.  It exists and should be in the top level directory

* go mod tidy
2023-02-10 16:48:53 +13:00
Prabhat Khera d73622ed9c fix cursor on autofill (#8378) 2023-02-10 09:19:43 +13:00
andres-portainer 4753d52532 fix(tls): specify the TLS MinVersion always EE-4427 (#7869) 2023-02-09 16:13:35 -03:00
Matt Hook f9bbe000fb fix(docker): remove prepended slash by default on container names [EE-3592] (#8195)
* remove prepended slash by default if present

* trimcontainername still needed
2023-02-09 17:18:19 +13:00
Matt Hook bfc610c192 feat(featureflags): improved feature flag handling [EE-4609] (#8222)
* updated and improved feature flags using new module

* merge init into parse

* update the package documentation

* better docs

* minor tidy
2023-02-09 17:17:46 +13:00
cmeng 51b9804fab fix(updater): specify docker client version [EE-5010] (#8459) 2023-02-08 17:00:22 +13:00
Matt Hook e2168d21c7 guard around kube actions for endpoint inspect (#8430) 2023-02-07 23:13:52 +13:00
Chaim Lev-Ari 2dddc1c6b9 refactor(ui/box-selector): replace all selectors [EE-3856] (#7902) 2023-02-07 09:03:57 +05:30
Ali c9253319d9 fix(texttip): fix texttip in edge groups [EE-4990] (#8441)
Co-authored-by: testa113 <testa113>
2023-02-03 13:39:45 +13:00
Chaim Lev-Ari 968fc98401 fix(home): show tooltip for disabled envs [EE-4859] (#8283) 2023-02-02 18:24:00 -03:00
LP B 921e9cfc6e fix(app/edge): updater/rollback calendar visual issues (#8386) 2023-02-02 12:57:53 -03:00
LP B 9b53960906 fix(api/edgestack): update deployments count when env relations are updated (#8433) 2023-02-02 12:04:58 -03:00
Oscar Zhou 402a62a5e2 fix(schedule): update date picker after removing edge group [EE-4963] (#8418) 2023-02-02 11:14:48 +13:00
cmeng 3470ea049a fix(update): prevent formik reinitialize [EE-4962] (#8426) 2023-02-02 09:59:17 +13:00
Ali 7fd263e8cc fix(texttip): fix texttip placement [EE-4990] (#8427)
Co-authored-by: testa113 <testa113>
2023-02-01 20:34:04 +13:00
Chaim Lev-Ari 36c6d3f21b fix(home): reduce update text for small screens [EE-4936] (#8422) 2023-02-01 12:14:53 +05:30
andres-portainer 5f3dd0a64f fix(edgestacks): fix edge stacks cache invalidation EE-4909 (#8399) 2023-02-01 02:16:04 -03:00
cmeng 42ca1287df fix(edge/stack): not clear stack status if version not updated [EE-4970] (#8408) 2023-02-01 09:18:04 +13:00
matias-portainer 2874a79279 fix(doc): update endpoint creation swagger documentation EE-4925 (#8415) 2023-01-31 11:06:27 -03:00
Ali 8574dd2371 fix(edge stacks): allow viewing existing kompose stacks [EE-4967] (#8405)
Co-authored-by: testa113 <testa113>
Co-authored-by: Matt Hook <hookenz@gmail.com>
2023-01-31 10:03:21 +13:00
Dakota Walsh 53eb5aa1ee fix(kube): 30 second delay to storage detection EE-4822 (#8360) 2023-01-31 09:58:57 +13:00
cmeng eb8644330e fix(edge/job): init endpoints if it is null [EE-4972] (#8411) 2023-01-27 22:08:13 +13:00
cmeng 8663de580a fix(settings): EE-4959 Cannot turn on Edge Compute Features on CE (#8396) 2023-01-27 17:04:40 +13:00
Oscar Zhou 34298d96c5 fix: pass endpoint entity instead of endpoint.id (#8407) 2023-01-27 12:41:54 +13:00
cmeng 9d103ffbeb fix(UI): EE-4937 low resolution hides add container button (#8401) 2023-01-27 09:18:48 +13:00
Chaim Lev-Ari 5847c2b8ef fix(system/update): submit license form [EE-4743] (#8381) 2023-01-26 20:35:04 +05:30
matias-portainer a09fe7e10c chore(edgejobs): AddEdgeJob disregards async mode EE-4855 (#8287) 2023-01-26 11:32:11 -03:00
Ali 5640cce4d6 chore(kompose): remove from settings [EE-4741] (#8375) 2023-01-26 16:03:44 +13:00
Chaim Lev-Ari 00bbf4ac63 refactor(auth): cache user data [EE-4935] (#8380) 2023-01-26 07:40:05 +05:30
matias-portainer a748e15c16 feat(ui): bump codemirror version EE-4892 (#8393) 2023-01-25 10:31:12 -03:00
matias-portainer cfdb9c126f fix(endpoints): check environment type before start metrics detection EE-4944 (#8391) 2023-01-25 10:29:11 -03:00
Chaim Lev-Ari 851a3346a9 fix(edge/update): remove schedule date for old envs [EE-3023] (#8315) 2023-01-24 12:20:55 +05:30
Oscar Zhou c9aae27b29 fix(ci/security): update the node and golang version (#8387) 2023-01-24 14:41:00 +13:00
Prabhat Khera 087848539f fix(kubernetes): detect metrics API for kubernetes endspoints EE-4865 (#8351) 2023-01-24 09:05:15 +13:00
LP B a74e389521 chore(dependencies): upgrade msw to fix xmldom CVE (#8362)
* chore(dependencies): upgrade msw to fix xmldom CVE

* refactor(msw): rename msw DefaultRequestBody to DefaultBodyType
2023-01-23 14:03:11 +01:00
Chaim Lev-Ari eff6ec9df9 fix(edge/group): show tag selector when no tags [EE-4924] (#8368) 2023-01-23 11:05:23 +05:30
Prabhat Khera 8dec95c2cd chore(lint): add golangci linter to GitHub workflow EE-4872 (#8366) 2023-01-23 18:31:15 +13:00
Ali 5b02f636d7 fix(namespace): move warning [EE-4885] (#8370)
Co-authored-by: testa113 <testa113>
2023-01-23 10:41:39 +13:00
Prabhat Khera ac458d0daa fix(ui): tooltip link color EE-4914 (#8365) 2023-01-23 10:15:47 +13:00
Chaim Lev-Ari 5b5dc320d5 fix(settings): save only the existing values [EE-4903] (#8326) 2023-01-23 02:34:39 +05:30
cmeng d04747b309 fix(home): EE-4874 homepage ui issues (#8319) 2023-01-23 09:14:06 +13:00
LP B 07dd6bbe84 fix(home): dont display disconnected status similar to disabled (#8311) 2023-01-20 17:34:16 +01:00
Chaim Lev-Ari 406ff8812c feat(system/upgrade): add get license dialog [EE-4743] (#8249) 2023-01-19 15:31:49 +02:00
Chaim Lev-Ari 5942f4ff58 fix(home): dark mode for edit buttons [EE-4828] (#8241) 2023-01-19 10:16:34 +02:00
Prabhat Khera adf92ce5e0 fix golang ci linter prehook (#8359) 2023-01-19 12:11:09 +13:00
cmeng fed3d14adf fix(home): EE-4906 home page tiles for edge devices have the wrong url (#8353) 2023-01-19 09:00:43 +13:00
Prabhat Khera 73db588080 chore(linting): configure go linter EE-4871 (#8288) 2023-01-18 15:20:42 +13:00
Ali 6769326c8b fix(ingress): update label [EE-4902] (#8330)
Co-authored-by: testa113 <testa113>
2023-01-18 13:29:59 +13:00
Ali e6d0e297dd fix(app): update redeploy wording [EE-4889] (#8317)
Co-authored-by: testa113 <testa113>
2023-01-18 08:30:06 +13:00
matias-portainer 0cd272211a fix(edgejobs): fix edge jobs log collection EE-4893 (#8328) 2023-01-17 14:21:13 -03:00
matias-portainer 6570f1f8eb fix(edgejobs): remove endpoint from edge job mapping on endpoint deletion EE-4764 (#8212) 2023-01-17 09:47:23 -03:00
Chaim Lev-Ari 1c180346e4 fix(ldap): sync user teams when needed [EE-4802] (#8235) 2023-01-16 10:41:32 +02:00
Chaim Lev-Ari 1d5d1bb12d fix(home): enable kubeconfig button [EE-4833] (#8233) 2023-01-15 11:32:38 +02:00
cmeng 0c27316034 fix(UI) EE-4883 stack repository method console error (#8304)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2023-01-13 16:07:52 +13:00
Chaim Lev-Ari d3bed3072b fix(home): link to correct env route [EE-4877] (#8295) 2023-01-12 15:02:37 +02:00
Chaim Lev-Ari 329e8bcad5 fix(home): allow non-admins [EE-4873] (#8297) 2023-01-12 15:01:52 +02:00
Chaim Lev-Ari 4bdf30c038 feat(home): remove margins from env list [EE-4868] (#8285) 2023-01-11 10:59:56 +02:00
Matt Hook 7793b98813 straight not back ticks (#8292) 2023-01-11 14:54:50 +13:00
Matt Hook 02de7b2715 feat(version): add version setting script bump version EE-2529 (#6820)
* version bumper

* tell the user to check update
2023-01-11 14:07:18 +13:00
Chaim Lev-Ari 9c0e0607a4 fix(sidebar): put behind modal [EE-4866] (#8282)
* fix(sidebar): put behind modal

* fix(system/upgrade): validate on open dialog
2023-01-11 08:32:03 +13:00
Chaim Lev-Ari baf9c3db0a feat(environments): add edge device [EE-4840] (#8246)
* feat(environments): add edge device [EE-4840]

fix [EE-4840]

* fix(home): fix tests
2023-01-11 08:30:49 +13:00
Prabhat Khera 6c193a8a45 refactor(log): log.fatal usage review EE-4607 (#8280) 2023-01-10 15:12:24 +13:00
Dakota Walsh 48a0f40621 fix(padding): fix margin-right on DatatableFooter pagination EE-4831 (#8237) 2023-01-10 10:14:46 +13:00
Matt Hook 4dc643acd9 fix(rolling-restart): wording and icon changes [EE-4834] (#8239)
* icon and wording changes

* fix inconsistencies and grammar

* fix(ui/buttons): show tooltip

* Change icon and confirmation dialog

* edit icon

* rename be-only-button to be-teaser-button for consistency

Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2023-01-10 10:02:07 +13:00
Ali 1d42db93f1 fix(rbac): fix false negative rbac result in github microk8s environments [EE-4829] 2023-01-09 17:55:28 +13:00
andres-portainer 33c3f8460c Bump to v2.18.0. (#8266) 2023-01-07 12:08:23 -03:00
andres-portainer dd0d1737b0 fix(performance): optimize performance for edge EE-3311 (#8040) 2023-01-06 16:25:41 -03:00
andres-portainer 3d28a6f877 chore(edgejobs): clean up EE-4850 (#8259) 2023-01-03 13:12:12 -03:00
andres-portainer 2fc518f221 chore(edgestacks): clean up EE-4851 (#8260) 2023-01-03 10:49:29 -03:00
andres-portainer 137ce37096 chore(nil): remove unnecessary nil checks EE-4847 (#8254) 2022-12-30 15:26:46 -03:00
andres-portainer e529327851 chore(utils): remove dead code EE-4846 (#8253) 2022-12-30 14:52:58 -03:00
andres-portainer 3625ab6faa fix(rand): seed the RNG EE-4845 (#8252) 2022-12-30 14:52:18 -03:00
Chaim Lev-Ari afb024d2a4 chore(docs): fix api reference [EE-4835] (#8242) 2022-12-25 10:47:21 +02:00
andres-portainer b2bc4b92d6 fix(ui): remove extra margins from Dockerfile details section EE-4790 (#8213) 2022-12-21 19:09:40 -03:00
Chaim Lev-Ari e5fd0c9595 fix(system): ignore failure to connect to docker [EE-4825] (#8231) 2022-12-21 18:08:18 +02:00
Chaim Lev-Ari 649c1c9cee feat(system): check BE image existence before upgrade [EE-4071] (#8230) 2022-12-21 17:17:51 +02:00
cmeng 919a854d93 feat(edge): EE-4570 allow pre-pull images with edge stack deployment (#8210)
Co-authored-by: Matt Hook <hookenz@gmail.com>
2022-12-21 13:18:51 +13:00
Chaim Lev-Ari 7fe0712b61 feat(home): move edge device to view [EE-4559] (#8189)
Co-authored-by: matias.spinarolli <matias.spinarolli@portainer.io>
2022-12-21 10:07:34 +13:00
Chaim Lev-Ari b4a6f6911c fix(home): wrap items on small screens [EE-4798] (#8220) 2022-12-20 10:57:59 +02:00
Chaim Lev-Ari 59d35d26d8 feat(home): open env on click [EE-4792] (#8219) 2022-12-20 10:57:25 +02:00
Dakota Walsh 95558ed4ad fix(EE-4782): add portainer internal label to created ingress rules (#8196) 2022-12-20 16:46:51 +13:00
matias-portainer e1b474d04f feat(edgejobs): support edge groups when using edge jobs EE-3873 (#8099) 2022-12-19 18:54:51 -03:00
Matt Hook 9732d1b5d8 remove kubeshell pod constraint (#8185) 2022-12-20 10:32:49 +13:00
Chaim Lev-Ari 701410d259 feat(app): clear env when log out [EE-4791] (#8218) 2022-12-19 08:56:39 +02:00
Chaim Lev-Ari 123754cee7 feat(system): make ui fixes to upgrade dialog [EE-4071] (#8199)
* feat(system): make ui fixes to upgrade dialog

* fix(system): hide on non-enabled platforms
2022-12-18 19:57:41 +02:00
Chaim Lev-Ari d75d2ba9ce fix(docker): fix links to docs [EE-4486] (#8166) 2022-12-18 10:13:18 +02:00
Dakota Walsh 046738c967 feat(kubernetes): cluster setup reasonable defaults EE-4518 (#8082) 2022-12-16 16:03:40 +13:00
Matt Hook 0436be7bc4 update pull latest image wording (#8191) 2022-12-16 14:57:07 +13:00
cmeng 94d64997cc fix(compose): EE-4777 Toast Error displays insufficient details when creating stack in Standalone (#8208) 2022-12-16 14:43:24 +13:00
Chaim Lev-Ari 294d1668d4 feat(edge/updates): sync code changes from EE [EE-3023] (#8186)
* feat(edge/updates): add info about each field

* feat(edge/update): add beta feature

* fix(edge/update): add info
2022-12-16 14:20:22 +13:00
Chaim Lev-Ari 4bd6618fb9 chore(deps): upgrade docker binary [EE-4545] (#8146)
closes [EE-4545]
upgrades docker to v20.10.21

Co-authored-by: Yi Chen <69284638+yi-portainer@users.noreply.github.com>
2022-12-16 14:07:21 +13:00
Chaim Lev-Ari 62197a67f7 chore(deps): upgrade docker compose [EE-4642] (#8147)
closes [EE-4642]
upgrades docker compose to v2.13.0
2022-12-16 14:05:14 +13:00
Matt Hook c1dc1b49d1 fix(pagination): vert center pagination control on all screens and add consistent spacing [EE-4006] (#8192) 2022-12-16 10:55:54 +13:00
andres-portainer b917e12b62 fix(fileservice): add missing interface EE-3458 (#8207) 2022-12-15 17:21:58 -03:00
andres-portainer a8ccd2b153 feat(filestore): add function to save mTLS certificates (#8206) 2022-12-15 16:16:29 -03:00
LP B 68975620c5 fix(app): cursor jumps to the end of inputs [EE-4359] (#8163)
* feat(app/react): introduce controlled inputs HOC to avoid creating uncontrolled React components

* fix(app/env): jumping inputs when adding gpus to existing environment
2022-12-15 17:49:36 +01:00
LP B 67d3abcc9d fix(app/analytics): exclude login page in analytics (#8172) 2022-12-15 17:48:59 +01:00
matias-portainer 90b0cb84f4 fix(wording): change wording on CE teaser EE-4590 (#8136) 2022-12-15 11:25:13 -03:00
Oscar Zhou b22cdb3559 fix(access/team): team member cannot change ownership to its own team [EE-4423] (#8052) 2022-12-15 21:27:18 +13:00
andres-portainer 37896661d6 fix(edgestacks): avoid a data race in edge stack status update endpoint EE-4737 (#8168) 2022-12-14 10:41:45 -03:00
Chaim Lev-Ari f38b8234d9 fix(edge/groups): show tags for environments [EE-4422] (#7940) 2022-12-14 09:17:49 +02:00
Chaim Lev-Ari 52e150fa29 fix(notifications): break lines for long message [EE-4521] (#8110) 2022-12-14 09:17:12 +02:00
Matt Hook 929749c0da incorrect variable name used (#8198) 2022-12-14 17:49:20 +13:00
Ali 09bf5d03f4 give docker specific tooltip link (#8197)
Co-authored-by: testa113 <testa113>
2022-12-14 17:26:19 +13:00
Ali ac6f52ab76 fix(gitapp): set manifest in git settings [EE-4734] (#8190) 2022-12-14 14:30:15 +13:00
Dakota Walsh 0ddcad66f3 fix(auth): invalidate session when permissions change EE-3320 (#8103) 2022-12-14 10:12:00 +13:00
Chaim Lev-Ari 930d9e5628 feat(edge/stacks): use namespace in manifest [EE-4507] (#8145) 2022-12-13 22:56:47 +02:00
Chaim Lev-Ari 8936ae9b7a feat(home): add connect and browse buttons [EE-4182] (#8175) 2022-12-13 22:56:09 +02:00
Chaim Lev-Ari db9d87c918 feat(system): path to upgrade swarm to BE [EE-4624] (#8124) 2022-12-13 22:52:06 +02:00
matias-portainer b59a0ba823 fix(settings): fix settings save/update EE-3604 (#8180) 2022-12-12 15:11:12 -03:00
Matt Hook 2188005b48 feat(kubernetes): add rolling restart button teaser [EE-4510] (#8126)
* rolling restart teaser button

* add be only rolling restart

* move position of button
2022-12-12 14:30:05 +13:00
Prabhat Khera a1528475ba feat(UI): tooltip html message support and width fix [EE-3445] (#8165)
Co-authored-by: testa113 <testa113>
2022-12-12 14:03:50 +13:00
Chaim Lev-Ari 5cbf52377d feat(system): path to upgrade standalone to BE [EE-4071] (#8095) 2022-12-11 08:58:22 +02:00
andres-portainer 756ac034ec fix(go): add Go workspace EE-4763 (#8182) 2022-12-09 13:23:01 -03:00
Oscar Zhou 1008afd1fe fix(session): reset the environment session storage after deleting it (#8119) 2022-12-09 11:11:24 +13:00
Ali 563ead85cc fix(gitoptions): git app edit ui tweaks [EE-4584] (#8159) 2022-12-09 10:41:11 +13:00
Chaim Lev-Ari eba5879ec8 feat(home): change layout of env tile [EE-4479] (#8061) 2022-12-07 16:51:20 +02:00
matias-portainer b48aa1274d fix(host): fix host info request EE-4641 (#8169) 2022-12-07 10:48:42 -03:00
Matt Hook 3e485c3152 feat(api): remove golang experimental packages [EE-3648] (#8081)
* remove golang experimental packages

* rebase and fix references
2022-12-07 17:15:52 +13:00
Oscar Zhou dffd45c5f9 refactor(ui): extract TagButton from TagSelector component [EE-4194] (#8127) 2022-12-07 16:15:09 +13:00
Ali c1cc8bad77 feat(rbac): detect if rbac is enabled [EE-4308] (#8139) 2022-12-07 15:53:06 +13:00
matias-portainer 8dcc5e4adb fix(edgestacks): fix repository edge stack creation EE-4732 (#8160) 2022-12-06 10:26:18 -03:00
Matt Hook 4558ce84cf fix(header): removed red dot from help menu [EE-4586] (#8133)
* remove red dot from help menu

* fix cursor, add menu-icon class back
2022-12-06 10:24:38 +13:00
Ali adc87b8f8e feat(deployment): PO feedback [EE-4416] (#8143) 2022-12-06 08:49:26 +13:00
Chaim Lev-Ari ce8455953e chore(deps): upgrade k8s client [EE-4543] (#8019) 2022-12-05 09:58:55 +02:00
Prabhat Khera a61b18dd93 feat(kubernetes): fix wordings for ingresses toggle EE-2647 (#8151) 2022-12-05 10:47:56 +13:00
Matt Hook d6a3fe23e9 feat(libhelm) update missed package paths [EE-4650] (#8134)
* add missing pkg paths

* fix go tests

* fixed pkg paths
2022-12-05 10:38:16 +13:00
Prabhat Khera cbaba43842 fix(ui): tooltip stays open on hover [EE-3445] (#8051) 2022-12-05 09:47:43 +13:00
Matt Hook c173888b64 remove old digest library and update path (#8141) 2022-12-02 10:46:59 +13:00
Chaim Lev-Ari 82e9e2a895 refactor(edge/updates): sync changes from EE [EE-4288] (#7726) 2022-12-01 08:40:52 +02:00
Matt Hook 4fee359247 feat(libhelm) import libhelm into CE pkg directory [EE-4650] (#8138)
* import libhelm and update some paths

* remove file

* update readme
2022-12-01 14:27:49 +13:00
Ali 9cdc0da615 feat(kompose): hide kompose [EE-4562] (#8084) 2022-12-01 13:46:23 +13:00
Matt Hook 8fd0efa34f add third_party digest lib to top level (#8140) 2022-12-01 13:26:39 +13:00
Matt Hook 79bfd8f6fe fix package import for docker/distribution (#8132) 2022-12-01 09:43:51 +13:00
Ali 2114c15f55 refactor(data-cy): duplicate data cy [EE-4656] (#8131) 2022-11-30 16:54:28 +13:00
Matt Hook d2f6d1e415 import libhelm into portainer (#8128) 2022-11-30 14:25:47 +13:00
Matt Hook 241440a474 fix(fdo): import deleted digest library [EE-4654] (#8129)
* import digest lib

* update references

* fix lint errors
2022-11-30 09:11:49 +13:00
LP B 2e19f4ea6d fix(docker/container): auto select private access when enabling UAC on public container edit (#8032) [EE-44-64] 2022-11-29 11:24:13 +01:00
Matt Hook 95bc508462 fix(migrator): fix setting version struct fields after migration [EE-4613] (#8090)
* fix setting version struct fields

* fix go tests

* remove versionUpdateRequired

* remove old code that was originally for debugging purposes
2022-11-28 19:28:10 +13:00
Ali d78b762f7b refactor(icons): replace fa icons [EE-4459] (#7907)
refactor(icons): remove fontawesome EE-4459

refactor(icon) replace feather with lucide EE-4472
2022-11-28 15:00:28 +13:00
andres-portainer 9dfac98a26 fix(tags): add missing error handling EE-4622 (#8102) 2022-11-24 14:16:04 -03:00
andres-portainer e26a607d28 fix(edgegroups): avoid a last-write-wins situation when updating edge groups concurrently EE-3732 (#8101) 2022-11-23 21:36:17 -03:00
Dakota Walsh 6dc1841c14 fix(kube): disable namespace resource quota by default EE-4421 (#8080) 2022-11-23 15:01:41 +13:00
andres-portainer c28be7aced fix(token-cache-manager): refactor to avoid data races EE-4438 (#8094) 2022-11-22 18:31:14 -03:00
Prabhat Khera dd01165224 show secrets in kube applications datatable (#8065) 2022-11-23 08:48:36 +13:00
Chaim Lev-Ari d484a0eb64 fix(docker): remove word break in details [EE-4481] (#7996) 2022-11-22 15:00:55 +02:00
Chaim Lev-Ari fe8e834dbf refactor(ui/datatables): migrate views to use datatable component [EE-4064] (#7609) 2022-11-22 14:16:34 +02:00
Prabhat Khera 0f0513c684 feat(kubernetes): edit yaml support EE-2855 (#8016) 2022-11-22 09:40:44 +13:00
Chaim Lev-Ari 7006c17ce4 fix(wizard): debounce name state [EE-4177] (#8042)
move debouncing to the component (from the validation).
debounce returns undefined when it's not calling the debounced function,
and undefined is considered a validation error.
2022-11-21 19:33:08 +02:00
Chaim Lev-Ari 253a3a2b40 fix(ui): remove loading of missing interceptors [EE-4604] (#8086) 2022-11-21 17:25:09 +02:00
Chaim Lev-Ari 1e4c4e2616 refactor(edge): move edge codebase to react (#7781) 2022-11-21 09:51:55 +02:00
Chaim Lev-Ari 75f40fe485 refactor(portainer): remove offline mode [EE-4348] (#7761)
Co-authored-by: deviantony <anthony.lapenna@portainer.io>
2022-11-21 09:50:47 +02:00
Ali 61e8e68c31 fix(svg): fix footer height [EE-4547] (#8071) 2022-11-21 09:22:41 +13:00
Matt Hook 583346321e feat(version): migrate version to semver [EE-3756] (#7693)
redisigned version bucket and migration code
2022-11-18 13:18:09 +13:00
cmeng 4cfa584c7c fix(git): EE-4577 Git Repository Fields are Missing in Edge Stacks (#8057) 2022-11-18 08:59:58 +13:00
Ali d012a4efc4 feat(deployment): enforce deployment options EE-4416 (#7974) 2022-11-17 22:00:34 +13:00
Oscar Zhou e0f3a8c0a2 fix(access/viwer): update the viwer filter key to user.id (#8055) 2022-11-17 10:38:34 +13:00
Yi Chen bb48ab00cb * remove empty examples (#7952)
* fix missing type
2022-11-17 06:55:08 +13:00
Chaim Lev-Ari eccc8131dd feat(system/upgrade): add upgrade banner [EE-4564] (#8046) 2022-11-16 18:38:39 +02:00
matias-portainer c21921a08d fix(edgestacks): return 400 instead of 500 on edge stack create when an invalid payload is provided EE-4429 (#7880) 2022-11-15 17:40:56 -03:00
Prabhat Khera 573e05d1c7 feat(kubernetes): BE teasure added EE-2647 (#7927) 2022-11-15 20:57:01 +13:00
Prabhat Khera 246e351817 remove wrong checked-in files from parent dir (#8048) 2022-11-15 09:01:28 +13:00
Prabhat Khera 6775c7b6ec clean database models directory (#8026) 2022-11-14 08:33:57 +13:00
Chaim Lev-Ari 881e99df53 refactor(nomad): sync frontend with EE [EE-3353] (#7758) 2022-11-13 12:29:25 +02:00
Chaim Lev-Ari 78dcba614d refactor(portainer): move to react [EE-3350] (#7915) 2022-11-13 10:10:18 +02:00
matias-portainer 30e23ea5b4 fix(ui): tidy up Edge Devices configuration EE-3604 (#7797) 2022-11-10 16:13:20 -03:00
Oscar Zhou e1e81731b9 fix(setting/auth): allow to save all auth method if custom ldap server is empty string (#7990) 2022-11-10 08:28:09 +13:00
Oscar Zhou 16377221f9 fix(stack): check if endpoint exists before checking the user permission on the endpoint (#7967) 2022-11-09 12:20:19 +13:00
Chaim Lev-Ari a0237852ef fix: fix occurred typo [EE-4536] (#7994) 2022-11-08 07:30:37 +02:00
andres-portainer 193e0c7d6f fix(snapshots): remove snapshots when removing endpoints EE-4527 (#7973)
* fix(snapshots): remove snapshots when removing endpoints EE-4527

* Fix nil pointer dereference.

Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2022-11-07 20:28:18 -03:00
Chaim Lev-Ari 77c29ff87e refactor(kubernetes): move react codebase [EE-3349] (#7953) 2022-11-07 08:03:11 +02:00
Chaim Lev-Ari 2868da296a fix(stack): validate original containers names [EE-4520] (#7978) 2022-11-06 10:40:33 +02:00
Chaim Lev-Ari ff10588383 fix(edge): show select all checkbox [EE-4129] (#7948) 2022-11-06 09:33:22 +02:00
LP B 6b02d9a1e3 fix(app/logs): change pattern to detect double serialized JSON logs [EE-4525] (#7962)
* fix(app/logs): change pattern to detect double serialized JSON logs

* fix(app/logs): fallback to raw display when parsing fails + include timestamp for Zerolog logs
2022-11-04 13:58:18 +01:00
Ali 9f3d5185b0 fix(slider): use and update react slider EE-4522 (#7987) 2022-11-04 14:12:53 +13:00
congs f94147b07b fix(registry): EE-4526 Registry Manage access link broken (#7975) 2022-11-04 12:10:42 +13:00
andres-portainer 49d02e0386 fix(db): remove a bucket overwrite EE-4424 (#7867) 2022-11-03 18:51:15 -03:00
Ali e82d0cfbdb fix(apps): fix edit error EE-4529 (#7963) 2022-11-03 13:04:56 +13:00
Oscar Zhou c8051b68d4 fix(auth/ldap): allow to save internal authentication if custom ldap server is not set [EE-3155] (#7959) 2022-11-03 10:06:56 +13:00
Chaim Lev-Ari 37d4a80769 refactor(environments): remove endpoints cache [DTD-100] (#6408) 2022-11-02 13:29:26 +02:00
Chaim Lev-Ari 9ef2e27aae fix(environments): debounce name validation [EE-4177] (#7889) 2022-11-02 12:44:31 +02:00
Oscar Zhou 9e1f80cf37 chore(ui/ldap): add texttip for group search configuration (#7944) 2022-11-02 11:31:44 +13:00
Dakota Walsh 459c95169a fix(ingresses): migrate to new allow/disallow format EE-4465 (#7893) 2022-11-02 11:17:32 +13:00
fhanportainer 5048f08b5f fix(box-selector): fixed incorrect wording for Private Box selector under UAC. (#7949) 2022-11-01 11:06:11 +13:00
fhanportainer e785d1572e fix(web-editor): fixed web editor scroll bar. (#7941) 2022-10-31 11:03:50 +13:00
Rex Wang 95a4f83466 fix(docker): docker template UI bug fix [EE-4034] (#7912)
* EE-4034 fix(docker): docker template UI bug fix

* EE-4034 fix(docker): fix ui
2022-10-30 14:56:23 +08:00
Dmitry Salakhov 4edf232e41 fix: document edge endoint url requirement (#7735) [EE-3425] 2022-10-28 13:00:12 +13:00
Dmitry Salakhov 903cf284e7 fix(image): build image from file (#7929) [EE-4501] 2022-10-27 23:31:31 +13:00
Prabhat Khera a550bfaedb fix showing namespaces for standard user (#7917) 2022-10-27 16:14:54 +13:00
Hao 446febb0f6 fix(image): hide button issues [EE-4166] (#7845)
* fix(image): hide button issues [EE-4166]
2022-10-25 15:02:59 +08:00
Oscar Zhou cb9fe2606c fix(team): disable team leader setting when external auth sync is enabled [EE-3579] (#7852) 2022-10-25 14:39:24 +13:00
Dakota Walsh 55211ef00e fix(ingress): allow none controller type EE-4420 (#7883)
Co-authored-by: testA113 <alex.harris@portainer.io>
2022-10-25 09:41:30 +13:00
Chaim Lev-Ari e48ceb15e9 refactor(environments): move environments ts code to react [EE-3443] (#7747) 2022-10-23 09:53:25 +03:00
Rex Wang 1b12cc9f31 EE-4376 fix(docker): fix malformed struct of template (#7803) 2022-10-21 16:29:18 +08:00
Hao 0365ed8e70 fix(docker): comfirm modal for removing secrets/networks/configs [EE-4211] (#7882)
* fix(ui): comfirm modal for removing secrets/networks/configs [EE-4211]

* fix(ui): comfirm modal for removing secrets/networks/configs [EE-4211]

* fix(ui): comfirm modal for removing secrets/networks/configs [EE-4211]
2022-10-21 16:03:41 +08:00
Chaim Lev-Ari 7624ff10ee chore(edge): add aria-label for edge-group selector [EE-4466] (#7896)
* chore(edge): add aria-label for edge-group selector

* style(edge): remove comment
2022-10-21 08:22:49 +03:00
andres-portainer 535a26412f fix(logging): default to pretty logging [EE-4371] (#7847)
* fix(logging): default to pretty logging EE-4371

* feat(app/logs): prettify stack traces in JSON logs

* feat(nomad/logs): prettify JSON logs in log viewer

* feat(kubernetes/logs): prettigy JSON logs in log viewers

* feat(app/logs): format and color zerolog prettified logs

* fix(app/logs): pre-parse logs when they are double serialized

Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2022-10-20 16:33:54 +02:00
Chaim Lev-Ari ee5600b6af chore(build): incremental ts build [EE-4204] (#7888) 2022-10-20 17:23:56 +03:00
Rex Wang 3f51d077ac fix(docker): create custom template [EE-4114] (#7812)
* EE-4114 fix(docker): create custom template

* Update customtemplate_create.go

remove white space
2022-10-20 16:42:49 +08:00
Rex Wang 0219d41ba7 fix(docker): Show stopped container on dashboard [EE-4327] (#7833)
* EE-4327 fix(docker): Show stopped container on dashboard

* Update ContainerStatus.tsx

remove comment

* EE-4327 fix(docker): show stopped container on dashboard
2022-10-20 15:10:39 +08:00
Prabhat Khera f3e2ccd487 fix volume claims with k8s app (#7899) 2022-10-20 15:43:19 +13:00
Ali 368e6b2a44 fix(helm): helm charts view bad icon aspect ratio EE-4451 (#7875) 2022-10-20 14:14:55 +13:00
Dmitry Salakhov 1100a2bd28 feat: move jwt lib to v4 (#7773) 2022-10-20 10:26:11 +13:00
itsconquest 16dc66f173 fix(UAC): put team into resource control when editing as team lead [EE-4457] (#7886)
* fix(UAC): put team into resource control when editing as team lead [EE-4457]

* populate form values & payload correctly
2022-10-20 10:18:56 +13:00
itsconquest c1f94be9b2 fix(notifications): sort by newest first by default [EE-4467] (#7891) 2022-10-19 15:25:20 +13:00
Matt Hook 58947fee69 fix(libhelm): new libhelm with relaxed validation when adding chart repo [EE-4440] (#7874)
update helm to fix adding some chart repos
2022-10-19 12:43:33 +13:00
Dakota Walsh 0c995ae1c8 fix(kubernetes): create proxied kubeclient EE-4326 (#7850) 2022-10-18 10:46:27 +13:00
itsconquest f6d6be90e4 fix(UAC): provide required UI context [EE-4415] (#7854) 2022-10-18 09:45:47 +13:00
andres-portainer 5488389278 fix(code): replace calls to ioutil EE-4425 (#7878) 2022-10-17 15:29:12 -03:00
andres-portainer 69f498c431 fix(tests): add missing context cancel EE-4433 (#7879) 2022-10-17 13:57:41 -03:00
Prabhat Khera 669327da7c fix reloading page when ing class disallowed (#7830) 2022-10-17 10:44:17 +13:00
andres-portainer 191f8e17ee fix(code): remove unused code EE-4431 (#7866) 2022-10-14 19:42:31 -03:00
andres-portainer ae2bec4bd9 fix(code): clean up EE-4432 (#7865) 2022-10-14 18:09:07 -03:00
andres-portainer 367f3dd6d4 fix(tags): remove a data race EE-4310 (#7862) 2022-10-13 11:12:12 -03:00
matias-portainer 8f1ac38963 fix(tags): get tags when loading associated endpoints selector EE-4140 (#7857) 2022-10-13 11:03:19 -03:00
Ali 7a6ff10268 fix(ing): nodeport validate and show errors [EE-4373] (#7801) 2022-10-12 10:06:57 +13:00
andres-portainer fd91de3571 fix(logging): remove remaining traces of logrus EE-4414 (#7848) 2022-10-11 16:53:27 -03:00
andres-portainer ab3a6f402e fix(build): add -trimpath EE-4406 (#7836) 2022-10-11 13:00:50 -03:00
andres-portainer d3edb7ebd5 fix(logging): convert missing cases to Zerolog EE-4400 (#7817) 2022-10-11 12:59:00 -03:00
Oscar Zhou c23b8b2816 fix(gitops): update the git ref cache key from url to url and pat (#7841) 2022-10-11 18:31:21 +13:00
itsconquest 724f1f63b7 fix(notifications): cleanup notifications code [EE-4274] (#7790)
* fix(notifications): cleanup notifications code [EE-4274]

* break long words
2022-10-11 14:05:53 +13:00
Ali c6ae8467c0 fix(ingress): update ingress tls after deletion EE-4387 (#7804)
* fix(ing): update tls value EE-4387
2022-10-10 09:32:30 +13:00
Ali 56087bcbb3 fix(clustersetup): dont show modal when loading (#7810) 2022-10-08 17:48:36 +13:00
Ali 315c1c7e1e fix(application): edit cluster ip services EE-4328 (#7775) 2022-10-07 16:55:11 +13:00
congs 819dc4d561 fix(UI): EE-4381 environment ID is shown instead of its name when deleting an environment (#7808) 2022-10-07 16:36:19 +13:00
congs 380a64d546 fix(wizard): EE-4350 Environment creating script should only showed for relevant type of environment (#7786) 2022-10-07 15:43:06 +13:00
congs 6429546462 fix(help): EE-4335 context sensitive help improvement (#7754) 2022-10-07 14:25:26 +13:00
matias-portainer ebfb71da05 fix(edge): fix docker proxy EE-4380 (#7799) 2022-10-06 11:12:39 -03:00
Ali ae0b9b1e30 fix(ingress): ingress indicate missing services EE-4358 (#7794) 2022-10-06 15:24:59 +13:00
Oscar Zhou e9de484c3e refactor(stack): stack build process backend only [EE-4342] (#7750) 2022-10-05 22:33:59 +13:00
Prabhat Khera 83a1ce9d2a bug(ingress): fix ingress class disallowed to not found issue EE-4311 (#7731)
Test / test-client (push) Has been cancelled
2022-10-05 15:17:53 +13:00
Rex Wang 66fd039933 EE-2681 fix(docker): fix message format (#7784) 2022-10-05 09:20:00 +08:00
Ali 1722257d68 fix(cluster): fix cluster setup no ingress develop EE-4352 (#7776)
* fix(cluster) update cluster wo controllers EE-4352

* fix(ing): stop errors in ns EE-4352
2022-10-04 12:13:56 +13:00
Ali 7d8b037761 fix(deploy): update option text EE-4362 (#7783) 2022-10-04 10:20:16 +13:00
Ali cd52e04a5a fix(customtemplate) fix custom var payload EE-4340 (#7752) 2022-10-03 09:49:28 +13:00
Xuing a0fa64781a fix(readme) update deploy portainer url (#7760) 2022-09-30 14:47:47 +13:00
Ali 43e3cb476b fix(clustersetup): set a default access mode (#7745) 2022-09-29 10:26:25 +13:00
Ali a1a88eb5e4 fix(secrets): allow edit sa token, refactor (#7732) 2022-09-29 09:57:39 +13:00
andres-portainer cb79dc18f8 chore(code): reduce divergence with EE EE-4344 (#7748) 2022-09-28 14:56:32 -03:00
andres-portainer e9384a6987 fix(database): fix a data race around the bucket sequences EE-4332 (#7738) 2022-09-27 16:14:00 -03:00
Dmitry Salakhov 90a0e6fe35 bump release version (#7733) 2022-09-27 13:55:20 +13:00
LP B e5f8466fb9 fix(app/environments): retain previously selected environments [EE-3233] (#7358) 2022-09-26 19:00:10 -03:00
Yi Chen c3110a85b2 * replace npm mirror with yarnpkg (#7730) 2022-09-27 10:08:47 +13:00
Dakota Walsh 89eda13eb3 feat(ingress): autodetect ingress controllers EE-673 (#7712) 2022-09-27 08:43:24 +13:00
Hao c96551e410 feat(stack): rebuild image for compose stack from git [EE-2681] (#7707)
* feat(stack): rebuild image for compose stack from git [EE-2681]

* feat(stack): rebuild image for compose stack from git [EE-2681]

* --no-edit

* UI
2022-09-26 14:22:38 +08:00
Rex Wang 9f7d5ac842 fix(docker): stack's env vars support empty value EE-1528 (#7592)
* EE-1528 fix(docker): stack's env vars support empty value

* EE-1528 fix(docker): handle no-value env as empty env
2022-09-24 20:05:20 +08:00
itsconquest 648c1db437 feat(notifications): track toast notifications [EE-4132] (#7711)
* feat(notifications): track toast notifications [EE-4132]

* suggested refactoring

* fix failing test

* remove duplicate styles

* applying spacing to context icon
2022-09-23 17:17:44 +12:00
Ali 4e20d70a99 feat(secrets): allow creating secrets beyond opaque [EE-2625] (#7709) 2022-09-23 16:35:47 +12:00
fhanportainer 3b2f0ff9eb fix(access-token): fixed create access token view. (#7716) 2022-09-23 16:29:25 +12:00
Prabhat Khera fcb76f570e feat(ingress): remove ingresses from add and edit application EE-4206 (#7677) 2022-09-23 16:11:35 +12:00
itsconquest c384d834f5 fix(build): restore aliases for uppercase imports [EE-4312] (#7723) 2022-09-23 15:55:05 +12:00
Dmitry Salakhov 45e2ed3d86 fix: miscofigured logging statements (#7721) 2022-09-23 13:15:26 +12:00
matias-portainer 6e0f83b99e feat(snapshots): separate snapshots from endpoint DB struct EE-4099 (#7614) 2022-09-22 17:05:10 -03:00
Prabhat Khera 4fe2a7c750 fix ingress screen loading (#7715) 2022-09-22 16:12:19 +12:00
congs f8b8d549fd feat(help): EE-2724 Context sensitive help (#7694) 2022-09-22 13:39:36 +12:00
LP B 1b0db4971f feat(app/logs): format Zerolog in logs viewer [EE-4226] (#7685)
* feat(app/logs): format Zerolog in logs viewer

* fix(app/logs): trim caller to only last 2 segments
2022-09-22 00:34:58 +02:00
LP B 6063f368ea fix(api/snapshot): convert error message only on matching env types (#7661) 2022-09-22 00:34:14 +02:00
Chao Geng 8ef584e41c feat(docker): new version message in BE side menu [EE-4079] (#7680)
* export GetLatestVersion and HasNewerVersion
2022-09-21 17:22:39 +08:00
Chaim Lev-Ari ceaee4e175 refactor(ui): replace ng selectors with react-select [EE-3608] (#7203)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2022-09-21 10:10:58 +03:00
Chaim Lev-Ari 1e21961e6a refactor(app): move settings components to react [EE-3442] (#7625) 2022-09-21 09:14:29 +03:00
Oscar Zhou 5777c18297 feat(gitops): support to list git repository refs and file tree [EE-2673] (#7100) 2022-09-21 17:47:02 +12:00
Prabhat Khera ef1d648c07 feat(ingress): ingresses datatable with add/edit ingresses EE-2615 (#7672) 2022-09-21 16:49:42 +12:00
Dmitry Salakhov 393d1fc91d fix: braking changes in compose (#7708) [EE-4258] 2022-09-21 15:59:40 +12:00
andres-portainer f9fe440401 feat(logging): trim paths from the build EE-4186 (#7710) 2022-09-20 18:48:07 -03:00
Chaim Lev-Ari fad376b415 refactor(ui): remove global providers [EE-4128] (#7578) 2022-09-20 21:14:24 +03:00
Chao Geng d3f094cb18 fix(image): better URL info text and a link to documentation to build image [EE-2409] (#7641)
* URL info text and a link to documentation to build image
2022-09-20 13:42:31 +08:00
Matt Hook 1950c4ca2b Sanitze kube labels (#7658) 2022-09-20 16:19:54 +12:00
Chao Geng 5232427a5b updated k8s stack deployment specification in Swagger (#7619) 2022-09-20 06:59:14 +08:00
andres-portainer 0fac1f85f7 feat(logging): redirect the standard logger to Zerolog EE-4186 (#7702) 2022-09-19 15:39:43 -03:00
Hao 70ce4e70d9 fix(registry): fix anonymous dockerhub name to make it same with BE [EE-4208] (#7690) 2022-09-19 16:52:15 +08:00
congs 47f2490059 fix(wizard) EE-2053 Add Docker Standalone option to agent install instructions (#7589) 2022-09-19 13:44:52 +12:00
Chaim Lev-Ari 4d123895ea feat(edge/update): select endpoints to update [EE-4043] (#7602) 2022-09-18 14:42:18 +03:00
andres-portainer 36e7981ab7 feat(logging): replace all the loggers with zerolog EE-4186 (#7663) 2022-09-16 13:18:44 -03:00
Oscar Zhou 53025178ef fix(access): support to list users or teams with specified endpoint [EE-1704] (#7610) 2022-09-16 14:45:14 +12:00
Rex Wang f71fe87ba7 fix(docker): ui link style [EE-4184] (#7655)
* EE-4184 fix(docker): ui link style
2022-09-15 17:33:49 +08:00
congs 6078234d07 fix(stack): EE-4213 Allow latest image to be pulled for stacks: backport backend logic (#7669) 2022-09-15 16:57:26 +12:00
Oscar Zhou fa162cafc1 feat(gitops): support to store git credentials [EE-2683] (#7066) 2022-09-15 16:32:05 +12:00
andres-portainer 9ef5636718 chore(handlers): replace structs by functions for HTTP errors EE-4227 (#7664) 2022-09-14 20:42:39 -03:00
Matt Hook 7accdf704c fix(kube): change warning text colour to match figma styling [EE-3045] (#7582)
* update warning text colour, icon and alignment to match figma
2022-09-15 11:09:19 +12:00
Chao Geng d570aee554 feat(image): upload local files for building image EE-3021 (#7507)
* support to make multiple files in archive buffer

* upload files by multipart
2022-09-14 14:47:24 +08:00
Chao Geng a7d458f0bd chore(tests): use t.TempDir to create temporary test directory [EE-3700] (#7612)
* create temporary test directory with t.TempDir
2022-09-14 13:59:47 +08:00
congs 1a9d793f2f fix(stack): EE-4213 Allow latest image to be pulled for stacks (#7653) 2022-09-14 10:17:32 +12:00
fhanportainer 0242c8e4ef fix(dropdown): fixed dropdown menu background color in dark mode. [EE-4026] (#7591)
* fix(dropdown): fixed dropdown menu background color in dark mode. [EE-4026]

* fix(dropdown): fixed table setting background color in dark mode.

* fix(dropdown): updated --bg-dropdown-menu-color in dark theme.

* fix(dropdown): fixed dropdown border radius issue

* fix(dropdown): fixed dropdown option text color in dark mode
2022-09-14 10:16:02 +12:00
Chaim Lev-Ari 6c4c958bf0 feat(edge/update): remote update structure [EE-4040] (#7553) 2022-09-13 16:56:38 +03:00
itsconquest dd1662c8b8 fix(extension): change ports to reduce conflicts [EE-3211] (#7596) 2022-09-13 11:03:37 +12:00
LP B fdfebcf731 fix(style): autofilled inputs use theme colors [EE-3828] (#7576) 2022-09-12 16:29:15 +02:00
itsconquest 9ce3e7d20d fix(theme): tabs and codeeditor darkmode correction [EE-4188] (#7643)
* fix(theme): tabs and codeeditor darkmode correction [EE-4188]

* correct codemirror background

* fix typo
2022-09-12 17:07:03 +12:00
congs bf8b9463d3 fix(security): EE-3202 Portainer CE and EE JS Dependencies (#7561) 2022-09-12 13:32:58 +12:00
Oscar Zhou 9375e577b0 feat(setting): display custom banner option as the limited feature for be (#7590) 2022-09-09 13:29:30 +12:00
itsconquest d95a67a567 fix(theme): env sidebar darkmode color [EE-4188] (#7638)
* fix(theme): env sidebar darkmode color [EE-4188]

* style usericon

* further dark mode changes
2022-09-09 12:47:06 +12:00
Dmitry Salakhov 160e210ffe feat: update compose and helm versions (#7536) [EE-3205] 2022-09-09 11:26:56 +12:00
itsconquest c9eaad6237 fix(auth): prevent trim on password [EE-4197] (#7633) 2022-09-08 13:50:21 +12:00
itsconquest 2edff939ef fix(theme): update dark mode colors [EE-4188] (#7623)
* fix(theme): update dark mode colors [EE-4188]

* fix sidebar hover/selected
2022-09-08 13:49:09 +12:00
congs 13338c46bb fix(wizard): EE-3728 Metadata is not working with Nomad (#7615) 2022-09-08 13:11:57 +12:00
LP B ea05814af4 fix(images/build): enforce file content length only when using the editor (#7630) 2022-09-08 02:32:36 +02:00
Dmitry Salakhov 0fe2ddf535 fix: don't url-escape socket paths (#7627) 2022-09-08 11:44:50 +12:00
Rex Wang 9af9395b73 fix(docker): prevent misconfigured stack from saving EE-3235 (#7585)
* EE-3235 fix(docker): add checker to editor

* support rollback to update stack file

Co-authored-by: chaogeng77977 <chao.geng@portainer.io>
2022-09-07 16:50:59 +08:00
Chaim Lev-Ari d9cc7eda51 refactor(app): move access-control components [EE-3441] (#7559) 2022-09-07 07:25:00 +03:00
fhanportainer 77c3f9131b fix(environment): update page title when no environment selected. (#7606)
* fix(environment): update page title when no environment selected.

* fix(environment): update page title when clearing environment from side bar.

* fix(environment): update page title when clearing environment from a non-environment page.
2022-09-07 11:08:45 +12:00
Dakota Walsh 2b2580fb61 fix(kubernetes): gke node stats (#7455) 2022-09-07 10:39:00 +12:00
congs f870619fb6 fix(git): EE-3727 nomad extension not available (#7595) 2022-09-06 10:54:21 +12:00
LP B 602e42739e feat(stacks): remove the ability to delete external swarm stacks [EE-2611] (#7560) 2022-09-05 15:00:49 +02:00
Rex Wang 326a8abdc7 EE-4021 fix(docker): rename deployed container (#7601) 2022-09-05 17:39:08 +08:00
Rex Wang c0f3d0193d EE-4125 fix(docker): fix creating container UI style (#7607) 2022-09-05 07:08:38 +08:00
Chaim Lev-Ari f9427c8fb2 refactor(teams): migrate teams to react [EE-2273] (#6691)
closes [EE-2273]
2022-09-02 18:30:34 +03:00
huib-portainer 9b02f575ef chore(readme): update readme to remove the outdated demo 2022-09-02 13:53:47 +12:00
itsconquest 5b4f6098d8 fix(boxselector): fix darkmode BE teaser style [EE-4145] (#7598)
* fix(boxselector): fix darkmode BE teaser style [EE-4145]

* make opacity same when selected

* add missing link to teaser

* style unchecked boxes + light mode

* revert colors for ligh theme
2022-09-02 12:42:48 +12:00
Oscar Zhou ccaf2bedb7 fix(stack/compose): remove the orphan containers if stack deployment is failed (#7599) 2022-09-02 08:11:02 +12:00
Rex Wang 88757d2617 fix(docker): style fixes [EE-4024] (#7569)
* EE-4042 update docker screens trash icon

* EE-4024 fix(docker): change styles
2022-09-01 19:02:21 +08:00
Matt Hook d79586cf6a chore(readme): update readme to display latest version (#7604)
* use badge to display latest version 
* use markdown syntax
2022-09-01 14:04:59 +12:00
Rex Wang a9b1a9c194 fix(docker): don't trimming when creating secret [EE-3265] (#7577)
* EE-3265 fix(docker): stop trimming when creating secret

* EE-3265 fix(docker): stop triming when creating secret in k8s
2022-08-31 23:19:14 +08:00
fhanportainer eb5036b96f fix(docker): removed docker.sock code in docker API [EE-3612] (#7586) 2022-08-31 20:32:01 +12:00
LP B 2f0dbf2ae1 fix(container/edit): fallback value when retrieving GPU config without snapshot available [EE-4110] (#7570) 2022-08-30 14:52:24 +02:00
itsconquest c79be58700 fix(sidebar): rework the update notification [EE-4119] (#7575) 2022-08-30 10:00:12 +12:00
Oscar Zhou d24e5ff71e feat(docker/container): support --shm-size configuration [EE-550] (#7547) 2022-08-30 09:22:27 +12:00
Chaim Lev-Ari 6536d36c24 feat(ui): hide user menu on docker extension [EE-4115] (#7563) 2022-08-29 05:07:07 +03:00
wheresolivia 6174940ac2 add data-cy attributes for docker image tag selectors (#7581) 2022-08-29 13:46:06 +12:00
fhanportainer 4c98fcd7db feat(analytis): EnableTelemetry defaults to false (#7539) 2022-08-29 11:09:47 +12:00
congs ad8054ac1f fix(stack): EE-3908 broken modal when updating/redeploying stacks: turn off toggle (#7573) 2022-08-26 17:54:10 +12:00
Matt Hook a54c54ef24 fix(swarm): fixed issue parsing url with no scheme [EE-4017] (#7502) 2022-08-26 11:55:55 +12:00
itsconquest 27095ede22 fix(stacks): orphaned stacks readonly [EE-4085] (#7552)
* fix(stacks): orphaned stacks readonly [EE-4085]

* correctly handle stack type in controller
2022-08-25 10:27:12 +12:00
congs e2789ab354 fix(container): EE-3995 gpus console error under stack list page (#7530) 2022-08-25 10:27:02 +12:00
Matt Hook d4f4bb532f fix(web-editor): add search hint text [EE-3967] (#7496) 2022-08-25 10:11:25 +12:00
Zhang Hao c6ab5d5717 fix(image): Add hide default registry teaser for CE version [EE-4038] (#7533)
* fix(image): Add hide default registry teaser for CE version [EE-4038].

* fix(image): Hide advanced mode only if there is no docker hub registries [EE-3734]

* sync with EE
2022-08-24 19:33:48 +08:00
Chaim Lev-Ari 234627f278 fix(ui/buttons): set hyperlink style [EE-4007] (#7524) 2022-08-24 07:40:50 +03:00
itsconquest 87214d48be fix(wizard): highcontrast style for BE only options (#7544) 2022-08-24 14:48:05 +12:00
itsconquest a2a35a1851 fix(azure): correctly sort container ports [EE-4076] (#7550) 2022-08-24 12:43:04 +12:00
Chaim Lev-Ari 11f0574ad3 fix(stack): hide containers for swarm stack [EE-3969] (#7504) 2022-08-23 09:47:09 +03:00
Chaim Lev-Ari 9fbc6177a6 fix(stack): hide containers for swarm stack [EE-3969] (#7503) 2022-08-23 09:46:55 +03:00
congs b91e06a60a fix(stack): EE-3908 broken modal when updating/redeploying stacks (#7497) 2022-08-23 14:22:18 +12:00
fhanportainer ad3f4ff711 fix(toggle): fixed disabled toggle color in dark and high contrast modes. (#7518)
* fix(toggle): fixed disabled toggle color in dark and high contrast modes.

* fix(switch): fixed switch color in dark and high contrast modes.

* fix(switch): fixed switch in LDAP secion.

* fix(switch): corrected the blue color of Switch in dark and high contrast themes.
2022-08-23 12:11:11 +12:00
Prabhat Khera 7edcfd6eab fix minor ui issues (#7510) 2022-08-23 08:55:41 +12:00
Prabhat Khera 735b2063ea fix(ui): minor ui issues EE-4004 (#7512) 2022-08-23 08:54:40 +12:00
matias-portainer bce4d02dd2 fix(edge): save edge checkin interval during endpoint creation (#7541) 2022-08-22 12:08:53 -03:00
fhanportainer e84126ec13 feat(label): uses --ui-white for control-label css class in Dark and High contrast themes (#7505)
* feat(label): uses `--ui-white` for control-label css class in Dark and High contrast themes.

* feat(label): uses `apply` in control-label css class.
2022-08-23 03:08:29 +12:00
matias-portainer 3a324acb0e fix(edge): fix edge URL placeholder EE-2598 (#7459) 2022-08-22 10:13:01 -03:00
Rex Wang c6f7427283 Fix(UI) fix color of file upload button in dark mode EE-4009 (#7535)
* fix snapshot url parsing issue for ip addresses (#7477)

* fix(ui/header): change font sizes [EE-3966] (#7484)

* fix(k8s/apps): show horizontal scrollbar [EE-3941] (#7476)

* fix(kubeconfig): update button and modal styles (#7480)

EE-3947

* fix(containers): make table wider [EE-3944] (#7486)

* Fix(UI) UI fixes on docker container screens (release/2.15) EE-3915 (#7499)

* EE-3915 ui fixes on docker container screens

* Update createcontainer.html

Update label

* EE-3916 fix container link under stack detail page (#7508)

* EE-4009 fix color of file-upload button in dark mode

Co-authored-by: Matt Hook <hookenz@gmail.com>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2022-08-22 20:25:26 +08:00
Chaim Lev-Ari ace01eac9d fix(ui): box-selector fixes [EE-3949] (#7489) 2022-08-22 11:55:48 +03:00
Ali 8d304b78cb fix(kubeshell): add back data-cy EE-4054 (#7538) 2022-08-22 16:52:24 +12:00
fhanportainer c17baa36ef fix(app-template): fixed the app template list not scroll to top issue (#7519)
* fix(app-template): fixed the app template list not scroll to top issue

* fix(templates): added id prop to PageHeader component.
2022-08-20 00:31:17 +12:00
fhanportainer 8cbff097e4 feat(docker): fixed info icon in docker feature config section. (#7492) 2022-08-19 12:55:29 +12:00
Matt Hook 294738cb0d bump version to 2.16 (#7475) 2022-08-19 10:34:47 +12:00
Rex Wang 69bc815acd EE-3998 bug fix (#7522) 2022-08-18 18:56:37 +08:00
Prabhat Khera fb62edefbc fix background (#7515) 2022-08-18 22:39:54 +12:00
fhanportainer 5e35ff8b8a feat(stack): fixed stack web editor scroll bar issue. (#7494) 2022-08-18 18:00:33 +12:00
Rex Wang 20053b1f07 EE-3916 fix container link under stack detail page (#7509) 2022-08-17 23:48:21 +08:00
Rex Wang cc6c5d45b7 Fix(UI) UI fixes on docker container screens EE-3915 (#7500)
* EE-3915 ui fixes on docker container pages

* Update createcontainer.html

Update label
2022-08-17 23:37:35 +08:00
Chaim Lev-Ari f480e0ccf6 fix(containers): make table wider [EE-3944] (#7487) 2022-08-17 12:49:29 +03:00
Ali d85149e328 fix(kubeconfig): update button and modal styles (#7481)
EE-3947
2022-08-17 20:01:04 +12:00
Chaim Lev-Ari cee241e77c fix(k8s/apps): show horizontal scrollbar [EE-3941] (#7472) 2022-08-16 20:59:01 +03:00
Chaim Lev-Ari 8ec9515225 fix(activity): fix angularjs error [EE-3968] (#7482) 2022-08-16 18:08:48 +03:00
Chaim Lev-Ari d4ffaaef2f fix(activity): fix angularjs error [EE-3968] (#7483) 2022-08-16 18:08:21 +03:00
Chaim Lev-Ari eda8347091 fix(ui/header): change font sizes [EE-3966] (#7485) 2022-08-16 18:08:08 +03:00
matias-portainer 4c23513a41 fix(home): remove edge devices from homepage list EE-3919 (#7471) 2022-08-16 09:57:55 -03:00
Matt Hook 81d1f35bdc fix snapshot url parsing issue for ip addresses (#7478) 2022-08-16 10:36:12 +12:00
5842 changed files with 383088 additions and 124052 deletions
+52
View File
@@ -0,0 +1,52 @@
root = "."
testdata_dir = "testdata"
tmp_dir = ".tmp"
[build]
args_bin = []
bin = "./dist/portainer"
cmd = "SKIP_GO_GET=true make build-server"
delay = 1000
exclude_dir = []
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = "./dist/portainer --log-level=DEBUG"
include_dir = ["api"]
include_ext = ["go"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true
-44
View File
@@ -1,44 +0,0 @@
version: "2"
checks:
argument-count:
enabled: false
complex-logic:
enabled: false
file-lines:
enabled: false
method-complexity:
enabled: false
method-count:
enabled: false
method-lines:
enabled: false
nested-control-flow:
enabled: false
return-statements:
enabled: false
similar-code:
enabled: false
identical-code:
enabled: false
plugins:
gofmt:
enabled: true
eslint:
enabled: true
channel: "eslint-5"
config:
config: .eslintrc.yml
exclude_patterns:
- assets/
- build/
- dist/
- distribution/
- node_modules
- test/
- webpack/
- gruntfile.js
- webpack.config.js
- api/
- "!app/kubernetes/**"
- .github/
- .tmp/
-3
View File
@@ -1,3 +0,0 @@
node_modules/
dist/
test/
-120
View File
@@ -1,120 +0,0 @@
env:
browser: true
jquery: true
node: true
es6: true
globals:
angular: true
extends:
- 'eslint:recommended'
- 'plugin:storybook/recommended'
- prettier
plugins:
- import
parserOptions:
ecmaVersion: 2018
sourceType: module
project: './tsconfig.json'
ecmaFeatures:
modules: true
rules:
no-control-regex: 'off'
no-empty: warn
no-empty-function: warn
no-useless-escape: 'off'
import/order:
[
'error',
{
pathGroups:
[
{ pattern: '@@/**', group: 'internal', position: 'after' },
{ pattern: '@/**', group: 'internal' },
{ pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' },
],
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
pathGroupsExcludedImportTypes: ['internal'],
},
]
settings:
'import/resolver':
alias:
map:
- ['@@', './app/react/components']
- ['@', './app']
extensions: ['.js', '.ts', '.tsx']
overrides:
- files:
- app/**/*.ts{,x}
parserOptions:
project: './tsconfig.json'
parser: '@typescript-eslint/parser'
plugins:
- '@typescript-eslint'
- 'regex'
extends:
- airbnb
- airbnb-typescript
- 'plugin:eslint-comments/recommended'
- 'plugin:react-hooks/recommended'
- 'plugin:react/jsx-runtime'
- 'plugin:@typescript-eslint/recommended'
- 'plugin:@typescript-eslint/eslint-recommended'
- 'plugin:promise/recommended'
- 'plugin:storybook/recommended'
- prettier # should be last
settings:
react:
version: 'detect'
rules:
import/order:
[
'error',
{
pathGroups: [{ pattern: '@@/**', group: 'internal', position: 'after' }, { pattern: '@/**', group: 'internal' }],
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
},
]
func-style: [error, 'declaration']
import/prefer-default-export: off
no-use-before-define: ['error', { functions: false }]
'@typescript-eslint/no-use-before-define': ['error', { functions: false }]
no-shadow: 'off'
'@typescript-eslint/no-shadow': off
jsx-a11y/no-autofocus: warn
react/forbid-prop-types: off
react/require-default-props: off
react/no-array-index-key: off
no-underscore-dangle: off
react/jsx-filename-extension: [0]
import/no-extraneous-dependencies: ['error', { devDependencies: true }]
'@typescript-eslint/explicit-module-boundary-types': off
'@typescript-eslint/no-unused-vars': 'error'
'@typescript-eslint/no-explicit-any': 'error'
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either' }]
'react/function-component-definition': ['error', { 'namedComponents': 'function-declaration' }]
'react/jsx-no-bind': off
'no-await-in-loop': 'off'
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }]
'regex/invalid': ['error', [{ 'regex': 'data-feather="(.*)"', 'message': 'Please use `react-feather` package instead' }]]
- files:
- app/**/*.test.*
extends:
- 'plugin:jest/recommended'
- 'plugin:jest/style'
env:
'jest/globals': true
rules:
'react/jsx-no-constructed-context-values': off
- files:
- app/**/*.stories.*
rules:
'no-alert': off
+4 -1
View File
@@ -2,4 +2,7 @@
cf5056d9c03b62d91a25c3b9127caac838695f98
# prettier v2
42e7db0ae7897d3cb72b0ea1ecf57ee2dd694169
42e7db0ae7897d3cb72b0ea1ecf57ee2dd694169
# tailwind prettier
58d66d3142950bb90a7d85511c034ac9fabba9ba
+11
View File
@@ -0,0 +1,11 @@
body:
- type: markdown
attributes:
value: |
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.
- type: textarea
attributes:
label: Ask a Question!
validations:
required: true
+38
View File
@@ -0,0 +1,38 @@
body:
- type: markdown
attributes:
value: |
# Welcome!
Thanks for suggesting an idea for Portainer!
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.
validations:
required: false
-54
View File
@@ -1,54 +0,0 @@
---
name: Bug report
about: Create a bug report
title: ''
labels: bug/need-confirmation, kind/bug
assignees: ''
---
<!--
Thanks for reporting a bug for Portainer !
You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/
Before opening a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
-->
**Bug description**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Portainer Logs**
Provide the logs of your Portainer container or Service.
You can see how [here](https://documentation.portainer.io/r/portainer-logs)
**Steps to reproduce the issue:**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Technical details:**
- Portainer version:
- Docker version (managed by Portainer):
- Kubernetes version (managed by Portainer):
- Platform (windows/linux):
- Command used to start Portainer (`docker run -p 9443:9443 portainer/portainer`):
- Browser:
- Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commercial setup.
- Have you reviewed our technical documentation and knowledge base? Yes/No
**Additional context**
Add any other context about the problem here.
+178
View File
@@ -0,0 +1,178 @@
name: Bug Report
description: Create a report to help us improve.
labels: kind/bug,bug/need-confirmation
body:
- type: markdown
attributes:
value: |
# Welcome!
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.
Example: Docker 24.0.3 | Docker Swarm 24.0.3 | Kubernetes 1.26
You can find our supported platforms [in our documentation](https://docs.portainer.io/start/requirements-and-prerequisites).
validations:
required: true
- type: input
attributes:
label: OS and Architecture
description: |
Enter your Operating System, Version and Architecture. Example: Ubuntu 22.04, AMD64 | Raspbian OS, ARM64
validations:
required: true
- type: input
attributes:
label: Browser
description: |
Enter your browser and version. Example: Google Chrome 114.0
validations:
required: false
- type: textarea
attributes:
label: What command did you use to deploy Portainer?
description: |
Example: `docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest`
If you deployed Portainer using a compose file or manifest you can provide this here as well.
render: bash
validations:
required: false
- type: textarea
attributes:
label: Additional Information
description: Any additional information about your environment, the bug, or anything else you think might be helpful.
validations:
required: false
+9 -3
View File
@@ -1,5 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Portainer Business Edition - Get 5 nodes free
url: https://portainer.io/pricing/take5
about: Portainer Business Edition has more features, more support and you can now get 5 nodes free for as long as you want.
- name: Question
url: https://github.com/orgs/portainer/discussions/new?category=help
about: Ask us a question about Portainer usage or deployment.
- name: Idea or Feature Request
url: https://github.com/orgs/portainer/discussions/new?category=ideas
about: Suggest an idea or feature/enhancement that should be added in Portainer.
- name: Portainer Business Edition - Get 3 Nodes Free
url: https://www.portainer.io/take-3
about: Portainer Business Edition has more features, more support and you can now get 3 nodes free for as long as you want.
+86
View File
@@ -0,0 +1,86 @@
name: Build image
on:
push:
branches: [develop]
tags: ['v*']
workflow_dispatch: {}
env:
IMAGE: ghcr.io/vvzvlad/portainer-ce
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Resolve version
id: ver
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
- name: Install client dependencies
# CI forces pnpm into --frozen-lockfile, which fails with
# ERR_PNPM_LOCKFILE_CONFIG_MISMATCH because the committed lockfile lacks
# the pnpmfileChecksum for the configDependencies in package.json.
# Reconcile the lockfile explicitly; the later frozen install in
# `make client-deps` then finds a matching lockfile. pnpm ignores the
# npm_config_frozen_lockfile env var, so an explicit flag is required.
run: pnpm install --no-frozen-lockfile
- name: Build client and server
env:
SKIP_GO_GET: "true"
CONTAINER_IMAGE_TAG: ${{ steps.ver.outputs.version }}
BUILDNUMBER: ${{ github.run_number }}
# Pin the embedded commit to the full SHA so it matches the image
# GIT_COMMIT build-arg and does not depend on the shallow checkout.
GIT_COMMIT_HASH: ${{ github.sha }}
# ENV=production selects webpack/webpack.production.js (minified bundle),
# matching the official CE image; the Makefile default is development.
run: make build-all ENV=production
- name: Ensure storybook directory exists
# make build-all does not produce dist/storybook, but alpine.Dockerfile
# has `COPY dist/storybook* /storybook/`; without a match the docker build fails.
run: mkdir -p dist/storybook
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image (linux/amd64, alpine base)
uses: docker/build-push-action@v6
with:
context: .
file: build/linux/alpine.Dockerfile
platforms: linux/amd64
push: true
tags: |
${{ env.IMAGE }}:${{ steps.ver.outputs.version }}
${{ env.IMAGE }}:latest
build-args: |
GIT_COMMIT=${{ github.sha }}
-15
View File
@@ -1,15 +0,0 @@
on:
push:
branches:
- develop
- 'release/**'
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: mschilde/auto-label-merge-conflicts@master
with:
CONFLICT_LABEL_NAME: 'has conflicts'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAX_RETRIES: 5
WAIT_MS: 5000
-38
View File
@@ -1,38 +0,0 @@
name: Lint
on:
push:
branches:
- master
- develop
- release/*
pull_request:
branches:
- master
- develop
- release/*
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'yarn'
- run: yarn --frozen-lockfile
- name: Run linters
uses: wearerequired/lint-action@v1
with:
eslint: true
eslint_extensions: ts,tsx,js,jsx
prettier: true
prettier_dir: app/
gofmt: true
gofmt_dir: api/
- name: Typecheck
uses: icrawl/action-tsc@v1
-230
View File
@@ -1,230 +0,0 @@
name: Nightly Code Security Scan
on:
schedule:
- cron: '0 8 * * *'
workflow_dispatch:
jobs:
client-dependencies:
name: Client dependency check
runs-on: ubuntu-latest
if: >- # only run for develop branch
github.ref == 'refs/heads/develop'
outputs:
js: ${{ steps.set-matrix.outputs.js_result }}
steps:
- uses: actions/checkout@master
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
continue-on-error: true # To make sure that artifact upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
json: true
- name: Upload js security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: js-security-scan-develop-result
path: snyk.json
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=table -export -export-filename="/data/js-result")
- name: Upload js result html file
uses: actions/upload-artifact@v3
with:
name: html-js-result-${{github.run_id}}
path: js-result.html
- name: Analyse the js result
id: set-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=matrix)
echo "::set-output name=js_result::${result}"
server-dependencies:
name: Server dependency check
runs-on: ubuntu-latest
if: >- # only run for develop branch
github.ref == 'refs/heads/develop'
outputs:
go: ${{ steps.set-matrix.outputs.go_result }}
steps:
- uses: actions/checkout@master
- name: Download go modules
run: cd ./api && go get -t -v -d ./...
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/golang@master
continue-on-error: true # To make sure that artifact upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --file=./api/go.mod
json: true
- name: Upload go security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: go-security-scan-develop-result
path: snyk.json
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=table -export -export-filename="/data/go-result")
- name: Upload go result html file
uses: actions/upload-artifact@v3
with:
name: html-go-result-${{github.run_id}}
path: go-result.html
- name: Analyse the go result
id: set-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=matrix)
echo "::set-output name=go_result::${result}"
image-vulnerability:
name: Build docker image and Image vulnerability check
runs-on: ubuntu-latest
if: >-
github.ref == 'refs/heads/develop'
outputs:
image: ${{ steps.set-matrix.outputs.image_result }}
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Use golang 1.18
uses: actions/setup-go@v3
with:
go-version: '1.18'
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Install packages and build
run: yarn install && yarn build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: build/linux/Dockerfile
tags: trivy-portainer:${{ github.sha }}
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
- name: Load docker image
run: |
docker load --input /tmp/trivy-portainer-image.tar
- name: Run Trivy vulnerability scanner
uses: docker://docker.io/aquasec/trivy:latest
continue-on-error: true
with:
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
- name: Upload image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-develop-result
path: image-trivy.json
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=trivy -path="/data/image-trivy.json" -output-type=table -export -export-filename="/data/image-result")
- name: Upload go result html file
uses: actions/upload-artifact@v3
with:
name: html-image-result-${{github.run_id}}
path: image-result.html
- name: Analyse the trivy result
id: set-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=trivy -path="/data/image-trivy.json" -output-type=matrix)
echo "::set-output name=image_result::${result}"
result-analysis:
name: Analyse scan result
needs: [client-dependencies, server-dependencies, image-vulnerability]
runs-on: ubuntu-latest
if: >-
github.ref == 'refs/heads/develop'
strategy:
matrix:
js: ${{fromJson(needs.client-dependencies.outputs.js)}}
go: ${{fromJson(needs.server-dependencies.outputs.go)}}
image: ${{fromJson(needs.image-vulnerability.outputs.image)}}
steps:
- name: Display the results of js, go and image
run: |
echo ${{ matrix.js.status }}
echo ${{ matrix.go.status }}
echo ${{ matrix.image.status }}
echo ${{ matrix.js.summary }}
echo ${{ matrix.go.summary }}
echo ${{ matrix.image.summary }}
- name: Send Slack message
if: >-
matrix.js.status == 'failure' ||
matrix.go.status == 'failure' ||
matrix.image.status == 'failure'
uses: slackapi/slack-github-action@v1.18.0
with:
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Code Scanning Result (*${{ github.repository }}*)\n*<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|GitHub Actions Workflow URL>*"
}
}
],
"attachments": [
{
"color": "#FF0000",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*JS dependency check*: *${{ matrix.js.status }}*\n${{ matrix.js.summary }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Go dependency check*: *${{ matrix.go.status }}*\n${{ matrix.go.summary }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Image vulnerability check*: *${{ matrix.image.status }}*\n${{ matrix.image.summary }}\n"
}
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SECURITY_SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
-233
View File
@@ -1,233 +0,0 @@
name: PR Code Security Scan
on:
pull_request_review:
types:
- submitted
- edited
paths:
- 'package.json'
- 'api/go.mod'
- 'gruntfile.js'
- 'build/linux/Dockerfile'
- 'build/linux/alpine.Dockerfile'
- 'build/windows/Dockerfile'
jobs:
client-dependencies:
name: Client dependency check
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
outputs:
jsdiff: ${{ steps.set-diff-matrix.outputs.js_diff_result }}
steps:
- uses: actions/checkout@master
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
continue-on-error: true # To make sure that artifact upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
json: true
- name: Upload js security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: js-security-scan-feat-result
path: snyk.json
- name: Download artifacts from develop branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mv ./snyk.json ./js-snyk-feature.json
(gh run download -n js-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
if [[ -e ./snyk.json ]]; then
mv ./snyk.json ./js-snyk-develop.json
else
echo "null" > ./js-snyk-develop.json
fi
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/js-snyk-feature.json" -compare-to="/data/js-snyk-develop.json" -output-type=table -export -export-filename="/data/js-result")
- name: Upload js result html file
uses: actions/upload-artifact@v3
with:
name: html-js-result-compare-to-develop-${{github.run_id}}
path: js-result.html
- name: Analyse the js diff result
id: set-diff-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/js-snyk-feature.json" -compare-to="./data/js-snyk-develop.json" -output-type=matrix)
echo "::set-output name=js_diff_result::${result}"
server-dependencies:
name: Server dependency check
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
outputs:
godiff: ${{ steps.set-diff-matrix.outputs.go_diff_result }}
steps:
- uses: actions/checkout@master
- name: Download go modules
run: cd ./api && go get -t -v -d ./...
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/golang@master
continue-on-error: true # To make sure that artifact upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --file=./api/go.mod
json: true
- name: Upload go security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: go-security-scan-feature-result
path: snyk.json
- name: Download artifacts from develop branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mv ./snyk.json ./go-snyk-feature.json
(gh run download -n go-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
if [[ -e ./snyk.json ]]; then
mv ./snyk.json ./go-snyk-develop.json
else
echo "null" > ./go-snyk-develop.json
fi
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/go-snyk-feature.json" -compare-to="/data/go-snyk-develop.json" -output-type=table -export -export-filename="/data/go-result")
- name: Upload go result html file
uses: actions/upload-artifact@v3
with:
name: html-go-result-compare-to-develop-${{github.run_id}}
path: go-result.html
- name: Analyse the go diff result
id: set-diff-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/go-snyk-feature.json" -compare-to="/data/go-snyk-develop.json" -output-type=matrix)
echo "::set-output name=go_diff_result::${result}"
image-vulnerability:
name: Build docker image and Image vulnerability check
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
outputs:
imagediff: ${{ steps.set-diff-matrix.outputs.image_diff_result }}
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Use golang 1.18
uses: actions/setup-go@v3
with:
go-version: '1.18'
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Install packages and build
run: yarn install && yarn build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: build/linux/Dockerfile
tags: trivy-portainer:${{ github.sha }}
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
- name: Load docker image
run: |
docker load --input /tmp/trivy-portainer-image.tar
- name: Run Trivy vulnerability scanner
uses: docker://docker.io/aquasec/trivy:latest
continue-on-error: true
with:
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
- name: Upload image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-feature-result
path: image-trivy.json
- name: Download artifacts from develop branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mv ./image-trivy.json ./image-trivy-feature.json
(gh run download -n image-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
if [[ -e ./image-trivy.json ]]; then
mv ./image-trivy.json ./image-trivy-develop.json
else
echo "null" > ./image-trivy-develop.json
fi
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=trivy -path="/data/image-trivy-feature.json" -compare-to="/data/image-trivy-develop.json" -output-type=table -export -export-filename="/data/image-result")
- name: Upload image result html file
uses: actions/upload-artifact@v3
with:
name: html-image-result-compare-to-develop-${{github.run_id}}
path: image-result.html
- name: Analyse the image diff result
id: set-diff-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=trivy -path="/data/image-trivy-feature.json" -compare-to="./data/image-trivy-develop.json" -output-type=matrix)
echo "::set-output name=image_diff_result::${result}"
result-analysis:
name: Analyse scan result compared to develop
needs: [client-dependencies, server-dependencies, image-vulnerability]
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
strategy:
matrix:
jsdiff: ${{fromJson(needs.client-dependencies.outputs.jsdiff)}}
godiff: ${{fromJson(needs.server-dependencies.outputs.godiff)}}
imagediff: ${{fromJson(needs.image-vulnerability.outputs.imagediff)}}
steps:
- name: Check job status of diff result
if: >-
matrix.jsdiff.status == 'failure' ||
matrix.godiff.status == 'failure' ||
matrix.imagediff.status == 'failure'
run: |
echo ${{ matrix.jsdiff.status }}
echo ${{ matrix.godiff.status }}
echo ${{ matrix.imagediff.status }}
echo ${{ matrix.jsdiff.summary }}
echo ${{ matrix.godiff.summary }}
echo ${{ matrix.imagediff.summary }}
exit 1
-19
View File
@@ -1,19 +0,0 @@
name: Automatic Rebase
on:
issue_comment:
types: [created]
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-27
View File
@@ -1,27 +0,0 @@
name: Close Stale Issues
on:
schedule:
- cron: '0 12 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v4.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Issue Config
days-before-issue-stale: 60
days-before-issue-close: 7
stale-issue-label: 'status/stale'
exempt-all-issue-milestones: true # Do not stale issues in a milestone
exempt-issue-labels: kind/enhancement, kind/style, kind/workaround, kind/refactor, bug/need-confirmation, bug/confirmed, status/discuss
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
-29
View File
@@ -1,29 +0,0 @@
name: Test
on: push
jobs:
test-client:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'yarn'
- run: yarn --frozen-lockfile
- name: Run tests
run: yarn test:client
# test-server:
# runs-on: ubuntu-latest
# env:
# GOPRIVATE: "github.com/portainer"
# steps:
# - uses: actions/checkout@v3
# - uses: actions/setup-go@v3
# with:
# go-version: '1.18'
# - name: Run tests
# run: |
# cd api
# go test ./...
@@ -1,53 +0,0 @@
name: Validate
on:
pull_request:
branches:
- master
- develop
- 'release/*'
jobs:
openapi-spec:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Setup Node v14
uses: actions/setup-node@v2
with:
node-version: 14
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Setup Go v1.17.3
uses: actions/setup-go@v2
with:
go-version: '^1.17.3'
- name: Prebuild docs
run: yarn prebuild:docs
- name: Build OpenAPI 2.0 Spec
run: yarn build:docs
# Install dependencies globally to bypass installing all frontend deps
- name: Install swagger2openapi and swagger-cli
run: yarn global add swagger2openapi @apidevtools/swagger-cli
# OpenAPI2.0 does not support multiple body params (which we utilise in some of our handlers).
# OAS3.0 however does support multiple body params - hence its best to convert the generated OAS 2.0
# to OAS 3.0 and validate the output of generated OAS 3.0 instead.
- name: Convert OpenAPI 2.0 to OpenAPI 3.0 and validate spec
run: yarn validate:docs
+6 -1
View File
@@ -4,6 +4,7 @@ dist
portainer-checksum.txt
api/cmd/portainer/portainer*
storybook-static
debug-storybook.log
.tmp
**/.vscode/settings.json
**/.vscode/tasks.json
@@ -11,8 +12,12 @@ storybook-static
*.DS_Store
.eslintcache
__debug_bin
__debug_bin*
api/docs
.idea
.env
go.work.sum
.vitest
-1
View File
@@ -1 +0,0 @@
portainer
+13
View File
@@ -0,0 +1,13 @@
version: '2'
linters:
default: none
enable:
- forbidigo
settings:
forbidigo:
forbid:
- pattern: ^dataservices.DataStore.(EdgeGroup|EdgeJob|EdgeStack|EndpointRelation|Endpoint|GitCredential|Registry|ResourceControl|Role|Settings|Snapshot|SSLSettings|Stack|Tag|User)$
msg: Use a transaction instead
- pattern: ^(filepath|path)\.Join$
msg: Use filesystem.JoinPaths() from github.com/portainer/portainer/api/filesystem to prevent path traversal attacks
analyze-types: true
+126
View File
@@ -0,0 +1,126 @@
version: '2'
run:
allow-parallel-runners: true
linters:
default: none
enable:
- gocritic
- bodyclose
- copyloopvar
- depguard
- errcheck
- errorlint
- forbidigo
- govet
- ineffassign
- intrange
- perfsprint
- staticcheck
- unused
- mirror
- durationcheck
- errorlint
- govet
- usetesting
- zerologlint
- testifylint
- modernize
- unconvert
- unused
- zerologlint
- exptostd
settings:
staticcheck:
checks: ['all', '-ST1003', '-ST1005', '-ST1016', '-SA1019', '-QF1003']
depguard:
rules:
main:
files:
- '!**/*_test.go'
- '!**/base.go'
- '!**/base_tx.go'
deny:
- pkg: encoding/json
desc: use github.com/segmentio/encoding/json
- pkg: golang.org/x/exp
desc: exp is not allowed
- pkg: github.com/portainer/libcrypto
desc: use github.com/portainer/portainer/pkg/libcrypto
- pkg: github.com/portainer/libhttp
desc: use github.com/portainer/portainer/pkg/libhttp
- pkg: golang.org/x/crypto
desc: golang.org/x/crypto is not allowed because of FIPS mode
- pkg: github.com/ProtonMail/go-crypto/openpgp
desc: github.com/ProtonMail/go-crypto/openpgp is not allowed because of FIPS mode
- pkg: github.com/cosi-project/runtime
desc: github.com/cosi-project/runtime is not allowed because of FIPS mode
- pkg: gopkg.in/yaml.v2
desc: use go.yaml.in/yaml/v3 instead
- pkg: gopkg.in/yaml.v3
desc: use go.yaml.in/yaml/v3 instead
- pkg: github.com/golang-jwt/jwt/v4
desc: use github.com/golang-jwt/jwt/v5 instead
- pkg: github.com/mitchellh/mapstructure
desc: use github.com/go-viper/mapstructure/v2 instead
- pkg: gopkg.in/alecthomas/kingpin.v2
desc: use github.com/alecthomas/kingpin/v2 instead
- pkg: github.com/jcmturner/gokrb5$
desc: use github.com/jcmturner/gokrb5/v8 instead
- pkg: github.com/gofrs/uuid
desc: use github.com/google/uuid
- pkg: github.com/Masterminds/semver$
desc: use github.com/Masterminds/semver/v3
- pkg: github.com/blang/semver
desc: use github.com/Masterminds/semver/v3
- pkg: github.com/coreos/go-semver
desc: use github.com/Masterminds/semver/v3
- pkg: github.com/hashicorp/go-version
desc: use github.com/Masterminds/semver/v3
gocritic:
disable-all: true
enabled-checks:
- ruleguard
settings:
ruleguard:
rules: './analysis/ssrf.go,./analysis/git.go'
forbidigo:
forbid:
- pattern: ^tls\.Config$
msg: Use crypto.CreateTLSConfiguration() instead
- pattern: ^tls\.Config\.(InsecureSkipVerify|MinVersion|MaxVersion|CipherSuites|CurvePreferences)$
msg: Do not set this field directly, use crypto.CreateTLSConfiguration() instead
- pattern: ^object\.(Commit|Tag)\.Verify$
msg: 'Not allowed because of FIPS mode'
- pattern: ^(types\.SystemContext\.)?(DockerDaemonInsecureSkipTLSVerify|DockerInsecureSkipTLSVerify|OCIInsecureSkipTLSVerify)$
msg: 'Not allowed because of FIPS mode'
- pattern: ^git\.PlainClone(Context|WithOptions)?$
msg: Use git.CloneContext with NewNoSymlinkFS to prevent symlink traversal attacks
analyze-types: true
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
rules:
- path: pkg/libhttp/ssrf
linters:
- gocritic
text: ruleguard
- path: pkg/libhttp/ssrf/builder\.go
linters:
- forbidigo
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd $(dirname -- "$0") && pnpm lint-staged
+4 -1
View File
@@ -1,2 +1,5 @@
dist
api/datastore/test_data
api/datastore/test_data
coverage
pnpm-lock.yaml
+4 -1
View File
@@ -2,6 +2,7 @@
"printWidth": 180,
"singleQuote": true,
"htmlWhitespaceSensitivity": "strict",
"trailingComma": "es5",
"overrides": [
{
"files": ["*.html"],
@@ -15,5 +16,7 @@
"printWidth": 80
}
}
]
],
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindFunctions": ["clsx"]
}
-38
View File
@@ -1,38 +0,0 @@
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
stories: ['../app/**/*.stories.mdx', '../app/**/*.stories.@(ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
{
name: '@storybook/addon-postcss',
options: {
cssLoaderOptions: {
importLoaders: 1,
modules: {
localIdentName: '[path][name]__[local]',
auto: true,
exportLocalsConvention: 'camelCaseOnly',
},
},
postcssLoaderOptions: {
implementation: require('postcss'),
},
},
},
],
webpackFinal: (config) => {
config.resolve.plugins = [
...(config.resolve.plugins || []),
new TsconfigPathsPlugin({
extensions: config.resolve.extensions,
}),
];
return config;
},
core: {
builder: 'webpack5',
},
staticDirs: ['./public'],
};
+115
View File
@@ -0,0 +1,115 @@
// This file has been automatically migrated to valid ESM format by Storybook.
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import path, { dirname } from 'path';
import { StorybookConfig } from '@storybook/react-webpack5';
import { Configuration } from 'webpack';
import postcss from 'postcss';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const require = createRequire(import.meta.url);
const config: StorybookConfig = {
stories: ['../app/**/*.stories.@(ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-webpack5-compiler-swc',
'@chromatic-com/storybook',
{
name: '@storybook/addon-styling-webpack',
options: {
rules: [
{
test: /\.css$/,
sideEffects: true,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: {
localIdentName: '[path][name]__[local]',
auto: true,
exportLocalsConvention: 'camelCaseOnly',
},
},
},
{
loader: require.resolve('postcss-loader'),
options: {
implementation: postcss,
},
},
],
},
],
},
},
'@storybook/addon-docs',
],
webpackFinal: (config) => {
const rules = config?.module?.rules || [];
const imageRule = rules.find((rule) => {
const test = (rule as { test: RegExp }).test;
if (!test) {
return false;
}
return test.test('.svg');
}) as { [key: string]: any };
imageRule.exclude = /\.svg$/;
rules.unshift({
test: /\.svg$/i,
type: 'asset',
resourceQuery: {
not: [/c/],
}, // exclude react component if *.svg?url
});
rules.unshift({
test: /\.svg$/i,
issuer: /\.(js|ts)(x)?$/,
resourceQuery: /c/,
// *.svg?c
use: [
{
loader: '@svgr/webpack',
options: {
icon: true,
},
},
],
});
return {
...config,
resolve: {
...config.resolve,
tsconfig: path.resolve(__dirname, '..', 'tsconfig.json'),
},
module: {
...config.module,
rules,
},
} satisfies Configuration;
},
staticDirs: ['./public'],
typescript: {
reactDocgen: 'react-docgen',
},
framework: {
name: '@storybook/react-webpack5',
options: {},
},
docs: {},
};
export default config;
-48
View File
@@ -1,48 +0,0 @@
import '../app/assets/css';
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
import { initialize as initMSW, mswDecorator } from 'msw-storybook-addon';
import { handlers } from '@/setup-tests/server-handlers';
import { QueryClient, QueryClientProvider } from 'react-query';
// Initialize MSW
initMSW({
onUnhandledRequest: ({ method, url }) => {
if (url.pathname.startsWith('/api')) {
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
`);
}
},
});
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
msw: {
handlers,
},
};
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
export const decorators = [
(Story) => (
<QueryClientProvider client={testQueryClient}>
<UIRouter plugins={[pushStateLocationPlugin]}>
<Story />
</UIRouter>
</QueryClientProvider>
),
mswDecorator,
];
+86
View File
@@ -0,0 +1,86 @@
import { useEffect } from 'react';
import '../app/assets/css';
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
import { initialize as initMSW, mswLoader } from 'msw-storybook-addon';
import { handlers } from '../app/setup-tests/server-handlers';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Preview } from '@storybook/react-webpack5';
initMSW(
{
onUnhandledRequest: ({ method, url }) => {
if (url.startsWith('/api')) {
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
`);
}
},
},
handlers
);
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const preview: Preview = {
globalTypes: {
theme: {
description: 'Portainer color theme',
toolbar: {
title: 'Theme',
icon: 'paintbrush',
items: [
{ value: 'light', title: 'Light', icon: 'sun' },
{ value: 'dark', title: 'Dark', icon: 'moon' },
{ value: 'highcontrast', title: 'High Contrast', icon: 'eye' },
],
dynamicTitle: true,
},
},
},
initialGlobals: {
theme: 'light',
},
decorators: (Story, context) => {
const theme = context.globals.theme;
useEffect(() => {
if (theme === 'light') {
document.documentElement.removeAttribute('theme');
} else {
document.documentElement.setAttribute('theme', theme);
}
}, [theme]);
return (
<QueryClientProvider client={testQueryClient}>
<UIRouter plugins={[pushStateLocationPlugin]}>
<Story />
</UIRouter>
</QueryClientProvider>
);
},
loaders: [mswLoader],
parameters: {
options: {
storySort: {
order: ['Design System', 'Components', '*'],
},
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
msw: {
handlers,
},
},
};
export default preview;
+209 -201
View File
@@ -2,26 +2,26 @@
/* tslint:disable */
/**
* Mock Service Worker (0.36.3).
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929';
const bypassHeaderName = 'x-msw-bypass';
const PACKAGE_VERSION = '2.12.10';
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82';
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse');
const activeClientIds = new Set();
self.addEventListener('install', function () {
return self.skipWaiting();
addEventListener('install', function () {
self.skipWaiting();
});
self.addEventListener('activate', async function (event) {
return self.clients.claim();
addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim());
});
self.addEventListener('message', async function (event) {
const clientId = event.source.id;
addEventListener('message', async function (event) {
const clientId = Reflect.get(event.source || {}, 'id');
if (!clientId || !self.clients) {
return;
@@ -33,7 +33,9 @@ self.addEventListener('message', async function (event) {
return;
}
const allClients = await self.clients.matchAll();
const allClients = await self.clients.matchAll({
type: 'window',
});
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
@@ -46,7 +48,10 @@ self.addEventListener('message', async function (event) {
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
});
break;
}
@@ -56,16 +61,16 @@ self.addEventListener('message', async function (event) {
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: true,
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
});
break;
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId);
break;
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId);
@@ -83,18 +88,98 @@ self.addEventListener('message', async function (event) {
}
});
// Resolve the "main" client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
addEventListener('fetch', function (event) {
const requestInterceptedAt = Date.now();
// Bypass navigation requests.
if (event.request.mode === 'navigate') {
return;
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') {
return;
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been terminated (still remains active until the next reload).
if (activeClientIds.size === 0) {
return;
}
const requestId = crypto.randomUUID();
event.respondWith(handleRequest(event, requestId, requestInterceptedAt));
});
/**
* @param {FetchEvent} event
* @param {string} requestId
* @param {number} requestInterceptedAt
*/
async function handleRequest(event, requestId, requestInterceptedAt) {
const client = await resolveMainClient(event);
const requestCloneForEvents = event.request.clone();
const response = await getResponse(event, client, requestId, requestInterceptedAt);
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
const serializedRequest = await serializeRequest(requestCloneForEvents);
// Clone the response so both the client and the library could consume it.
const responseClone = response.clone();
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
isMockedResponse: IS_MOCKED_RESPONSE in response,
request: {
id: requestId,
...serializedRequest,
},
response: {
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
headers: Object.fromEntries(responseClone.headers.entries()),
body: responseClone.body,
},
},
},
responseClone.body ? [serializedRequest.body, responseClone.body] : []
);
}
return response;
}
/**
* Resolve the main client for the given event.
* Client that issues a request doesn't necessarily equal the client
* that registered the worker. It's with the latter the worker should
* communicate with during the response resolving phase.
* @param {FetchEvent} event
* @returns {Promise<Client | undefined>}
*/
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId);
if (client.frameType === 'top-level') {
if (activeClientIds.has(event.clientId)) {
return client;
}
const allClients = await self.clients.matchAll();
if (client?.frameType === 'top-level') {
return client;
}
const allClients = await self.clients.matchAll({
type: 'window',
});
return allClients
.filter((client) => {
@@ -108,43 +193,44 @@ async function resolveMainClient(event) {
});
}
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event);
const response = await getResponse(event, client, requestId);
/**
* @param {FetchEvent} event
* @param {Client | undefined} client
* @param {string} requestId
* @param {number} requestInterceptedAt
* @returns {Promise<Response>}
*/
async function getResponse(event, client, requestId, requestInterceptedAt) {
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = event.request.clone();
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
(async function () {
const clonedResponse = response.clone();
sendToClient(client, {
type: 'RESPONSE',
payload: {
requestId,
type: clonedResponse.type,
ok: clonedResponse.ok,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
body: clonedResponse.body === null ? null : await clonedResponse.text(),
headers: serializeHeaders(clonedResponse.headers),
redirected: clonedResponse.redirected,
},
});
})();
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers);
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept');
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim());
const filteredValues = values.filter((value) => value !== 'msw/passthrough');
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '));
} else {
headers.delete('accept');
}
}
return fetch(requestClone, { headers });
}
return response;
}
async function getResponse(event, client, requestId) {
const { request } = event;
const requestClone = request.clone();
const getOriginalResponse = () => fetch(requestClone);
// Bypass mocking when the request client is not active.
// Bypass mocking when the client is not active.
if (!client) {
return getOriginalResponse();
return passthrough();
}
// Bypass initial page load requests (i.e. static assets).
@@ -152,145 +238,44 @@ async function getResponse(event, client, requestId) {
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return await getOriginalResponse();
return passthrough();
}
// Bypass requests with the explicit bypass header
if (requestClone.headers.get(bypassHeaderName) === 'true') {
const cleanRequestHeaders = serializeHeaders(requestClone.headers);
// Remove the bypass header to comply with the CORS preflight check.
delete cleanRequestHeaders[bypassHeaderName];
const originalRequest = new Request(requestClone, {
headers: new Headers(cleanRequestHeaders),
});
return fetch(originalRequest);
}
// Send the request to the client-side MSW.
const reqHeaders = serializeHeaders(request.headers);
const body = await request.text();
const clientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: reqHeaders,
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body,
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
// Notify the client that a request has been intercepted.
const serializedRequest = await serializeRequest(event.request);
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
interceptedAt: requestInterceptedAt,
...serializedRequest,
},
},
});
[serializedRequest.body]
);
switch (clientMessage.type) {
case 'MOCK_SUCCESS': {
return delayPromise(() => respondWithMock(clientMessage), clientMessage.payload.delay);
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data);
}
case 'MOCK_NOT_FOUND': {
return getOriginalResponse();
}
case 'NETWORK_ERROR': {
const { name, message } = clientMessage.payload;
const networkError = new Error(message);
networkError.name = name;
// Rejecting a request Promise emulates a network error.
throw networkError;
}
case 'INTERNAL_ERROR': {
const parsedBody = JSON.parse(clientMessage.payload.body);
console.error(
`\
[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
);
return respondWithMock(clientMessage);
case 'PASSTHROUGH': {
return passthrough();
}
}
return getOriginalResponse();
return passthrough();
}
self.addEventListener('fetch', function (event) {
const { request } = event;
const accept = 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.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return;
}
// 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;
}
const requestId = uuidv4();
return event.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.`,
request.method,
request.url,
`${error.name}: ${error.message}`
);
})
);
});
function serializeHeaders(headers) {
const reqHeaders = {};
headers.forEach((value, name) => {
reqHeaders[name] = reqHeaders[name] ? [].concat(reqHeaders[name]).concat(value) : value;
});
return reqHeaders;
}
function sendToClient(client, message) {
/**
* @param {Client} client
* @param {any} message
* @param {Array<Transferable>} transferrables
* @returns {Promise<any>}
*/
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel();
@@ -302,27 +287,50 @@ function sendToClient(client, message) {
resolve(event.data);
};
client.postMessage(JSON.stringify(message), [channel.port2]);
client.postMessage(message, [channel.port2, ...transferrables.filter(Boolean)]);
});
}
function delayPromise(cb, duration) {
return new Promise((resolve) => {
setTimeout(() => resolve(cb()), duration);
/**
* @param {Response} response
* @returns {Response}
*/
function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error();
}
const mockedResponse = new Response(response.body, response);
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
});
return mockedResponse;
}
function respondWithMock(clientMessage) {
return new Response(clientMessage.payload.body, {
...clientMessage.payload,
headers: clientMessage.payload.headers,
});
}
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
const v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
/**
* @param {Request} request
*/
async function serializeRequest(request) {
return {
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: await request.arrayBuffer(),
keepalive: request.keepalive,
};
}
-19
View File
@@ -1,19 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/api/cmd/portainer",
"cwd": "${workspaceRoot}",
"env": {},
"showLog": true,
"args": ["--data", "${env:HOME}/portainer-data", "--assets", "${workspaceRoot}/dist"]
}
]
}
-182
View File
@@ -1,182 +0,0 @@
{
// Place your portainer workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Component": {
"scope": "javascript",
"prefix": "mycomponent",
"description": "Dummy Angularjs Component",
"body": [
"import angular from 'angular';",
"import controller from './${TM_FILENAME_BASE}Controller'",
"",
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').component('$TM_FILENAME_BASE', {",
" templateUrl: './$TM_FILENAME_BASE.html',",
" controller,",
"});",
""
]
},
"Controller": {
"scope": "javascript",
"prefix": "mycontroller",
"body": [
"class ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/} {",
"\t/* @ngInject */",
"\tconstructor($0) {",
"\t}",
"}",
"",
"export default ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/};"
],
"description": "Dummy ES6+ controller"
},
"Service": {
"scope": "javascript",
"prefix": "myservice",
"description": "Dummy ES6+ service",
"body": [
"import angular from 'angular';",
"import PortainerError from 'Portainer/error';",
"",
"class $1 {",
" /* @ngInject */",
" constructor(\\$async, $0) {",
" this.\\$async = \\$async;",
"",
" this.getAsync = this.getAsync.bind(this);",
" this.getAllAsync = this.getAllAsync.bind(this);",
" this.createAsync = this.createAsync.bind(this);",
" this.updateAsync = this.updateAsync.bind(this);",
" this.deleteAsync = this.deleteAsync.bind(this);",
" }",
"",
" /**",
" * GET",
" */",
" async getAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" async getAllAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" get() {",
" if () {",
" return this.\\$async(this.getAsync);",
" }",
" return this.\\$async(this.getAllAsync);",
" }",
"",
" /**",
" * CREATE",
" */",
" async createAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" create() {",
" return this.\\$async(this.createAsync);",
" }",
"",
" /**",
" * UPDATE",
" */",
" async updateAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" update() {",
" return this.\\$async(this.updateAsync);",
" }",
"",
" /**",
" * DELETE",
" */",
" async deleteAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" delete() {",
" return this.\\$async(this.deleteAsync);",
" }",
"}",
"",
"export default $1;",
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').service('$1', $1);"
]
},
"swagger-api-doc": {
"prefix": "swapi",
"scope": "go",
"description": "Snippet for a api doc",
"body": [
"// @id ",
"// @summary ",
"// @description ",
"// @description **Access policy**: ",
"// @tags ",
"// @security ApiKeyAuth",
"// @security jwt",
"// @accept json",
"// @produce json",
"// @param id path int true \"identifier\"",
"// @param body body Object true \"details\"",
"// @success 200 {object} portainer. \"Success\"",
"// @success 204 \"Success\"",
"// @failure 400 \"Invalid request\"",
"// @failure 403 \"Permission denied\"",
"// @failure 404 \" not found\"",
"// @failure 500 \"Server error\"",
"// @router /{id} [get]"
]
},
"analytics": {
"prefix": "nlt",
"body": ["analytics-on", "analytics-category=\"$1\"", "analytics-event=\"$2\""],
"description": "analytics"
},
"analytics-if": {
"prefix": "nltf",
"body": ["analytics-if=\"$1\""],
"description": "analytics"
},
"analytics-metadata": {
"prefix": "nltm",
"body": "analytics-properties=\"{ metadata: { $1 } }\""
}
}
-8
View File
@@ -1,8 +0,0 @@
{
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast", "-E", "exportloopref"],
"gopls": {
"build.expandWorkspaceToModule": false
},
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"]
}
+68
View File
@@ -0,0 +1,68 @@
# Portainer Community Edition
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).
## Frontend Guidelines
- [docs/guidelines/frontend-conventions.md](../../docs/guidelines/frontend-conventions.md) — component structure, React Query patterns, shared components, forms, theming
- [docs/guidelines/typescript-conventions.md](../../docs/guidelines/typescript-conventions.md) — types, anti-patterns, union types, named constants
- [docs/guidelines/frontend-unit-testing.md](../../docs/guidelines/frontend-unit-testing.md) — Vitest, React Testing Library
## Backend Guidelines
- [docs/guidelines/go-conventions.md](../../docs/guidelines/go-conventions.md) — error handling, naming, testing, code style
- [docs/guidelines/server-architecture.md](../../docs/guidelines/server-architecture.md) — Clean Architecture layers, transactions, CE/EE sharing patterns
- [docs/guidelines/logging.md](../../docs/guidelines/logging.md) — zerolog usage, log levels, message style
- [docs/guidelines/backend-code-reusability.md](../../docs/guidelines/backend-code-reusability.md) — how CE and EE share backend code
## Package Manager
- **PNPM** 10+ (for frontend)
- **Go** 1.26.1 (for backend)
## Build Commands
```bash
# Full build
make build # Build both client and server
make build-client # Build React/AngularJS frontend
make build-server # Build Go binary
make build-image # Build Docker image
# Development
make dev # Run both in dev mode
make dev-client # Start webpack-dev-server (port 8999)
make dev-server # Run containerized Go server
# Frontend
pnpm dev # Webpack dev server
pnpm build # Build frontend with webpack
pnpm typecheck # Run typecheck for frontend (with tsc)
pnpm lint # lint frontend (with eslint)
pnpm test # test frontend (with vitest)
pnpm format # format frontend (with prettier)
# Testing
make test # All tests (backend + frontend)
make test-server # Backend tests only
make lint # Lint all code
make format # Format code
```
## Development Servers
- Frontend: http://localhost:8999
- Backend: http://localhost:9000 (HTTP) / https://localhost:9443 (HTTPS)
## Local demo stand
To build an image from one or more feature branches and run it (e.g. to demo open
PRs together), see [docs/dev-stand.md](docs/dev-stand.md). **Read its Gotchas
first** — most importantly, build the image with `make build-image ENV=production`
(without it, `build-image` ships a development client bundle that the CSP blocks,
leaving the UI stuck forever on "Loading Portainer…"), and note that the admin
password must be simple/special-char-free but at least 12 characters long.
+11 -11
View File
@@ -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.
+25 -11
View File
@@ -25,7 +25,7 @@ Each commit message should include a **type**, a **scope** and a **subject**:
<type>(<scope>): <subject>
```
Lines should not exceed 100 characters. This allows the message to be easier to read on github as well as in various git tools and produces a nice, neat commit log ie:
Lines should not exceed 100 characters. This allows the message to be easier to read on GitHub as well as in various git tools and produces a nice, neat commit log ie:
```
#271 feat(containers): add exposed ports in the containers view
@@ -63,7 +63,7 @@ The subject contains succinct description of the change:
## Contribution process
Our contribution process is described below. Some of the steps can be visualized inside Github via specific `status/` labels, such as `status/1-functional-review` or `status/2-technical-review`.
Our contribution process is described below. Some of the steps can be visualized inside GitHub via specific `status/` labels, such as `status/1-functional-review` or `status/2-technical-review`.
### Bug report
@@ -77,32 +77,44 @@ The feature request process is similar to the bug report process but has an extr
## Build and run Portainer locally
Ensure you have Docker, Node.js, yarn, and Golang installed in the correct versions.
Ensure you have Docker, Node.js, pnpm, and Golang installed in the correct versions.
Install dependencies with yarn:
Install dependencies:
```sh
$ yarn
$ make deps
```
Then build and run the project in a Docker container:
```sh
$ yarn start
$ make dev
```
Portainer can now be accessed at <https://localhost:9443>.
Portainer server can now be accessed at <https://localhost:9443>. and UI dev server runs on <http://localhost:8999>.
Find more detailed steps at <https://documentation.portainer.io/contributing/instructions/>.
if you want to build the project you can run:
### Build customisation
```sh
make build-all
```
You can customise the following settings:
For additional make commands, run `make help`.
Find more detailed steps at <https://docs.portainer.io/contribute/build>.
### Build customization
You can customize the following settings:
- `PORTAINER_DATA`: The host dir or volume name used by portainer (default is `/tmp/portainer`, which won't persist over reboots).
- `PORTAINER_PROJECT`: The root dir of the repository - `${portainerRoot}/dist/` is imported into the container to get the build artifacts and external tools (defaults to `your current dir`).
- `PORTAINER_FLAGS`: a list of flags to be used on the portainer commandline, in the form `--admin-password=<pwd hash> --feat fdo=false --feat open-amt` (default: `""`).
## Testing your build
The `--log-level=DEBUG` flag can be passed to the Portainer container in order to provide additional debug output which may be useful when troubleshooting your builds. Please note that this flag was originally intended for internal use and as such the format, functionality and output may change between releases without warning.
## Adding api docs
When adding a new resource (or a route handler), we should add a new tag to api/http/handler/handler.go#L136 like this:
@@ -135,7 +147,9 @@ When adding a new route to an existing handler use the following as a template (
// @router /{id} [get]
```
explanation about each line can be found (here)[https://github.com/swaggo/swag#api-operation]
explanation about each line can be found [here](https://github.com/swaggo/swag#api-operation)
After changing these annotations, regenerate the TypeScript API client and types — see [Generating API types](./README.md#generating-api-types).
## Licensing
+145
View File
@@ -0,0 +1,145 @@
# build target, can be one of "production", "testing", "development"
ENV=development
WEBPACK_CONFIG=webpack/webpack.$(ENV).js
TAG=local
SWAG=go run github.com/swaggo/swag/cmd/swag@v1.16.6
GOTESTSUM_VERSION?=v1.13.0
GOTESTSUM=go run gotest.tools/gotestsum@$(GOTESTSUM_VERSION)
GOLANGCI_LINT_VERSION := $(shell cat $(shell git rev-parse --show-toplevel)/.golangci-version)
# Don't change anything below this line unless you know what you're doing
.DEFAULT_GOAL := help
##@ Building
.PHONY: all init-dist build-storybook build build-client build-server build-image devops
init-dist:
@mkdir -p dist
all: tidy deps build-server build-client ## Build the client, server and download external dependancies (doesn't build an image)
build-all: all ## Alias for the 'all' target (used by CI)
build-client: init-dist ## Build the client
export NODE_ENV=$(ENV) && pnpm run build --config $(WEBPACK_CONFIG)
build-server: init-dist ## Build the server binary
./build/build_binary.sh "$(PLATFORM)" "$(ARCH)"
build-image: build-all ## Build the Portainer image locally
docker buildx build --load -t portainerci/portainer-ce:$(TAG) -f build/linux/Dockerfile .
build-storybook: ## Build and serve the storybook files
pnpm run storybook:build
##@ Build dependencies
.PHONY: deps server-deps client-deps tidy
deps: server-deps client-deps ## Download all client and server build dependancies
## This is empty because the pipeline requires it but ce has no server deps
server-deps: init-dist ## Download dependant server binaries
client-deps: ## Install client dependencies
pnpm install
tidy: ## Tidy up the go.mod file
@go mod tidy
##@ Cleanup
.PHONY: clean
clean: ## Remove all build and download artifacts
@echo "Clearing the dist directory..."
@rm -rf dist/*
##@ Testing
.PHONY: test test-client test-server
test: test-server test-client ## Run all tests
test-client: ## Run client tests
pnpm run test $(ARGS) --coverage
TEST_PACKAGES?=./...
test-server: ## Run server tests
$(GOTESTSUM) --format pkgname-and-test-fails --format-hide-empty-pkg --hide-summary skipped -- -cover -covermode=atomic -coverprofile=coverage.out $(TEST_PACKAGES)
##@ Dev
.PHONY: dev dev-client dev-server
dev: ## Run both the client and server in development mode
make dev-server
make dev-client
dev-client: ## Run the client in development mode
pnpm install && pnpm run dev
dev-server: build-server ## Run the server in development mode
@./dev/run_container.sh
dev-server-podman: build-server ## Run the server in development mode
@./dev/run_container_podman.sh
##@ Format
.PHONY: format format-client format-server
format: format-client format-server ## Format all code
format-client: ## Format client code
pnpm run format
format-server: ## Format server code
go fmt ./...
##@ Lint
.PHONY: lint lint-client lint-server check-lint-version
lint: lint-client lint-server ## Lint all code
lint-client: ## Lint client code
pnpm run lint
check-lint-version:
@installed=v$$(golangci-lint --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1); \
if [ "$$installed" = "v" ]; then \
echo "ERROR: golangci-lint not found, need $(GOLANGCI_LINT_VERSION)"; \
echo "Install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)"; \
exit 1; \
elif [ "$$installed" != "$(GOLANGCI_LINT_VERSION)" ]; then \
echo "ERROR: golangci-lint $$installed installed, need $(GOLANGCI_LINT_VERSION)"; \
echo "Install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)"; \
exit 1; \
fi
lint-server: tidy check-lint-version ## Lint server code
golangci-lint run --timeout=10m -c .golangci.yaml
golangci-lint run --timeout=10m --new-from-rev=HEAD~ -c .golangci-forward.yaml
##@ Extension
.PHONY: dev-extension
dev-extension: build-server build-client ## Run the extension in development mode
make local -f build/docker-extension/Makefile
##@ Docs
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
docs-build: init-dist ## Build docs
go mod download
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 -p pascalcase --markdownFiles ./ --overridesFile .swaggo
docs-validate: docs-build ## Validate docs
pnpm swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml
pnpm swagger-cli validate dist/docs/openapi.yaml
.PHONY: docs-serve
docs-serve: docs-build ## Serve docs locally with Swagger UI on port 8080
docker run -p 8080:8080 \
-e SWAGGER_JSON=/foo/swagger.yaml \
-v $(PWD)/dist/docs:/foo \
swaggerapi/swagger-ui
.PHONY: generate-api
generate-api: docs-validate ## Generate API client and types from OpenAPI spec
pnpm generate-api
##@ Helpers
.PHONY: help
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
+40 -24
View File
@@ -8,65 +8,81 @@ 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)
- [Take5 – get 5 free nodes of Portainer Business for as long as you want them](https://portainer.io/pricing/take5)
- [Portainer BE install guide](https://install.portainer.io)
## Demo
You can try out the public demo instance: http://demo.portainer.io/ (login with the username **admin** and the password **tryportainer**).
Please note that the public demo cluster is **reset every 15min**.
- [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://academy.portainer.io/install/)
## Latest Version
Portainer CE is updated regularly. We aim to do an update release every couple of months.
**The latest version of Portainer is 2.13.x**.
[![latest version](https://img.shields.io/github/v/release/portainer/portainer?color=%2344cc11&label=Latest%20release&style=for-the-badge)](https://github.com/portainer/portainer/releases/latest)
## Getting started
- [Deploy Portainer](https://docs.portainer.io/v/ce-2.9/start/install)
- [Documentation](https://documentation.portainer.io)
- [Contribute to the project](https://documentation.portainer.io/contributing/instructions/)
- [Deploy Portainer](https://docs.portainer.io/start/install-ce)
- [Documentation](https://docs.portainer.io)
- [Contribute to the project](https://docs.portainer.io/contribute/contribute)
## Features & Functions
View [this](https://www.portainer.io/products) table to see all of the Portainer CE functionality and compare to Portainer Business.
- [Portainer CE for Docker / Docker Swarm](https://www.portainer.io/solutions/docker)
- [Portainer CE for Kubernetes](https://www.portainer.io/solutions/kubernetes-ui)
- [Portainer CE for Azure ACI](https://www.portainer.io/solutions/serverless-containers)
View [this](https://www.portainer.io/features) table to see all of the Portainer CE functionality and compare to Portainer Business.
## Getting help
Portainer CE is an open source project and is supported by the community. You can buy a supported version of Portainer at portainer.io
Learn more about Portainer's community support channels [here.](https://www.portainer.io/community_help)
Learn more about Portainer's community support channels [here.](https://www.portainer.io/resources/get-help/get-support)
- Issues: https://github.com/portainer/portainer/issues
- Slack (chat): [https://portainer.io/slack](https://portainer.io/slack)
You can join the Portainer Community by visiting community.portainer.io. This will give you advance notice of events, content and other related Portainer content.
You can join the Portainer Community by visiting [https://www.portainer.io/join-our-community](https://www.portainer.io/join-our-community). This will give you advance notice of events, content and other related Portainer content.
## Reporting bugs and contributing
- 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://documentation.portainer.io/contributing/instructions/) to build it locally and make a pull request.
- 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)
→ dist/docs/openapi.yaml (swagger2openapi + validation)
→ app/react/portainer/generated-api/portainer/ (hey-api/openapi-ts)
```
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/documentation/in-app-analytics-and-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.
## Limitations
+60
View File
@@ -0,0 +1,60 @@
# Security Policy
## Supported Versions
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).
| Version Type | Support Status |
| ------------------------ | ------------------------------------------- |
| LTS (Long-Term Support) | Supported for critical security fixes |
| 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)
+118
View File
@@ -0,0 +1,118 @@
import {
Children,
useState,
useEffect,
useRef,
useContext,
createContext,
ReactNode,
} from 'react';
type MenuCtxType = {
isOpen: boolean;
setOpen: (v: boolean) => void;
menuRef: React.RefObject<HTMLDivElement>;
label: string;
setLabel: (v: string) => void;
};
const MenuCtx = createContext<MenuCtxType | null>(null);
export function Menu({ children }: { children?: ReactNode }) {
const [isOpen, setOpen] = useState(false);
const [label, setLabel] = useState('');
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleDocDown(e: MouseEvent) {
const target = e.target as Node | null;
if (
isOpen &&
menuRef.current &&
target &&
!menuRef.current.contains(target)
) {
setOpen(false);
}
}
document.addEventListener('mousedown', handleDocDown);
return () => document.removeEventListener('mousedown', handleDocDown);
}, [isOpen]);
return (
<MenuCtx.Provider value={{ isOpen, setOpen, menuRef, label, setLabel }}>
<div ref={menuRef}>{children}</div>
</MenuCtx.Provider>
);
}
export function MenuButton({
children,
onClick: externalOnClick,
...props
}: {
children?: ReactNode;
onClick?: () => void;
[key: string]: unknown;
}) {
const ctx = useContext(MenuCtx);
useEffect(() => {
const firstText = Children.toArray(children).find(
(c) => typeof c === 'string'
);
if (firstText) ctx?.setLabel(firstText as string);
});
function handleClick() {
externalOnClick?.();
ctx?.setOpen(!ctx.isOpen);
}
return (
<button type="button" onClick={handleClick} {...props}>
{children}
</button>
);
}
export function MenuList({
children,
className,
}: {
children?: ReactNode;
className?: string;
}) {
const ctx = useContext(MenuCtx);
if (!ctx?.isOpen) return null;
return (
<div role="menu" aria-label={ctx.label || undefined} className={className}>
{children}
</div>
);
}
export function MenuItem({
children,
onSelect,
className,
}: {
children?: ReactNode;
onSelect?: () => void;
className?: string;
}) {
const ctx = useContext(MenuCtx);
function handleClick() {
onSelect?.();
ctx?.setOpen(false);
}
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus
<div role="menuitem" onClick={handleClick} className={className}>
{children}
</div>
);
}
+18
View File
@@ -0,0 +1,18 @@
//go:build ignore
package gorules
import "github.com/quasilyte/go-ruleguard/dsl"
// inMemoryCloneWithWorktree flags git clone calls that use memory.NewStorage() as
// the storer while also writing files to a real worktree. This holds all git objects
// in heap for the duration of the clone, which is unbounded for user-supplied repos.
func inMemoryCloneWithWorktree(m dsl.Matcher) {
m.Match(`git.CloneContext($_, memory.NewStorage(), $wt, $_)`).
Where(m["wt"].Text != "nil").
Report(`git.CloneContext with memory.NewStorage() holds all git objects in heap; use gogitfs.NewStorage with a filesystem storer instead`)
m.Match(`git.Clone(memory.NewStorage(), $wt, $_)`).
Where(m["wt"].Text != "nil").
Report(`git.Clone with memory.NewStorage() holds all git objects in heap; use gogitfs.NewStorage with a filesystem storer instead`)
}
+75
View File
@@ -0,0 +1,75 @@
//go:build ignore
package gorules
import "github.com/quasilyte/go-ruleguard/dsl"
// unwrappedHTTPTransport flags any bare http.Transport composite literal.
// All transports must be created via ssrf.NewTransport or ssrf.NewInternalTransport,
// which clone http.DefaultTransport and handle SSRF protection internally.
func unwrappedHTTPTransport(m dsl.Matcher) {
m.Match(`$f(&http.Transport{$*_})`).
Report(`$f receives a bare *http.Transport; use ssrf.NewTransport(tlsConfig) or ssrf.NewInternalTransport(tlsConfig) instead`)
m.Match(`$_ := &http.Transport{$*_}`).
Report(`bare *http.Transport variable; use ssrf.NewTransport(tlsConfig) or ssrf.NewInternalTransport(tlsConfig) instead`)
m.Match(`$_.Transport = &http.Transport{$*_}`).
Report(`bare *http.Transport field assignment; use ssrf.NewTransport(tlsConfig) or ssrf.NewInternalTransport(tlsConfig) instead`)
}
// helmGetterTransport flags getter.WithTransport calls that receive a bare *http.Transport.
// Helm v4 installs its own transport and bypasses http.DefaultTransport, so the transport
// passed here must be created via ssrf.NewTransport.
func helmGetterTransport(m dsl.Matcher) {
m.Match(`getter.WithTransport(&http.Transport{$*_})`).
Report(`getter.WithTransport called with a bare *http.Transport; use ssrf.NewTransport(tlsConfig) as Helm v4 bypasses http.DefaultTransport`)
}
// cloneDefaultTransport flags direct clones of *http.Transport outside main.go.
// The one legitimate clone is in main.go where http.DefaultTransport is globally
// wrapped with SSRF protection at server startup.
func cloneDefaultTransport(m dsl.Matcher) {
m.Match(`$_.(*http.Transport).Clone()`).
Where(!m.File().Name.Matches(`^main\.go$`)).
Report(`cloning *http.Transport directly is forbidden; use ssrf.NewTransport(tlsConfig) or ssrf.NewInternalTransport(tlsConfig) instead`)
}
// internalTransportMisuse flags calls to NewInternalTransport outside the proxy
// factory files where Chisel-tunnel and in-cluster K8s destinations are valid exemptions.
func internalTransportMisuse(m dsl.Matcher) {
m.Match(`ssrf.NewInternalTransport($*_)`).
Where(
!(m.File().PkgPath.Matches(`proxy/factory`) &&
m.File().Name.Matches(`^(docker|agent|local_transport|edge_transport|docker_unix|docker_windows)\.go$`))).
Report(`NewInternalTransport bypasses SSRF validation; only valid in the proxy factory files for local sockets and internally-routed endpoints`)
}
// dialerOverride flags direct assignments to any of the dialer fields on a transport.
// The only valid assignments are in docker_unix.go and docker_windows.go where a
// custom dialer is required for unix sockets and named pipes.
func dialerOverride(m dsl.Matcher) {
m.Match(`$_.DialContext = $*_`).
Where(
!(m.File().PkgPath.Matches(`proxy/factory`) &&
m.File().Name.Matches(`^(docker_unix|docker_windows)\.go$`))).
Report(`direct DialContext assignment replaces the transport dialer; use ssrf.NewTransport or ssrf.NewInternalTransport instead`)
m.Match(`$_.Dial = $*_`).
Where(
!(m.File().PkgPath.Matches(`proxy/factory`) &&
m.File().Name.Matches(`^(docker_unix|docker_windows)\.go$`))).
Report(`direct Dial assignment replaces the transport dialer; use ssrf.NewTransport or ssrf.NewInternalTransport instead`)
m.Match(`$_.DialTLSContext = $*_`).
Where(
!(m.File().PkgPath.Matches(`proxy/factory`) &&
m.File().Name.Matches(`^(docker_unix|docker_windows)\.go$`))).
Report(`direct DialTLSContext assignment replaces the transport dialer; use ssrf.NewTransport or ssrf.NewInternalTransport instead`)
m.Match(`$_.DialTLS = $*_`).
Where(
!(m.File().PkgPath.Matches(`proxy/factory`) &&
m.File().Name.Matches(`^(docker_unix|docker_windows)\.go$`))).
Report(`direct DialTLS assignment replaces the transport dialer; use ssrf.NewTransport or ssrf.NewInternalTransport instead`)
}
+5
View File
@@ -0,0 +1,5 @@
//go:build tools
package gorules
import _ "github.com/quasilyte/go-ruleguard/dsl"
+1
View File
@@ -0,0 +1 @@
replace k8s.io/apimachinery/pkg/apis/meta/v1.Duration string
+27 -26
View File
@@ -2,42 +2,39 @@ package adminmonitor
import (
"context"
"log"
"net/http"
"strings"
"sync"
"time"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
httperror "github.com/portainer/portainer/pkg/libhttp/error"
var logFatalf = log.Fatalf
"github.com/rs/zerolog/log"
)
const RedirectReasonAdminInitTimeout string = "AdminInitTimeout"
type Monitor struct {
timeout time.Duration
datastore dataservices.DataStore
shutdownCtx context.Context
cancellationFunc context.CancelFunc
mu sync.Mutex
mu sync.RWMutex
adminInitDisabled bool
}
// New creates a monitor that when started will wait for the timeout duration and then sends the timeout signal to disable the application
func New(timeout time.Duration, datastore dataservices.DataStore, shutdownCtx context.Context) *Monitor {
// New creates a monitor that when started will wait for the timeout duration and then shutdown the application unless it has been initialized.
func New(timeout time.Duration, datastore dataservices.DataStore) *Monitor {
return &Monitor{
timeout: timeout,
datastore: datastore,
shutdownCtx: shutdownCtx,
adminInitDisabled: false,
}
}
// Starts starts the monitor. Active monitor could be stopped or shuttted down by cancelling the shutdown context.
func (m *Monitor) Start() {
// Start starts the monitor. The monitor will stop when ctx is cancelled, or when Stop is called.
func (m *Monitor) Start(ctx context.Context) {
m.mu.Lock()
defer m.mu.Unlock()
@@ -45,28 +42,31 @@ func (m *Monitor) Start() {
return
}
cancellationCtx, cancellationFunc := context.WithCancel(context.Background())
cancellationCtx, cancellationFunc := context.WithCancel(ctx)
m.cancellationFunc = cancellationFunc
go func() {
log.Println("[DEBUG] [internal,init] [message: start initialization monitor ]")
log.Debug().Msg("start initialization monitor")
select {
case <-time.After(m.timeout):
initialized, err := m.WasInitialized()
if err != nil {
logFatalf("%s", err)
log.Error().Err(err).Msg("AdminMonitor failed to determine if Portainer is Initialized")
return
}
if !initialized {
log.Println("[INFO] [internal,init] The Portainer instance timed out for security purposes. To re-enable your Portainer instance, you will need to restart Portainer")
log.Info().Msg("the Portainer instance timed out for security purposes, to re-enable your Portainer instance, you will need to restart Portainer")
m.mu.Lock()
defer m.mu.Unlock()
m.adminInitDisabled = true
return
}
case <-cancellationCtx.Done():
log.Println("[DEBUG] [internal,init] [message: canceling initialization monitor]")
case <-m.shutdownCtx.Done():
log.Println("[DEBUG] [internal,init] [message: shutting down initialization monitor]")
log.Debug().Msg("canceling initialization monitor")
}
}()
}
@@ -79,6 +79,7 @@ func (m *Monitor) Stop() {
if m.cancellationFunc == nil {
return
}
m.cancellationFunc()
m.cancellationFunc = nil
}
@@ -89,12 +90,14 @@ func (m *Monitor) WasInitialized() (bool, error) {
if err != nil {
return false, err
}
return len(users) > 0, nil
}
func (m *Monitor) WasInstanceDisabled() bool {
m.mu.Lock()
defer m.mu.Unlock()
m.mu.RLock()
defer m.mu.RUnlock()
return m.adminInitDisabled
}
@@ -102,12 +105,10 @@ func (m *Monitor) WasInstanceDisabled() bool {
// Otherwise, it will pass through the request to next
func (m *Monitor) WithRedirect(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if m.WasInstanceDisabled() {
if strings.HasPrefix(r.RequestURI, "/api") && r.RequestURI != "/api/status" && r.RequestURI != "/api/settings/public" {
w.Header().Set("redirect-reason", RedirectReasonAdminInitTimeout)
httperror.WriteError(w, http.StatusSeeOther, "Administrator initialization timeout", nil)
return
}
if m.WasInstanceDisabled() && strings.HasPrefix(r.RequestURI, "/api") && r.RequestURI != "/api/status" && r.RequestURI != "/api/settings/public" {
w.Header().Set("redirect-reason", RedirectReasonAdminInitTimeout)
httperror.WriteError(w, http.StatusSeeOther, "Administrator initialization timeout", nil)
return
}
next.ServeHTTP(w, r)
+19 -10
View File
@@ -1,8 +1,8 @@
package adminmonitor
import (
"context"
"testing"
"testing/synctest"
"time"
portainer "github.com/portainer/portainer/api"
@@ -11,21 +11,28 @@ import (
)
func Test_stopWithoutStarting(t *testing.T) {
monitor := New(1*time.Minute, nil, nil)
t.Parallel()
monitor := New(1*time.Minute, nil)
monitor.Stop()
}
func Test_stopCouldBeCalledMultipleTimes(t *testing.T) {
monitor := New(1*time.Minute, nil, nil)
t.Parallel()
monitor := New(1*time.Minute, nil)
monitor.Stop()
monitor.Stop()
}
func Test_startOrStopCouldBeCalledMultipleTimesConcurrently(t *testing.T) {
monitor := New(1*time.Minute, nil, context.Background())
t.Parallel()
synctest.Test(t, test_startOrStopCouldBeCalledMultipleTimesConcurrently)
}
go monitor.Start()
monitor.Start()
func test_startOrStopCouldBeCalledMultipleTimesConcurrently(t *testing.T) {
monitor := New(1*time.Minute, nil)
go monitor.Start(t.Context())
monitor.Start(t.Context())
go monitor.Stop()
monitor.Stop()
@@ -34,8 +41,9 @@ func Test_startOrStopCouldBeCalledMultipleTimesConcurrently(t *testing.T) {
}
func Test_canStopStartedMonitor(t *testing.T) {
monitor := New(1*time.Minute, nil, context.Background())
monitor.Start()
t.Parallel()
monitor := New(1*time.Minute, nil)
monitor.Start(t.Context())
assert.NotNil(t, monitor.cancellationFunc, "cancellation function is missing in started monitor")
monitor.Stop()
@@ -43,11 +51,12 @@ func Test_canStopStartedMonitor(t *testing.T) {
}
func Test_start_shouldDisableInstanceAfterTimeout_ifNotInitialized(t *testing.T) {
t.Parallel()
timeout := 10 * time.Millisecond
datastore := i.NewDatastore(i.WithUsers([]portainer.User{}))
monitor := New(timeout, datastore, context.Background())
monitor.Start()
monitor := New(timeout, datastore)
monitor.Start(t.Context())
<-time.After(20 * timeout)
assert.True(t, monitor.WasInstanceDisabled(), "monitor should have been timeout and instance is disabled")
+17 -8
View File
@@ -1,29 +1,34 @@
package agent
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/url"
"github.com/portainer/portainer/api/url"
"github.com/portainer/portainer/pkg/libhttp/ssrf"
"github.com/rs/zerolog/log"
)
// GetAgentVersionAndPlatform returns the agent version and platform
//
// it sends a ping to the agent and parses the version and platform from the headers
func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (portainer.AgentPlatform, string, error) {
httpCli := &http.Client{
Timeout: 3 * time.Second,
func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (portainer.AgentPlatform, string, error) { //nolint:forbidigo
if err := ssrf.CheckURL(context.Background(), endpointUrl); err != nil {
return 0, "", err
}
httpCli := &http.Client{Timeout: 3 * time.Second}
if tlsConfig != nil {
httpCli.Transport = &http.Transport{
TLSClientConfig: tlsConfig,
}
httpCli.Transport = ssrf.NewTransport(tlsConfig)
}
parsedURL, err := url.ParseURL(endpointUrl + "/ping")
@@ -42,7 +47,11 @@ func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (port
if err != nil {
return 0, "", err
}
defer resp.Body.Close()
_, _ = io.Copy(io.Discard, resp.Body)
if err := resp.Body.Close(); err != nil {
log.Warn().Err(err).Msg("failed to close response body")
}
if resp.StatusCode != http.StatusNoContent {
return 0, "", fmt.Errorf("Failed request with status %d", resp.StatusCode)
+119
View File
@@ -0,0 +1,119 @@
package agent
import (
"net/http"
"net/http/httptest"
"strconv"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/require"
)
func tlsServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
t.Helper()
srv := httptest.NewTLSServer(handler)
t.Cleanup(srv.Close)
return srv
}
func TestGetAgentVersionAndPlatform_Success(t *testing.T) {
t.Parallel()
srv := tlsServer(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(portainer.PortainerAgentHeader, "2.19.0")
w.Header().Set(portainer.HTTPResponseAgentPlatform, "1")
w.WriteHeader(http.StatusNoContent)
})
tlsCfg := srv.Client().Transport.(*http.Transport).TLSClientConfig
platform, version, err := GetAgentVersionAndPlatform(srv.URL, tlsCfg)
require.NoError(t, err)
require.Equal(t, portainer.AgentPlatformDocker, platform)
require.Equal(t, "2.19.0", version)
}
func TestGetAgentVersionAndPlatform_NonOKStatus(t *testing.T) {
t.Parallel()
srv := tlsServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
})
tlsCfg := srv.Client().Transport.(*http.Transport).TLSClientConfig
_, _, err := GetAgentVersionAndPlatform(srv.URL, tlsCfg)
require.Error(t, err)
}
func TestGetAgentVersionAndPlatform_MissingVersionHeader(t *testing.T) {
t.Parallel()
srv := tlsServer(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(portainer.HTTPResponseAgentPlatform, "1")
w.WriteHeader(http.StatusNoContent)
})
tlsCfg := srv.Client().Transport.(*http.Transport).TLSClientConfig
_, _, err := GetAgentVersionAndPlatform(srv.URL, tlsCfg)
require.Error(t, err)
}
func TestGetAgentVersionAndPlatform_MissingPlatformHeader(t *testing.T) {
t.Parallel()
srv := tlsServer(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(portainer.PortainerAgentHeader, "2.19.0")
w.WriteHeader(http.StatusNoContent)
})
tlsCfg := srv.Client().Transport.(*http.Transport).TLSClientConfig
_, _, err := GetAgentVersionAndPlatform(srv.URL, tlsCfg)
require.Error(t, err)
}
func TestGetAgentVersionAndPlatform_InvalidPlatformZero(t *testing.T) {
t.Parallel()
srv := tlsServer(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(portainer.PortainerAgentHeader, "2.19.0")
w.Header().Set(portainer.HTTPResponseAgentPlatform, "0")
w.WriteHeader(http.StatusNoContent)
})
tlsCfg := srv.Client().Transport.(*http.Transport).TLSClientConfig
_, _, err := GetAgentVersionAndPlatform(srv.URL, tlsCfg)
require.Error(t, err)
}
func TestGetAgentVersionAndPlatform_NonNumericPlatform(t *testing.T) {
t.Parallel()
srv := tlsServer(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(portainer.PortainerAgentHeader, "2.19.0")
w.Header().Set(portainer.HTTPResponseAgentPlatform, "docker")
w.WriteHeader(http.StatusNoContent)
})
tlsCfg := srv.Client().Transport.(*http.Transport).TLSClientConfig
_, _, err := GetAgentVersionAndPlatform(srv.URL, tlsCfg)
require.Error(t, err)
}
func TestGetAgentVersionAndPlatform_PingPathAppended(t *testing.T) {
t.Parallel()
var gotPath string
srv := tlsServer(t, func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
w.Header().Set(portainer.PortainerAgentHeader, "2.19.0")
w.Header().Set(portainer.HTTPResponseAgentPlatform, strconv.Itoa(int(portainer.AgentPlatformKubernetes)))
w.WriteHeader(http.StatusNoContent)
})
tlsCfg := srv.Client().Transport.(*http.Transport).TLSClientConfig
_, _, err := GetAgentVersionAndPlatform(srv.URL, tlsCfg)
require.NoError(t, err)
require.Equal(t, "/ping", gotPath)
}
-64
View File
@@ -1,64 +0,0 @@
Portainer API is an HTTP API served by Portainer. It is used by the Portainer UI and everything you can do with the UI can be done using the HTTP API.
Examples are available at https://documentation.portainer.io/api/api-examples/
You can find out more about Portainer at [http://portainer.io](http://portainer.io) and get some support on [Slack](http://portainer.io/slack/).
# Authentication
Most of the API environments(endpoints) require to be authenticated as well as some level of authorization to be used.
Portainer API uses JSON Web Token to manage authentication and thus requires you to provide a token in the **Authorization** header of each request
with the **Bearer** authentication mechanism.
Example:
```
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE
```
# Security
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/).
+61
View File
@@ -0,0 +1,61 @@
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.
Example:
```
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE
```
# Security
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.
Example encoded value:
```
eyJyZWdpc3RyeUlkIjoxfQ==
```
+2 -15
View File
@@ -1,30 +1,17 @@
package apikey
import (
"crypto/rand"
"io"
portainer "github.com/portainer/portainer/api"
)
// APIKeyService represents a service for managing API keys.
type APIKeyService interface {
HashRaw(rawKey string) []byte
HashRaw(rawKey string) string
GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error)
GetAPIKey(apiKeyID portainer.APIKeyID) (*portainer.APIKey, error)
GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey, error)
GetDigestUserAndKey(digest []byte) (portainer.User, portainer.APIKey, error)
GetDigestUserAndKey(digest string) (portainer.User, portainer.APIKey, error)
UpdateAPIKey(apiKey *portainer.APIKey) error
DeleteAPIKey(apiKeyID portainer.APIKeyID) error
InvalidateUserKeyCache(userId portainer.UserID) bool
}
// generateRandomKey generates a random key of specified length
// source: https://github.com/gorilla/securecookie/blob/master/securecookie.go#L515
func generateRandomKey(length int) []byte {
k := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, k); err != nil {
return nil
}
return k
}
+17 -14
View File
@@ -7,43 +7,46 @@ import (
)
func Test_generateRandomKey(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
wantLenth int
name string
wantLength int
}{
{
name: "Generate a random key of length 16",
wantLenth: 16,
name: "Generate a random key of length 16",
wantLength: 16,
},
{
name: "Generate a random key of length 32",
wantLenth: 32,
name: "Generate a random key of length 32",
wantLength: 32,
},
{
name: "Generate a random key of length 64",
wantLenth: 64,
name: "Generate a random key of length 64",
wantLength: 64,
},
{
name: "Generate a random key of length 128",
wantLenth: 128,
name: "Generate a random key of length 128",
wantLength: 128,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := generateRandomKey(tt.wantLenth)
is.Equal(tt.wantLenth, len(got))
got := GenerateRandomKey(tt.wantLength)
is.Len(got, tt.wantLength)
})
}
t.Run("Generated keys are unique", func(t *testing.T) {
keys := make(map[string]bool)
for i := 0; i < 100; i++ {
key := generateRandomKey(8)
for range 100 {
key := GenerateRandomKey(8)
_, ok := keys[string(key)]
is.False(ok)
keys[string(key)] = true
}
})
+31 -21
View File
@@ -1,69 +1,79 @@
package apikey
import (
lru "github.com/hashicorp/golang-lru"
portainer "github.com/portainer/portainer/api"
lru "github.com/hashicorp/golang-lru"
)
const defaultAPIKeyCacheSize = 1024
const DefaultAPIKeyCacheSize = 1024
// entry is a tuple containing the user and API key associated to an API key digest
type entry struct {
user portainer.User
type entry[T any] struct {
user T
apiKey portainer.APIKey
}
// apiKeyCache is a concurrency-safe, in-memory cache which primarily exists for to reduce database roundtrips.
type UserCompareFn[T any] func(T, portainer.UserID) bool
// ApiKeyCache is a concurrency-safe, in-memory cache which primarily exists for to reduce database roundtrips.
// We store the api-key digest (keys) and the associated user and key-data (values) in the cache.
// This is required because HTTP requests will contain only the api-key digest in the x-api-key request header;
// digest value must be mapped to a portainer user (and respective key data) for validation.
// This cache is used to avoid multiple database queries to retrieve these user/key associated to the digest.
type apiKeyCache struct {
type ApiKeyCache[T any] struct {
// cache type [string]entry cache (key: string(digest), value: user/key entry)
// note: []byte keys are not supported by golang-lru Cache
cache *lru.Cache
cache *lru.Cache
userCmpFn UserCompareFn[T]
}
// NewAPIKeyCache creates a new cache for API keys
func NewAPIKeyCache(cacheSize int) *apiKeyCache {
func NewAPIKeyCache[T any](cacheSize int, userCompareFn UserCompareFn[T]) *ApiKeyCache[T] {
cache, _ := lru.New(cacheSize)
return &apiKeyCache{cache: cache}
return &ApiKeyCache[T]{cache: cache, userCmpFn: userCompareFn}
}
// Get returns the user/key associated to an api-key's digest
// This is required because HTTP requests will contain the digest of the API key in header,
// the digest value must be mapped to a portainer user.
func (c *apiKeyCache) Get(digest []byte) (portainer.User, portainer.APIKey, bool) {
val, ok := c.cache.Get(string(digest))
func (c *ApiKeyCache[T]) Get(digest string) (T, portainer.APIKey, bool) {
val, ok := c.cache.Get(digest)
if !ok {
return portainer.User{}, portainer.APIKey{}, false
var t T
return t, portainer.APIKey{}, false
}
tuple := val.(entry)
tuple := val.(entry[T])
return tuple.user, tuple.apiKey, true
}
// Set persists a user/key entry to the cache
func (c *apiKeyCache) Set(digest []byte, user portainer.User, apiKey portainer.APIKey) {
c.cache.Add(string(digest), entry{
func (c *ApiKeyCache[T]) Set(digest string, user T, apiKey portainer.APIKey) {
c.cache.Add(digest, entry[T]{
user: user,
apiKey: apiKey,
})
}
// Delete evicts a digest's user/key entry key from the cache
func (c *apiKeyCache) Delete(digest []byte) {
c.cache.Remove(string(digest))
func (c *ApiKeyCache[T]) Delete(digest string) {
c.cache.Remove(digest)
}
// InvalidateUserKeyCache loops through all the api-keys associated to a user and removes them from the cache
func (c *apiKeyCache) InvalidateUserKeyCache(userId portainer.UserID) bool {
func (c *ApiKeyCache[T]) InvalidateUserKeyCache(userId portainer.UserID) bool {
present := false
for _, k := range c.cache.Keys() {
user, _, _ := c.Get([]byte(k.(string)))
if user.ID == userId {
present = c.cache.Remove(k)
user, _, _ := c.Get(k.(string))
if c.userCmpFn(user, userId) {
present = c.cache.Remove(k) || present
}
}
return present
}
+31 -26
View File
@@ -8,34 +8,35 @@ import (
)
func Test_apiKeyCacheGet(t *testing.T) {
t.Parallel()
is := assert.New(t)
keyCache := NewAPIKeyCache(10)
keyCache := NewAPIKeyCache(10, compareUser)
// pre-populate cache
keyCache.cache.Add(string("foo"), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string(""), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string(""), entry[portainer.User]{user: portainer.User{}, apiKey: portainer.APIKey{}})
tests := []struct {
digest []byte
digest string
found bool
}{
{
digest: []byte("foo"),
digest: "foo",
found: true,
},
{
digest: []byte(""),
digest: "",
found: true,
},
{
digest: []byte("bar"),
digest: "bar",
found: false,
},
}
for _, test := range tests {
t.Run(string(test.digest), func(t *testing.T) {
t.Run(test.digest, func(t *testing.T) {
_, _, found := keyCache.Get(test.digest)
is.Equal(test.found, found)
})
@@ -43,50 +44,53 @@ func Test_apiKeyCacheGet(t *testing.T) {
}
func Test_apiKeyCacheSet(t *testing.T) {
t.Parallel()
is := assert.New(t)
keyCache := NewAPIKeyCache(10)
keyCache := NewAPIKeyCache(10, compareUser)
// pre-populate cache
keyCache.Set([]byte("bar"), portainer.User{ID: 2}, portainer.APIKey{})
keyCache.Set([]byte("foo"), portainer.User{ID: 1}, portainer.APIKey{})
keyCache.Set("bar", portainer.User{ID: 2}, portainer.APIKey{})
keyCache.Set("foo", portainer.User{ID: 1}, portainer.APIKey{})
// overwrite existing entry
keyCache.Set([]byte("foo"), portainer.User{ID: 3}, portainer.APIKey{})
keyCache.Set("foo", portainer.User{ID: 3}, portainer.APIKey{})
val, ok := keyCache.cache.Get(string("bar"))
is.True(ok)
tuple := val.(entry)
tuple := val.(entry[portainer.User])
is.Equal(portainer.User{ID: 2}, tuple.user)
val, ok = keyCache.cache.Get(string("foo"))
is.True(ok)
tuple = val.(entry)
tuple = val.(entry[portainer.User])
is.Equal(portainer.User{ID: 3}, tuple.user)
}
func Test_apiKeyCacheDelete(t *testing.T) {
t.Parallel()
is := assert.New(t)
keyCache := NewAPIKeyCache(10)
keyCache := NewAPIKeyCache(10, compareUser)
t.Run("Delete an existing entry", func(t *testing.T) {
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.Delete([]byte("foo"))
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.Delete("foo")
_, ok := keyCache.cache.Get(string("foo"))
is.False(ok)
})
t.Run("Delete a non-existing entry", func(t *testing.T) {
nonPanicFunc := func() { keyCache.Delete([]byte("non-existent-key")) }
nonPanicFunc := func() { keyCache.Delete("non-existent-key") }
is.NotPanics(nonPanicFunc)
})
}
func Test_apiKeyCacheLRU(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
@@ -128,19 +132,19 @@ func Test_apiKeyCacheLRU(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
keyCache := NewAPIKeyCache(test.cacheLen)
keyCache := NewAPIKeyCache(test.cacheLen, compareUser)
for _, key := range test.key {
keyCache.Set([]byte(key), portainer.User{ID: 1}, portainer.APIKey{})
keyCache.Set(key, portainer.User{ID: 1}, portainer.APIKey{})
}
for _, key := range test.foundKeys {
_, _, found := keyCache.Get([]byte(key))
_, _, found := keyCache.Get(key)
is.True(found, "Key %s not found", key)
}
for _, key := range test.evictedKeys {
_, _, found := keyCache.Get([]byte(key))
_, _, found := keyCache.Get(key)
is.False(found, "key %s should have been evicted", key)
}
})
@@ -148,12 +152,13 @@ func Test_apiKeyCacheLRU(t *testing.T) {
}
func Test_apiKeyCacheInvalidateUserKeyCache(t *testing.T) {
t.Parallel()
is := assert.New(t)
keyCache := NewAPIKeyCache(10)
keyCache := NewAPIKeyCache(10, compareUser)
t.Run("Removes users keys from cache", func(t *testing.T) {
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
ok := keyCache.InvalidateUserKeyCache(1)
is.True(ok)
@@ -163,8 +168,8 @@ func Test_apiKeyCacheInvalidateUserKeyCache(t *testing.T) {
})
t.Run("Does not affect other keys", func(t *testing.T) {
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("bar"), entry{user: portainer.User{ID: 2}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("bar"), entry[portainer.User]{user: portainer.User{ID: 2}, apiKey: portainer.APIKey{}})
ok := keyCache.InvalidateUserKeyCache(1)
is.True(ok)
+35 -19
View File
@@ -1,15 +1,17 @@
package apikey
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"time"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/pkg/errors"
)
const portainerAPIKeyPrefix = "ptr_"
@@ -19,30 +21,45 @@ var ErrInvalidAPIKey = errors.New("Invalid API key")
type apiKeyService struct {
apiKeyRepository dataservices.APIKeyRepository
userRepository dataservices.UserService
cache *apiKeyCache
cache *ApiKeyCache[portainer.User]
}
// GenerateRandomKey generates a random key of specified length
// source: https://github.com/gorilla/securecookie/blob/master/securecookie.go#L515
func GenerateRandomKey(length int) []byte {
k := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, k); err != nil {
return nil
}
return k
}
func compareUser(u portainer.User, id portainer.UserID) bool {
return u.ID == id
}
func NewAPIKeyService(apiKeyRepository dataservices.APIKeyRepository, userRepository dataservices.UserService) *apiKeyService {
return &apiKeyService{
apiKeyRepository: apiKeyRepository,
userRepository: userRepository,
cache: NewAPIKeyCache(defaultAPIKeyCacheSize),
cache: NewAPIKeyCache(DefaultAPIKeyCacheSize, compareUser),
}
}
// HashRaw computes a hash digest of provided raw API key.
func (a *apiKeyService) HashRaw(rawKey string) []byte {
func (a *apiKeyService) HashRaw(rawKey string) string {
hashDigest := sha256.Sum256([]byte(rawKey))
return hashDigest[:]
return base64.StdEncoding.EncodeToString(hashDigest[:])
}
// GenerateApiKey generates a raw API key for a user (for one-time display).
// The generated API key is stored in the cache and database.
func (a *apiKeyService) GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error) {
randKey := generateRandomKey(32)
randKey := GenerateRandomKey(32)
encodedRawAPIKey := base64.StdEncoding.EncodeToString(randKey)
prefixedAPIKey := portainerAPIKeyPrefix + encodedRawAPIKey
hashDigest := a.HashRaw(prefixedAPIKey)
apiKey := &portainer.APIKey{
@@ -53,8 +70,7 @@ func (a *apiKeyService) GenerateApiKey(user portainer.User, description string)
Digest: hashDigest,
}
err := a.apiKeyRepository.CreateAPIKey(apiKey)
if err != nil {
if err := a.apiKeyRepository.Create(apiKey); err != nil {
return "", nil, errors.Wrap(err, "Unable to create API key")
}
@@ -66,7 +82,7 @@ func (a *apiKeyService) GenerateApiKey(user portainer.User, description string)
// GetAPIKey returns an API key by its ID.
func (a *apiKeyService) GetAPIKey(apiKeyID portainer.APIKeyID) (*portainer.APIKey, error) {
return a.apiKeyRepository.GetAPIKey(apiKeyID)
return a.apiKeyRepository.Read(apiKeyID)
}
// GetAPIKeys returns all the API keys associated to a user.
@@ -76,8 +92,7 @@ func (a *apiKeyService) GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey,
// GetDigestUserAndKey returns the user and api-key associated to a specified hash digest.
// A cache lookup is performed first; if the user/api-key is not found in the cache, respective database lookups are performed.
func (a *apiKeyService) GetDigestUserAndKey(digest []byte) (portainer.User, portainer.APIKey, error) {
// get api key from cache if possible
func (a *apiKeyService) GetDigestUserAndKey(digest string) (portainer.User, portainer.APIKey, error) {
cachedUser, cachedKey, ok := a.cache.Get(digest)
if ok {
return cachedUser, cachedKey, nil
@@ -88,7 +103,7 @@ func (a *apiKeyService) GetDigestUserAndKey(digest []byte) (portainer.User, port
return portainer.User{}, portainer.APIKey{}, errors.Wrap(err, "Unable to retrieve API key")
}
user, err := a.userRepository.User(apiKey.UserID)
user, err := a.userRepository.Read(apiKey.UserID)
if err != nil {
return portainer.User{}, portainer.APIKey{}, errors.Wrap(err, "Unable to retrieve digest user")
}
@@ -105,21 +120,22 @@ func (a *apiKeyService) UpdateAPIKey(apiKey *portainer.APIKey) error {
if err != nil {
return errors.Wrap(err, "Unable to retrieve API key")
}
a.cache.Set(apiKey.Digest, user, *apiKey)
return a.apiKeyRepository.UpdateAPIKey(apiKey)
return a.apiKeyRepository.Update(apiKey.ID, apiKey)
}
// DeleteAPIKey deletes an API key and removes the digest/api-key entry from the cache.
func (a *apiKeyService) DeleteAPIKey(apiKeyID portainer.APIKeyID) error {
// get api-key digest to remove from cache
apiKey, err := a.apiKeyRepository.GetAPIKey(apiKeyID)
apiKey, err := a.apiKeyRepository.Read(apiKeyID)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Unable to retrieve API key: %d", apiKeyID))
}
// delete the user/api-key from cache
a.cache.Delete(apiKey.Digest)
return a.apiKeyRepository.DeleteAPIKey(apiKeyID)
return a.apiKeyRepository.Delete(apiKeyID)
}
func (a *apiKeyService) InvalidateUserKeyCache(userId portainer.UserID) bool {
+55 -49
View File
@@ -2,33 +2,38 @@ package apikey
import (
"crypto/sha256"
"log"
"encoding/base64"
"fmt"
"strings"
"testing"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Implements((*APIKeyService)(nil), NewAPIKeyService(nil, nil))
}
func Test_GenerateApiKey(t *testing.T) {
t.Parallel()
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
t.Run("Successfully generates API key", func(t *testing.T) {
desc := "test-1"
rawKey, apiKey, err := service.GenerateApiKey(portainer.User{ID: 1}, desc)
is.NoError(err)
require.NoError(t, err)
is.NotEmpty(rawKey)
is.NotEmpty(apiKey)
is.Equal(desc, apiKey.Description)
@@ -36,7 +41,7 @@ func Test_GenerateApiKey(t *testing.T) {
t.Run("Api key prefix is 7 chars", func(t *testing.T) {
rawKey, apiKey, err := service.GenerateApiKey(portainer.User{ID: 1}, "test-2")
is.NoError(err)
require.NoError(t, err)
is.Equal(rawKey[:7], apiKey.Prefix)
is.Len(apiKey.Prefix, 7)
@@ -44,7 +49,7 @@ func Test_GenerateApiKey(t *testing.T) {
t.Run("Api key has 'ptr_' as prefix", func(t *testing.T) {
rawKey, _, err := service.GenerateApiKey(portainer.User{ID: 1}, "test-x")
is.NoError(err)
require.NoError(t, err)
is.Equal(portainerAPIKeyPrefix, "ptr_")
is.True(strings.HasPrefix(rawKey, "ptr_"))
@@ -53,7 +58,7 @@ func Test_GenerateApiKey(t *testing.T) {
t.Run("Successfully caches API key", func(t *testing.T) {
user := portainer.User{ID: 1}
_, apiKey, err := service.GenerateApiKey(user, "test-3")
is.NoError(err)
require.NoError(t, err)
userFromCache, apiKeyFromCache, ok := service.cache.Get(apiKey.Digest)
is.True(ok)
@@ -63,70 +68,70 @@ func Test_GenerateApiKey(t *testing.T) {
t.Run("Decoded raw api-key digest matches generated digest", func(t *testing.T) {
rawKey, apiKey, err := service.GenerateApiKey(portainer.User{ID: 1}, "test-4")
is.NoError(err)
require.NoError(t, err)
generatedDigest := sha256.Sum256([]byte(rawKey))
is.Equal(apiKey.Digest, generatedDigest[:])
is.Equal(apiKey.Digest, base64.StdEncoding.EncodeToString(generatedDigest[:]))
})
}
func Test_GetAPIKey(t *testing.T) {
t.Parallel()
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
t.Run("Successfully returns all API keys", func(t *testing.T) {
user := portainer.User{ID: 1}
_, apiKey, err := service.GenerateApiKey(user, "test-1")
is.NoError(err)
require.NoError(t, err)
apiKeyGot, err := service.GetAPIKey(apiKey.ID)
is.NoError(err)
require.NoError(t, err)
is.Equal(apiKey, apiKeyGot)
})
}
func Test_GetAPIKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
t.Run("Successfully returns all API keys", func(t *testing.T) {
user := portainer.User{ID: 1}
_, _, err := service.GenerateApiKey(user, "test-1")
is.NoError(err)
require.NoError(t, err)
_, _, err = service.GenerateApiKey(user, "test-2")
is.NoError(err)
require.NoError(t, err)
keys, err := service.GetAPIKeys(user.ID)
is.NoError(err)
require.NoError(t, err)
is.Len(keys, 2)
})
}
func Test_GetDigestUserAndKey(t *testing.T) {
t.Parallel()
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
t.Run("Successfully returns user and api key associated to digest", func(t *testing.T) {
user := portainer.User{ID: 1}
_, apiKey, err := service.GenerateApiKey(user, "test-1")
is.NoError(err)
require.NoError(t, err)
userGot, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
is.NoError(err)
require.NoError(t, err)
is.Equal(user, userGot)
is.Equal(*apiKey, apiKeyGot)
})
@@ -134,10 +139,10 @@ func Test_GetDigestUserAndKey(t *testing.T) {
t.Run("Successfully caches user and api key associated to digest", func(t *testing.T) {
user := portainer.User{ID: 1}
_, apiKey, err := service.GenerateApiKey(user, "test-1")
is.NoError(err)
require.NoError(t, err)
userGot, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
is.NoError(err)
require.NoError(t, err)
is.Equal(user, userGot)
is.Equal(*apiKey, apiKeyGot)
@@ -149,36 +154,37 @@ func Test_GetDigestUserAndKey(t *testing.T) {
}
func Test_UpdateAPIKey(t *testing.T) {
t.Parallel()
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
t.Run("Successfully updates the api-key LastUsed time", func(t *testing.T) {
user := portainer.User{ID: 1}
store.User().Create(&user)
err := store.User().Create(&user)
require.NoError(t, err)
_, apiKey, err := service.GenerateApiKey(user, "test-x")
is.NoError(err)
require.NoError(t, err)
apiKey.LastUsed = time.Now().UTC().Unix()
err = service.UpdateAPIKey(apiKey)
is.NoError(err)
require.NoError(t, err)
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
is.NoError(err)
require.NoError(t, err)
log.Println(apiKey)
log.Println(apiKeyGot)
log.Debug().Str("wanted", fmt.Sprintf("%+v", apiKey)).Str("got", fmt.Sprintf("%+v", apiKeyGot)).Msg("")
is.Equal(apiKey.LastUsed, apiKeyGot.LastUsed)
})
t.Run("Successfully updates api-key in cache upon api-key update", func(t *testing.T) {
_, apiKey, err := service.GenerateApiKey(portainer.User{ID: 1}, "test-x2")
is.NoError(err)
require.NoError(t, err)
_, apiKeyFromCache, ok := service.cache.Get(apiKey.Digest)
is.True(ok)
@@ -188,7 +194,7 @@ func Test_UpdateAPIKey(t *testing.T) {
is.NotEqual(*apiKey, apiKeyFromCache)
err = service.UpdateAPIKey(apiKey)
is.NoError(err)
require.NoError(t, err)
_, updatedAPIKeyFromCache, ok := service.cache.Get(apiKey.Digest)
is.True(ok)
@@ -197,40 +203,40 @@ func Test_UpdateAPIKey(t *testing.T) {
}
func Test_DeleteAPIKey(t *testing.T) {
t.Parallel()
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
t.Run("Successfully updates the api-key", func(t *testing.T) {
user := portainer.User{ID: 1}
_, apiKey, err := service.GenerateApiKey(user, "test-1")
is.NoError(err)
require.NoError(t, err)
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
is.NoError(err)
require.NoError(t, err)
is.Equal(*apiKey, apiKeyGot)
err = service.DeleteAPIKey(apiKey.ID)
is.NoError(err)
require.NoError(t, err)
_, _, err = service.GetDigestUserAndKey(apiKey.Digest)
is.Error(err)
require.Error(t, err)
})
t.Run("Successfully removes api-key from cache upon deletion", func(t *testing.T) {
user := portainer.User{ID: 1}
_, apiKey, err := service.GenerateApiKey(user, "test-1")
is.NoError(err)
require.NoError(t, err)
_, apiKeyFromCache, ok := service.cache.Get(apiKey.Digest)
is.True(ok)
is.Equal(*apiKey, apiKeyFromCache)
err = service.DeleteAPIKey(apiKey.ID)
is.NoError(err)
require.NoError(t, err)
_, _, ok = service.cache.Get(apiKey.Digest)
is.False(ok)
@@ -238,10 +244,10 @@ func Test_DeleteAPIKey(t *testing.T) {
}
func Test_InvalidateUserKeyCache(t *testing.T) {
t.Parallel()
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -249,10 +255,10 @@ func Test_InvalidateUserKeyCache(t *testing.T) {
// generate api keys
user := portainer.User{ID: 1}
_, apiKey1, err := service.GenerateApiKey(user, "test-1")
is.NoError(err)
require.NoError(t, err)
_, apiKey2, err := service.GenerateApiKey(user, "test-2")
is.NoError(err)
require.NoError(t, err)
// verify api keys are present in cache
_, apiKeyFromCache, ok := service.cache.Get(apiKey1.Digest)
@@ -279,11 +285,11 @@ func Test_InvalidateUserKeyCache(t *testing.T) {
// generate keys for 2 users
user1 := portainer.User{ID: 1}
_, apiKey1, err := service.GenerateApiKey(user1, "test-1")
is.NoError(err)
require.NoError(t, err)
user2 := portainer.User{ID: 2}
_, apiKey2, err := service.GenerateApiKey(user2, "test-2")
is.NoError(err)
require.NoError(t, err)
// verify keys in cache
_, apiKeyFromCache, ok := service.cache.Get(apiKey1.Digest)
+40 -6
View File
@@ -17,20 +17,54 @@ func TarFileInBuffer(fileContent []byte, fileName string, mode int64) ([]byte, e
Size: int64(len(fileContent)),
}
err := tarWriter.WriteHeader(header)
if err != nil {
if err := tarWriter.WriteHeader(header); err != nil {
return nil, err
}
_, err = tarWriter.Write(fileContent)
if err != nil {
if _, err := tarWriter.Write(fileContent); err != nil {
return nil, err
}
err = tarWriter.Close()
if err != nil {
if err := tarWriter.Close(); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
// tarFileInBuffer represents a tar archive buffer.
type tarFileInBuffer struct {
b *bytes.Buffer
w *tar.Writer
}
func NewTarFileInBuffer() *tarFileInBuffer {
var b bytes.Buffer
return &tarFileInBuffer{b: &b, w: tar.NewWriter(&b)}
}
// Put puts a single file to tar archive buffer.
func (t *tarFileInBuffer) Put(fileContent []byte, fileName string, mode int64) error {
hdr := &tar.Header{
Name: fileName,
Mode: mode,
Size: int64(len(fileContent)),
}
if err := t.w.WriteHeader(hdr); err != nil {
return err
}
_, err := t.w.Write(fileContent)
return err
}
// Bytes returns the archive as a byte array.
func (t *tarFileInBuffer) Bytes() []byte {
return t.b.Bytes()
}
func (t *tarFileInBuffer) Close() error {
return t.w.Close()
}
+35 -22
View File
@@ -3,28 +3,33 @@ package archive
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/logs"
)
// TarGzDir creates a tar.gz archive and returns it's path.
// abosolutePath should be an absolute path to a directory.
// Archive name will be <directoryName>.tar.gz and will be placed next to the directory.
func TarGzDir(absolutePath string) (string, error) {
targzPath := filepath.Join(absolutePath, fmt.Sprintf("%s.tar.gz", filepath.Base(absolutePath)))
targzPath := filepath.Join(absolutePath, filepath.Base(absolutePath)+".tar.gz")
outFile, err := os.Create(targzPath)
if err != nil {
return "", err
}
defer outFile.Close()
defer logs.CloseAndLogErr(outFile)
zipWriter := gzip.NewWriter(outFile)
defer zipWriter.Close()
defer logs.CloseAndLogErr(zipWriter)
tarWriter := tar.NewWriter(zipWriter)
defer tarWriter.Close()
defer logs.CloseAndLogErr(tarWriter)
err = filepath.Walk(absolutePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
@@ -47,18 +52,6 @@ func TarGzDir(absolutePath string) (string, error) {
}
func addToArchive(tarWriter *tar.Writer, pathInArchive string, path string, info os.FileInfo) error {
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
header.Name = pathInArchive // use relative paths in archive
err = tarWriter.WriteHeader(header)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
@@ -67,6 +60,26 @@ func addToArchive(tarWriter *tar.Writer, pathInArchive string, path string, info
if err != nil {
return err
}
stat, err := file.Stat()
if err != nil {
return err
}
header, err := tar.FileInfoHeader(stat, stat.Name())
if err != nil {
return err
}
header.Name = pathInArchive // use relative paths in archive
err = tarWriter.WriteHeader(header)
if err != nil {
return err
}
if stat.IsDir() {
return nil
}
_, err = io.Copy(tarWriter, file)
return err
}
@@ -77,14 +90,14 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
if err != nil {
return err
}
defer zipReader.Close()
defer logs.CloseAndLogErr(zipReader)
tarReader := tar.NewReader(zipReader)
for {
header, err := tarReader.Next()
if err == io.EOF {
if errors.Is(err, io.EOF) {
break
}
@@ -96,8 +109,8 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
case tar.TypeDir:
// skip, dir will be created with a file
case tar.TypeReg:
p := filepath.Clean(filepath.Join(outputDirPath, header.Name))
if err := os.MkdirAll(filepath.Dir(p), 0744); err != nil {
p := filesystem.JoinPaths(outputDirPath, header.Name)
if err := os.MkdirAll(filepath.Dir(p), 0o744); err != nil {
return fmt.Errorf("Failed to extract dir %s", filepath.Dir(p))
}
outFile, err := os.Create(p)
@@ -107,9 +120,9 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
if _, err := io.Copy(outFile, tarReader); err != nil {
return fmt.Errorf("Failed to extract file %s", header.Name)
}
outFile.Close()
logs.CloseAndLogErr(outFile)
default:
return fmt.Errorf("Tar: uknown type: %v in %s",
return fmt.Errorf("tar: unknown type: %v in %s",
header.Typeflag,
header.Name)
}
+109 -40
View File
@@ -1,59 +1,70 @@
package archive
import (
"fmt"
"io/ioutil"
"archive/tar"
"compress/gzip"
"os"
"os/exec"
"path"
"path/filepath"
"testing"
"github.com/docker/docker/pkg/ioutils"
"github.com/portainer/portainer/api/filesystem"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func listFiles(dir string) []string {
items := make([]string, 0)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if path == dir {
return nil
}
items = append(items, path)
return nil
})
}); err != nil {
log.Warn().Err(err).Msg("failed to list files in directory")
}
return items
}
func Test_shouldCreateArhive(t *testing.T) {
tmpdir, _ := ioutils.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
func Test_shouldCreateArchive(t *testing.T) {
t.Parallel()
tmpdir := t.TempDir()
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
err := os.WriteFile(filesystem.JoinPaths(tmpdir, "outer"), content, 0600)
require.NoError(t, err)
err = os.MkdirAll(filesystem.JoinPaths(tmpdir, "dir"), 0700)
require.NoError(t, err)
err = os.WriteFile(filesystem.JoinPaths(tmpdir, "dir", ".dotfile"), content, 0600)
require.NoError(t, err)
err = os.WriteFile(filesystem.JoinPaths(tmpdir, "dir", "inner"), content, 0600)
require.NoError(t, err)
gzPath, err := TarGzDir(tmpdir)
assert.Nil(t, err)
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
extractionDir, _ := ioutils.TempDir("", "extract")
defer os.RemoveAll(extractionDir)
require.NoError(t, err)
assert.Equal(t, filesystem.JoinPaths(tmpdir, filepath.Base(tmpdir)+".tar.gz"), gzPath)
extractionDir := t.TempDir()
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
err = cmd.Run()
if err != nil {
if err := cmd.Run(); err != nil {
t.Fatal("Failed to extract archive: ", err)
}
extractedFiles := listFiles(extractionDir)
wasExtracted := func(p string) {
fullpath := path.Join(extractionDir, p)
fullpath := filesystem.JoinPaths(extractionDir, p)
assert.Contains(t, extractedFiles, fullpath)
copyContent, _ := ioutil.ReadFile(fullpath)
copyContent, err := os.ReadFile(fullpath)
require.NoError(t, err)
assert.Equal(t, content, copyContent)
}
@@ -62,34 +73,38 @@ func Test_shouldCreateArhive(t *testing.T) {
wasExtracted("dir/.dotfile")
}
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
tmpdir, _ := ioutils.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
func Test_shouldCreateArchive2(t *testing.T) {
t.Parallel()
tmpdir := t.TempDir()
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
err := os.WriteFile(filesystem.JoinPaths(tmpdir, "outer"), content, 0600)
require.NoError(t, err)
err = os.MkdirAll(filesystem.JoinPaths(tmpdir, "dir"), 0700)
require.NoError(t, err)
err = os.WriteFile(filesystem.JoinPaths(tmpdir, "dir", ".dotfile"), content, 0600)
require.NoError(t, err)
err = os.WriteFile(filesystem.JoinPaths(tmpdir, "dir", "inner"), content, 0600)
require.NoError(t, err)
gzPath, err := TarGzDir(tmpdir)
assert.Nil(t, err)
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
extractionDir, _ := ioutils.TempDir("", "extract")
defer os.RemoveAll(extractionDir)
require.NoError(t, err)
assert.Equal(t, filesystem.JoinPaths(tmpdir, filepath.Base(tmpdir)+".tar.gz"), gzPath)
extractionDir := t.TempDir()
r, _ := os.Open(gzPath)
ExtractTarGz(r, extractionDir)
if err != nil {
if err := ExtractTarGz(r, extractionDir); err != nil {
t.Fatal("Failed to extract archive: ", err)
}
extractedFiles := listFiles(extractionDir)
wasExtracted := func(p string) {
fullpath := path.Join(extractionDir, p)
fullpath := filesystem.JoinPaths(extractionDir, p)
assert.Contains(t, extractedFiles, fullpath)
copyContent, _ := ioutil.ReadFile(fullpath)
copyContent, _ := os.ReadFile(fullpath)
assert.Equal(t, content, copyContent)
}
@@ -97,3 +112,57 @@ func Test_shouldCreateArhiveXXXXX(t *testing.T) {
wasExtracted("dir/inner")
wasExtracted("dir/.dotfile")
}
func TestExtractTarGzPathTraversal(t *testing.T) {
t.Parallel()
testDir := t.TempDir()
// Create an evil file with a path traversal attempt
tarPath := filesystem.JoinPaths(testDir, "evil.tar.gz")
evilFile, err := os.Create(tarPath)
require.NoError(t, err)
gzWriter := gzip.NewWriter(evilFile)
tarWriter := tar.NewWriter(gzWriter)
content := []byte("evil content")
header := &tar.Header{
Name: "../evil.txt",
Mode: 0600,
Size: int64(len(content)),
Typeflag: tar.TypeReg,
}
err = tarWriter.WriteHeader(header)
require.NoError(t, err)
_, err = tarWriter.Write(content)
require.NoError(t, err)
err = tarWriter.Close()
require.NoError(t, err)
err = gzWriter.Close()
require.NoError(t, err)
err = evilFile.Close()
require.NoError(t, err)
// Attempt to extract the evil file
extractionDir := filesystem.JoinPaths(testDir, "extraction")
err = os.Mkdir(extractionDir, 0700)
require.NoError(t, err)
tarFile, err := os.Open(tarPath)
require.NoError(t, err)
// Check that the file didn't escape
err = ExtractTarGz(tarFile, extractionDir)
require.NoError(t, err)
require.NoFileExists(t, filesystem.JoinPaths(testDir, "evil.txt"))
err = tarFile.Close()
require.NoError(t, err)
}
+15 -56
View File
@@ -2,60 +2,17 @@ package archive
import (
"archive/zip"
"bytes"
"fmt"
"github.com/pkg/errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/portainer/portainer/api/logs"
"github.com/pkg/errors"
)
// UnzipArchive will unzip an archive from bytes into the dest destination folder on disk
func UnzipArchive(archiveData []byte, dest string) error {
zipReader, err := zip.NewReader(bytes.NewReader(archiveData), int64(len(archiveData)))
if err != nil {
return err
}
for _, zipFile := range zipReader.File {
err := extractFileFromArchive(zipFile, dest)
if err != nil {
return err
}
}
return nil
}
func extractFileFromArchive(file *zip.File, dest string) error {
f, err := file.Open()
if err != nil {
return err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return err
}
fpath := filepath.Join(dest, file.Name)
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return err
}
_, err = io.Copy(outFile, bytes.NewReader(data))
if err != nil {
return err
}
return outFile.Close()
}
// UnzipFile will decompress a zip archive, moving all files and folders
// within the zip file (parameter 1) to an output directory (parameter 2).
func UnzipFile(src string, dest string) error {
@@ -63,7 +20,7 @@ func UnzipFile(src string, dest string) error {
if err != nil {
return err
}
defer r.Close()
defer logs.CloseAndLogErr(r)
for _, f := range r.File {
p := filepath.Join(dest, f.Name)
@@ -75,12 +32,14 @@ func UnzipFile(src string, dest string) error {
if f.FileInfo().IsDir() {
// Make Folder
os.MkdirAll(p, os.ModePerm)
if err := os.MkdirAll(p, os.ModePerm); err != nil {
return err
}
continue
}
err = unzipFile(f, p)
if err != nil {
if err := unzipFile(f, p); err != nil {
return err
}
}
@@ -93,20 +52,20 @@ func unzipFile(f *zip.File, p string) error {
if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
return errors.Wrapf(err, "unzipFile: can't make a path %s", p)
}
outFile, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return errors.Wrapf(err, "unzipFile: can't create file %s", p)
}
defer outFile.Close()
defer logs.CloseAndLogErr(outFile)
rc, err := f.Open()
if err != nil {
return errors.Wrapf(err, "unzipFile: can't open zip file %s in the archive", f.Name)
}
defer rc.Close()
defer logs.CloseAndLogErr(rc)
_, err = io.Copy(outFile, rc)
if err != nil {
if _, err = io.Copy(outFile, rc); err != nil {
return errors.Wrapf(err, "unzipFile: can't copy an archived file content")
}
+12 -12
View File
@@ -1,17 +1,17 @@
package archive
import (
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/portainer/portainer/api/filesystem"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUnzipFile(t *testing.T) {
dir, err := ioutil.TempDir("", "unzip-test-")
assert.NoError(t, err)
defer os.RemoveAll(dir)
t.Parallel()
dir := t.TempDir()
/*
Archive structure.
0
@@ -21,12 +21,12 @@ func TestUnzipFile(t *testing.T) {
0.txt
*/
err = UnzipFile("./testdata/sample_archive.zip", dir)
err := UnzipFile("./testdata/sample_archive.zip", dir)
assert.NoError(t, err)
require.NoError(t, err)
archiveDir := dir + "/sample_archive"
assert.FileExists(t, filepath.Join(archiveDir, "0.txt"))
assert.FileExists(t, filepath.Join(archiveDir, "0", "1.txt"))
assert.FileExists(t, filepath.Join(archiveDir, "0", "1", "2.txt"))
assert.FileExists(t, filesystem.JoinPaths(archiveDir, "0.txt"))
assert.FileExists(t, filesystem.JoinPaths(archiveDir, "0", "1.txt"))
assert.FileExists(t, filesystem.JoinPaths(archiveDir, "0", "1", "2.txt"))
}
+7 -7
View File
@@ -3,19 +3,19 @@ package ecr
import (
"context"
"encoding/base64"
"fmt"
"errors"
"strings"
"time"
)
func (s *Service) GetEncodedAuthorizationToken() (token *string, expiry *time.Time, err error) {
getAuthorizationTokenOutput, err := s.client.GetAuthorizationToken(context.TODO(), nil)
func (s *Service) GetEncodedAuthorizationToken(ctx context.Context) (token *string, expiry *time.Time, err error) {
getAuthorizationTokenOutput, err := s.client.GetAuthorizationToken(ctx, nil)
if err != nil {
return
}
if len(getAuthorizationTokenOutput.AuthorizationData) == 0 {
err = fmt.Errorf("AuthorizationData is empty")
err = errors.New("AuthorizationData is empty")
return
}
@@ -27,8 +27,8 @@ func (s *Service) GetEncodedAuthorizationToken() (token *string, expiry *time.Ti
return
}
func (s *Service) GetAuthorizationToken() (token *string, expiry *time.Time, err error) {
tokenEncodedStr, expiry, err := s.GetEncodedAuthorizationToken()
func (s *Service) GetAuthorizationToken(ctx context.Context) (token *string, expiry *time.Time, err error) {
tokenEncodedStr, expiry, err := s.GetEncodedAuthorizationToken(ctx)
if err != nil {
return
}
@@ -50,7 +50,7 @@ func (s *Service) ParseAuthorizationToken(token string) (username string, passwo
splitToken := strings.Split(token, ":")
if len(splitToken) < 2 {
err = fmt.Errorf("invalid ECR authorization token")
err = errors.New("invalid ECR authorization token")
return
}
+9
View File
@@ -6,6 +6,15 @@ import (
"github.com/aws/aws-sdk-go-v2/service/ecr"
)
// Registry represents an ECR registry endpoint information.
// This struct is used to parse and validate ECR endpoint URLs.
type Registry struct {
ID string // AWS account ID (empty for accountless endpoints like "ecr-fips.us-west-1.amazonaws.com")
FIPS bool // Whether this is a FIPS endpoint (contains "-fips" in the URL)
Region string // AWS region (e.g., "us-east-1", "us-gov-west-1")
Public bool // Whether this is ecr-public.aws.com
}
type (
Service struct {
accessKey string
+70
View File
@@ -0,0 +1,70 @@
package ecr
import (
"fmt"
"net/url"
"regexp"
"strings"
)
// ecrEndpointPattern matches all valid ECR endpoints including account-prefixed and accountless formats.
// Based on AWS ECR credential helper regex but extended to support accountless endpoints.
//
// Supported formats:
// - Account-prefixed: 123456789012.dkr.ecr-fips.us-east-1.amazonaws.com
// - Account-prefixed (hyphen): 123456789012.dkr-ecr-fips.us-west-1.on.aws
// - Accountless service: ecr-fips.us-west-1.amazonaws.com
// - Accountless API: ecr-fips.us-east-1.api.aws
// - Non-FIPS variants: All formats above without "-fips"
//
// Regex groups:
// - Group 1: Full account prefix (optional) - e.g., "123456789012.dkr." or "123456789012.dkr-"
// - Group 2: Account ID (optional) - e.g., "123456789012"
// - Group 3: FIPS flag (optional) - either "-fips" or empty string
// - Group 4: Region - e.g., "us-east-1", "us-gov-west-1"
// - Group 5: Domain suffix - e.g., "amazonaws.com", "api.aws"
var ecrEndpointPattern = regexp.MustCompile(
`^((\d{12})\.dkr[\.\-])?ecr(\-fips)?\.([a-zA-Z0-9][a-zA-Z0-9-_]*)\.(amazonaws\.(?:com(?:\.cn)?|eu)|api\.aws|on\.(?:aws|amazonwebservices\.com\.cn)|sc2s\.sgov\.gov|c2s\.ic\.gov|cloud\.adc-e\.uk|csp\.hci\.ic\.gov)$`,
)
// ParseECREndpoint parses an ECR registry URL and extracts registry information.
// This function replaces the AWS ECR credential helper library's ExtractRegistry function,
// which only supports account-prefixed endpoints.
//
// Reference: https://docs.aws.amazon.com/general/latest/gr/ecr.html
func ParseECREndpoint(urlStr string) (*Registry, error) {
// Normalize URL by adding https:// prefix if not present
if !strings.HasPrefix(urlStr, "https://") && !strings.HasPrefix(urlStr, "http://") {
urlStr = "https://" + urlStr
}
u, err := url.Parse(urlStr)
if err != nil {
return nil, fmt.Errorf("invalid URL: %w", err)
}
hostname := u.Hostname()
// Special case: ECR Public
// ECR Public uses a different domain and doesn't have FIPS variant
if hostname == "ecr-public.aws.com" {
return &Registry{
FIPS: false,
Public: true,
}, nil
}
// Parse standard ECR endpoints using regex
matches := ecrEndpointPattern.FindStringSubmatch(hostname)
if len(matches) == 0 {
return nil, fmt.Errorf("not a valid ECR endpoint: %s", hostname)
}
return &Registry{
ID: matches[2], // Account ID (may be empty for accountless endpoints)
FIPS: matches[3] == "-fips", // Check if "-fips" is present
Region: matches[4], // AWS region
Public: false,
}, nil
}
+254
View File
@@ -0,0 +1,254 @@
package ecr
import (
"testing"
)
func TestParseECREndpoint(t *testing.T) {
t.Parallel()
tests := []struct {
name string
url string
want *Registry
wantError bool
}{
// Standard AWS Commercial - Account-prefixed FIPS
{
name: "account-prefixed FIPS us-east-1",
url: "123456789012.dkr.ecr-fips.us-east-1.amazonaws.com",
want: &Registry{
ID: "123456789012",
FIPS: true,
Region: "us-east-1",
Public: false,
},
},
{
name: "account-prefixed FIPS us-west-2",
url: "123456789012.dkr.ecr-fips.us-west-2.amazonaws.com",
want: &Registry{
ID: "123456789012",
FIPS: true,
Region: "us-west-2",
Public: false,
},
},
// Accountless FIPS service endpoints
{
name: "accountless FIPS us-west-1",
url: "ecr-fips.us-west-1.amazonaws.com",
want: &Registry{
ID: "",
FIPS: true,
Region: "us-west-1",
Public: false,
},
},
{
name: "accountless FIPS us-east-2",
url: "ecr-fips.us-east-2.amazonaws.com",
want: &Registry{
ID: "",
FIPS: true,
Region: "us-east-2",
Public: false,
},
},
// Accountless FIPS API endpoints
{
name: "accountless FIPS API us-west-1",
url: "ecr-fips.us-west-1.api.aws",
want: &Registry{
ID: "",
FIPS: true,
Region: "us-west-1",
Public: false,
},
},
{
name: "accountless FIPS API us-east-1",
url: "ecr-fips.us-east-1.api.aws",
want: &Registry{
ID: "",
FIPS: true,
Region: "us-east-1",
Public: false,
},
},
// on.aws domain with hyphen separator
{
name: "account-prefixed FIPS hyphen us-west-1",
url: "123456789012.dkr-ecr-fips.us-west-1.on.aws",
want: &Registry{
ID: "123456789012",
FIPS: true,
Region: "us-west-1",
Public: false,
},
},
{
name: "account-prefixed FIPS hyphen us-east-2",
url: "123456789012.dkr-ecr-fips.us-east-2.on.aws",
want: &Registry{
ID: "123456789012",
FIPS: true,
Region: "us-east-2",
Public: false,
},
},
// AWS GovCloud
{
name: "account-prefixed FIPS us-gov-east-1",
url: "123456789012.dkr.ecr-fips.us-gov-east-1.amazonaws.com",
want: &Registry{
ID: "123456789012",
FIPS: true,
Region: "us-gov-east-1",
Public: false,
},
},
{
name: "account-prefixed FIPS us-gov-west-1",
url: "123456789012.dkr.ecr-fips.us-gov-west-1.amazonaws.com",
want: &Registry{
ID: "123456789012",
FIPS: true,
Region: "us-gov-west-1",
Public: false,
},
},
{
name: "accountless FIPS us-gov-west-1",
url: "ecr-fips.us-gov-west-1.amazonaws.com",
want: &Registry{
ID: "",
FIPS: true,
Region: "us-gov-west-1",
Public: false,
},
},
{
name: "accountless FIPS API us-gov-east-1",
url: "ecr-fips.us-gov-east-1.api.aws",
want: &Registry{
ID: "",
FIPS: true,
Region: "us-gov-east-1",
Public: false,
},
},
// ECR Public
{
name: "ecr-public",
url: "ecr-public.aws.com",
want: &Registry{
ID: "",
FIPS: false,
Region: "",
Public: true,
},
},
// Non-FIPS endpoints (valid ECR but FIPS=false)
{
name: "account-prefixed non-FIPS us-east-1",
url: "123456789012.dkr.ecr.us-east-1.amazonaws.com",
want: &Registry{
ID: "123456789012",
FIPS: false,
Region: "us-east-1",
Public: false,
},
},
{
name: "accountless non-FIPS us-west-1",
url: "ecr.us-west-1.amazonaws.com",
want: &Registry{
ID: "",
FIPS: false,
Region: "us-west-1",
Public: false,
},
},
{
name: "accountless non-FIPS API us-east-2",
url: "ecr.us-east-2.api.aws",
want: &Registry{
ID: "",
FIPS: false,
Region: "us-east-2",
Public: false,
},
},
// URLs with https:// prefix
{
name: "with https prefix",
url: "https://ecr-fips.us-west-1.amazonaws.com",
want: &Registry{
ID: "",
FIPS: true,
Region: "us-west-1",
Public: false,
},
},
// Invalid endpoints
{
name: "not an ECR URL",
url: "not-an-ecr-url.com",
wantError: true,
},
{
name: "invalid account ID length",
url: "123.dkr.ecr-fips.us-east-1.amazonaws.com",
wantError: true,
},
{
name: "empty string",
url: "",
wantError: true,
},
{
name: "docker hub",
url: "docker.io",
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseECREndpoint(tt.url)
if tt.wantError {
if err == nil {
t.Errorf("ParseECREndpoint() expected error but got none")
}
return
}
if err != nil {
t.Errorf("ParseECREndpoint() unexpected error: %v", err)
return
}
if got.ID != tt.want.ID {
t.Errorf("ParseECREndpoint() ID = %v, want %v", got.ID, tt.want.ID)
}
if got.FIPS != tt.want.FIPS {
t.Errorf("ParseECREndpoint() FIPS = %v, want %v", got.FIPS, tt.want.FIPS)
}
if got.Region != tt.want.Region {
t.Errorf("ParseECREndpoint() Region = %v, want %v", got.Region, tt.want.Region)
}
if got.Public != tt.want.Public {
t.Errorf("ParseECREndpoint() Public = %v, want %v", got.Public, tt.want.Public)
}
})
}
}
+46 -45
View File
@@ -7,19 +7,22 @@ import (
"path/filepath"
"time"
"github.com/pkg/errors"
"github.com/portainer/portainer/api/archive"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/offlinegate"
"github.com/sirupsen/logrus"
"github.com/portainer/portainer/api/logs"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
const rwxr__r__ os.FileMode = 0744
const rwxr__r__ os.FileMode = 0o744
var filesToBackup = []string{
"certs",
"chisel",
"compose",
"config.json",
"custom_templates",
@@ -33,35 +36,9 @@ var filesToBackup = []string{
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
unlock := gate.Lock()
defer unlock()
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
return "", errors.Wrap(err, "Failed to create backup dir")
}
{
// new export
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
err := datastore.Export(exportFilename)
if err != nil {
logrus.WithError(err).Debugf("failed to export to %s", exportFilename)
} else {
logrus.Debugf("exported to %s", exportFilename)
}
}
if err := backupDb(backupDirPath, datastore); err != nil {
return "", errors.Wrap(err, "Failed to backup database")
}
for _, filename := range filesToBackup {
err := filesystem.CopyPath(filepath.Join(filestorePath, filename), backupDirPath)
if err != nil {
return "", errors.Wrap(err, "Failed to create backup file")
}
backupDirPath, err := backupDatabaseAndFilesystem(gate, datastore, filestorePath)
if err != nil {
return "", err
}
archivePath, err := archive.TarGzDir(backupDirPath)
@@ -79,15 +56,41 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
return archivePath, nil
}
func backupDatabaseAndFilesystem(gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
unlock := gate.Lock()
defer unlock()
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
return "", errors.Wrap(err, "Failed to create backup dir")
}
// new export
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
if err := datastore.Export(exportFilename); err != nil {
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
} else {
log.Debug().Str("filename", exportFilename).Msg("file exported")
}
if err := backupDb(backupDirPath, datastore); err != nil {
return "", errors.Wrap(err, "Failed to backup database")
}
for _, filename := range filesToBackup {
if err := filesystem.CopyPath(filepath.Join(filestorePath, filename), backupDirPath); err != nil {
return "", errors.Wrap(err, "Failed to create backup file")
}
}
return backupDirPath, nil
}
func backupDb(backupDirPath string, datastore dataservices.DataStore) error {
backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
if err != nil {
return err
}
if err = datastore.BackupTo(backupWriter); err != nil {
return err
}
return backupWriter.Close()
dbFileName := datastore.Connection().GetDatabaseFileName()
_, err := datastore.Backup(filepath.Join(backupDirPath, dbFileName))
return err
}
func encrypt(path string, passphrase string) (string, error) {
@@ -95,15 +98,13 @@ func encrypt(path string, passphrase string) (string, error) {
if err != nil {
return "", err
}
defer in.Close()
defer logs.CloseAndLogErr(in)
outFileName := fmt.Sprintf("%s.encrypted", path)
outFileName := path + ".encrypted"
out, err := os.Create(outFileName)
if err != nil {
return "", err
}
err = crypto.AesEncrypt(in, out, []byte(passphrase))
return outFileName, err
return outFileName, crypto.AesEncrypt(in, out, []byte(passphrase))
}
+274
View File
@@ -0,0 +1,274 @@
package backup
import (
"bytes"
"context"
"io"
"os"
"path/filepath"
"testing"
"github.com/portainer/portainer/api/archive"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/offlinegate"
"github.com/portainer/portainer/pkg/fips"
"github.com/stretchr/testify/require"
)
func init() {
fips.InitFIPS(false)
}
func TestGetRestoreSourcePath_DBAtRoot(t *testing.T) {
t.Parallel()
dir := t.TempDir()
err := os.WriteFile(filesystem.JoinPaths(dir, "portainer.db"), []byte("db"), 0o600)
require.NoError(t, err)
result, err := getRestoreSourcePath(dir)
require.NoError(t, err)
require.Equal(t, dir, result)
}
func TestGetRestoreSourcePath_EncryptedDBAtRoot(t *testing.T) {
t.Parallel()
dir := t.TempDir()
err := os.WriteFile(filesystem.JoinPaths(dir, "portainer.edb"), []byte("db"), 0o600)
require.NoError(t, err)
result, err := getRestoreSourcePath(dir)
require.NoError(t, err)
require.Equal(t, dir, result)
}
func TestGetRestoreSourcePath_DBInSubdirectory(t *testing.T) {
t.Parallel()
dir := t.TempDir()
sub := filesystem.JoinPaths(dir, "backup-2024-01-01")
err := os.Mkdir(sub, 0o700)
require.NoError(t, err)
err = os.WriteFile(filesystem.JoinPaths(sub, "portainer.db"), []byte("db"), 0o600)
require.NoError(t, err)
result, err := getRestoreSourcePath(dir)
require.NoError(t, err)
require.Equal(t, sub, result)
}
func TestGetRestoreSourcePath_NoDBFile(t *testing.T) {
t.Parallel()
dir := t.TempDir()
err := os.WriteFile(filesystem.JoinPaths(dir, "other.file"), []byte("data"), 0o600)
require.NoError(t, err)
result, err := getRestoreSourcePath(dir)
require.NoError(t, err)
require.Equal(t, dir, result)
}
func TestGetRestoreSourcePath_EmptyDir(t *testing.T) {
t.Parallel()
dir := t.TempDir()
result, err := getRestoreSourcePath(dir)
require.NoError(t, err)
require.Equal(t, dir, result)
}
func TestEncryptDecrypt_RoundTrip(t *testing.T) {
t.Parallel()
dir := t.TempDir()
plaintext := []byte("sensitive portainer backup data")
srcPath := filesystem.JoinPaths(dir, "archive.tar.gz")
err := os.WriteFile(srcPath, plaintext, 0o600)
require.NoError(t, err)
encryptedPath, err := encrypt(srcPath, "mysecretpassword")
require.NoError(t, err)
require.Equal(t, srcPath+".encrypted", encryptedPath)
encryptedData, err := os.ReadFile(encryptedPath)
require.NoError(t, err)
decryptedReader, err := crypto.AesDecrypt(bytes.NewReader(encryptedData), []byte("mysecretpassword"))
require.NoError(t, err)
decrypted, err := io.ReadAll(decryptedReader)
require.NoError(t, err)
require.Equal(t, plaintext, decrypted)
}
func TestEncryptDecrypt_WrongPassword(t *testing.T) {
t.Parallel()
dir := t.TempDir()
srcPath := filesystem.JoinPaths(dir, "archive.tar.gz")
err := os.WriteFile(srcPath, []byte("data"), 0o600)
require.NoError(t, err)
encryptedPath, err := encrypt(srcPath, "correctpassword")
require.NoError(t, err)
encryptedData, err := os.ReadFile(encryptedPath)
require.NoError(t, err)
_, err = crypto.AesDecrypt(bytes.NewReader(encryptedData), []byte("wrongpassword"))
require.Error(t, err)
}
func TestCreateBackupArchive_NoPassword(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, true, false)
storePath := store.GetConnection().GetStorePath()
gate := offlinegate.NewOfflineGate()
archivePath, err := CreateBackupArchive("", gate, store, storePath)
require.NoError(t, err)
f, err := os.Open(archivePath)
require.NoError(t, err)
t.Cleanup(func() {
err := f.Close()
require.NoError(t, err)
})
extractDir := t.TempDir()
err = archive.ExtractTarGz(f, extractDir)
require.NoError(t, err)
dbFound := false
err = filepath.Walk(extractDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Name() == "portainer.db" {
dbFound = true
}
return nil
})
require.NoError(t, err)
require.True(t, dbFound, "archive should contain portainer.db")
}
func TestCreateBackupArchive_WithPassword(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, true, false)
storePath := store.GetConnection().GetStorePath()
gate := offlinegate.NewOfflineGate()
archivePath, err := CreateBackupArchive("backup-secret", gate, store, storePath)
require.NoError(t, err)
require.Contains(t, archivePath, ".encrypted")
encryptedData, err := os.ReadFile(archivePath)
require.NoError(t, err)
decryptedReader, err := crypto.AesDecrypt(bytes.NewReader(encryptedData), []byte("backup-secret"))
require.NoError(t, err)
extractDir := t.TempDir()
err = archive.ExtractTarGz(decryptedReader, extractDir)
require.NoError(t, err)
dbFound := false
err = filepath.Walk(extractDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Name() == "portainer.db" {
dbFound = true
}
return nil
})
require.NoError(t, err)
require.True(t, dbFound, "decrypted archive should contain portainer.db")
}
func TestRestoreArchive_NoPassword(t *testing.T) {
t.Parallel()
_, store1 := datastore.MustNewTestStore(t, true, false)
storePath1 := store1.GetConnection().GetStorePath()
gate := offlinegate.NewOfflineGate()
archivePath, err := CreateBackupArchive("", gate, store1, storePath1)
require.NoError(t, err)
archiveData, err := os.ReadFile(archivePath)
require.NoError(t, err)
_, store2 := datastore.MustNewTestStore(t, true, false)
storePath2 := store2.GetConnection().GetStorePath()
ctx, cancel := context.WithCancel(t.Context())
err = RestoreArchive(bytes.NewReader(archiveData), "", storePath2, gate, store2, cancel)
require.NoError(t, err)
require.ErrorIs(t, ctx.Err(), context.Canceled)
_, err = os.Stat(filesystem.JoinPaths(storePath2, "portainer.db"))
require.NoError(t, err)
}
func TestRestoreArchive_WithPassword(t *testing.T) {
t.Parallel()
_, store1 := datastore.MustNewTestStore(t, true, false)
storePath1 := store1.GetConnection().GetStorePath()
gate := offlinegate.NewOfflineGate()
archivePath, err := CreateBackupArchive("restore-secret", gate, store1, storePath1)
require.NoError(t, err)
archiveData, err := os.ReadFile(archivePath)
require.NoError(t, err)
_, store2 := datastore.MustNewTestStore(t, true, false)
storePath2 := store2.GetConnection().GetStorePath()
ctx, cancel := context.WithCancel(t.Context())
err = RestoreArchive(bytes.NewReader(archiveData), "restore-secret", storePath2, gate, store2, cancel)
require.NoError(t, err)
require.ErrorIs(t, ctx.Err(), context.Canceled)
_, err = os.Stat(filesystem.JoinPaths(storePath2, "portainer.db"))
require.NoError(t, err)
}
func TestRestoreArchive_WrongPassword(t *testing.T) {
t.Parallel()
_, store1 := datastore.MustNewTestStore(t, true, false)
storePath1 := store1.GetConnection().GetStorePath()
gate := offlinegate.NewOfflineGate()
archivePath, err := CreateBackupArchive("correct-password", gate, store1, storePath1)
require.NoError(t, err)
archiveData, err := os.ReadFile(archivePath)
require.NoError(t, err)
_, store2 := datastore.MustNewTestStore(t, true, false)
storePath2 := store2.GetConnection().GetStorePath()
_, cancel := context.WithCancel(t.Context())
err = RestoreArchive(bytes.NewReader(archiveData), "wrong-password", storePath2, gate, store2, cancel)
require.Error(t, err)
}
+48 -12
View File
@@ -3,8 +3,10 @@ package backup
import (
"context"
"io"
"io/fs"
"os"
"path/filepath"
"regexp"
"time"
"github.com/pkg/errors"
@@ -14,6 +16,8 @@ import (
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/offlinegate"
"github.com/rs/zerolog/log"
)
var filesToRestore = append(filesToBackup, "portainer.db")
@@ -24,26 +28,35 @@ func RestoreArchive(archive io.Reader, password string, filestorePath string, ga
if password != "" {
archive, err = decrypt(archive, password)
if err != nil {
return errors.Wrap(err, "failed to decrypt the archive")
return errors.Wrap(err, "failed to decrypt the archive. Please ensure the password is correct and try again")
}
}
restorePath := filepath.Join(filestorePath, "restore", time.Now().Format("20060102150405"))
defer os.RemoveAll(filepath.Dir(restorePath))
defer func() {
if err := os.RemoveAll(filepath.Dir(restorePath)); err != nil {
log.Warn().Err(err).Msg("failed to clean up restore files")
}
}()
err = extractArchive(archive, restorePath)
if err != nil {
if err := extractArchive(archive, restorePath); err != nil {
return errors.Wrap(err, "cannot extract files from the archive. Please ensure the password is correct and try again")
}
unlock := gate.Lock()
defer unlock()
if err = datastore.Close(); err != nil {
if err := datastore.Close(); err != nil {
return errors.Wrap(err, "Failed to stop db")
}
if err = restoreFiles(restorePath, filestorePath); err != nil {
// At some point, backups were created containing a subdirectory, now we need to handle both
restorePath, err = getRestoreSourcePath(restorePath)
if err != nil {
return errors.Wrap(err, "failed to restore from backup. Portainer database missing from backup file")
}
if err := restoreFiles(restorePath, filestorePath); err != nil {
return errors.Wrap(err, "failed to restore the system state")
}
@@ -59,10 +72,29 @@ func extractArchive(r io.Reader, destinationDirPath string) error {
return archive.ExtractTarGz(r, destinationDirPath)
}
func getRestoreSourcePath(dir string) (string, error) {
// find portainer.db or portainer.edb file. Return the parent directory
var portainerdbRegex = regexp.MustCompile(`^portainer.e?db$`)
backupDirPath := dir
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if portainerdbRegex.MatchString(d.Name()) {
backupDirPath = filepath.Dir(path)
return filepath.SkipDir
}
return nil
})
return backupDirPath, err
}
func restoreFiles(srcDir string, destinationDir string) error {
for _, filename := range filesToRestore {
err := filesystem.CopyPath(filepath.Join(srcDir, filename), destinationDir)
if err != nil {
if err := filesystem.CopyPath(filepath.Join(srcDir, filename), destinationDir); err != nil {
return err
}
}
@@ -70,14 +102,18 @@ func restoreFiles(srcDir string, destinationDir string) error {
// TODO: This is very boltdb module specific once again due to the filename. Move to bolt module? Refactor for another day
// Prevent the possibility of having both databases. Remove any default new instance
os.Remove(filepath.Join(destinationDir, boltdb.DatabaseFileName))
os.Remove(filepath.Join(destinationDir, boltdb.EncryptedDatabaseFileName))
if err := os.Remove(filepath.Join(destinationDir, boltdb.DatabaseFileName)); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.Remove(filepath.Join(destinationDir, boltdb.EncryptedDatabaseFileName)); err != nil && !os.IsNotExist(err) {
return err
}
// Now copy the database. It'll be either portainer.db or portainer.edb
// Note: CopyPath does not return an error if the source file doesn't exist
err := filesystem.CopyPath(filepath.Join(srcDir, boltdb.EncryptedDatabaseFileName), destinationDir)
if err != nil {
if err := filesystem.CopyPath(filepath.Join(srcDir, boltdb.EncryptedDatabaseFileName), destinationDir); err != nil {
return err
}
-9
View File
@@ -1,9 +0,0 @@
package build
// Variables to be set during the build time
var BuildNumber string
var ImageTag string
var NodejsVersion string
var YarnVersion string
var WebpackVersion string
var GoVersion string
+61
View File
@@ -0,0 +1,61 @@
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"math/big"
chshare "github.com/jpillora/chisel/share"
)
var one = new(big.Int).SetInt64(1)
// GenerateGo119CompatibleKey This function is basically copied from chshare.GenerateKey.
func GenerateGo119CompatibleKey(seed string) ([]byte, error) {
r := chshare.NewDetermRand([]byte(seed))
priv, err := ecdsaGenerateKey(elliptic.P256(), r)
if err != nil {
return nil, err
}
b, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("Unable to marshal ECDSA private key: %w", err)
}
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}), nil
}
// This function is copied from Go1.19
func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) {
params := c.Params()
// Note that for P-521 this will actually be 63 bits more than the order, as
// division rounds down, but the extra bit is inconsequential.
b := make([]byte, params.N.BitLen()/8+8)
_, err = io.ReadFull(rand, b)
if err != nil {
return
}
k = new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, one)
k.Mod(k, n)
k.Add(k, one)
return
}
// This function is copied from Go1.19
func ecdsaGenerateKey(c elliptic.Curve, rand io.Reader) (*ecdsa.PrivateKey, error) {
k, err := randFieldElement(c, rand)
if err != nil {
return nil, err
}
priv := new(ecdsa.PrivateKey)
priv.Curve = c
priv.D = k
priv.X, priv.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}
+38
View File
@@ -0,0 +1,38 @@
package crypto
import (
"reflect"
"testing"
)
func TestGenerateGo119CompatibleKey(t *testing.T) {
t.Parallel()
type args struct {
seed string
}
tests := []struct {
name string
args args
want []byte
wantErr bool
}{
{
name: "Generate Go 1.19 compatible private key with a given seed",
args: args{seed: "94qh17MCIk8BOkiI"},
want: []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIHeohwk0Gy3RHVVViaHz7pz/HOiqA7fkv1FTM3mGgfT3oAoGCCqGSM49\nAwEHoUQDQgAEN7riX06xDsLNPuUmOvYFluNEakcFwZZRVvOcIYk/9VYnanDzW0Km\n8/BUUiKyJDuuGdS4fj9SlQ4iL8yBK01uKg==\n-----END EC PRIVATE KEY-----\n"),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GenerateGo119CompatibleKey(tt.args.seed)
if (err != nil) != tt.wantErr {
t.Errorf("GenerateGo119CompatibleKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GenerateGo119CompatibleKey()\ngot: Z %v\nwant: %v", got, tt.want)
}
})
}
}
-47
View File
@@ -1,47 +0,0 @@
package chisel
import (
portainer "github.com/portainer/portainer/api"
)
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *portainer.EdgeJob) {
service.mu.Lock()
tunnel := service.getTunnelDetails(endpointID)
existingJobIndex := -1
for idx, existingJob := range tunnel.Jobs {
if existingJob.ID == edgeJob.ID {
existingJobIndex = idx
break
}
}
if existingJobIndex == -1 {
tunnel.Jobs = append(tunnel.Jobs, *edgeJob)
} else {
tunnel.Jobs[existingJobIndex] = *edgeJob
}
service.mu.Unlock()
}
// RemoveEdgeJob will remove the specified Edge job from each tunnel it was registered with.
func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
service.mu.Lock()
for _, tunnel := range service.tunnelDetailsMap {
// Filter in-place
n := 0
for _, edgeJob := range tunnel.Jobs {
if edgeJob.ID != edgeJobID {
tunnel.Jobs[n] = edgeJob
n++
}
}
tunnel.Jobs = tunnel.Jobs[:n]
}
service.mu.Unlock()
}
+212 -112
View File
@@ -3,91 +3,142 @@ package chisel
import (
"context"
"fmt"
"log"
"io"
"net/http"
"sync"
"time"
"github.com/dchest/uniuri"
chserver "github.com/jpillora/chisel/server"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/pkg/schedule"
chserver "github.com/jpillora/chisel/server"
"github.com/jpillora/chisel/share/ccrypto"
"github.com/rs/zerolog/log"
)
const (
tunnelCleanupInterval = 10 * time.Second
requiredTimeout = 15 * time.Second
activeTimeout = 4*time.Minute + 30*time.Second
pingTimeout = 3 * time.Second
)
// Service represents a service to manage the state of multiple reverse tunnels.
// It is used to start a reverse tunnel server and to manage the connection status of each tunnel
// connected to the tunnel server.
type Service struct {
serverFingerprint string
serverPort string
tunnelDetailsMap map[portainer.EndpointID]*portainer.TunnelDetails
dataStore dataservices.DataStore
snapshotService portainer.SnapshotService
chiselServer *chserver.Server
shutdownCtx context.Context
ProxyManager *proxy.Manager
mu sync.Mutex
serverFingerprint string
serverPort string
activeTunnels map[portainer.EndpointID]*portainer.TunnelDetails
edgeJobs map[portainer.EndpointID][]portainer.EdgeJob
dataStore dataservices.DataStore
snapshotService portainer.SnapshotService
chiselServer *chserver.Server
shutdownCtx context.Context
ProxyManager *proxy.Manager
mu sync.RWMutex
fileService portainer.FileService
defaultCheckinInterval int
}
// NewService returns a pointer to a new instance of Service
func NewService(dataStore dataservices.DataStore, shutdownCtx context.Context) *Service {
func NewService(dataStore dataservices.DataStore, shutdownCtx context.Context, fileService portainer.FileService) *Service {
defaultCheckinInterval := portainer.DefaultEdgeAgentCheckinIntervalInSeconds
settings, err := dataStore.Settings().Settings()
if err == nil {
defaultCheckinInterval = settings.EdgeAgentCheckinInterval
} else {
log.Error().Err(err).Msg("unable to retrieve the settings from the database")
}
return &Service{
tunnelDetailsMap: make(map[portainer.EndpointID]*portainer.TunnelDetails),
dataStore: dataStore,
shutdownCtx: shutdownCtx,
activeTunnels: make(map[portainer.EndpointID]*portainer.TunnelDetails),
edgeJobs: make(map[portainer.EndpointID][]portainer.EdgeJob),
dataStore: dataStore,
shutdownCtx: shutdownCtx,
fileService: fileService,
defaultCheckinInterval: defaultCheckinInterval,
}
}
// pingAgent ping the given agent so that the agent can keep the tunnel alive
func (service *Service) pingAgent(endpointID portainer.EndpointID) error {
tunnel := service.GetTunnelDetails(endpointID)
requestURL := fmt.Sprintf("http://127.0.0.1:%d/ping", tunnel.Port)
endpoint, err := service.dataStore.Endpoint().Endpoint(endpointID)
if err != nil {
return err
}
tunnelAddr, err := service.TunnelAddr(endpoint)
if err != nil {
return err
}
requestURL := fmt.Sprintf("http://%s/ping", tunnelAddr)
req, err := http.NewRequest(http.MethodHead, requestURL, nil)
if err != nil {
return err
}
httpClient := &http.Client{
Timeout: 3 * time.Second,
Timeout: pingTimeout,
}
_, err = httpClient.Do(req)
return err
resp, err := httpClient.Do(req)
if err != nil {
return err
}
_, _ = io.Copy(io.Discard, resp.Body)
return resp.Body.Close()
}
// KeepTunnelAlive keeps the tunnel of the given environment for maxAlive duration, or until ctx is done
func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx context.Context, maxAlive time.Duration) {
go func() {
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: start for %.0f minutes]\n", endpointID, maxAlive.Minutes())
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
pingTicker := time.NewTicker(tunnelCleanupInterval)
defer pingTicker.Stop()
go service.keepTunnelAlive(endpointID, ctx, maxAlive)
}
for {
select {
case <-pingTicker.C:
service.SetTunnelStatusToActive(endpointID)
err := service.pingAgent(endpointID)
if err != nil {
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [warning: ping agent err=%s]\n", endpointID, err)
}
case <-maxAliveTicker.C:
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as %.0f minutes timeout]\n", endpointID, maxAlive.Minutes())
return
case <-ctx.Done():
err := ctx.Err()
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as err=%s]\n", endpointID, err)
return
func (service *Service) keepTunnelAlive(endpointID portainer.EndpointID, ctx context.Context, maxAlive time.Duration) {
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("max_alive_minutes", maxAlive.Minutes()).
Msg("KeepTunnelAlive: start")
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
pingTicker := time.NewTicker(tunnelCleanupInterval)
defer pingTicker.Stop()
for {
select {
case <-pingTicker.C:
service.UpdateLastActivity(endpointID)
if err := service.pingAgent(endpointID); err != nil {
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("KeepTunnelAlive: ping agent")
}
case <-maxAliveTicker.C:
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("timeout_minutes", maxAlive.Minutes()).
Msg("KeepTunnelAlive: tunnel keep alive timeout")
return
case <-ctx.Done():
err := ctx.Err()
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("KeepTunnelAlive: tunnel stop")
return
}
}()
}
}
// StartTunnelServer starts a tunnel server on the specified addr and port.
@@ -96,14 +147,14 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
// It starts the tunnel status verification process in the background.
// The snapshotter is used in the tunnel status verification process.
func (service *Service) StartTunnelServer(addr, port string, snapshotService portainer.SnapshotService) error {
keySeed, err := service.retrievePrivateKeySeed()
privateKeyFile, err := service.retrievePrivateKeyFile()
if err != nil {
return err
}
config := &chserver.Config{
Reverse: true,
KeySeed: keySeed,
KeyFile: privateKeyFile,
}
chiselServer, err := chserver.NewServer(config)
@@ -114,21 +165,21 @@ func (service *Service) StartTunnelServer(addr, port string, snapshotService por
service.serverFingerprint = chiselServer.GetFingerprint()
service.serverPort = port
err = chiselServer.Start(addr, port)
if err != nil {
if err := chiselServer.Start(addr, port); err != nil {
return err
}
service.chiselServer = chiselServer
// TODO: work-around Chisel default behavior.
// By default, Chisel will allow anyone to connect if no user exists.
username, password := generateRandomCredentials()
err = service.chiselServer.AddUser(username, password, "127.0.0.1")
if err != nil {
if err = service.chiselServer.AddUser(username, password, "127.0.0.1"); err != nil {
return err
}
service.snapshotService = snapshotService
go service.startTunnelVerificationLoop()
return nil
@@ -139,82 +190,138 @@ func (service *Service) StopTunnelServer() error {
return service.chiselServer.Close()
}
func (service *Service) retrievePrivateKeySeed() (string, error) {
var serverInfo *portainer.TunnelServerInfo
func (service *Service) retrievePrivateKeyFile() (string, error) {
privateKeyFile := service.fileService.GetDefaultChiselPrivateKeyPath()
serverInfo, err := service.dataStore.TunnelServer().Info()
if service.dataStore.IsErrObjectNotFound(err) {
keySeed := uniuri.NewLen(16)
if exists, _ := service.fileService.FileExists(privateKeyFile); exists {
log.Info().
Str("private-key", privateKeyFile).
Msg("found Chisel private key file on disk")
serverInfo = &portainer.TunnelServerInfo{
PrivateKeySeed: keySeed,
}
return privateKeyFile, nil
}
log.Debug().
Str("private-key", privateKeyFile).
Msg("chisel private key file does not exist")
privateKey, err := ccrypto.GenerateKey("")
if err != nil {
log.Error().
Err(err).
Msg("failed to generate chisel private key")
err := service.dataStore.TunnelServer().UpdateInfo(serverInfo)
if err != nil {
return "", err
}
} else if err != nil {
return "", err
}
return serverInfo.PrivateKeySeed, nil
if err = service.fileService.StoreChiselPrivateKey(privateKey); err != nil {
log.Error().
Err(err).
Msg("failed to save Chisel private key to disk")
return "", err
}
log.Info().
Str("private-key", privateKeyFile).
Msg("generated a new Chisel private key file")
return privateKeyFile, nil
}
func (service *Service) startTunnelVerificationLoop() {
log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
ticker := time.NewTicker(tunnelCleanupInterval)
log.Debug().
Float64("check_interval_seconds", tunnelCleanupInterval.Seconds()).
Msg("starting tunnel management process")
for {
select {
case <-ticker.C:
service.checkTunnels()
case <-service.shutdownCtx.Done():
log.Println("[DEBUG] Shutting down tunnel service")
if err := service.StopTunnelServer(); err != nil {
log.Printf("Stopped tunnel service: %s", err)
}
ticker.Stop()
return
schedule.RunOnInterval(service.shutdownCtx, tunnelCleanupInterval, service.checkTunnels, func() {
log.Debug().Msg("shutting down tunnel service")
if err := service.StopTunnelServer(); err != nil {
log.Debug().Err(err).Msg("stopped tunnel service")
}
}
})
}
// checkTunnels finds tunnels that need snapshots and processes them one at a time.
// For active tunnels missing an initial snapshot, it takes one without closing the tunnel.
// For tunnels idle past activeTimeout, it snapshots and closes them.
func (service *Service) checkTunnels() {
tunnels := make(map[portainer.EndpointID]portainer.TunnelDetails)
service.mu.Lock()
for key, tunnel := range service.tunnelDetailsMap {
tunnels[key] = *tunnel
}
service.mu.Unlock()
for endpointID, tunnel := range tunnels {
if tunnel.LastActivity.IsZero() || tunnel.Status == portainer.EdgeAgentIdle {
continue
}
service.mu.RLock()
for endpointID, tunnel := range service.activeTunnels {
elapsed := time.Since(tunnel.LastActivity)
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [message: environment tunnel monitoring]", endpointID, tunnel.Status, elapsed.Seconds())
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("last_activity_seconds", elapsed.Seconds()).
Msg("environment tunnel monitoring")
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() < requiredTimeout.Seconds() {
continue
} else if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() > requiredTimeout.Seconds() {
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: REQUIRED state timeout exceeded]", endpointID, tunnel.Status, elapsed.Seconds(), requiredTimeout.Seconds())
}
tunnelPort := tunnel.Port
if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() < activeTimeout.Seconds() {
continue
} else if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() > activeTimeout.Seconds() {
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: ACTIVE state timeout exceeded]", endpointID, tunnel.Status, elapsed.Seconds(), activeTimeout.Seconds())
if !tunnel.HasSnapshot && elapsed < activeTimeout {
service.mu.RUnlock()
err := service.snapshotEnvironment(endpointID, tunnel.Port)
if err != nil {
log.Printf("[ERROR] [snapshot] Unable to snapshot Edge environment (id: %d): %s", endpointID, err)
if endpointHasSnapshot(service.dataStore, endpointID) {
service.markSnapshotTaken(endpointID)
return
}
log.Debug().
Int("endpoint_id", int(endpointID)).
Msg("taking initial snapshot for active Edge environment")
if service.snapshotAndLog(endpointID, tunnelPort) {
service.markSnapshotTaken(endpointID)
}
return
}
service.SetTunnelStatusToIdle(portainer.EndpointID(endpointID))
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed < activeTimeout {
continue
}
service.mu.RUnlock()
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("last_activity_seconds", elapsed.Seconds()).
Float64("timeout_seconds", activeTimeout.Seconds()).
Msg("last activity timeout exceeded")
service.snapshotAndLog(endpointID, tunnelPort)
service.close(endpointID)
return
}
service.mu.RUnlock()
}
func (service *Service) snapshotAndLog(endpointID portainer.EndpointID, tunnelPort int) bool {
if err := service.snapshotEnvironment(endpointID, tunnelPort); err != nil {
log.Error().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("unable to snapshot Edge environment")
if service.dataStore.IsErrObjectNotFound(err) {
service.close(endpointID)
}
return false
}
return true
}
func (service *Service) markSnapshotTaken(endpointID portainer.EndpointID) {
service.mu.Lock()
defer service.mu.Unlock()
if tun, ok := service.activeTunnels[endpointID]; ok {
tun.HasSnapshot = true
}
}
@@ -224,14 +331,7 @@ func (service *Service) snapshotEnvironment(endpointID portainer.EndpointID, tun
return err
}
endpointURL := endpoint.URL
endpoint.URL = fmt.Sprintf("tcp://127.0.0.1:%d", tunnelPort)
err = service.snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
return err
}
endpoint.URL = endpointURL
return service.dataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
return service.snapshotService.SnapshotEndpoint(endpoint)
}
+238
View File
@@ -0,0 +1,238 @@
package chisel
import (
"context"
"errors"
"net"
"net/http"
"testing"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/pkg/fips"
"github.com/stretchr/testify/require"
)
func init() {
fips.InitFIPS(false)
}
type mockSnapshotService struct {
snapshotFn func(endpoint *portainer.Endpoint) error
}
func (m *mockSnapshotService) Start(_ context.Context) {}
func (m *mockSnapshotService) SetSnapshotInterval(_ string) error { return nil }
func (m *mockSnapshotService) SnapshotEndpoint(endpoint *portainer.Endpoint) error {
if m.snapshotFn != nil {
return m.snapshotFn(endpoint)
}
return nil
}
func (m *mockSnapshotService) FillSnapshotData(_ *portainer.Endpoint, _ bool) error { return nil }
func newEdgeEndpoint(id portainer.EndpointID) *portainer.Endpoint {
return &portainer.Endpoint{
ID: id,
EdgeID: "test-edge-id",
Type: portainer.EdgeAgentOnDockerEnvironment,
UserTrusted: true,
}
}
func TestPingAgentPanic(t *testing.T) {
t.Parallel()
endpoint := newEdgeEndpoint(1)
_, store := datastore.MustNewTestStore(t, false, true)
s := NewService(store, nil, nil)
defer func() {
require.Nil(t, recover())
}()
mux := http.NewServeMux()
mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(pingTimeout + 1*time.Second)
})
ln, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
require.NoError(t, err)
srv := &http.Server{Handler: mux}
errCh := make(chan error)
go func() {
errCh <- srv.Serve(ln)
}()
err = s.Open(endpoint)
require.NoError(t, err)
s.activeTunnels[endpoint.ID].Port = ln.Addr().(*net.TCPAddr).Port
require.Error(t, s.pingAgent(endpoint.ID))
require.NoError(t, srv.Shutdown(t.Context()))
require.ErrorIs(t, <-errCh, http.ErrServerClosed)
}
func TestOpenDefaultsHasSnapshotToFalse(t *testing.T) {
t.Parallel()
endpoint := newEdgeEndpoint(1)
_, store := datastore.MustNewTestStore(t, false, true)
s := NewService(store, nil, nil)
err := s.Open(endpoint)
require.NoError(t, err)
require.False(t, s.activeTunnels[endpoint.ID].HasSnapshot)
}
func TestCheckTunnelsSetsHasSnapshotWhenSnapshotExists(t *testing.T) {
t.Parallel()
endpoint := newEdgeEndpoint(2)
_, store := datastore.MustNewTestStore(t, false, true)
err := store.Endpoint().Create(endpoint)
require.NoError(t, err)
snap := &portainer.Snapshot{
EndpointID: endpoint.ID,
Docker: &portainer.DockerSnapshot{},
}
err = store.Snapshot().Create(snap)
require.NoError(t, err)
s := NewService(store, nil, nil)
s.activeTunnels[endpoint.ID] = &portainer.TunnelDetails{
Status: portainer.EdgeAgentManagementRequired,
Port: 50003,
LastActivity: time.Now(),
}
s.checkTunnels()
require.NotNil(t, s.activeTunnels[endpoint.ID], "tunnel must remain open")
require.True(t, s.activeTunnels[endpoint.ID].HasSnapshot)
}
func TestCheckTunnelsSnapshotsActiveEnvironmentAndKeepsTunnelAlive(t *testing.T) {
t.Parallel()
endpoint := newEdgeEndpoint(3)
_, store := datastore.MustNewTestStore(t, false, true)
err := store.Endpoint().Create(endpoint)
require.NoError(t, err)
snapshotCalled := false
svc := &mockSnapshotService{
snapshotFn: func(_ *portainer.Endpoint) error {
snapshotCalled = true
return nil
},
}
s := NewService(store, nil, nil)
s.snapshotService = svc
s.activeTunnels[endpoint.ID] = &portainer.TunnelDetails{
Status: portainer.EdgeAgentManagementRequired,
Port: 50000,
LastActivity: time.Now(),
}
s.checkTunnels()
require.True(t, snapshotCalled)
require.NotNil(t, s.activeTunnels[endpoint.ID], "tunnel must remain open after snapshot")
require.True(t, s.activeTunnels[endpoint.ID].HasSnapshot)
}
func TestCheckTunnelsKeepsHasSnapshotFalseOnSnapshotFailure(t *testing.T) {
t.Parallel()
endpoint := newEdgeEndpoint(4)
_, store := datastore.MustNewTestStore(t, false, true)
err := store.Endpoint().Create(endpoint)
require.NoError(t, err)
svc := &mockSnapshotService{
snapshotFn: func(_ *portainer.Endpoint) error {
return errors.New("snapshot failed")
},
}
s := NewService(store, nil, nil)
s.snapshotService = svc
s.activeTunnels[endpoint.ID] = &portainer.TunnelDetails{
Status: portainer.EdgeAgentManagementRequired,
Port: 50001,
LastActivity: time.Now(),
}
s.checkTunnels()
require.NotNil(t, s.activeTunnels[endpoint.ID], "tunnel must remain open after failed snapshot")
require.False(t, s.activeTunnels[endpoint.ID].HasSnapshot, "HasSnapshot must stay false after failure")
}
func TestCheckTunnelsClosesStaleEntryForDeletedEndpoint(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, false, true)
// Endpoint is not created in the store, simulates deletion while tunnel stays open.
s := NewService(store, nil, nil)
s.activeTunnels[1] = &portainer.TunnelDetails{
Status: portainer.EdgeAgentManagementRequired,
Port: 50010,
LastActivity: time.Now(),
}
s.checkTunnels()
require.Nil(t, s.activeTunnels[1], "stale tunnel for deleted endpoint must be removed immediately")
}
func TestCheckTunnelsClosesIdleTunnelAndSnapshots(t *testing.T) {
t.Parallel()
endpoint := newEdgeEndpoint(5)
_, store := datastore.MustNewTestStore(t, false, true)
err := store.Endpoint().Create(endpoint)
require.NoError(t, err)
snapshotCalled := false
svc := &mockSnapshotService{
snapshotFn: func(_ *portainer.Endpoint) error {
snapshotCalled = true
return nil
},
}
s := NewService(store, nil, nil)
s.snapshotService = svc
s.activeTunnels[endpoint.ID] = &portainer.TunnelDetails{
Status: portainer.EdgeAgentManagementRequired,
Port: 50002,
LastActivity: time.Now().Add(-(activeTimeout + time.Second)),
}
s.checkTunnels()
require.True(t, snapshotCalled)
require.Nil(t, s.activeTunnels[endpoint.ID], "tunnel must be closed after idle timeout")
}
+209 -130
View File
@@ -2,14 +2,22 @@ package chisel
import (
"encoding/base64"
"errors"
"fmt"
"math/rand"
"net"
"strings"
"time"
"github.com/dchest/uniuri"
"github.com/portainer/libcrypto"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/edge"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/portainer/portainer/pkg/libcrypto"
"github.com/portainer/portainer/pkg/librand"
"github.com/dchest/uniuri"
"github.com/rs/zerolog/log"
)
const (
@@ -17,153 +25,209 @@ const (
maxAvailablePort = 65535
)
var (
ErrNonEdgeEnv = errors.New("cannot open a tunnel for non-edge environments")
ErrAsyncEnv = errors.New("cannot open a tunnel for async edge environments")
ErrInvalidEnv = errors.New("cannot open a tunnel for an invalid environment")
)
// Open will mark the tunnel as REQUIRED so the agent opens it
func (s *Service) Open(endpoint *portainer.Endpoint) error {
if !endpointutils.IsEdgeEndpoint(endpoint) {
return ErrNonEdgeEnv
}
if endpoint.Edge.AsyncMode {
return ErrAsyncEnv
}
if endpoint.ID == 0 || endpoint.EdgeID == "" || !endpoint.UserTrusted {
return ErrInvalidEnv
}
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.activeTunnels[endpoint.ID]; ok {
return nil
}
defer cache.Del(endpoint.ID)
tun := &portainer.TunnelDetails{
Status: portainer.EdgeAgentManagementRequired,
Port: s.getUnusedPort(),
LastActivity: time.Now(),
}
username, password := generateRandomCredentials()
if s.chiselServer != nil {
authorizedRemote := fmt.Sprintf("^R:0.0.0.0:%d$", tun.Port)
if err := s.chiselServer.AddUser(username, password, authorizedRemote); err != nil {
return err
}
}
credentials, err := encryptCredentials(username, password, endpoint.EdgeID)
if err != nil {
return err
}
tun.Credentials = credentials
s.activeTunnels[endpoint.ID] = tun
return nil
}
// close removes the tunnel from the map so the agent will close it.
// The lock is released before cleaning up the chisel user and proxy to avoid
// blocking Config/Open callers while DeleteUser interacts with chisel internals.
func (s *Service) close(endpointID portainer.EndpointID) {
s.mu.Lock()
tun, ok := s.activeTunnels[endpointID]
if !ok {
s.mu.Unlock()
return
}
delete(s.activeTunnels, endpointID)
cache.Del(endpointID)
s.mu.Unlock()
if s.chiselServer != nil {
user, _, _ := strings.Cut(tun.Credentials, ":")
s.chiselServer.DeleteUser(user)
}
if s.ProxyManager != nil {
s.ProxyManager.DeleteEndpointProxy(endpointID)
}
}
// Config returns the tunnel details needed for the agent to connect
func (s *Service) Config(endpointID portainer.EndpointID) portainer.TunnelDetails {
s.mu.RLock()
defer s.mu.RUnlock()
if tun, ok := s.activeTunnels[endpointID]; ok {
return *tun
}
return portainer.TunnelDetails{Status: portainer.EdgeAgentIdle}
}
// TunnelAddr returns the address of the local tunnel, including the port, it
// will block until the tunnel is ready
func (s *Service) TunnelAddr(endpoint *portainer.Endpoint) (string, error) {
if err := s.Open(endpoint); err != nil {
return "", err
}
tun := s.Config(endpoint.ID)
checkinInterval := time.Duration(s.tryEffectiveCheckinInterval(endpoint)) * time.Second
for t0 := time.Now(); ; {
if time.Since(t0) > 2*checkinInterval {
s.close(endpoint.ID)
return "", errors.New("unable to open the tunnel")
}
// Check if the tunnel is established
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: tun.Port})
if err != nil {
time.Sleep(checkinInterval / 100)
continue
}
if err := conn.Close(); err != nil {
log.Warn().Err(err).Msg("failed to close tcp connection")
}
break
}
s.UpdateLastActivity(endpoint.ID)
return fmt.Sprintf("127.0.0.1:%d", tun.Port), nil
}
// tryEffectiveCheckinInterval avoids a potential deadlock by returning a
// previous known value after a timeout
func (s *Service) tryEffectiveCheckinInterval(endpoint *portainer.Endpoint) int {
ch := make(chan int, 1)
go func() {
ch <- edge.EffectiveCheckinInterval(s.dataStore, endpoint)
}()
select {
case <-time.After(50 * time.Millisecond):
s.mu.RLock()
defer s.mu.RUnlock()
return s.defaultCheckinInterval
case i := <-ch:
s.mu.Lock()
s.defaultCheckinInterval = i
s.mu.Unlock()
return i
}
}
// UpdateLastActivity sets the current timestamp to avoid the tunnel timeout
func (s *Service) UpdateLastActivity(endpointID portainer.EndpointID) {
s.mu.Lock()
defer s.mu.Unlock()
if tun, ok := s.activeTunnels[endpointID]; ok {
tun.LastActivity = time.Now()
}
}
// NOTE: it needs to be called with the lock acquired
// getUnusedPort is used to generate an unused random port in the dynamic port range.
// Dynamic ports (also called private ports) are 49152 to 65535.
func (service *Service) getUnusedPort() int {
port := randomInt(minAvailablePort, maxAvailablePort)
for _, tunnel := range service.tunnelDetailsMap {
for _, tunnel := range service.activeTunnels {
if tunnel.Port == port {
return service.getUnusedPort()
}
}
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port})
if err == nil {
if err := conn.Close(); err != nil {
log.Warn().Msg("failed to close tcp connection that checks if port is free")
}
log.Debug().
Int("port", port).
Msg("selected port is in use, trying a different one")
return service.getUnusedPort()
}
return port
}
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// NOTE: it needs to be called with the lock acquired
func (service *Service) getTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
if tunnel, ok := service.tunnelDetailsMap[endpointID]; ok {
return tunnel
}
tunnel := &portainer.TunnelDetails{
Status: portainer.EdgeAgentIdle,
}
service.tunnelDetailsMap[endpointID] = tunnel
return tunnel
}
// GetTunnelDetails returns information about the tunnel associated to an environment(endpoint).
func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) portainer.TunnelDetails {
service.mu.Lock()
defer service.mu.Unlock()
return *service.getTunnelDetails(endpointID)
}
// GetActiveTunnel retrieves an active tunnel which allows communicating with edge agent
func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (portainer.TunnelDetails, error) {
tunnel := service.GetTunnelDetails(endpoint.ID)
if tunnel.Status == portainer.EdgeAgentActive {
// update the LastActivity
service.SetTunnelStatusToActive(endpoint.ID)
}
if tunnel.Status == portainer.EdgeAgentIdle || tunnel.Status == portainer.EdgeAgentManagementRequired {
err := service.SetTunnelStatusToRequired(endpoint.ID)
if err != nil {
return portainer.TunnelDetails{}, fmt.Errorf("failed opening tunnel to endpoint: %w", err)
}
if endpoint.EdgeCheckinInterval == 0 {
settings, err := service.dataStore.Settings().Settings()
if err != nil {
return portainer.TunnelDetails{}, fmt.Errorf("failed fetching settings from db: %w", err)
}
endpoint.EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
}
time.Sleep(2 * time.Duration(endpoint.EdgeCheckinInterval) * time.Second)
}
return service.GetTunnelDetails(endpoint.ID), nil
}
// SetTunnelStatusToActive update the status of the tunnel associated to the specified environment(endpoint).
// It sets the status to ACTIVE.
func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID) {
service.mu.Lock()
tunnel := service.getTunnelDetails(endpointID)
tunnel.Status = portainer.EdgeAgentActive
tunnel.Credentials = ""
tunnel.LastActivity = time.Now()
service.mu.Unlock()
}
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified environment(endpoint).
// It sets the status to IDLE.
// It removes any existing credentials associated to the tunnel.
func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
service.mu.Lock()
tunnel := service.getTunnelDetails(endpointID)
tunnel.Status = portainer.EdgeAgentIdle
tunnel.Port = 0
tunnel.LastActivity = time.Now()
credentials := tunnel.Credentials
if credentials != "" {
tunnel.Credentials = ""
service.chiselServer.DeleteUser(strings.Split(credentials, ":")[0])
}
service.ProxyManager.DeleteEndpointProxy(endpointID)
service.mu.Unlock()
}
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified environment(endpoint).
// It sets the status to REQUIRED.
// If no port is currently associated to the tunnel, it will associate a random unused port to the tunnel
// and generate temporary credentials that can be used to establish a reverse tunnel on that port.
// Credentials are encrypted using the Edge ID associated to the environment(endpoint).
func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
tunnel := service.getTunnelDetails(endpointID)
service.mu.Lock()
defer service.mu.Unlock()
if tunnel.Port == 0 {
endpoint, err := service.dataStore.Endpoint().Endpoint(endpointID)
if err != nil {
return err
}
tunnel.Status = portainer.EdgeAgentManagementRequired
tunnel.Port = service.getUnusedPort()
tunnel.LastActivity = time.Now()
username, password := generateRandomCredentials()
authorizedRemote := fmt.Sprintf("^R:0.0.0.0:%d$", tunnel.Port)
err = service.chiselServer.AddUser(username, password, authorizedRemote)
if err != nil {
return err
}
credentials, err := encryptCredentials(username, password, endpoint.EdgeID)
if err != nil {
return err
}
tunnel.Credentials = credentials
}
return nil
return min + librand.Intn(max-min)
}
func generateRandomCredentials() (string, string) {
username := uniuri.NewLen(8)
password := uniuri.NewLen(8)
return username, password
}
@@ -177,3 +241,18 @@ func encryptCredentials(username, password, key string) (string, error) {
return base64.RawStdEncoding.EncodeToString(encryptedCredentials), nil
}
func endpointHasSnapshot(dataStore dataservices.DataStore, endpointID portainer.EndpointID) bool {
var hasSnapshot bool
_ = dataStore.ViewTx(func(tx dataservices.DataStoreTx) error {
s, err := tx.Snapshot().Read(endpointID)
if err != nil {
return err
}
hasSnapshot = s.Docker != nil || s.Kubernetes != nil
return nil
})
return hasSnapshot
}
+80
View File
@@ -0,0 +1,80 @@
package chisel
import (
"net"
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
type testSettingsService struct {
dataservices.SettingsService
}
func (s *testSettingsService) Settings() (*portainer.Settings, error) {
return &portainer.Settings{
EdgeAgentCheckinInterval: 1,
}, nil
}
type testStore struct {
dataservices.DataStore
}
func (s *testStore) Settings() dataservices.SettingsService {
return &testSettingsService{}
}
func TestGetUnusedPort(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
existingTunnels map[portainer.EndpointID]*portainer.TunnelDetails
expectedError error
}{
{
name: "simple case",
},
{
name: "existing tunnels",
existingTunnels: map[portainer.EndpointID]*portainer.TunnelDetails{
portainer.EndpointID(1): {
Port: 53072,
},
portainer.EndpointID(2): {
Port: 63072,
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
store := &testStore{}
s := NewService(store, nil, nil)
s.activeTunnels = tc.existingTunnels
port := s.getUnusedPort()
if port < 49152 || port > 65535 {
t.Fatalf("Expected port to be inbetween 49152 and 65535 but got %d", port)
}
for _, tun := range tc.existingTunnels {
if tun.Port == port {
t.Fatalf("returned port %d already has an existing tunnel", port)
}
}
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port})
if err == nil {
// Ignore error
_ = conn.Close()
t.Fatalf("expected port %d to be unused", port)
} else if !strings.Contains(err.Error(), "connection refused") {
t.Fatalf("unexpected error: %v", err)
}
})
}
}
+136 -56
View File
@@ -2,55 +2,43 @@ package cli
import (
"errors"
"log"
"os"
"path/filepath"
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"os"
"path/filepath"
"strings"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/alecthomas/kingpin/v2"
"github.com/rs/zerolog/log"
)
// Service implements the CLIService interface
type Service struct{}
var (
errInvalidEndpointProtocol = errors.New("Invalid environment protocol: Portainer only supports unix://, npipe:// or tcp://")
errSocketOrNamedPipeNotFound = errors.New("Unable to locate Unix socket or named pipe")
errInvalidSnapshotInterval = errors.New("Invalid snapshot interval")
errAdminPassExcludeAdminPassFile = errors.New("Cannot use --admin-password with --admin-password-file")
ErrInvalidEndpointProtocol = errors.New("Invalid environment protocol: Portainer only supports unix://, npipe:// or tcp://")
ErrSocketOrNamedPipeNotFound = errors.New("Unable to locate Unix socket or named pipe")
ErrInvalidSnapshotInterval = errors.New("Invalid snapshot interval")
ErrAdminPassExcludeAdminPassFile = errors.New("Cannot use --admin-password with --admin-password-file")
)
// ParseFlags parse the CLI flags and return a portainer.Flags struct
func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
kingpin.Version(version)
flags := &portainer.CLIFlags{
func CLIFlags() *portainer.CLIFlags {
return &portainer.CLIFlags{
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
AddrHTTPS: kingpin.Flag("bind-https", "Address and port to serve Portainer via https").Default(defaultHTTPSBindAddress).String(),
TunnelAddr: kingpin.Flag("tunnel-addr", "Address to serve the tunnel server").Default(defaultTunnelServerAddress).String(),
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
DemoEnvironment: kingpin.Flag("demo", "Demo environment").Bool(),
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
FeatureFlags: BoolPairs(kingpin.Flag("feat", "List of feature flags").Hidden()),
FeatureFlags: kingpin.Flag("feat", "List of feature flags").Envar(portainer.FeatureFlagEnvVar).Strings(),
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app (deprecated)").Bool(),
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
TLSSkipVerify: kingpin.Flag("tlsskipverify", "Disable TLS server verification").Default(defaultTLSSkipVerify).Bool(),
TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String(),
TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(),
TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).String(),
HTTPDisabled: kingpin.Flag("http-disabled", "Serve portainer only on https").Default(defaultHTTPDisabled).Bool(),
HTTPEnabled: kingpin.Flag("http-enabled", "Serve portainer on http").Default(defaultHTTPEnabled).Bool(),
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
Rollback: kingpin.Flag("rollback", "Rollback the database to the previous backup").Bool(),
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").String(),
AdminPassword: kingpin.Flag("admin-password", "Set admin password with provided hash").String(),
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
@@ -62,38 +50,127 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
MaxBatchSize: kingpin.Flag("max-batch-size", "Maximum size of a batch").Int(),
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("NOCOLOR", "PRETTY", "JSON"),
PullLimitCheckDisabled: kingpin.Flag("pull-limit-check-disabled", "Pull limit check").Envar(portainer.PullLimitCheckDisabledEnvVar).Default(defaultPullLimitCheckDisabled).Bool(),
TrustedOrigins: kingpin.Flag("trusted-origins", "List of trusted origins for CSRF protection. Separate multiple origins with a comma.").Envar(portainer.TrustedOriginsEnvVar).String(),
CSP: kingpin.Flag("csp", "Content Security Policy (CSP) header").Envar(portainer.CSPEnvVar).Default("true").Bool(),
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
func (Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
kingpin.Version(version)
var hasSSLFlag, hasSSLCertFlag, hasSSLKeyFlag bool
sslFlag := kingpin.Flag(
"ssl",
"Secure Portainer instance using SSL (deprecated)",
).Default(defaultSSL).IsSetByUser(&hasSSLFlag)
ssl := sslFlag.Bool()
sslCertFlag := kingpin.Flag(
"sslcert",
"Path to the SSL certificate used to secure the Portainer instance",
).IsSetByUser(&hasSSLCertFlag)
sslCert := sslCertFlag.String()
sslKeyFlag := kingpin.Flag(
"sslkey",
"Path to the SSL key used to secure the Portainer instance",
).IsSetByUser(&hasSSLKeyFlag)
sslKey := sslKeyFlag.String()
flags := CLIFlags()
var hasTLSFlag, hasTLSCertFlag, hasTLSKeyFlag bool
tlsFlag := kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).IsSetByUser(&hasTLSFlag)
flags.TLS = tlsFlag.Bool()
tlsCertFlag := kingpin.Flag(
"tlscert",
"Path to the TLS certificate file",
).Default(defaultTLSCertPath).IsSetByUser(&hasTLSCertFlag)
flags.TLSCert = tlsCertFlag.String()
tlsKeyFlag := kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).IsSetByUser(&hasTLSKeyFlag)
flags.TLSKey = tlsKeyFlag.String()
flags.TLSCacert = kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String()
var hasKubectlShellImageFlag bool
kubectlShellImageFlag := kingpin.Flag(
"kubectl-shell-image",
"Kubectl shell image",
).Envar(portainer.KubectlShellImageEnvVar).
Default(portainer.DefaultKubectlShellImage).
IsSetByUser(&hasKubectlShellImageFlag)
flags.KubectlShellImage = kubectlShellImageFlag.String()
kingpin.Parse()
_, kubectlShellImageEnvVarSet := os.LookupEnv(portainer.KubectlShellImageEnvVar)
flags.KubectlShellImageSet = hasKubectlShellImageFlag || kubectlShellImageEnvVarSet
if !filepath.IsAbs(*flags.Assets) {
ex, err := os.Executable()
if err != nil {
panic(err)
}
*flags.Assets = filepath.Join(filepath.Dir(ex), *flags.Assets)
}
// If the user didn't provide a tls flag remove the defaults to match previous behaviour
if !hasTLSFlag {
if !hasTLSCertFlag {
*flags.TLSCert = ""
}
if !hasTLSKeyFlag {
*flags.TLSKey = ""
}
}
if hasSSLFlag {
log.Warn().Msgf("the %q flag is deprecated. use %q instead.", sslFlag.Model().Name, tlsFlag.Model().Name)
if !hasTLSFlag {
flags.TLS = ssl
}
}
if hasSSLCertFlag {
log.Warn().Msgf("the %q flag is deprecated. use %q instead.", sslCertFlag.Model().Name, tlsCertFlag.Model().Name)
if !hasTLSCertFlag {
flags.TLSCert = sslCert
}
}
if hasSSLKeyFlag {
log.Warn().Msgf("the %q flag is deprecated. use %q instead.", sslKeyFlag.Model().Name, tlsKeyFlag.Model().Name)
if !hasTLSKeyFlag {
flags.TLSKey = sslKey
}
}
return flags, nil
}
// ValidateFlags validates the values of the flags.
func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
func (Service) ValidateFlags(flags *portainer.CLIFlags) error {
displayDeprecationWarnings(flags)
err := validateEndpointURL(*flags.EndpointURL)
if err != nil {
if err := ValidateEndpointURL(*flags.EndpointURL); err != nil {
return err
}
err = validateSnapshotInterval(*flags.SnapshotInterval)
if err != nil {
if err := ValidateSnapshotInterval(*flags.SnapshotInterval); err != nil {
return err
}
if *flags.AdminPassword != "" && *flags.AdminPasswordFile != "" {
return errAdminPassExcludeAdminPassFile
return ErrAdminPassExcludeAdminPassFile
}
return nil
@@ -101,40 +178,43 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
if *flags.NoAnalytics {
log.Println("Warning: 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.Println("Warning: 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.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")
}
}
func validateEndpointURL(endpointURL string) error {
if endpointURL != "" {
if !strings.HasPrefix(endpointURL, "unix://") && !strings.HasPrefix(endpointURL, "tcp://") && !strings.HasPrefix(endpointURL, "npipe://") {
return errInvalidEndpointProtocol
}
func ValidateEndpointURL(endpointURL string) error {
if endpointURL == "" {
return nil
}
if strings.HasPrefix(endpointURL, "unix://") || strings.HasPrefix(endpointURL, "npipe://") {
socketPath := strings.TrimPrefix(endpointURL, "unix://")
socketPath = strings.TrimPrefix(socketPath, "npipe://")
if _, err := os.Stat(socketPath); err != nil {
if os.IsNotExist(err) {
return errSocketOrNamedPipeNotFound
}
return err
if !strings.HasPrefix(endpointURL, "unix://") && !strings.HasPrefix(endpointURL, "tcp://") && !strings.HasPrefix(endpointURL, "npipe://") {
return ErrInvalidEndpointProtocol
}
if strings.HasPrefix(endpointURL, "unix://") || strings.HasPrefix(endpointURL, "npipe://") {
socketPath := strings.TrimPrefix(endpointURL, "unix://")
socketPath = strings.TrimPrefix(socketPath, "npipe://")
if _, err := os.Stat(socketPath); err != nil {
if os.IsNotExist(err) {
return ErrSocketOrNamedPipeNotFound
}
return err
}
}
return nil
}
func validateSnapshotInterval(snapshotInterval string) error {
if snapshotInterval != "" {
_, err := time.ParseDuration(snapshotInterval)
if err != nil {
return errInvalidSnapshotInterval
}
func ValidateSnapshotInterval(snapshotInterval string) error {
if snapshotInterval == "" {
return nil
}
if _, err := time.ParseDuration(snapshotInterval); err != nil {
return ErrInvalidSnapshotInterval
}
return nil
}
+263
View File
@@ -0,0 +1,263 @@
package cli
import (
"io"
"os"
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
zerolog "github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
)
func TestOptionParser(t *testing.T) {
p := Service{}
require.NotNil(t, p)
a := os.Args
defer func() { os.Args = a }()
os.Args = []string{"portainer", "--edge-compute"}
opts, err := p.ParseFlags("2.34.5")
require.NoError(t, err)
require.False(t, *opts.HTTPDisabled)
require.True(t, *opts.EnableEdgeComputeFeatures)
}
func TestParseKubectlShellImageFlag(t *testing.T) {
tests := []struct {
name string
args []string
envVars map[string]string
expectedKubectlShellImageSet bool
expectedKubectlShellFlag string
}{
{
name: "no flag, no env var",
expectedKubectlShellImageSet: false,
expectedKubectlShellFlag: portainer.DefaultKubectlShellImage,
},
{
name: "explicit flag",
args: []string{"portainer", "--kubectl-shell-image=myimage:v2"},
expectedKubectlShellImageSet: true,
expectedKubectlShellFlag: "myimage:v2",
},
{
name: "env var",
envVars: map[string]string{portainer.KubectlShellImageEnvVar: "myimage:v3"},
expectedKubectlShellImageSet: true,
expectedKubectlShellFlag: "myimage:v3",
},
{
name: "both env var and flag set",
args: []string{"portainer", "--kubectl-shell-image=myimage:v2"},
envVars: map[string]string{portainer.KubectlShellImageEnvVar: "myimage:v3"},
expectedKubectlShellImageSet: true,
expectedKubectlShellFlag: "myimage:v2",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if tc.args == nil {
tc.args = []string{"portainer"}
}
setOsArgs(t, tc.args)
for k, v := range tc.envVars {
t.Setenv(k, v)
}
flags, err := Service{}.ParseFlags("test-version")
require.NoError(t, err)
require.Equal(t, tc.expectedKubectlShellImageSet, flags.KubectlShellImageSet)
require.Equal(t, tc.expectedKubectlShellFlag, *flags.KubectlShellImage)
})
}
}
func TestParseTLSFlags(t *testing.T) {
testCases := []struct {
name string
args []string
expectedTLSFlag bool
expectedTLSCertFlag string
expectedTLSKeyFlag string
expectedLogMessages []string
}{
{
name: "no flags",
expectedTLSFlag: false,
expectedTLSCertFlag: "",
expectedTLSKeyFlag: "",
},
{
name: "only ssl flag",
args: []string{
"portainer",
"--ssl",
},
expectedTLSFlag: true,
expectedTLSCertFlag: "",
expectedTLSKeyFlag: "",
},
{
name: "only tls flag",
args: []string{
"portainer",
"--tlsverify",
},
expectedTLSFlag: true,
expectedTLSCertFlag: defaultTLSCertPath,
expectedTLSKeyFlag: defaultTLSKeyPath,
},
{
name: "partial ssl flags",
args: []string{
"portainer",
"--ssl",
"--sslcert=ssl-cert-flag-value",
},
expectedTLSFlag: true,
expectedTLSCertFlag: "ssl-cert-flag-value",
expectedTLSKeyFlag: "",
},
{
name: "partial tls flags",
args: []string{
"portainer",
"--tlsverify",
"--tlscert=tls-cert-flag-value",
},
expectedTLSFlag: true,
expectedTLSCertFlag: "tls-cert-flag-value",
expectedTLSKeyFlag: defaultTLSKeyPath,
},
{
name: "partial tls and ssl flags",
args: []string{
"portainer",
"--tlsverify",
"--tlscert=tls-cert-flag-value",
"--sslkey=ssl-key-flag-value",
},
expectedTLSFlag: true,
expectedTLSCertFlag: "tls-cert-flag-value",
expectedTLSKeyFlag: "ssl-key-flag-value",
},
{
name: "partial tls and ssl flags 2",
args: []string{
"portainer",
"--ssl",
"--tlscert=tls-cert-flag-value",
"--sslkey=ssl-key-flag-value",
},
expectedTLSFlag: true,
expectedTLSCertFlag: "tls-cert-flag-value",
expectedTLSKeyFlag: "ssl-key-flag-value",
},
{
name: "ssl flags",
args: []string{
"portainer",
"--ssl",
"--sslcert=ssl-cert-flag-value",
"--sslkey=ssl-key-flag-value",
},
expectedTLSFlag: true,
expectedTLSCertFlag: "ssl-cert-flag-value",
expectedTLSKeyFlag: "ssl-key-flag-value",
expectedLogMessages: []string{
"the \\\"ssl\\\" flag is deprecated. use \\\"tlsverify\\\" instead.",
"the \\\"sslcert\\\" flag is deprecated. use \\\"tlscert\\\" instead.",
"the \\\"sslkey\\\" flag is deprecated. use \\\"tlskey\\\" instead.",
},
},
{
name: "tls flags",
args: []string{
"portainer",
"--tlsverify",
"--tlscert=tls-cert-flag-value",
"--tlskey=tls-key-flag-value",
},
expectedTLSFlag: true,
expectedTLSCertFlag: "tls-cert-flag-value",
expectedTLSKeyFlag: "tls-key-flag-value",
},
{
name: "tls and ssl flags",
args: []string{
"portainer",
"--tlsverify",
"--tlscert=tls-cert-flag-value",
"--tlskey=tls-key-flag-value",
"--ssl",
"--sslcert=ssl-cert-flag-value",
"--sslkey=ssl-key-flag-value",
},
expectedTLSFlag: true,
expectedTLSCertFlag: "tls-cert-flag-value",
expectedTLSKeyFlag: "tls-key-flag-value",
expectedLogMessages: []string{
"the \\\"ssl\\\" flag is deprecated. use \\\"tlsverify\\\" instead.",
"the \\\"sslcert\\\" flag is deprecated. use \\\"tlscert\\\" instead.",
"the \\\"sslkey\\\" flag is deprecated. use \\\"tlskey\\\" instead.",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var logOutput strings.Builder
setupLogOutput(t, &logOutput)
if tc.args == nil {
tc.args = []string{"portainer"}
}
setOsArgs(t, tc.args)
s := Service{}
flags, err := s.ParseFlags("test-version")
if err != nil {
t.Fatalf("error parsing flags: %v", err)
}
if flags.TLS == nil {
t.Fatal("TLS flag was nil")
}
require.Equal(t, tc.expectedTLSFlag, *flags.TLS, "tlsverify flag didn't match")
require.Equal(t, tc.expectedTLSCertFlag, *flags.TLSCert, "tlscert flag didn't match")
require.Equal(t, tc.expectedTLSKeyFlag, *flags.TLSKey, "tlskey flag didn't match")
for _, expectedLogMessage := range tc.expectedLogMessages {
require.Contains(t, logOutput.String(), expectedLogMessage, "Log didn't contain expected message")
}
})
}
}
func setOsArgs(t *testing.T, args []string) {
t.Helper()
previousArgs := os.Args
os.Args = args
t.Cleanup(func() {
os.Args = previousArgs
})
}
func setupLogOutput(t *testing.T, w io.Writer) {
t.Helper()
oldLogger := zerolog.Logger
zerolog.Logger = zerolog.Output(w)
t.Cleanup(func() {
zerolog.Logger = oldLogger
})
}
+5 -6
View File
@@ -2,23 +2,22 @@ package cli
import (
"bufio"
"log"
"fmt"
"os"
"strings"
)
// Confirm starts a rollback db cli application
func Confirm(message string) (bool, error) {
log.Printf("%s [y/N]", message)
fmt.Printf("%s [y/N] ", message)
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return false, err
}
answer = strings.Replace(answer, "\n", "", -1)
answer = strings.ToLower(answer)
return answer == "y" || answer == "yes", nil
answer = strings.ReplaceAll(answer, "\n", "")
return strings.EqualFold(answer, "y") || strings.EqualFold(answer, "yes"), nil
}
+17 -17
View File
@@ -1,23 +1,23 @@
//go:build !windows
// +build !windows
package cli
const (
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "/data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "/certs/ca.pem"
defaultTLSCertPath = "/certs/cert.pem"
defaultTLSKeyPath = "/certs/key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "/data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "/certs/ca.pem"
defaultTLSCertPath = "/certs/cert.pem"
defaultTLSKeyPath = "/certs/key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultPullLimitCheckDisabled = "false"
)
+18 -17
View File
@@ -1,21 +1,22 @@
package cli
const (
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "C:\\data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "C:\\certs\\ca.pem"
defaultTLSCertPath = "C:\\certs\\cert.pem"
defaultTLSKeyPath = "C:\\certs\\key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultSnapshotInterval = "5m"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "C:\\data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "C:\\certs\\ca.pem"
defaultTLSCertPath = "C:\\certs\\cert.pem"
defaultTLSKeyPath = "C:\\certs\\key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultSnapshotInterval = "5m"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultPullLimitCheckDisabled = "false"
)
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"strings"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/alecthomas/kingpin/v2"
)
type pairList []portainer.Pair
-45
View File
@@ -1,45 +0,0 @@
package cli
import (
portainer "github.com/portainer/portainer/api"
"strings"
"gopkg.in/alecthomas/kingpin.v2"
)
type pairListBool []portainer.Pair
// Set implementation for a list of portainer.Pair
func (l *pairListBool) Set(value string) error {
p := new(portainer.Pair)
// default to true. example setting=true is equivalent to setting
parts := strings.SplitN(value, "=", 2)
if len(parts) != 2 {
p.Name = parts[0]
p.Value = "true"
} else {
p.Name = parts[0]
p.Value = parts[1]
}
*l = append(*l, *p)
return nil
}
// String implementation for a list of pair
func (l *pairListBool) String() string {
return ""
}
// IsCumulative implementation for a list of pair
func (l *pairListBool) IsCumulative() bool {
return true
}
func BoolPairs(s kingpin.Settings) (target *[]portainer.Pair) {
target = new([]portainer.Pair)
s.SetValue((*pairListBool)(target))
return
}
-29
View File
@@ -1,29 +0,0 @@
package main
import (
"log"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/sirupsen/logrus"
)
func importFromJson(fileService portainer.FileService, store *datastore.Store) {
// EXPERIMENTAL - if used with an incomplete json file, it will fail, as we don't have a way to default the model values
importFile := "/data/import.json"
if exists, _ := fileService.FileExists(importFile); exists {
if err := store.Import(importFile); err != nil {
logrus.WithError(err).Debugf("Import %s failed", importFile)
// TODO: should really rollback on failure, but then we have nothing.
} else {
logrus.Printf("Successfully imported %s to new portainer database", importFile)
}
// TODO: this is bad - its to ensure that any defaults that were broken in import, or migrations get set back to what we want
// I also suspect that everything from "Init to Init" is potentially a migration
err := store.Init()
if err != nil {
log.Fatalf("Failed initializing data store: %v", err)
}
}
}
-20
View File
@@ -1,20 +0,0 @@
package main
import (
"log"
"github.com/sirupsen/logrus"
)
func configureLogger() {
logger := logrus.New() // logger is to implicitly substitute stdlib's log
log.SetOutput(logger.Writer())
formatter := &logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true}
logger.SetFormatter(formatter)
logrus.SetFormatter(formatter)
logger.SetLevel(logrus.DebugLevel)
logrus.SetLevel(logrus.DebugLevel)
}
+385 -410
View File
File diff suppressed because it is too large Load Diff
+133 -84
View File
@@ -1,110 +1,159 @@
package main
import (
"fmt"
"os"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/stretchr/testify/assert"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/stretchr/testify/require"
)
type mockKingpinSetting string
func Test_resolveSetupToken(t *testing.T) {
t.Parallel()
func (m mockKingpinSetting) SetValue(value kingpin.Value) {
value.Set(string(m))
t.Run("admin already exists — returns empty token", func(t *testing.T) {
admin := portainer.User{Role: portainer.AdministratorRole}
store := testhelpers.NewDatastore(testhelpers.WithUsers([]portainer.User{admin}))
token, err := resolveSetupToken(store, "")
require.NoError(t, err)
assert.Empty(t, token)
})
t.Run("no admin — generates a 64-char hex token", func(t *testing.T) {
store := testhelpers.NewDatastore(testhelpers.WithUsers([]portainer.User{}))
token, err := resolveSetupToken(store, "")
require.NoError(t, err)
assert.Len(t, token, 64)
token2, err := resolveSetupToken(store, "")
require.NoError(t, err)
assert.NotEqual(t, token, token2)
})
t.Run("no admin — uses provided token", func(t *testing.T) {
store := testhelpers.NewDatastore(testhelpers.WithUsers([]portainer.User{}))
token, err := resolveSetupToken(store, "mysecrettoken")
require.NoError(t, err)
assert.Equal(t, "mysecrettoken", token)
})
t.Run("admin already exists — ignores provided token", func(t *testing.T) {
admin := portainer.User{Role: portainer.AdministratorRole}
store := testhelpers.NewDatastore(testhelpers.WithUsers([]portainer.User{admin}))
token, err := resolveSetupToken(store, "mysecrettoken")
require.NoError(t, err)
assert.Empty(t, token)
})
}
func Test_enableFeaturesFromFlags(t *testing.T) {
is := assert.New(t)
const secretFileName = "secret.txt"
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
func createPasswordFile(t *testing.T, secretPath, password string) string {
err := os.WriteFile(secretPath, []byte(password), 0o600)
require.NoError(t, err)
return secretPath
}
func TestLoadEncryptionSecretKey(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
secretPath := filesystem.JoinPaths(tempDir, secretFileName)
// first pointing to file that does not exist, gives nil hash (no encryption)
encryptionKey := loadEncryptionSecretKey(secretPath)
require.Nil(t, encryptionKey)
// point to a directory instead of a file
encryptionKey = loadEncryptionSecretKey(tempDir)
require.Nil(t, encryptionKey)
password := "portainer@1234"
createPasswordFile(t, secretPath, password)
encryptionKey = loadEncryptionSecretKey(secretPath)
require.NotNil(t, encryptionKey)
// should be 32 bytes for aes256 encryption
require.Len(t, encryptionKey, 32)
}
func TestUpdateSettingsFromFlags_KubectlShellImage(t *testing.T) {
const existingImage = "existing-image:v1"
const newImage = "new-image:v2"
emptyString := ""
falseBool := false
var emptyLabels []portainer.Pair
tests := []struct {
featureFlag string
isSupported bool
name string
imageSet bool
flagImage string
expectedKubectlShellImage string
}{
{"test", false},
{
name: "flag not set — DB image unchanged",
imageSet: false,
flagImage: portainer.DefaultKubectlShellImage,
expectedKubectlShellImage: existingImage,
},
{
name: "flag set — DB image updated",
imageSet: true,
flagImage: newImage,
expectedKubectlShellImage: newImage,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s succeeds:%v", test.featureFlag, test.isSupported), func(t *testing.T) {
mockKingpinSetting := mockKingpinSetting(test.featureFlag)
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
err := enableFeaturesFromFlags(store, flags)
if test.isSupported {
is.NoError(err)
} else {
is.Error(err)
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
store := testhelpers.NewDatastore(
testhelpers.WithSettingsService(&portainer.Settings{
KubectlShellImage: existingImage,
}),
testhelpers.WithSSLSettingsService(&portainer.SSLSettings{}),
)
flags := &portainer.CLIFlags{
SnapshotInterval: &emptyString,
Logo: &emptyString,
EnableEdgeComputeFeatures: &falseBool,
Templates: &emptyString,
Labels: &emptyLabels,
HTTPDisabled: &falseBool,
HTTPEnabled: &falseBool,
}
flags.KubectlShellImage = &tc.flagImage
flags.KubectlShellImageSet = tc.imageSet
err := updateSettingsFromFlags(store, flags)
require.NoError(t, err)
settings, err := store.Settings().Settings()
require.NoError(t, err)
require.Equal(t, tc.expectedKubectlShellImage, settings.KubectlShellImage)
})
}
t.Run("passes for all supported feature flags", func(t *testing.T) {
for _, flag := range portainer.SupportedFeatureFlags {
mockKingpinSetting := mockKingpinSetting(flag)
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
err := enableFeaturesFromFlags(store, flags)
is.NoError(err)
}
})
}
const FeatTest portainer.Feature = "optional-test"
func optionalFunc(dataStore dataservices.DataStore) string {
// TODO: this is a code smell - finding out if a feature flag is enabled should not require having access to the store, and asking for a settings obj.
// ideally, the `if` should look more like:
// if featureflags.FlagEnabled(FeatTest) {}
settings, err := dataStore.Settings().Settings()
if err != nil {
return err.Error()
func TestDBSecretPath(t *testing.T) {
t.Parallel()
tests := []struct {
keyFilenameFlag string
expected string
}{
{keyFilenameFlag: "secret.txt", expected: "/run/secrets/secret.txt"},
{keyFilenameFlag: "/tmp/secret.txt", expected: "/tmp/secret.txt"},
{keyFilenameFlag: "/run/secrets/secret.txt", expected: "/run/secrets/secret.txt"},
{keyFilenameFlag: "./secret.txt", expected: "/run/secrets/secret.txt"},
{keyFilenameFlag: "../secret.txt", expected: "/run/secret.txt"},
{keyFilenameFlag: "foo/bar/secret.txt", expected: "/run/secrets/foo/bar/secret.txt"},
}
if settings.FeatureFlagSettings[FeatTest] {
return "enabled"
for _, test := range tests {
assert.Equal(t, test.expected, dbSecretPath(test.keyFilenameFlag))
}
return "disabled"
}
func Test_optionalFeature(t *testing.T) {
portainer.SupportedFeatureFlags = append(portainer.SupportedFeatureFlags, FeatTest)
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
// Enable the test feature
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
mockKingpinSetting := mockKingpinSetting(FeatTest)
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
err := enableFeaturesFromFlags(store, flags)
is.NoError(err)
is.Equal("enabled", optionalFunc(store))
})
// Same store, so the feature flag should still be enabled
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
is.Equal("enabled", optionalFunc(store))
})
// disable the test feature
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
mockKingpinSetting := mockKingpinSetting(FeatTest + "=false")
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
err := enableFeaturesFromFlags(store, flags)
is.NoError(err)
is.Equal("disabled", optionalFunc(store))
})
// Same store, so feature flag should still be disabled
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
is.Equal("disabled", optionalFunc(store))
})
}
+148
View File
@@ -0,0 +1,148 @@
// Package concurrent provides utilities for running multiple functions concurrently in Go.
// For example, many kubernetes calls can take a while to fulfill. Oftentimes in Portainer
// we need to get a list of objects from multiple kubernetes REST APIs. We can often call these
// apis concurrently to speed up the response time.
// This package provides a clean way to do just that.
//
// Examples:
// The ConfigMaps and Secrets function converted using concurrent.Run.
/*
// GetConfigMapsAndSecrets gets all the ConfigMaps AND all the Secrets for a
// given namespace in a k8s endpoint. The result is a list of both config maps
// and secrets. The IsSecret boolean property indicates if a given struct is a
// secret or configmap.
func (kcl *KubeClient) GetConfigMapsAndSecrets(namespace string) ([]models.K8sConfigMapOrSecret, error) {
// use closures to capture the current kube client and namespace by declaring wrapper functions
// that match the interface signature for concurrent.Func
listConfigMaps := func(ctx context.Context) (any, error) {
return kcl.cli.CoreV1().ConfigMaps(namespace).List(context.Background(), meta.ListOptions{})
}
listSecrets := func(ctx context.Context) (any, error) {
return kcl.cli.CoreV1().Secrets(namespace).List(context.Background(), meta.ListOptions{})
}
// run the functions concurrently and wait for results. We can also pass in a context to cancel.
// e.g. Deadline timer.
results, err := concurrent.Run(context.TODO(), listConfigMaps, listSecrets)
if err != nil {
return nil, err
}
var configMapList *core.ConfigMapList
var secretList *core.SecretList
for _, r := range results {
switch v := r.Result.(type) {
case *core.ConfigMapList:
configMapList = v
case *core.SecretList:
secretList = v
}
}
// TODO: Applications
var combined []models.K8sConfigMapOrSecret
for _, m := range configMapList.Items {
var cm models.K8sConfigMapOrSecret
cm.UID = string(m.UID)
cm.Name = m.Name
cm.Namespace = m.Namespace
cm.Annotations = m.Annotations
cm.Data = m.Data
cm.CreationDate = m.CreationTimestamp.Time.UTC().Format(time.RFC3339)
combined = append(combined, cm)
}
for _, s := range secretList.Items {
var secret models.K8sConfigMapOrSecret
secret.UID = string(s.UID)
secret.Name = s.Name
secret.Namespace = s.Namespace
secret.Annotations = s.Annotations
secret.Data = msbToMss(s.Data)
secret.CreationDate = s.CreationTimestamp.Time.UTC().Format(time.RFC3339)
secret.IsSecret = true
secret.SecretType = string(s.Type)
combined = append(combined, secret)
}
return combined, nil
}
*/
package concurrent
import (
"context"
"sync"
)
// Result contains the result and any error returned from running a client task function
type Result struct {
Result any // the result of running the task function
Err error // any error that occurred while running the task function
}
// Func is a function returns a result or error
type Func func(ctx context.Context) (any, error)
// Run runs a list of functions returns the results
func Run(ctx context.Context, maxConcurrency int, tasks ...Func) ([]Result, error) {
var wg sync.WaitGroup
resultsChan := make(chan Result, len(tasks))
taskChan := make(chan Func, len(tasks))
localCtx, cancelCtx := context.WithCancel(ctx)
defer cancelCtx()
runTask := func() {
defer wg.Done()
for fn := range taskChan {
result, err := fn(localCtx)
resultsChan <- Result{Result: result, Err: err}
}
}
// Set maxConcurrency to the number of tasks if zero or negative
if maxConcurrency <= 0 {
maxConcurrency = len(tasks)
}
// Start worker goroutines
for range maxConcurrency {
wg.Add(1)
go runTask()
}
// Add tasks to the task channel
for _, fn := range tasks {
taskChan <- fn
}
// Close the task channel to signal workers to stop when all tasks are done
close(taskChan)
// Wait for all workers to complete
wg.Wait()
close(resultsChan)
// Collect the results and cancel on error
results := make([]Result, 0, len(tasks))
for r := range resultsChan {
if r.Err != nil {
cancelCtx()
return nil, r.Err
}
results = append(results, r)
}
return results, nil
}
+149
View File
@@ -0,0 +1,149 @@
package concurrent
import (
"context"
"errors"
"sync/atomic"
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/require"
)
func TestRun_AllSucceed(t *testing.T) {
t.Parallel()
fn1 := func(ctx context.Context) (any, error) { return "one", nil }
fn2 := func(ctx context.Context) (any, error) { return "two", nil }
fn3 := func(ctx context.Context) (any, error) { return "three", nil }
results, err := Run(t.Context(), 0, fn1, fn2, fn3)
require.NoError(t, err)
require.Len(t, results, 3)
values := make([]string, 0, len(results))
for _, r := range results {
values = append(values, r.Result.(string))
}
require.ElementsMatch(t, []string{"one", "two", "three"}, values)
}
func TestRun_OneError(t *testing.T) {
t.Parallel()
sentinel := errors.New("task failed")
fn1 := func(ctx context.Context) (any, error) { return "ok", nil }
fn2 := func(ctx context.Context) (any, error) { return nil, sentinel }
_, err := Run(t.Context(), 0, fn1, fn2)
require.ErrorIs(t, err, sentinel)
}
func TestRun_NoTasks(t *testing.T) {
t.Parallel()
results, err := Run(t.Context(), 0)
require.NoError(t, err)
require.Empty(t, results)
}
func TestRun_MaxConcurrency(t *testing.T) {
t.Parallel()
const numTasks = 10
var peak atomic.Int32
var active atomic.Int32
task := func(ctx context.Context) (any, error) {
current := active.Add(1)
if current > peak.Load() {
peak.Store(current)
}
time.Sleep(10 * time.Millisecond)
active.Add(-1)
return nil, nil
}
tasks := make([]Func, numTasks)
for i := range tasks {
tasks[i] = task
}
synctest.Test(t, func(t *testing.T) {
results, err := Run(t.Context(), 3, tasks...)
require.NoError(t, err)
require.Len(t, results, numTasks)
require.LessOrEqual(t, peak.Load(), int32(3))
})
}
func TestRun_ZeroConcurrencyUsesAllTasks(t *testing.T) {
t.Parallel()
const numTasks = 5
var peak atomic.Int32
var active atomic.Int32
task := func(ctx context.Context) (any, error) {
current := active.Add(1)
if current > peak.Load() {
peak.Store(current)
}
time.Sleep(20 * time.Millisecond)
active.Add(-1)
return nil, nil
}
tasks := make([]Func, numTasks)
for i := range tasks {
tasks[i] = task
}
synctest.Test(t, func(t *testing.T) {
results, err := Run(t.Context(), 0, tasks...)
require.NoError(t, err)
require.Len(t, results, numTasks)
require.Equal(t, int32(numTasks), peak.Load())
})
}
func TestRun_ContextCancelledBeforeStart(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(t.Context())
cancel()
called := atomic.Bool{}
fn := func(ctx context.Context) (any, error) {
called.Store(true)
return nil, ctx.Err()
}
_, err := Run(ctx, 1, fn, fn, fn)
require.Error(t, err)
}
func TestRun_ContextPassedToTasks(t *testing.T) {
t.Parallel()
type key struct{}
ctx := context.WithValue(t.Context(), key{}, "testvalue")
fn := func(ctx context.Context) (any, error) {
return ctx.Value(key{}), nil
}
results, err := Run(ctx, 0, fn)
require.NoError(t, err)
require.Equal(t, "testvalue", results[0].Result)
}
+32 -16
View File
@@ -4,10 +4,36 @@ import (
"io"
)
type ReadTransaction interface {
GetObject(bucketName string, key []byte, object any) error
GetRawBytes(bucketName string, key []byte) ([]byte, error)
GetAll(bucketName string, obj any, append func(o any) (any, error)) error
GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, append func(o any) (any, error)) error
KeyExists(bucketName string, key []byte) (bool, error)
}
type Transaction interface {
ReadTransaction
SetServiceName(bucketName string) error
UpdateObject(bucketName string, key []byte, object any) error
DeleteObject(bucketName string, key []byte) error
CreateObject(bucketName string, fn func(uint64) (int, any)) error
CreateObjectWithId(bucketName string, id int, obj any) error
CreateObjectWithStringId(bucketName string, id []byte, obj any) error
DeleteAllObjects(bucketName string, obj any, matching func(o any) (id int, ok bool)) error
GetNextIdentifier(bucketName string) int
}
type Connection interface {
Transaction
Open() error
Close() error
UpdateTx(fn func(Transaction) error) error
ViewTx(fn func(Transaction) error) error
// write the db contents to filename as json (the schema needs defining)
ExportRaw(filename string) error
@@ -16,25 +42,15 @@ type Connection interface {
GetDatabaseFileName() string
GetDatabaseFilePath() string
GetStorePath() string
GetDatabaseFileSize() (int64, error)
IsEncryptedStore() bool
NeedsEncryptionMigration() (bool, error)
SetEncrypted(encrypted bool)
SetEncrypted(encrypted bool) error
SetServiceName(bucketName string) error
GetObject(bucketName string, key []byte, object interface{}) error
UpdateObject(bucketName string, key []byte, object interface{}) error
DeleteObject(bucketName string, key []byte) error
DeleteAllObjects(bucketName string, matching func(o interface{}) (id int, ok bool)) error
GetNextIdentifier(bucketName string) int
CreateObject(bucketName string, fn func(uint64) (int, interface{})) error
CreateObjectWithId(bucketName string, id int, obj interface{}) error
CreateObjectWithStringId(bucketName string, id []byte, obj interface{}) error
CreateObjectWithSetSequence(bucketName string, id int, obj interface{}) error
GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
BackupMetadata() (map[string]any, error)
RestoreMetadata(s map[string]any) error
UpdateObjectFunc(bucketName string, key []byte, object any, updateFn func()) error
ConvertToKey(v int) []byte
BackupMetadata() (map[string]interface{}, error)
RestoreMetadata(s map[string]interface{}) error
}
+201
View File
@@ -0,0 +1,201 @@
package containerautomation
import (
"context"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/rs/zerolog/log"
)
const (
// retryWindow is the rolling window over which max restarts per container are counted.
retryWindow = 10 * time.Minute
// restartCooldown is the minimum delay between two restarts of the same container,
// giving its healthcheck time to recover before we try again.
restartCooldown = 60 * time.Second
// endpointTimeout bounds the container-list call for a single endpoint.
endpointTimeout = 30 * time.Second
// restartTimeoutBuffer is added on top of a container's stop-timeout to derive
// the deadline of its own restart context, leaving room for the engine to kill
// and start the container after the graceful stop window elapses.
restartTimeoutBuffer = 15 * time.Second
)
// retryState tracks restart accounting for a single container across ticks.
type retryState struct {
attempts int
windowStart time.Time
lastRestart time.Time
}
// retryPolicy holds the cooldown/window parameters applied to a container.
type retryPolicy struct {
maxRetries int
window time.Duration
cooldown time.Duration
}
// decideRestart is a pure function that decides whether an unhealthy container
// should be restarted now, given its current retry state and policy. It returns
// the decision and the updated state to persist.
//
// Rules, in order:
// - reset the window (and attempts) when the window has elapsed;
// - deny while still within the cooldown since the last restart;
// - deny once the max number of restarts in the current window is reached;
// - otherwise restart, incrementing the attempt counter.
func decideRestart(state retryState, policy retryPolicy, now time.Time) (bool, retryState) {
if state.windowStart.IsZero() || now.Sub(state.windowStart) >= policy.window {
state.windowStart = now
state.attempts = 0
}
if !state.lastRestart.IsZero() && now.Sub(state.lastRestart) < policy.cooldown {
return false, state
}
if state.attempts >= policy.maxRetries {
return false, state
}
state.attempts++
state.lastRestart = now
return true, state
}
// heal runs a single auto-heal pass over every reachable Docker endpoint.
// It is registered with the scheduler and guarded against overlapping ticks by
// the Service. Errors are logged per endpoint/container so one failure does not
// abort the whole pass; it always returns nil so the scheduler keeps the job.
func (s *Service) heal() error {
if !s.running.CompareAndSwap(false, true) {
log.Debug().Msg("auto-heal: previous run still in progress, skipping tick")
return nil
}
defer s.running.Store(false)
scope := s.scope()
endpoints, err := s.dataStore.Endpoint().Endpoints()
if err != nil {
log.Warn().Err(err).Msg("auto-heal: unable to list environments")
return nil
}
for i := range endpoints {
endpoint := &endpoints[i]
// M1 scope: native Docker endpoints only. Kubernetes is not applicable and
// Edge/async endpoints are not reachable synchronously from the scheduler.
if !endpointutils.IsDockerEndpoint(endpoint) || endpointutils.IsEdgeEndpoint(endpoint) {
continue
}
// Per-endpoint opt-out (M5): skip environments where automation is disabled,
// independently of the global switch. Zero value participates, so existing
// installs are unaffected.
if !AutomationEnabledForEndpoint(endpoint) {
log.Debug().Int("endpoint_id", int(endpoint.ID)).
Msg("auto-heal: automation disabled for this environment, skipping")
continue
}
s.healEndpoint(endpoint, scope)
}
// Drop retry state only for containers whose retry window has fully elapsed
// since their last restart. A container that briefly leaves the unhealthy
// filter (e.g. while "starting" after a restart) keeps its accounting, so the
// cooldown / max-retries storm guard survives flapping.
s.pruneRetries(time.Now())
return nil
}
// healEndpoint restarts the in-scope unhealthy containers of a single endpoint.
func (s *Service) healEndpoint(endpoint *portainer.Endpoint, scope string) {
endpointID := int(endpoint.ID)
// Swarm note (M1 limitation): we connect to the endpoint's primary node only
// (nodeName ""). Containers scheduled on other Swarm nodes are not healed here;
// per-node iteration is deferred to a later milestone.
clientTimeout := endpointTimeout
cli, err := s.clientFactory.CreateClient(endpoint, "", &clientTimeout)
if err != nil {
log.Warn().Err(err).Int("endpoint_id", endpointID).Msg("auto-heal: unable to create Docker client")
return
}
defer cli.Close()
listCtx, cancel := context.WithTimeout(s.baseCtx, endpointTimeout)
defer cancel()
// List running unhealthy containers only (All:false). Docker keeps
// Health.Status=="unhealthy" on stopped containers, so listing with All:true
// would let us "restart" (i.e. start) an intentionally-stopped container.
listFilters := filters.NewArgs(filters.Arg("health", "unhealthy"))
containers, err := cli.ContainerList(listCtx, container.ListOptions{All: false, Filters: listFilters})
if err != nil {
log.Warn().Err(err).Int("endpoint_id", endpointID).Msg("auto-heal: unable to list containers")
return
}
s.healContainers(cli, endpoint, scope, containers)
}
// healContainers applies the restart decision + heal-restart to each listed
// unhealthy container of an endpoint. It is split out from healEndpoint (which
// creates the client and lists the containers) so the restart loop can be
// exercised with a fake dockerClient in tests. cli is typed as the interface;
// the concrete *dockerclient.Client returned by CreateClient satisfies it.
func (s *Service) healContainers(cli dockerClient, endpoint *portainer.Endpoint, scope string, containers []container.Summary) {
endpointID := int(endpoint.ID)
for _, c := range containers {
if !InScope(scope, c.Labels) {
continue
}
policy := retryPolicy{
maxRetries: MaxRetries(c.Labels),
window: retryWindow,
cooldown: restartCooldown,
}
ok, newState := decideRestart(s.getRetry(c.ID), policy, time.Now())
s.setRetry(c.ID, newState)
if !ok {
log.Debug().Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-heal: restart skipped (cooldown or max retries reached)")
continue
}
timeout := StopTimeout(c.Labels)
// Each restart gets its own context, bounded by the container's stop-timeout
// plus a buffer, so one slow restart cannot starve the others and a hung
// engine call is bounded independently of the list deadline.
restartTimeout := time.Duration(timeout)*time.Second + restartTimeoutBuffer
restartCtx, restartCancel := context.WithTimeout(s.baseCtx, restartTimeout)
err := cli.ContainerRestart(restartCtx, c.ID, container.StopOptions{Timeout: &timeout})
restartCancel()
if err != nil {
log.Warn().Err(err).Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-heal: failed to restart unhealthy container")
continue
}
log.Info().Str("container_id", c.ID).Int("endpoint_id", endpointID).Int("attempt", newState.attempts).
Msg("auto-heal: restarted unhealthy container")
s.notifier.Notify(Event{
Kind: EventHealRestarted, EndpointID: endpointID, ContainerID: c.ID, ContainerName: containerName(c.Names),
Message: "restarted unhealthy container",
})
}
}
+137
View File
@@ -0,0 +1,137 @@
package containerautomation
import (
"testing"
"time"
)
func TestDecideRestart(t *testing.T) {
policy := retryPolicy{
maxRetries: 3,
window: 10 * time.Minute,
cooldown: 60 * time.Second,
}
base := time.Date(2026, 6, 28, 12, 0, 0, 0, time.UTC)
t.Run("first restart on empty state", func(t *testing.T) {
ok, state := decideRestart(retryState{}, policy, base)
if !ok {
t.Fatal("expected restart on first unhealthy observation")
}
if state.attempts != 1 {
t.Errorf("attempts = %d, want 1", state.attempts)
}
if !state.windowStart.Equal(base) || !state.lastRestart.Equal(base) {
t.Error("windowStart/lastRestart should be set to now")
}
})
t.Run("blocked during cooldown", func(t *testing.T) {
_, state := decideRestart(retryState{}, policy, base)
ok, _ := decideRestart(state, policy, base.Add(30*time.Second))
if ok {
t.Error("expected restart to be blocked within cooldown")
}
})
t.Run("allowed after cooldown", func(t *testing.T) {
_, state := decideRestart(retryState{}, policy, base)
ok, state := decideRestart(state, policy, base.Add(61*time.Second))
if !ok {
t.Error("expected restart allowed after cooldown")
}
if state.attempts != 2 {
t.Errorf("attempts = %d, want 2", state.attempts)
}
})
t.Run("max retries enforced within window", func(t *testing.T) {
state := retryState{}
now := base
allowed := 0
for i := 0; i < 6; i++ {
ok, newState := decideRestart(state, policy, now)
state = newState
if ok {
allowed++
}
now = now.Add(policy.cooldown + time.Second)
}
if allowed != policy.maxRetries {
t.Errorf("allowed %d restarts, want %d (max per window)", allowed, policy.maxRetries)
}
})
t.Run("counter resets after window elapses", func(t *testing.T) {
state := retryState{attempts: 3, windowStart: base, lastRestart: base}
ok, newState := decideRestart(state, policy, base.Add(policy.window+time.Second))
if !ok {
t.Error("expected restart allowed once the window elapsed")
}
if newState.attempts != 1 {
t.Errorf("attempts = %d, want 1 after window reset", newState.attempts)
}
})
}
func TestPruneRetries(t *testing.T) {
now := time.Date(2026, 6, 28, 12, 0, 0, 0, time.UTC)
s := &Service{retries: map[string]retryState{
// within the window -> retained
"fresh": {attempts: 1, windowStart: now.Add(-time.Minute), lastRestart: now.Add(-time.Minute)},
// exactly at the window boundary -> pruned
"edge": {attempts: 2, windowStart: now.Add(-retryWindow), lastRestart: now.Add(-retryWindow)},
// long past the window -> pruned
"stale": {attempts: 3, windowStart: now.Add(-2 * retryWindow), lastRestart: now.Add(-2 * retryWindow)},
}}
s.pruneRetries(now)
if _, ok := s.retries["fresh"]; !ok {
t.Error("entry within the retry window should be retained")
}
if _, ok := s.retries["edge"]; ok {
t.Error("entry exactly at the window boundary should be pruned")
}
if _, ok := s.retries["stale"]; ok {
t.Error("entry past the retry window should be pruned")
}
}
// TestRetryStateSurvivesStartingTick locks in the F1 fix: a container that flaps
// through "starting" right after a restart (and so briefly drops out of the
// health=unhealthy filter) must keep its retry accounting across the tick where
// it is not observed, otherwise the cooldown / max-retries storm guard is
// defeated and the next unhealthy observation triggers an immediate restart.
func TestRetryStateSurvivesStartingTick(t *testing.T) {
policy := retryPolicy{maxRetries: 3, window: retryWindow, cooldown: restartCooldown}
const id = "flapper"
s := &Service{retries: make(map[string]retryState)}
t0 := time.Date(2026, 6, 28, 12, 0, 0, 0, time.UTC)
// Tick 1: container is unhealthy -> first restart.
ok, state := decideRestart(s.getRetry(id), policy, t0)
s.setRetry(id, state)
if !ok || state.attempts != 1 {
t.Fatalf("tick 1: ok=%v attempts=%d, want restart with attempts=1", ok, state.attempts)
}
// Tick 2 (t0+30s): the container is "starting" and not in the unhealthy list.
// Prune must NOT drop its state because the window has not elapsed.
s.pruneRetries(t0.Add(30 * time.Second))
if _, kept := s.retries[id]; !kept {
t.Fatal("tick 2: retry state was pruned while the container was 'starting'")
}
// Tick 3 (t0+45s): unhealthy again, still within the cooldown. The surviving
// state must block the restart and the attempt count must not be reset.
ok, state = decideRestart(s.getRetry(id), policy, t0.Add(45*time.Second))
s.setRetry(id, state)
if ok {
t.Error("tick 3: restart should be blocked by the surviving cooldown")
}
if state.attempts != 1 {
t.Errorf("tick 3: attempts = %d, want 1 (state survived, not reset)", state.attempts)
}
}
+597
View File
@@ -0,0 +1,597 @@
package containerautomation
import (
"context"
"fmt"
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/docker/images"
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/portainer/portainer/api/stacks/deployments"
"github.com/portainer/portainer/api/stacks/stackutils"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/rs/zerolog/log"
)
const (
// statusCheckTimeout bounds a single container image-status resolution
// (container inspect + remote digest fetch).
statusCheckTimeout = 30 * time.Second
// recreateTimeout bounds a standalone recreate (pull + stop + create + start).
// Pulls can be slow, so it is generous.
recreateTimeout = 10 * time.Minute
// stackRedeployTimeout bounds a single stack redeploy-with-pull.
stackRedeployTimeout = 15 * time.Minute
)
// update runs a single auto-update pass over every reachable Docker endpoint.
// It is registered with the scheduler and guarded against overlapping ticks by
// the Service. Errors are logged per endpoint/container so one failure does not
// abort the whole pass; it always returns nil so the scheduler keeps the job.
func (s *Service) update() error {
if !s.updateRunning.CompareAndSwap(false, true) {
log.Debug().Msg("auto-update: previous run still in progress, skipping tick")
return nil
}
defer s.updateRunning.Store(false)
settings, err := s.dataStore.Settings().Settings()
if err != nil {
log.Warn().Err(err).Msg("auto-update: unable to read settings")
return nil
}
scope := ScopeLabeled
if settings.ContainerAutomation.AutoUpdate.Scope == ScopeAll {
scope = ScopeAll
}
opts := updateOptions{
cleanup: settings.ContainerAutomation.AutoUpdate.Cleanup,
rollback: settings.ContainerAutomation.AutoUpdate.RollbackOnFailure,
rollbackTimeout: parseRollbackTimeout(settings.ContainerAutomation.AutoUpdate.RollbackTimeout),
}
endpoints, err := s.dataStore.Endpoint().Endpoints()
if err != nil {
log.Warn().Err(err).Msg("auto-update: unable to list environments")
return nil
}
for i := range endpoints {
endpoint := &endpoints[i]
// Native Docker endpoints only: Kubernetes is not applicable and
// Edge/async endpoints are not reachable synchronously from the scheduler.
if !endpointutils.IsDockerEndpoint(endpoint) || endpointutils.IsEdgeEndpoint(endpoint) {
continue
}
// Per-endpoint opt-out (M5): skip environments where automation is disabled,
// independently of the global switch. Zero value participates, so existing
// installs are unaffected.
if !AutomationEnabledForEndpoint(endpoint) {
log.Debug().Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: automation disabled for this environment, skipping")
continue
}
s.updateEndpoint(endpoint, scope, opts)
}
// Drop rolled-back records whose cooldown has fully elapsed (mirrors auto-heal's
// pruneRetries), so the loop-guard map cannot grow unbounded.
s.pruneRolledBack(time.Now())
return nil
}
// updateOptions carries the per-pass auto-update toggles resolved from settings.
type updateOptions struct {
// cleanup removes the now-dangling old image after a confirmed-good update.
cleanup bool
// rollback enables the health gate + rollback of a failed standalone update.
rollback bool
// rollbackTimeout bounds how long the health gate waits before rolling back.
rollbackTimeout time.Duration
}
// parseRollbackTimeout resolves the configured rollback timeout, falling back to
// the default when empty or unparseable.
func parseRollbackTimeout(raw string) time.Duration {
d, err := time.ParseDuration(raw)
if err != nil || d <= 0 {
return defaultRollbackTimeout
}
return d
}
// updateEndpoint applies image updates to the in-scope, outdated containers of a
// single endpoint, routing each container to the standalone / stack / external
// apply path. Stack-managed candidates are grouped so each owning stack is
// redeployed at most once per tick.
func (s *Service) updateEndpoint(endpoint *portainer.Endpoint, scope string, opts updateOptions) {
endpointID := int(endpoint.ID)
// Swarm note (M4 limitation, mirrors auto-heal): we connect to the endpoint's
// primary node only (nodeName ""). Containers scheduled on other Swarm nodes
// are not updated here; stacks are redeployed cluster-wide by the swarm engine.
clientTimeout := endpointTimeout
cli, err := s.clientFactory.CreateClient(endpoint, "", &clientTimeout)
if err != nil {
log.Warn().Err(err).Int("endpoint_id", endpointID).Msg("auto-update: unable to create Docker client")
return
}
defer cli.Close()
listCtx, cancel := context.WithTimeout(s.baseCtx, endpointTimeout)
defer cancel()
// Running containers only: a stopped container has nothing to update now and
// would be started by a bare recreate.
containers, err := cli.ContainerList(listCtx, container.ListOptions{All: false})
if err != nil {
log.Warn().Err(err).Int("endpoint_id", endpointID).Msg("auto-update: unable to list containers")
return
}
// Collect the in-scope, outdated, non-monitor-only containers as candidates.
// An in-scope monitor-only container is still status-checked (keeping its badge
// cache warm) but never auto-applied. This only covers in-scope containers: in
// "labeled" scope a monitor-only container without the enable label is filtered
// out below before any status check, so its badge is not refreshed here.
var candidates []UpdateCandidate
for _, c := range containers {
if !InUpdateScope(scope, c.Labels) {
continue
}
// Resolve the image status. This also refreshes the package-level status
// cache that backs the badge, so in-scope monitor-only containers are still
// checked even though they are never auto-applied.
statusCtx, statusCancel := context.WithTimeout(s.baseCtx, statusCheckTimeout)
status, err := s.digestClient.ContainerImageStatus(statusCtx, c.ID, endpoint, "")
statusCancel()
if err != nil {
// Pull / registry-auth / network failure: leave the running container
// untouched, never recreate on a failed check.
log.Warn().Err(err).Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-update: image status check failed, leaving container untouched")
continue
}
if status != images.Outdated {
continue
}
// Monitor-only: detect-only, never auto-apply (status already cached above).
if IsMonitorOnly(c.Labels) {
log.Info().Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-update: outdated image detected but container is monitor-only, not applying")
continue
}
candidates = append(candidates, UpdateCandidate{ID: c.ID, Name: containerName(c.Names), ImageID: c.ImageID, Image: c.Image, Labels: c.Labels})
}
// Route and de-duplicate: one redeploy per stack per tick.
grouped := groupContainersForUpdate(candidates, s.stackLookupForEndpoint(endpoint.ID))
for _, ext := range grouped.External {
log.Debug().Str("container_id", ext.ID).Int("endpoint_id", endpointID).
Msg("auto-update: outdated externally-managed compose container, detect only")
}
for _, c := range grouped.Standalone {
s.updateStandalone(cli, endpoint, c, opts)
}
for _, st := range grouped.Stacks {
s.updateStack(cli, endpoint, st)
}
}
// stackLookupForEndpoint builds a compose-project-name -> Portainer compose stack
// resolver for a single endpoint. Only Docker Compose stacks on this endpoint
// match; a same-named swarm/kubernetes stack is treated as external (mirrors
// M3's resolveContainerUpdatePath).
func (s *Service) stackLookupForEndpoint(endpointID portainer.EndpointID) func(project string) *StackMatch {
stacks, err := s.dataStore.Stack().ReadAll()
if err != nil {
log.Warn().Err(err).Int("endpoint_id", int(endpointID)).
Msg("auto-update: unable to read stacks, treating compose containers as external")
return func(string) *StackMatch { return nil }
}
byName := make(map[string]*StackMatch)
for i := range stacks {
st := &stacks[i]
if st.EndpointID != endpointID || st.Type != portainer.DockerComposeStack {
continue
}
byName[st.Name] = &StackMatch{StackID: int(st.ID), IsGit: st.WorkflowID != 0}
}
return func(project string) *StackMatch {
return byName[project]
}
}
// updateStandalone recreates a standalone container with a re-pull of its image,
// then (when rollback is enabled and the container has a healthcheck) holds a
// health gate over the new container and rolls back to the previous image if it
// fails to become healthy. The old-image cleanup is deliberately ordered AFTER
// the health gate, so the rollback target is never removed before the update is
// confirmed good.
//
// Sequence: capture old image id + original ref + healthcheck -> recreate(pull)
// -> [health gate] -> on healthy: cleanup (if enabled); on unhealthy: rollback
// (never cleanup).
func (s *Service) updateStandalone(cli dockerClient, endpoint *portainer.Endpoint, c UpdateCandidate, opts updateOptions) {
endpointID := int(endpoint.ID)
// Loop-guard safety: the rolled-back map is keyed by endpoint+name (the only
// identifier that survives a recreate). An unnamed container cannot be recorded
// (recordRolledBack skips it), so with rollback enabled a container that keeps
// failing its health gate would update->rollback every tick with NO suppression.
// Skip the unnamed case when rollback is on so it cannot enter that
// unsuppressable loop; detection/badge refresh already happened upstream and is
// unaffected. (With rollback off there is no rollback to loop, so we proceed.)
if skipUnnamedForRollback(opts.rollback, c.Name) {
log.Info().Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-update: skipping unnamed standalone container, rollback is enabled but there is no stable name to key the loop guard")
return
}
// Update->rollback loop guard: if this container's update was rolled back
// recently and the remote still points at the SAME failed image, skip it until
// the cooldown elapses. A genuinely new upstream image (a changed remote digest)
// is not blocked.
rollbackMapKey := rollbackKey(endpoint.ID, c.Name)
if rec, ok := s.getRolledBack(rollbackMapKey); ok && s.shouldSkipRolledBack(rollbackMapKey, rec) {
log.Info().Str("container_id", c.ID).Str("container", c.Name).Str("image", rec.ref).Int("endpoint_id", endpointID).
Msg("auto-update: skipping update, a recent rollback failed on this image and the remote is unchanged (cooldown)")
return
}
// Capture the pre-update image identity for a possible rollback. The container
// list gives us the old image id; an inspect adds the original reference (re-tag
// target), whether a usable healthcheck exists, and the healthcheck start_period
// (which must be waited out before deciding). We only health-gate when rollback
// is enabled, the container has a healthcheck, we resolved both the old image id
// and its reference, and that reference is a proper tag (a digest-pinned or bare
// image id cannot be re-tagged, so the gate could never roll back).
oldImageID := c.ImageID
var originalRef string
var startPeriod time.Duration
healthGated := false
if opts.rollback {
// Bound the inspect like every other engine call so a hung/unreachable engine
// cannot block the whole sequential tick until shutdown.
inspectCtx, inspectCancel := context.WithTimeout(s.baseCtx, endpointTimeout)
inspect, err := cli.ContainerInspect(inspectCtx, c.ID)
inspectCancel()
if err != nil {
log.Warn().Err(err).Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-update: unable to inspect container before update, proceeding without a health gate")
} else {
originalRef = inspect.Config.Image
if oldImageID == "" {
oldImageID = inspect.Image
}
if hc := inspect.Config.Healthcheck; hc != nil {
startPeriod = hc.StartPeriod
}
switch {
case !hasHealthGate(inspect.Config.Healthcheck):
log.Info().Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-update: container has no healthcheck, updating without a rollback gate")
case oldImageID == "" || originalRef == "":
log.Info().Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-update: unable to resolve previous image identity, updating without a rollback gate")
case !isTagReference(originalRef):
log.Info().Str("container_id", c.ID).Str("image", originalRef).Int("endpoint_id", endpointID).
Msg("auto-update: health gate skipped, image is digest-pinned and cannot be rolled back")
default:
healthGated = true
}
}
}
ctx, cancel := context.WithTimeout(s.baseCtx, recreateTimeout)
defer cancel()
newContainer, err := s.containerService.Recreate(ctx, endpoint, c.ID, true, "", "")
if err != nil {
// Recreate preserves config and rolls back on a create failure; a pull or
// create failure leaves the original container running.
log.Warn().Err(err).Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-update: failed to recreate standalone container")
s.notifier.Notify(Event{
Kind: EventUpdateFailed, EndpointID: endpointID, ContainerID: c.ID, ContainerName: c.Name,
Message: "failed to recreate standalone container", Err: err,
})
return
}
log.Info().Str("container_id", c.ID).Int("endpoint_id", endpointID).
Msg("auto-update: recreated standalone container with updated image")
newImage := ""
if newContainer != nil {
newImage = newContainer.Config.Image
}
// Health gate: roll back if the new container does not become healthy in time.
// The old image is preserved (not cleaned up) until the gate confirms health,
// so the rollback target is still available. The "updated" event is held until
// the gate confirms health, so an observer never sees a misleading
// "updated" -> "rollback" sequence for the same container; on the rollback path
// only EventRollback (or update-failed) is emitted.
if healthGated {
switch s.healthGate(cli, newContainer.ID, opts.rollbackTimeout, startPeriod) {
case gateAborted:
// Server shutdown mid-gate: leave the new container in place, do not roll
// back and do not emit an event (we never observed a real failure).
return
case gateRollback:
s.rollback(cli, endpoint, newContainer.ID, oldImageID, originalRef, c.Name)
return
case gateHealthy:
// Confirmed healthy: fall through to emit "updated" and clean up.
}
}
// Emit "updated" now: either there was no gate (emitted right after recreate,
// as before), or the gate confirmed the new container is healthy.
s.notifier.Notify(Event{
Kind: EventUpdated, EndpointID: endpointID, ContainerID: newContainer.ID, ContainerName: c.Name,
Image: newImage, OldDigest: oldImageID, NewDigest: newContainer.Image,
Message: "updated standalone container",
})
if opts.cleanup && newContainer != nil && newContainer.Image != oldImageID {
s.cleanupOldImage(cli, endpoint, oldImageID)
}
}
// containerName returns a container's primary name without the leading slash, or
// "" when none is reported. The name is stable across a recreate (Recreate
// assigns a new container ID but preserves the name), so it keys the rolled-back
// loop-guard map.
func containerName(names []string) string {
if len(names) == 0 {
return ""
}
return strings.TrimPrefix(names[0], "/")
}
// skipUnnamedForRollback reports whether a standalone update must be skipped
// because rollback is enabled but the container has no stable name to key the
// loop guard. The rolled-back map is keyed by endpoint+name (the only identifier
// that survives a recreate); without a name the guard cannot record a failed
// target, so a repeatedly-failing update would loop update->rollback every tick
// with no suppression. When rollback is off there is nothing to loop, so an
// unnamed container is still allowed to update.
func skipUnnamedForRollback(rollback bool, name string) bool {
return rollback && name == ""
}
// rollbackKey identifies a standalone container in the rolled-back map by its
// endpoint and (recreate-stable) name. A recreate assigns a new container ID, so
// the ID cannot key state across an update; the name is preserved.
func rollbackKey(endpointID portainer.EndpointID, name string) string {
return fmt.Sprintf("%d/%s", int(endpointID), name)
}
// resolveRemoteDigest fetches the current remote image digest for a reference. It
// tells whether a rolled-back container's upstream target is still the same
// failed image (skip) or a new push (retry).
func (s *Service) resolveRemoteDigest(ctx context.Context, ref string) (string, error) {
img, err := images.ParseImage(images.ParseImageOptions{Name: ref})
if err != nil {
return "", err
}
dig, err := s.digestClient.RemoteDigest(ctx, img)
if err != nil {
return "", err
}
return dig.String(), nil
}
// recordRolledBack stores the failed target after a successful rollback so the
// next poll skips re-pulling the same broken image. The failed remote digest is
// resolved now (the registry is reachable, the image was just pulled); if it
// cannot be resolved the record is still stored with an empty digest and the
// guard skips conservatively until the cooldown elapses.
func (s *Service) recordRolledBack(endpoint *portainer.Endpoint, name, ref string) {
if name == "" {
// Without a stable key we cannot reliably match the container next tick.
log.Debug().Str("image", ref).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: rolled-back container has no name, loop guard not recorded")
return
}
ctx, cancel := context.WithTimeout(s.baseCtx, statusCheckTimeout)
digest, err := s.resolveRemoteDigest(ctx, ref)
cancel()
if err != nil {
log.Debug().Err(err).Str("image", ref).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: could not resolve failed remote digest, loop guard will skip conservatively until cooldown")
}
s.setRolledBack(rollbackKey(endpoint.ID, name), rolledBackTarget{ref: ref, digest: digest, at: time.Now()})
}
// shouldSkipRolledBack reports whether a standalone container must be skipped this
// tick to avoid the update->rollback loop, clearing the record once the skip no
// longer applies (cooldown elapsed or a new upstream image). It resolves the
// current remote digest so a genuinely new image is never blocked.
func (s *Service) shouldSkipRolledBack(key string, rec rolledBackTarget) bool {
now := time.Now()
// Fast paths that avoid a registry call: cooldown elapsed -> clear & proceed;
// no recorded digest -> skip conservatively while the cooldown is open.
if now.Sub(rec.at) >= updateRollbackCooldown {
s.clearRolledBack(key)
return false
}
if rec.digest == "" {
return true
}
ctx, cancel := context.WithTimeout(s.baseCtx, statusCheckTimeout)
currentDigest, err := s.resolveRemoteDigest(ctx, rec.ref)
cancel()
if err != nil {
// Cannot confirm the upstream target changed: stay conservative and skip to
// avoid re-entering the loop, until the cooldown elapses.
log.Debug().Err(err).Str("image", rec.ref).
Msg("auto-update: cannot resolve remote digest for a rolled-back container, skipping until cooldown")
return true
}
if decideUpdateSkip(rec, currentDigest, now, updateRollbackCooldown) {
return true
}
// New upstream image (changed digest): the failed target is gone, clear the
// record and let the update proceed.
s.clearRolledBack(key)
return false
}
// cleanupOldImage attempts a conservative removal of the previous image after a
// standalone update. The removal is NOT forced: Docker refuses to delete an
// image that still carries tags or is referenced by any container, so this only
// succeeds when the old image has become genuinely dangling (untagged and
// unused). It never touches a tagged image still in use.
func (s *Service) cleanupOldImage(cli dockerClient, endpoint *portainer.Endpoint, oldImageID string) {
if oldImageID == "" {
return
}
ctx, cancel := context.WithTimeout(s.baseCtx, endpointTimeout)
defer cancel()
if _, err := cli.ImageRemove(ctx, oldImageID, image.RemoveOptions{Force: false, PruneChildren: false}); err != nil {
log.Debug().Err(err).Str("image_id", oldImageID).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: old image not removed (still tagged or in use)")
return
}
log.Info().Str("image_id", oldImageID).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: removed dangling old image after update")
}
// updateStack applies an image update to a Portainer-managed compose stack so its
// containers are recreated by the stack engine and stay part of the stack. It is
// called at most once per stack per tick.
//
// - git stacks: detect-only here. A git stack's source of truth is its commit;
// this tick's trigger is an image-only update (same compose manifest, newer
// upstream digest), which the git redeploy path (RedeployWhenChanged) would
// short-circuit without applying — while still doing a real git fetch every
// tick. So we skip git stacks: the image update lands on the stack's next git
// change or via a manual "Update now", and we do not fetch git every tick.
// - file stacks: the deployer is driven directly with forcePullImage=true,
// applying the image update immediately.
//
// On a successful file-stack redeploy it emits one EventUpdated per member
// container that triggered the update (not a single aggregate stack event), each
// carrying the stack name and a best-effort post-redeploy new image id.
func (s *Service) updateStack(cli dockerClient, endpoint *portainer.Endpoint, st StackUpdate) {
if st.IsGit {
// Detect-only: leave git bookkeeping to the git redeploy path. Logged at
// debug so it does not repeat at info on every tick (it would otherwise
// fire for an unchanged git stack indefinitely).
log.Debug().Int("stack_id", st.StackID).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: outdated git stack image detected, detect only (applied on next git change or manual update)")
return
}
ctx, cancel := context.WithTimeout(s.baseCtx, stackRedeployTimeout)
defer cancel()
stack, err := s.dataStore.Stack().Read(portainer.StackID(st.StackID))
if err != nil {
log.Warn().Err(err).Int("stack_id", st.StackID).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: unable to read stack for redeploy")
return
}
// Resolve registries the same way the established userless/system redeploy does
// (RedeployWhenChanged): scope them to the stack author's access on the endpoint
// and refresh ECR tokens, so an ECR-backed stack authenticates with fresh
// credentials instead of the stale token a raw ReadAll() would pass.
registries, err := deployments.ResolveStackRegistries(s.dataStore, stack, endpoint.ID)
if err != nil {
log.Warn().Err(err).Int("stack_id", st.StackID).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: unable to resolve registries for stack redeploy")
return
}
// prune=false (conservative: do not remove resources the user may rely on),
// forcePullImage=true (the whole point), forceRecreate=false.
if stackutils.IsRelativePathStack(stack) {
err = s.stackDeployer.DeployRemoteComposeStack(ctx, stack, endpoint, registries, false, true, false)
} else {
err = s.stackDeployer.DeployComposeStack(ctx, stack, endpoint, registries, false, true, false)
}
if err != nil {
log.Warn().Err(err).Int("stack_id", st.StackID).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: failed to redeploy compose stack with re-pull")
return
}
log.Info().Int("stack_id", st.StackID).Int("endpoint_id", int(endpoint.ID)).
Msg("auto-update: redeployed compose stack with updated images")
// One notification PER updated container (the maintainer's requirement), each
// showing the container's stack name. The stack was redeployed as a whole, so the
// per-container new image id is not in hand; re-inspect each container by its
// (compose-stable) name to fill in the "new" digest best-effort. A failed inspect
// leaves NewDigest empty and the message falls back to "image updated" — never a
// blocked delivery.
for _, c := range st.Containers {
s.notifier.Notify(Event{
Kind: EventUpdated, EndpointID: int(endpoint.ID), StackID: st.StackID,
StackName: c.Labels[composeProjectLabel], ContainerName: c.Name,
Image: c.Image, OldDigest: c.ImageID, NewDigest: s.inspectImageID(cli, c.Name),
Message: "updated stack container",
})
}
}
// inspectImageID re-inspects a container by its (compose-stable) name after a stack
// redeploy to recover the new local image id for the update notification. It is
// best-effort: any failure (or an empty name) yields "", and the caller degrades the
// message to "image updated" rather than blocking delivery. The inspect is bounded
// like every other engine call so a hung engine cannot stall the tick.
func (s *Service) inspectImageID(cli dockerClient, containerName string) string {
if containerName == "" {
return ""
}
ctx, cancel := context.WithTimeout(s.baseCtx, endpointTimeout)
defer cancel()
inspect, err := cli.ContainerInspect(ctx, containerName)
if err != nil {
log.Debug().Err(err).Str("container", containerName).
Msg("auto-update: unable to inspect stack container for its new image id, notifying without it")
return ""
}
return inspect.Image
}
+148
View File
@@ -0,0 +1,148 @@
package containerautomation
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/docker/docker/api/types/container"
dockerclient "github.com/docker/docker/client"
"github.com/stretchr/testify/require"
)
// newStackInspectClient builds a Docker client wired to a test server that answers
// ContainerInspect by name, returning the given new image id. It is the seam the
// post-redeploy best-effort "new digest" re-inspect uses.
func newStackInspectClient(t *testing.T, newImageIDByName map[string]string) *dockerclient.Client {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for name, imageID := range newImageIDByName {
if strings.HasSuffix(r.URL.Path, "/containers/"+name+"/json") {
_ = json.NewEncoder(w).Encode(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{ID: name, Image: imageID},
Config: &container.Config{},
})
return
}
}
http.Error(w, "not found", http.StatusNotFound)
}))
t.Cleanup(srv.Close)
cli, err := dockerclient.NewClientWithOpts(
dockerclient.WithHost(srv.URL),
dockerclient.WithHTTPClient(http.DefaultClient),
)
require.NoError(t, err)
return cli
}
// TestUpdateStackEmitsPerContainerEvents proves the maintainer's requirement: a
// (file) stack redeploy emits one EventUpdated PER updated member container, each
// carrying the compose stack name (from the container's label, not a Stack().Read)
// and a best-effort post-redeploy new image id — never a single aggregate stack
// event.
func TestUpdateStackEmitsPerContainerEvents(t *testing.T) {
_, store := datastore.MustNewTestStore(t, true, false)
// A stack author must exist for registry resolution; an admin resolves to the
// (empty) registry set without needing endpoint/team wiring.
require.NoError(t, store.User().Create(&portainer.User{ID: 1, Username: "auto", Role: portainer.AdministratorRole}))
endpoint := &portainer.Endpoint{ID: 1, Name: "nebula.lc"}
require.NoError(t, store.Endpoint().Create(endpoint))
require.NoError(t, store.Stack().Create(&portainer.Stack{
ID: 7, EndpointID: 1, Name: "cache-demo", Type: portainer.DockerComposeStack, CreatedBy: "auto",
}))
const (
oldEsphome = "sha256:59b94983c73a000000000000000000000000000000000000000000000000aaaa"
newEsphome = "sha256:2231ca5d676d000000000000000000000000000000000000000000000000bbbb"
oldOther = "sha256:1111111111110000000000000000000000000000000000000000000000000000"
newOther = "sha256:2222222222220000000000000000000000000000000000000000000000000000"
)
cli := newStackInspectClient(t, map[string]string{
"esphome": newEsphome,
"other": newOther,
})
rec := &recordingNotifier{}
s := &Service{
baseCtx: context.Background(),
dataStore: store,
stackDeployer: testhelpers.NewTestStackDeployer(),
notifier: rec,
}
st := StackUpdate{
StackID: 7,
IsGit: false,
Containers: []UpdateCandidate{
{Name: "esphome", ImageID: oldEsphome, Image: "esphome/esphome:latest", Labels: map[string]string{composeProjectLabel: "cache-demo"}},
{Name: "other", ImageID: oldOther, Image: "redis:7", Labels: map[string]string{composeProjectLabel: "cache-demo"}},
},
}
s.updateStack(cli, endpoint, st)
require.Len(t, rec.events, 2, "one EventUpdated per updated member container, not one aggregate stack event")
byContainer := map[string]Event{}
for _, e := range rec.events {
require.Equal(t, EventUpdated, e.Kind)
require.Equal(t, "cache-demo", e.StackName, "each per-container event carries the compose stack name")
require.Equal(t, 7, e.StackID)
byContainer[e.ContainerName] = e
}
esphome, ok := byContainer["esphome"]
require.True(t, ok, "expected a per-container event for esphome")
require.Equal(t, oldEsphome, esphome.OldDigest)
require.Equal(t, newEsphome, esphome.NewDigest, "the new image id is recovered by re-inspecting the container after redeploy")
other, ok := byContainer["other"]
require.True(t, ok, "expected a per-container event for other")
require.Equal(t, oldOther, other.OldDigest)
require.Equal(t, newOther, other.NewDigest)
}
// TestUpdateStackGitIsDetectOnly guards that a git stack stays detect-only: it is
// not redeployed and emits no notification (its image update lands on the next git
// change or a manual update).
func TestUpdateStackGitIsDetectOnly(t *testing.T) {
_, store := datastore.MustNewTestStore(t, true, false)
endpoint := &portainer.Endpoint{ID: 1, Name: "nebula.lc"}
require.NoError(t, store.Endpoint().Create(endpoint))
deployer := testhelpers.NewTestStackDeployer()
rec := &recordingNotifier{}
s := &Service{
baseCtx: context.Background(),
dataStore: store,
stackDeployer: deployer,
notifier: rec,
}
cli := newStackInspectClient(t, nil)
s.updateStack(cli, endpoint, StackUpdate{
StackID: 9, IsGit: true,
Containers: []UpdateCandidate{{Name: "esphome", Labels: map[string]string{composeProjectLabel: "cache-demo"}}},
})
require.Empty(t, rec.events, "a git stack is detect-only, no per-container notification")
require.Zero(t, deployer.DeployComposeCallCount, "a git stack must not be redeployed here")
}
@@ -0,0 +1,236 @@
package containerautomation
import (
"context"
"strings"
"testing"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/docker/images"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/require"
)
const (
// 64-hex content-addressable image ids for the pre/post-update identities.
oldImageID = "sha256:1111111111111111111111111111111111111111111111111111111111111111"
newImageID = "sha256:2222222222222222222222222222222222222222222222222222222222222222"
)
// preUpdateInspect is the pre-update ContainerInspect the standalone path issues
// on the OLD container to capture its original image ref + healthcheck (so the
// rollback health gate is armed).
func preUpdateInspect(id, ref string) container.InspectResponse {
return container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{ID: id, Image: oldImageID},
Config: &container.Config{
Image: ref,
Healthcheck: &container.HealthConfig{Test: []string{"CMD", "true"}},
},
}
}
// healthInspect is the health-gate ContainerInspect on the NEW container,
// reporting the given health status.
func healthInspect(id string, status container.HealthStatus) container.InspectResponse {
return container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: id,
State: &container.State{Running: true, Health: &container.Health{Status: status}},
},
Config: &container.Config{},
}
}
func countPrefix(calls []string, prefix string) int {
n := 0
for _, c := range calls {
if strings.HasPrefix(c, prefix) {
n++
}
}
return n
}
// TestUpdateStandaloneHappyPathCleansUpAfterHealthGate locks in the happy-path
// wiring #20 calls out: pull/recreate -> health gate confirms healthy -> the
// "updated" event is emitted only AFTER health is confirmed -> the old-image
// cleanup runs strictly AFTER the healthy gate (so a rollback target could never
// be deleted before the update is confirmed good).
func TestUpdateStandaloneHappyPathCleansUpAfterHealthGate(t *testing.T) {
const (
oldID = "old-id"
newID = "new-id"
ref = "nginx:1.21"
)
seq := &callSeq{}
notif := &seqNotifier{seq: seq}
cli := newFakeDockerClient(seq)
cli.inspectByID[oldID] = preUpdateInspect(oldID, ref)
cli.inspectByID[newID] = healthInspect(newID, container.Healthy)
rec := &fakeRecreator{seq: seq, result: &types.ContainerJSON{
ContainerJSONBase: &container.ContainerJSONBase{ID: newID, Image: newImageID},
Config: &container.Config{Image: ref},
}}
s := &Service{
baseCtx: context.Background(),
containerService: rec,
notifier: notif,
rolledBack: map[string]rolledBackTarget{},
}
endpoint := &portainer.Endpoint{ID: 1}
c := UpdateCandidate{ID: oldID, Name: "web", ImageID: oldImageID, Image: ref}
opts := updateOptions{cleanup: true, rollback: true, rollbackTimeout: 60 * time.Second}
s.updateStandalone(cli, endpoint, c, opts)
calls := seq.snapshot()
iRecreate := seq.indexOf("recreate:" + oldID)
iGate := seq.indexOf("inspect:" + newID)
iUpdated := seq.indexOf("event:" + string(EventUpdated))
iCleanup := seq.indexOf("imageremove:" + oldImageID)
require.NotEqual(t, -1, iRecreate, "recreate must run")
require.NotEqual(t, -1, iGate, "the health gate must poll the new container")
require.NotEqual(t, -1, iUpdated, "an updated event must be emitted")
require.NotEqual(t, -1, iCleanup, "cleanup must remove the old image")
require.Less(t, iRecreate, iGate, "recreate must happen before the health gate")
require.Less(t, iGate, iUpdated, "the updated event must be held until health is confirmed")
require.Less(t, iUpdated, iCleanup, "cleanup must run strictly AFTER the healthy gate/updated event")
require.Equal(t, oldImageID, calls[iCleanup][len("imageremove:"):], "cleanup targets the OLD image, never the new/rollback target")
updated, n := notif.only(EventUpdated)
require.Equal(t, 1, n, "exactly one updated event")
require.Equal(t, newID, updated.ContainerID)
require.Equal(t, oldImageID, updated.OldDigest)
require.Equal(t, newImageID, updated.NewDigest)
_, rollbacks := notif.only(EventRollback)
require.Zero(t, rollbacks, "no rollback on the happy path")
}
// TestUpdateStandaloneRollbackPreservesTarget locks in the rollback-path wiring:
// a new container that fails the health gate is rolled back to the previous image
// (re-tag -> recreate on the old image with NO pull), EventRollback (not
// EventUpdated) is emitted, and the old-image cleanup NEVER runs — so the rollback
// target is never deleted early.
func TestUpdateStandaloneRollbackPreservesTarget(t *testing.T) {
_, store := datastore.MustNewTestStore(t, true, false)
const (
oldID = "old-id"
newID = "new-id"
// A registry ref that resolves to a fast connection-refused so the loop-guard's
// best-effort remote-digest resolution fails immediately (offline-safe) without
// blocking; the record is still stored with an empty digest.
ref = "localhost:1/web:v1"
)
seq := &callSeq{}
notif := &seqNotifier{seq: seq}
cli := newFakeDockerClient(seq)
cli.inspectByID[oldID] = preUpdateInspect(oldID, ref)
cli.inspectByID[newID] = healthInspect(newID, container.Unhealthy)
rec := &fakeRecreator{seq: seq, result: &types.ContainerJSON{
ContainerJSONBase: &container.ContainerJSONBase{ID: newID, Image: newImageID},
Config: &container.Config{Image: ref},
}}
s := &Service{
baseCtx: context.Background(),
containerService: rec,
digestClient: images.NewClientWithRegistry(images.NewRegistryClient(store), nil),
notifier: notif,
rolledBack: map[string]rolledBackTarget{},
}
endpoint := &portainer.Endpoint{ID: 1}
c := UpdateCandidate{ID: oldID, Name: "web", ImageID: oldImageID, Image: ref}
// cleanup enabled to prove it is NOT run on the rollback path.
opts := updateOptions{cleanup: true, rollback: true, rollbackTimeout: 60 * time.Second}
s.updateStandalone(cli, endpoint, c, opts)
calls := seq.snapshot()
iGate := seq.indexOf("inspect:" + newID)
iTag := seq.indexOf("imagetag:" + oldImageID + "->" + ref)
iRollbackRecreate := seq.indexOf("recreate:" + newID)
iRollbackEvent := seq.indexOf("event:" + string(EventRollback))
require.NotEqual(t, -1, iGate, "the health gate must poll the new container")
require.NotEqual(t, -1, iTag, "rollback must re-tag the previous image onto the original ref")
require.NotEqual(t, -1, iRollbackRecreate, "rollback must recreate on the previous image")
require.NotEqual(t, -1, iRollbackEvent, "a rollback event must be emitted")
require.Less(t, iGate, iTag, "rollback happens after the failed gate")
require.Less(t, iTag, iRollbackRecreate, "re-tag the old image before recreating on it")
require.Less(t, iRollbackRecreate, iRollbackEvent, "the rollback event follows the rollback recreate")
require.Zero(t, countPrefix(calls, "imageremove:"), "cleanup must NOT run on the rollback path (rollback target preserved)")
_, updates := notif.only(EventUpdated)
require.Zero(t, updates, "no updated event on the rollback path")
rollback, n := notif.only(EventRollback)
require.Equal(t, 1, n, "exactly one rollback event")
require.Equal(t, ref, rollback.Image, "the rollback event carries the restored original ref")
// The recreate seam saw two calls: the initial pull-recreate on the old container,
// then the rollback recreate on the new container WITHOUT a pull (resolves the
// re-tagged previous image).
require.Len(t, rec.calls, 2)
require.Equal(t, recreateCall{containerID: oldID, forcePullImage: true}, rec.calls[0])
require.Equal(t, recreateCall{containerID: newID, forcePullImage: false}, rec.calls[1])
}
// TestHealContainersRestartsUnhealthyThenRespectsCooldown locks in the auto-heal
// restart loop: an unhealthy container is restarted (restart precedes the heal
// event), and a second immediate pass is suppressed by the restart cooldown/loop
// guard so a flapping container is not restart-stormed.
func TestHealContainersRestartsUnhealthyThenRespectsCooldown(t *testing.T) {
seq := &callSeq{}
notif := &seqNotifier{seq: seq}
cli := newFakeDockerClient(seq)
s := &Service{
baseCtx: context.Background(),
notifier: notif,
retries: map[string]retryState{},
}
endpoint := &portainer.Endpoint{ID: 1}
containers := []container.Summary{{ID: "c1", Names: []string{"/web"}}}
// First pass: the unhealthy container is restarted and a heal event is emitted.
s.healContainers(cli, endpoint, ScopeAll, containers)
require.Equal(t, []string{"restart:c1", "event:" + string(EventHealRestarted)}, seq.snapshot(),
"the restart must precede the heal event")
require.Equal(t, 1, s.getRetry("c1").attempts, "the restart is accounted against the retry budget")
ev, n := notif.only(EventHealRestarted)
require.Equal(t, 1, n)
require.Equal(t, "c1", ev.ContainerID)
require.Equal(t, "web", ev.ContainerName)
// Second immediate pass: within the restart cooldown, the loop guard suppresses a
// second restart (no new restart call, no new event).
s.healContainers(cli, endpoint, ScopeAll, containers)
require.Equal(t, 1, countPrefix(seq.snapshot(), "restart:"), "no second restart within the cooldown")
_, n2 := notif.only(EventHealRestarted)
require.Equal(t, 1, n2, "the flap is suppressed by the restart cooldown")
}
+152
View File
@@ -0,0 +1,152 @@
package containerautomation
import (
"context"
"fmt"
"sync"
portainer "github.com/portainer/portainer/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
)
// callSeq is a shared, ordered recorder the daemon-path fakes write to, so a test
// can assert the SEQUENCE of operations across the docker client, the recreator
// and the notifier on a single timeline (e.g. cleanup strictly after the healthy
// gate, "updated" held until health is confirmed).
type callSeq struct {
mu sync.Mutex
calls []string
}
func (s *callSeq) record(entry string) {
s.mu.Lock()
defer s.mu.Unlock()
s.calls = append(s.calls, entry)
}
// snapshot returns a copy of the recorded calls in order.
func (s *callSeq) snapshot() []string {
s.mu.Lock()
defer s.mu.Unlock()
out := make([]string, len(s.calls))
copy(out, s.calls)
return out
}
// indexOf returns the position of the first recorded call equal to entry, or -1.
func (s *callSeq) indexOf(entry string) int {
for i, c := range s.snapshot() {
if c == entry {
return i
}
}
return -1
}
// fakeDockerClient is a programmable, call-recording implementation of the
// dockerClient seam. Inspect responses/errors are keyed by container id so the
// pre-update inspect (old id) and the health-gate poll (new id) can return
// different states within one update.
type fakeDockerClient struct {
seq *callSeq
inspectByID map[string]container.InspectResponse
inspectErrByID map[string]error
imageTagErr error
imageRemoveErr error
restartErrByID map[string]error
}
func newFakeDockerClient(seq *callSeq) *fakeDockerClient {
return &fakeDockerClient{
seq: seq,
inspectByID: map[string]container.InspectResponse{},
inspectErrByID: map[string]error{},
restartErrByID: map[string]error{},
}
}
func (f *fakeDockerClient) ContainerInspect(_ context.Context, containerID string) (container.InspectResponse, error) {
f.seq.record("inspect:" + containerID)
if err := f.inspectErrByID[containerID]; err != nil {
return container.InspectResponse{}, err
}
resp, ok := f.inspectByID[containerID]
if !ok {
return container.InspectResponse{}, fmt.Errorf("fake: no inspect programmed for %q", containerID)
}
return resp, nil
}
func (f *fakeDockerClient) ContainerRestart(_ context.Context, containerID string, _ container.StopOptions) error {
f.seq.record("restart:" + containerID)
return f.restartErrByID[containerID]
}
func (f *fakeDockerClient) ImageTag(_ context.Context, source, target string) error {
f.seq.record("imagetag:" + source + "->" + target)
return f.imageTagErr
}
func (f *fakeDockerClient) ImageRemove(_ context.Context, imageID string, _ image.RemoveOptions) ([]image.DeleteResponse, error) {
f.seq.record("imageremove:" + imageID)
if f.imageRemoveErr != nil {
return nil, f.imageRemoveErr
}
return []image.DeleteResponse{{Deleted: imageID}}, nil
}
// recreateCall records the salient arguments of a single Recreate invocation.
type recreateCall struct {
containerID string
forcePullImage bool
}
// fakeRecreator is a programmable, call-recording implementation of the
// containerRecreator seam. It returns the same result for every call; the
// standalone rollback path recreates a second time (on the previous image) and
// ignores that return value.
type fakeRecreator struct {
seq *callSeq
result *types.ContainerJSON
err error
calls []recreateCall
}
func (f *fakeRecreator) Recreate(_ context.Context, _ *portainer.Endpoint, containerID string, forcePullImage bool, _, _ string) (*types.ContainerJSON, error) {
f.seq.record("recreate:" + containerID)
f.calls = append(f.calls, recreateCall{containerID: containerID, forcePullImage: forcePullImage})
if f.err != nil {
return nil, f.err
}
return f.result, nil
}
// seqNotifier records each event onto the shared timeline (as "event:<kind>") and
// keeps the full events for content assertions.
type seqNotifier struct {
seq *callSeq
events []Event
}
func (n *seqNotifier) Notify(event Event) {
n.seq.record("event:" + string(event.Kind))
n.events = append(n.events, event)
}
// only returns the single recorded event of the given kind, requiring exactly one.
func (n *seqNotifier) only(kind EventKind) (Event, int) {
var found Event
count := 0
for _, e := range n.events {
if e.Kind == kind {
found = e
count++
}
}
return found, count
}
+257
View File
@@ -0,0 +1,257 @@
package containerautomation
import "strconv"
const (
// Scope values shared by the auto-heal and auto-update global settings.
ScopeLabeled = "labeled"
ScopeAll = "all"
// Primary labels (with community aliases) controlling per-container auto-heal.
labelEnable = "io.portainer.autoheal.enable"
labelEnableAlias = "autoheal"
labelStopTimeout = "io.portainer.autoheal.stop-timeout"
labelStopTimeoutAlias = "autoheal.stop.timeout"
labelRetries = "io.portainer.autoheal.retries"
// Primary labels (with watchtower aliases) controlling per-container auto-update.
labelUpdateEnable = "io.portainer.update.enable"
labelUpdateEnableAlias = "com.centurylinklabs.watchtower.enable"
labelUpdateMonitorOnly = "io.portainer.update.monitor-only"
labelUpdateMonitorOnlyAlias = "com.centurylinklabs.watchtower.monitor-only"
// composeProjectLabel identifies the compose project a container belongs to.
composeProjectLabel = "com.docker.compose.project"
// Defaults used when a label is missing or holds an invalid value.
defaultStopTimeout = 10
defaultRetries = 3
)
// InScope reports whether a container is subject to auto-heal given the global
// scope and the container's labels.
//
// - "all": every container is in scope, unless it explicitly opts out with the
// enable label set to false.
// - "labeled" (default): only containers with the enable label set to true.
func InScope(scope string, labels map[string]string) bool {
enabled, present := boolLabel(labels, labelEnable, labelEnableAlias)
switch scope {
case ScopeAll:
if present && !enabled {
return false
}
return true
default: // ScopeLabeled
return present && enabled
}
}
// boolLabel resolves a boolean label (primary key first, alias second).
// It returns the parsed value and whether the label was present at all.
// Invalid values are treated as false but still count as "present".
func boolLabel(labels map[string]string, key, alias string) (value bool, present bool) {
raw, ok := labels[key]
if !ok {
raw, ok = labels[alias]
}
if !ok {
return false, false
}
parsed, err := strconv.ParseBool(raw)
if err != nil {
return false, true
}
return parsed, true
}
// InUpdateScope reports whether a container is subject to auto-update given the
// global scope and the container's labels. It mirrors InScope but reads the
// update enable label (io.portainer.update.enable / watchtower alias):
//
// - "all": every container is in scope, unless it explicitly opts out with the
// update enable label set to false.
// - "labeled" (default): only containers with the update enable label true.
func InUpdateScope(scope string, labels map[string]string) bool {
enabled, present := boolLabel(labels, labelUpdateEnable, labelUpdateEnableAlias)
switch scope {
case ScopeAll:
if present && !enabled {
return false
}
return true
default: // ScopeLabeled
return present && enabled
}
}
// IsMonitorOnly reports whether a container is flagged detect-only via the
// monitor-only label (io.portainer.update.monitor-only / watchtower alias).
// Such containers have their image status resolved (for the badge cache) but are
// never auto-applied.
func IsMonitorOnly(labels map[string]string) bool {
value, present := boolLabel(labels, labelUpdateMonitorOnly, labelUpdateMonitorOnlyAlias)
return present && value
}
// UpdateKind is the apply path resolved for an outdated container.
type UpdateKind string
const (
// UpdateStandalone: recreate-with-pull (no compose project).
UpdateStandalone UpdateKind = "standalone"
// UpdateStack: redeploy the owning Portainer compose stack with re-pull, so
// the container stays part of its stack.
UpdateStack UpdateKind = "stack"
// UpdateExternal: compose-managed but with no matching Portainer compose
// stack record; Portainer must not touch it (would detach it / drift).
UpdateExternal UpdateKind = "external"
)
// StackMatch is the Portainer Docker Compose stack a compose project resolves to.
type StackMatch struct {
StackID int
// IsGit routes file vs git redeploy at apply time.
IsGit bool
}
// UpdateRouting is the decision returned by resolveContainerUpdateRouting.
type UpdateRouting struct {
Kind UpdateKind
StackID int
IsGit bool
}
// resolveContainerUpdateRouting decides how a container's image update must be
// applied, given a lookup that resolves a compose project name to a matching
// Portainer Docker Compose stack (nil when none exists or it is not a compose
// stack). It is the Go equivalent of M3's TS resolveContainerUpdatePath: pure
// and side-effect free so it can be unit-tested without Docker or the datastore.
//
// - No compose project label -> standalone (recreate-with-pull).
// - Compose project matching a Portainer compose stack -> stack
// (redeploy-with-pull, keeps the container in its stack).
// - Compose project with no matching Portainer compose stack -> external
// (managed outside Portainer / a same-named stack of another type), left
// untouched to avoid detaching it or drifting.
func resolveContainerUpdateRouting(labels map[string]string, stackLookup func(project string) *StackMatch) UpdateRouting {
project := labels[composeProjectLabel]
if project == "" {
return UpdateRouting{Kind: UpdateStandalone}
}
match := stackLookup(project)
if match == nil {
return UpdateRouting{Kind: UpdateExternal}
}
return UpdateRouting{Kind: UpdateStack, StackID: match.StackID, IsGit: match.IsGit}
}
// UpdateCandidate is an outdated, in-scope container considered for auto-update.
type UpdateCandidate struct {
ID string
// Name is the container's primary name (no leading slash). It is stable across
// a recreate and keys the update->rollback loop guard.
Name string
// ImageID is the pre-update local image id ("sha256:..."), the "old" digest in a
// per-container update notification.
ImageID string
// Image is the container's image reference (e.g. "nginx:latest"), carried for the
// notification.
Image string
Labels map[string]string
}
// StackUpdate identifies a Portainer stack to redeploy once, together with the
// affected member containers so each updated container can emit its own
// notification (with the stack name) after the redeploy.
type StackUpdate struct {
StackID int
IsGit bool
// Containers are the outdated member containers that triggered this stack
// redeploy, threaded through from detection so a per-container notification can
// be emitted for each (name + old image id + image + labels/stack name).
Containers []UpdateCandidate
}
// GroupedUpdates partitions candidates into their apply paths, de-duplicating
// stack containers so each owning stack is redeployed at most once per tick
// (the overlap guard for stack fan-out). Pure and unit-testable, the Go analogue
// of M3's groupContainersForUpdate.
type GroupedUpdates struct {
Standalone []UpdateCandidate
External []UpdateCandidate
Stacks []StackUpdate
}
// groupContainersForUpdate routes each candidate and collapses stack candidates
// so a stack with several outdated containers is redeployed only once.
func groupContainersForUpdate(candidates []UpdateCandidate, stackLookup func(project string) *StackMatch) GroupedUpdates {
grouped := GroupedUpdates{}
// stackIndex maps a stack id to its slot in grouped.Stacks so a stack is
// redeployed once, while every member container is still collected for its own
// notification (rather than discarded at the collapse).
stackIndex := make(map[int]int)
for _, c := range candidates {
routing := resolveContainerUpdateRouting(c.Labels, stackLookup)
switch routing.Kind {
case UpdateStandalone:
grouped.Standalone = append(grouped.Standalone, c)
case UpdateExternal:
grouped.External = append(grouped.External, c)
case UpdateStack:
idx, ok := stackIndex[routing.StackID]
if !ok {
grouped.Stacks = append(grouped.Stacks, StackUpdate{StackID: routing.StackID, IsGit: routing.IsGit})
idx = len(grouped.Stacks) - 1
stackIndex[routing.StackID] = idx
}
grouped.Stacks[idx].Containers = append(grouped.Stacks[idx].Containers, c)
}
}
return grouped
}
// StopTimeout returns the per-container stop timeout (in seconds) from labels,
// falling back to the default when missing or invalid.
func StopTimeout(labels map[string]string) int {
return positiveIntLabel(labels, labelStopTimeout, labelStopTimeoutAlias, defaultStopTimeout)
}
// MaxRetries returns the per-container max restarts per window from labels,
// falling back to the default when missing or invalid.
func MaxRetries(labels map[string]string) int {
return positiveIntLabel(labels, labelRetries, "", defaultRetries)
}
// positiveIntLabel reads an integer label (primary first, optional alias second)
// and returns it when strictly positive, otherwise the provided default.
func positiveIntLabel(labels map[string]string, key, alias string, fallback int) int {
raw, ok := labels[key]
if !ok && alias != "" {
raw, ok = labels[alias]
}
if !ok {
return fallback
}
value, err := strconv.Atoi(raw)
if err != nil || value <= 0 {
return fallback
}
return value
}

Some files were not shown because too many files have changed in this diff Show More