├── depot.json ├── backend ├── .gitignore ├── resources │ ├── migrations │ │ ├── sqlite │ │ │ ├── 016_drop_image_table.up.sql │ │ │ ├── 012_add_user_locale.up.sql │ │ │ ├── 023_add_apprise_settings.down.sql │ │ │ ├── 008_clean_compose_templates.up.sql │ │ │ ├── 018_add_project_status_reason.down.sql │ │ │ ├── 018_add_project_status_reason.up.sql │ │ │ ├── 022_unique_oidc_subject_index.down.sql │ │ │ ├── 008_clean_compose_templates.down.sql │ │ │ ├── 019_add_user_requires_password_change.down.sql │ │ │ ├── 021_add_notifications.down.sql │ │ │ ├── 009_add_image_update_auth_fields.down.sql │ │ │ ├── 011_add_environment_name.down.sql │ │ │ ├── 019_add_user_requires_password_change.up.sql │ │ │ ├── 020_remove_mobile_navigation_scroll_to_hide_setting.up.sql │ │ │ ├── 012_add_user_locale.down.sql │ │ │ ├── 017_drop_unused_docker_tables.up.sql │ │ │ ├── 014_containers_project_fk.down.sql │ │ │ ├── 014_containers_project_fk.up.sql │ │ │ ├── 020_remove_mobile_navigation_scroll_to_hide_setting.down.sql │ │ │ ├── 011_add_environment_name.up.sql │ │ │ ├── 013_stacks_to_projects.up.sql │ │ │ ├── 007_add_user_oidc_tokens.down.sql │ │ │ ├── 024_add_api_keys.down.sql │ │ │ ├── 007_add_user_oidc_tokens.up.sql │ │ │ ├── 004_project_cache.down.sql │ │ │ ├── 015_rename_stacks_directory_setting.up.sql │ │ │ ├── 015_rename_stacks_directory_setting.down.sql │ │ │ ├── 005_drop_user_sessions_table.up.sql │ │ │ ├── 010_rework_environments.down.sql │ │ │ ├── 025_add_environment_api_key_pairing.down.sql │ │ │ ├── 010_rework_environments.up.sql │ │ │ ├── 026_add_notification_sent_to_image_updates.up.sql │ │ │ ├── 009_add_image_update_auth_fields.up.sql │ │ │ ├── 022_unique_oidc_subject_index.up.sql │ │ │ ├── 003_remove_events_environment_fk.down.sql │ │ │ ├── 002_create_events_table.down.sql │ │ │ ├── 023_add_apprise_settings.up.sql │ │ │ ├── 025_add_environment_api_key_pairing.up.sql │ │ │ ├── 006_rename_tables.down.sql │ │ │ ├── 006_rename_tables.up.sql │ │ │ ├── 016_drop_image_table.down.sql │ │ │ ├── 024_add_api_keys.up.sql │ │ │ ├── 013_stacks_to_projects.down.sql │ │ │ └── 005_drop_user_sessions_table.down.sql │ │ └── postgres │ │ │ ├── 016_drop_image_table.up.sql │ │ │ ├── 012_add_user_locale.down.sql │ │ │ ├── 023_add_apprise_settings.down.sql │ │ │ ├── 012_add_user_locale.up.sql │ │ │ ├── 018_add_project_status_reason.down.sql │ │ │ ├── 018_add_project_status_reason.up.sql │ │ │ ├── 022_unique_oidc_subject_index.down.sql │ │ │ ├── 008_clean_compose_templates.up.sql │ │ │ ├── 003_remove_events_environment_fk.up.sql │ │ │ ├── 008_clean_compose_templates.down.sql │ │ │ ├── 019_add_user_requires_password_change.down.sql │ │ │ ├── 021_add_notifications.down.sql │ │ │ ├── 011_add_environment_name.down.sql │ │ │ ├── 020_remove_mobile_navigation_scroll_to_hide_setting.up.sql │ │ │ ├── 019_add_user_requires_password_change.up.sql │ │ │ ├── 020_remove_mobile_navigation_scroll_to_hide_setting.down.sql │ │ │ ├── 011_add_environment_name.up.sql │ │ │ ├── 017_drop_unused_docker_tables.up.sql │ │ │ ├── 003_remove_events_environment_fk.down.sql │ │ │ ├── 024_add_api_keys.down.sql │ │ │ ├── 007_add_user_oidc_tokens.down.sql │ │ │ ├── 013_stacks_to_projects.up.sql │ │ │ ├── 004_project_cache.down.sql │ │ │ ├── 007_add_user_oidc_tokens.up.sql │ │ │ ├── 026_add_notification_sent_to_image_updates.down.sql │ │ │ ├── 022_unique_oidc_subject_index.up.sql │ │ │ ├── 005_drop_user_sessions_table.up.sql │ │ │ ├── 009_add_image_update_auth_fields.down.sql │ │ │ ├── 025_add_environment_api_key_pairing.down.sql │ │ │ ├── 015_rename_stacks_directory_setting.up.sql │ │ │ ├── 015_rename_stacks_directory_setting.down.sql │ │ │ ├── 026_add_notification_sent_to_image_updates.up.sql │ │ │ ├── 009_add_image_update_auth_fields.up.sql │ │ │ ├── 010_rework_environments.down.sql │ │ │ ├── 010_rework_environments.up.sql │ │ │ ├── 002_create_events_table.down.sql │ │ │ ├── 025_add_environment_api_key_pairing.up.sql │ │ │ ├── 023_add_apprise_settings.up.sql │ │ │ ├── 006_rename_tables.down.sql │ │ │ ├── 006_rename_tables.up.sql │ │ │ ├── 016_drop_image_table.down.sql │ │ │ ├── 014_containers_project_fk.down.sql │ │ │ └── 024_add_api_keys.up.sql │ ├── images │ │ ├── favicon.ico │ │ └── profile.webp │ ├── fonts │ │ ├── Geist │ │ │ ├── geist.woff2 │ │ │ └── geist-mono.woff2 │ │ └── Calistoga │ │ │ └── Calistoga-Regular.woff2 │ ├── email-templates │ │ ├── test_text.tmpl │ │ └── batch-image-updates_text.tmpl │ └── embed.go ├── frontend │ ├── shared.go │ └── excluded.go ├── internal │ ├── models │ │ └── updater.go │ ├── utils │ │ ├── ptr_util.go │ │ ├── docker │ │ │ └── network_utils.go │ │ ├── registry │ │ │ ├── constants.go │ │ │ ├── client.go │ │ │ └── helpers.go │ │ ├── pagination │ │ │ ├── types.go │ │ │ └── pagination.go │ │ ├── http │ │ │ └── client.go │ │ └── image │ │ │ └── image_util.go │ ├── config │ │ └── version.go │ └── bootstrap │ │ └── db_bootstrap.go ├── cmd │ └── main.go └── cli │ └── version.go ├── frontend ├── project.inlang │ ├── .gitignore │ ├── project_id │ └── settings.json ├── .env.example ├── src │ ├── lib │ │ ├── components │ │ │ ├── ui │ │ │ │ ├── sonner │ │ │ │ │ └── index.ts │ │ │ │ ├── spinner │ │ │ │ │ ├── index.ts │ │ │ │ │ └── spinner.svelte │ │ │ │ ├── input │ │ │ │ │ └── index.ts │ │ │ │ ├── label │ │ │ │ │ ├── index.ts │ │ │ │ │ └── label.svelte │ │ │ │ ├── checkbox │ │ │ │ │ └── index.ts │ │ │ │ ├── switch │ │ │ │ │ └── index.ts │ │ │ │ ├── progress │ │ │ │ │ └── index.ts │ │ │ │ ├── skeleton │ │ │ │ │ ├── index.ts │ │ │ │ │ └── skeleton.svelte │ │ │ │ ├── textarea │ │ │ │ │ └── index.ts │ │ │ │ ├── separator │ │ │ │ │ ├── index.ts │ │ │ │ │ └── separator.svelte │ │ │ │ ├── badge │ │ │ │ │ └── index.ts │ │ │ │ ├── snippet │ │ │ │ │ └── index.ts │ │ │ │ ├── copy-button │ │ │ │ │ └── index.ts │ │ │ │ ├── responsive-dialog │ │ │ │ │ └── index.ts │ │ │ │ ├── data-table │ │ │ │ │ └── index.ts │ │ │ │ ├── radio-group │ │ │ │ │ ├── index.ts │ │ │ │ │ └── radio-group.svelte │ │ │ │ ├── tooltip │ │ │ │ │ ├── tooltip-portal.svelte │ │ │ │ │ ├── tooltip-provider.svelte │ │ │ │ │ ├── tooltip.svelte │ │ │ │ │ ├── tooltip-trigger.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── scroll-area │ │ │ │ │ └── index.ts │ │ │ │ ├── select │ │ │ │ │ ├── select-group.svelte │ │ │ │ │ ├── select-separator.svelte │ │ │ │ │ ├── select-label.svelte │ │ │ │ │ ├── select-group-heading.svelte │ │ │ │ │ ├── select-scroll-up-button.svelte │ │ │ │ │ └── select-scroll-down-button.svelte │ │ │ │ ├── sheet │ │ │ │ │ ├── sheet-close.svelte │ │ │ │ │ ├── sheet-trigger.svelte │ │ │ │ │ ├── sheet-title.svelte │ │ │ │ │ ├── sheet-description.svelte │ │ │ │ │ ├── sheet-header.svelte │ │ │ │ │ ├── sheet-footer.svelte │ │ │ │ │ └── sheet-overlay.svelte │ │ │ │ ├── dialog │ │ │ │ │ ├── dialog-close.svelte │ │ │ │ │ ├── dialog-trigger.svelte │ │ │ │ │ ├── dialog-title.svelte │ │ │ │ │ ├── dialog-description.svelte │ │ │ │ │ ├── dialog-header.svelte │ │ │ │ │ ├── dialog-footer.svelte │ │ │ │ │ └── dialog-overlay.svelte │ │ │ │ ├── drawer │ │ │ │ │ ├── drawer-close.svelte │ │ │ │ │ ├── drawer-trigger.svelte │ │ │ │ │ ├── drawer-title.svelte │ │ │ │ │ ├── drawer.svelte │ │ │ │ │ ├── drawer-nested.svelte │ │ │ │ │ ├── drawer-description.svelte │ │ │ │ │ ├── drawer-header.svelte │ │ │ │ │ ├── drawer-footer.svelte │ │ │ │ │ └── drawer-overlay.svelte │ │ │ │ ├── avatar │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── avatar-image.svelte │ │ │ │ │ ├── avatar.svelte │ │ │ │ │ └── avatar-fallback.svelte │ │ │ │ ├── sidebar │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── sidebar-footer.svelte │ │ │ │ │ ├── sidebar-header.svelte │ │ │ │ │ ├── sidebar-separator.svelte │ │ │ │ │ ├── sidebar-group.svelte │ │ │ │ │ ├── sidebar-input.svelte │ │ │ │ │ ├── sidebar-group-content.svelte │ │ │ │ │ ├── sidebar-menu-item.svelte │ │ │ │ │ ├── sidebar-menu-sub-item.svelte │ │ │ │ │ ├── sidebar-menu.svelte │ │ │ │ │ └── sidebar-inset.svelte │ │ │ │ ├── tree-view │ │ │ │ │ ├── tree-view.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── tree-view-file.svelte │ │ │ │ │ └── types.ts │ │ │ │ ├── collapsible │ │ │ │ │ ├── collapsible-content.svelte │ │ │ │ │ ├── collapsible-trigger.svelte │ │ │ │ │ ├── collapsible.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── alert-dialog │ │ │ │ │ ├── alert-dialog-trigger.svelte │ │ │ │ │ ├── alert-dialog-title.svelte │ │ │ │ │ ├── alert-dialog-description.svelte │ │ │ │ │ ├── alert-dialog-action.svelte │ │ │ │ │ ├── alert-dialog-cancel.svelte │ │ │ │ │ ├── alert-dialog-header.svelte │ │ │ │ │ ├── alert-dialog-footer.svelte │ │ │ │ │ └── alert-dialog-overlay.svelte │ │ │ │ ├── dropdown-menu │ │ │ │ │ ├── dropdown-menu-group.svelte │ │ │ │ │ ├── dropdown-menu-trigger.svelte │ │ │ │ │ ├── dropdown-menu-radio-group.svelte │ │ │ │ │ ├── dropdown-menu-separator.svelte │ │ │ │ │ ├── dropdown-menu-shortcut.svelte │ │ │ │ │ ├── dropdown-menu-label.svelte │ │ │ │ │ └── dropdown-menu-group-heading.svelte │ │ │ │ ├── button-group │ │ │ │ │ ├── index.ts │ │ │ │ │ └── button-group-separator.svelte │ │ │ │ ├── accordion │ │ │ │ │ ├── accordion-item.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ └── accordion-content.svelte │ │ │ │ ├── pagination │ │ │ │ │ ├── pagination-item.svelte │ │ │ │ │ ├── pagination-content.svelte │ │ │ │ │ ├── pagination.svelte │ │ │ │ │ ├── pagination-ellipsis.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── pagination-next-button.svelte │ │ │ │ │ └── pagination-prev-button.svelte │ │ │ │ ├── alert │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── alert-description.svelte │ │ │ │ │ └── alert-title.svelte │ │ │ │ ├── popover │ │ │ │ │ ├── popover-trigger.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── field-set │ │ │ │ │ ├── field-set-content.svelte │ │ │ │ │ ├── field-set-footer.svelte │ │ │ │ │ ├── field-set.svelte │ │ │ │ │ └── field-set-title.svelte │ │ │ │ ├── calendar │ │ │ │ │ ├── calendar-grid-body.svelte │ │ │ │ │ ├── calendar-grid-head.svelte │ │ │ │ │ ├── calendar-grid-row.svelte │ │ │ │ │ ├── calendar-heading.svelte │ │ │ │ │ ├── calendar-grid.svelte │ │ │ │ │ ├── calendar-month.svelte │ │ │ │ │ ├── calendar-head-cell.svelte │ │ │ │ │ ├── calendar-header.svelte │ │ │ │ │ ├── calendar-months.svelte │ │ │ │ │ ├── calendar-nav.svelte │ │ │ │ │ └── calendar-cell.svelte │ │ │ │ ├── command │ │ │ │ │ ├── command-empty.svelte │ │ │ │ │ ├── command-separator.svelte │ │ │ │ │ ├── command-list.svelte │ │ │ │ │ ├── command.svelte │ │ │ │ │ └── command-shortcut.svelte │ │ │ │ ├── button │ │ │ │ │ └── index.ts │ │ │ │ ├── empty │ │ │ │ │ ├── empty-title.svelte │ │ │ │ │ ├── empty-header.svelte │ │ │ │ │ ├── empty-content.svelte │ │ │ │ │ ├── empty-description.svelte │ │ │ │ │ ├── empty.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── tabs │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── tabs-list.svelte │ │ │ │ │ ├── tabs-content.svelte │ │ │ │ │ └── tabs-trigger.svelte │ │ │ │ ├── breadcrumb │ │ │ │ │ ├── breadcrumb.svelte │ │ │ │ │ ├── breadcrumb-item.svelte │ │ │ │ │ ├── breadcrumb-list.svelte │ │ │ │ │ ├── breadcrumb-page.svelte │ │ │ │ │ ├── breadcrumb-separator.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ └── breadcrumb-ellipsis.svelte │ │ │ │ ├── form │ │ │ │ │ ├── form-description.svelte │ │ │ │ │ ├── form-legend.svelte │ │ │ │ │ ├── form-label.svelte │ │ │ │ │ └── form-fieldset.svelte │ │ │ │ ├── card │ │ │ │ │ ├── card-content.svelte │ │ │ │ │ ├── card-description.svelte │ │ │ │ │ ├── card-footer.svelte │ │ │ │ │ ├── card-title.svelte │ │ │ │ │ ├── card-action.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── table │ │ │ │ │ ├── table-caption.svelte │ │ │ │ │ ├── table-header.svelte │ │ │ │ │ ├── table-body.svelte │ │ │ │ │ ├── table.svelte │ │ │ │ │ ├── table-cell.svelte │ │ │ │ │ ├── table-footer.svelte │ │ │ │ │ ├── table-row.svelte │ │ │ │ │ ├── table-head.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── input-group │ │ │ │ │ ├── input-group-text.svelte │ │ │ │ │ ├── input-group-textarea.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ └── input-group-input.svelte │ │ │ │ ├── item │ │ │ │ │ ├── item-separator.svelte │ │ │ │ │ ├── item-actions.svelte │ │ │ │ │ ├── item-group.svelte │ │ │ │ │ ├── item-footer.svelte │ │ │ │ │ ├── item-header.svelte │ │ │ │ │ ├── item-title.svelte │ │ │ │ │ ├── item-content.svelte │ │ │ │ │ └── item-description.svelte │ │ │ │ └── dropdown-button │ │ │ │ │ └── dropdown-button-separator.svelte │ │ │ ├── tab-bar │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── badges │ │ │ │ └── index.ts │ │ │ ├── arcane-tooltip │ │ │ │ ├── index.ts │ │ │ │ └── context.svelte.ts │ │ │ └── arcane-table │ │ │ │ ├── arcane-table-checkbox.svelte │ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── constants.ts │ │ ├── types │ │ │ ├── variable.type.ts │ │ │ ├── event.type.ts │ │ │ ├── application-configuration.ts │ │ │ ├── system-stats.type.ts │ │ │ ├── api-key.type.ts │ │ │ ├── settings-search.type.ts │ │ │ ├── notification.type.ts │ │ │ ├── auth.type.ts │ │ │ ├── customize-search.type.ts │ │ │ ├── user.type.ts │ │ │ ├── container-registry.type.ts │ │ │ └── environment.type.ts │ │ ├── hooks │ │ │ ├── is-mobile.svelte.ts │ │ │ ├── is-tablet.svelte.ts │ │ │ ├── index.ts │ │ │ └── use-environment-refresh.svelte.ts │ │ ├── utils │ │ │ ├── utils.ts │ │ │ ├── try-catch.ts │ │ │ ├── string.utils.ts │ │ │ ├── status.utils.ts │ │ │ └── ansi.ts │ │ ├── layouts │ │ │ └── index.ts │ │ ├── stores │ │ │ ├── user-store.ts │ │ │ └── config-store.ts │ │ ├── utils.ts │ │ └── services │ │ │ ├── settings-search.ts │ │ │ ├── customize-search.ts │ │ │ └── event-service.ts │ ├── routes │ │ ├── +page.ts │ │ ├── (auth) │ │ │ ├── +layout.svelte │ │ │ ├── logout │ │ │ │ └── +page.ts │ │ │ └── login │ │ │ │ └── +page.ts │ │ ├── (app) │ │ │ ├── customize │ │ │ │ ├── variables │ │ │ │ │ └── +page.ts │ │ │ │ └── templates │ │ │ │ │ ├── components │ │ │ │ │ └── TemplatesBrowser.svelte │ │ │ │ │ └── [id] │ │ │ │ │ └── +page.ts │ │ │ ├── settings │ │ │ │ ├── +layout.ts │ │ │ │ ├── notifications │ │ │ │ │ └── +page.ts │ │ │ │ ├── users │ │ │ │ │ └── +page.ts │ │ │ │ └── api-keys │ │ │ │ │ └── +page.ts │ │ │ ├── environments │ │ │ │ └── [id] │ │ │ │ │ └── +page.ts │ │ │ ├── networks │ │ │ │ └── [networkId] │ │ │ │ │ └── +page.ts │ │ │ ├── projects │ │ │ │ └── [projectId] │ │ │ │ │ └── +page.ts │ │ │ └── events │ │ │ │ └── +page.ts │ │ ├── auth │ │ │ └── oidc │ │ │ │ └── callback │ │ │ │ └── +page.ts │ │ └── +error.svelte │ ├── app.d.ts │ └── hooks.client.ts ├── static │ ├── img │ │ └── pwa │ │ │ ├── icon-72x72.png │ │ │ ├── icon-96x96.png │ │ │ ├── icon-128x128.png │ │ │ ├── icon-144x144.png │ │ │ ├── icon-152x152.png │ │ │ ├── icon-192x192.png │ │ │ ├── icon-384x384.png │ │ │ └── icon-512x512.png │ └── browserconfig.xml ├── .prettierignore ├── .prettierrc ├── components.json ├── jsrepo.json ├── tsconfig.json └── svelte.config.js ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── assets │ ├── arcane.jpeg │ ├── arcane.png │ └── img │ │ └── PNG-3.png ├── ISSUE_TEMPLATE │ ├── config.yml │ └── language-request.yml └── matchers │ ├── deadcode-matcher.json │ └── svelte-check-matcher.json ├── .arcane.json ├── .devcontainer ├── Dockerfile └── compose.yaml ├── go.work ├── types ├── types.go ├── env │ └── env.go ├── system │ └── health.go └── search │ └── search.go ├── .vscode ├── extensions.json └── settings.json ├── cli ├── main.go └── pkg │ └── generate │ └── generate.go ├── tests ├── types │ ├── volumes.type.ts │ ├── containers.type.ts │ ├── networks.type.ts │ ├── api-key.type.ts │ └── project.type.ts ├── tsconfig.json ├── setup │ ├── projects │ │ └── test-project-static │ │ │ └── compose.yaml │ ├── auth.setup.ts │ ├── compose.yaml │ └── project.data.ts └── package.json ├── crowdin.yml ├── pnpm-workspace.yaml ├── email-templates ├── props.ts └── tsconfig.json ├── docker ├── next-builds │ ├── Dockerfile-next-distroless │ ├── Dockerfile-agent-distroless │ ├── Dockerfile-static │ └── Dockerfile-agent-static └── examples │ ├── compose.agent.yaml │ └── compose.basic.yaml └── .gitignore /depot.json: -------------------------------------------------------------------------------- 1 | {"id":"np622krb2x"} 2 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Build artifacts 2 | -------------------------------------------------------------------------------- /frontend/project.inlang/.gitignore: -------------------------------------------------------------------------------- 1 | cache -------------------------------------------------------------------------------- /frontend/project.inlang/project_id: -------------------------------------------------------------------------------- 1 | p0aEGYNG2ug5IMDBWo -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @getarcaneapp/maintainers 2 | -------------------------------------------------------------------------------- /.arcane.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.1", 3 | "revision": "345b62c0" 4 | } 5 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/base:bookworm 2 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.25.4 2 | 3 | use ( 4 | ./backend 5 | ./cli 6 | ./types 7 | ) 8 | -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=development 2 | DEV_BACKEND_URL=http://localhost:3552 3 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/016_drop_image_table.up.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS images_table; -------------------------------------------------------------------------------- /types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | LOCAL_DOCKER_ENVIRONMENT_ID = "0" 5 | ) 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: kmendell 4 | 5 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/016_drop_image_table.up.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS images_table; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/012_add_user_locale.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN locale TEXT; -------------------------------------------------------------------------------- /.github/assets/arcane.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/.github/assets/arcane.jpeg -------------------------------------------------------------------------------- /.github/assets/arcane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/.github/assets/arcane.png -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sonner/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toaster } from './sonner.svelte'; 2 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/spinner/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Spinner } from './spinner.svelte'; 2 | -------------------------------------------------------------------------------- /frontend/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /.github/assets/img/PNG-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/.github/assets/img/PNG-3.png -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/012_add_user_locale.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users DROP COLUMN IF EXISTS locale; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/023_add_apprise_settings.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS apprise_settings; 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["actboy168.tasks", "svelte.svelte-vscode", "golang.go"] 3 | } 4 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/023_add_apprise_settings.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS apprise_settings; 2 | -------------------------------------------------------------------------------- /frontend/src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_NETWORK_NAMES = new Set(['host', 'bridge', 'none', 'ingress']); 2 | -------------------------------------------------------------------------------- /frontend/src/lib/types/variable.type.ts: -------------------------------------------------------------------------------- 1 | export interface Variable { 2 | key: string; 3 | value: string; 4 | } 5 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/012_add_user_locale.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN IF NOT EXISTS locale TEXT; -------------------------------------------------------------------------------- /backend/resources/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/backend/resources/images/favicon.ico -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/018_add_project_status_reason.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects DROP COLUMN status_reason; 2 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/008_clean_compose_templates.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE compose_templates DROP COLUMN meta_updated_at; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/018_add_project_status_reason.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects DROP COLUMN status_reason; 2 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/018_add_project_status_reason.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects ADD COLUMN status_reason TEXT; 2 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/022_unique_oidc_subject_index.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS idx_users_oidc_subject_id_unique; -------------------------------------------------------------------------------- /backend/resources/images/profile.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/backend/resources/images/profile.webp -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/018_add_project_status_reason.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects ADD COLUMN status_reason TEXT; 2 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/022_unique_oidc_subject_index.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS idx_users_oidc_subject_id_unique; -------------------------------------------------------------------------------- /frontend/static/img/pwa/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/frontend/static/img/pwa/icon-72x72.png -------------------------------------------------------------------------------- /frontend/static/img/pwa/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/frontend/static/img/pwa/icon-96x96.png -------------------------------------------------------------------------------- /backend/resources/fonts/Geist/geist.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/backend/resources/fonts/Geist/geist.woff2 -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/008_clean_compose_templates.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE compose_templates ADD COLUMN meta_updated_at TEXT; -------------------------------------------------------------------------------- /cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import cli "github.com/getarcaneapp/arcane/cli/pkg" 4 | 5 | func main() { 6 | cli.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /frontend/static/img/pwa/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/frontend/static/img/pwa/icon-128x128.png -------------------------------------------------------------------------------- /frontend/static/img/pwa/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/frontend/static/img/pwa/icon-144x144.png -------------------------------------------------------------------------------- /frontend/static/img/pwa/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/frontend/static/img/pwa/icon-152x152.png -------------------------------------------------------------------------------- /frontend/static/img/pwa/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/frontend/static/img/pwa/icon-192x192.png -------------------------------------------------------------------------------- /frontend/static/img/pwa/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/frontend/static/img/pwa/icon-384x384.png -------------------------------------------------------------------------------- /frontend/static/img/pwa/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/frontend/static/img/pwa/icon-512x512.png -------------------------------------------------------------------------------- /tests/types/volumes.type.ts: -------------------------------------------------------------------------------- 1 | export interface VolumeUsageCounts { 2 | inuse: number; 3 | unused: number; 4 | total: number; 5 | } 6 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/019_add_user_requires_password_change.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users DROP COLUMN requires_password_change; 2 | -------------------------------------------------------------------------------- /backend/resources/fonts/Geist/geist-mono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/backend/resources/fonts/Geist/geist-mono.woff2 -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/008_clean_compose_templates.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE compose_templates 2 | DROP COLUMN IF EXISTS meta_updated_at; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './input.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Input 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/label/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './label.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Label 7 | }; 8 | -------------------------------------------------------------------------------- /backend/frontend/shared.go: -------------------------------------------------------------------------------- 1 | package frontend 2 | 3 | import "errors" 4 | 5 | var ErrFrontendNotIncluded = errors.New("frontend is not included") 6 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/003_remove_events_environment_fk.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE events DROP CONSTRAINT IF EXISTS events_environment_id_fkey; -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | 6 | # Compiled files 7 | .svelte-kit/ 8 | build/ -------------------------------------------------------------------------------- /frontend/src/lib/components/tab-bar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as TabBar } from './tab-bar.svelte'; 2 | export type { TabItem } from './types.ts'; 3 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './checkbox.svelte'; 2 | export { 3 | Root, 4 | // 5 | Root as Checkbox 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/switch/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './switch.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Switch 7 | }; 8 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/008_clean_compose_templates.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE compose_templates 2 | ADD COLUMN IF NOT EXISTS meta_updated_at TEXT; -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/019_add_user_requires_password_change.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users DROP COLUMN IF EXISTS requires_password_change; 2 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/021_add_notifications.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS notification_logs; 2 | DROP TABLE IF EXISTS notification_settings; 3 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/progress/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './progress.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Progress 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/skeleton/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './skeleton.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Skeleton 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/textarea/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './textarea.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Textarea 7 | }; 8 | -------------------------------------------------------------------------------- /backend/internal/models/updater.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Reserved for future persisted updater state or config overrides specific to the updater module. 4 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/021_add_notifications.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS notification_logs; 2 | DROP TABLE IF EXISTS notification_settings; 3 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/009_add_image_update_auth_fields.down.sql: -------------------------------------------------------------------------------- 1 | -- no-op: dropping columns in SQLite requires table rebuild; intentionally left empty -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/011_add_environment_name.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE environments DROP COLUMN name; 2 | DROP INDEX IF EXISTS idx_environments_name; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/separator/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./separator.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Separator, 7 | }; 8 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/019_add_user_requires_password_change.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN requires_password_change BOOLEAN NOT NULL DEFAULT 0; 2 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/020_remove_mobile_navigation_scroll_to_hide_setting.up.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM settings WHERE key = 'mobileNavigationScrollToHide'; 2 | 3 | -------------------------------------------------------------------------------- /backend/resources/fonts/Calistoga/Calistoga-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getarcaneapp/arcane/HEAD/backend/resources/fonts/Calistoga/Calistoga-Regular.woff2 -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/011_add_environment_name.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE environments DROP COLUMN IF EXISTS name; 2 | DROP INDEX IF EXISTS idx_environments_name; -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/020_remove_mobile_navigation_scroll_to_hide_setting.up.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM settings WHERE key = 'mobileNavigationScrollToHide'; 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/badge/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Badge } from './badge.svelte'; 2 | export { badgeVariants, type BadgeVariant } from './badge.svelte'; 3 | -------------------------------------------------------------------------------- /backend/resources/email-templates/test_text.tmpl: -------------------------------------------------------------------------------- 1 | {{define "root"}}TEST EMAIL 2 | 3 | Your email setup is working correctly! 4 | 5 | Open Arcane Dashboard → {{.AppURL}}{{end}} -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/012_add_user_locale.down.sql: -------------------------------------------------------------------------------- 1 | -- SQLite down migration: to rollback, recreate the users table without the 'locale' column and copy data back. -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/017_drop_unused_docker_tables.up.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS containers; 2 | DROP TABLE IF EXISTS volumes; 3 | DROP TABLE IF EXISTS networks; 4 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/019_add_user_requires_password_change.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN IF NOT EXISTS requires_password_change BOOLEAN NOT NULL DEFAULT FALSE; 2 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/014_containers_project_fk.down.sql: -------------------------------------------------------------------------------- 1 | PRAGMA foreign_keys=off; 2 | ALTER TABLE containers RENAME COLUMN project_id TO stack_id; 3 | PRAGMA foreign_keys=on; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/014_containers_project_fk.up.sql: -------------------------------------------------------------------------------- 1 | PRAGMA foreign_keys=off; 2 | ALTER TABLE containers RENAME COLUMN stack_id TO project_id; 3 | PRAGMA foreign_keys=on; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/020_remove_mobile_navigation_scroll_to_hide_setting.down.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO settings (key, value) VALUES ('mobileNavigationScrollToHide', 'true'); 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/snippet/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Installed from @ieedan/shadcn-svelte-extras 3 | */ 4 | 5 | import Snippet from './snippet.svelte'; 6 | 7 | export { Snippet }; 8 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/020_remove_mobile_navigation_scroll_to_hide_setting.down.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO settings (key, value) VALUES ('mobileNavigationScrollToHide', 'true'); 2 | 3 | -------------------------------------------------------------------------------- /backend/resources/embed.go: -------------------------------------------------------------------------------- 1 | package resources 2 | 3 | import "embed" 4 | 5 | // Embedded file systems for the project 6 | 7 | //go:embed migrations images email-templates fonts 8 | var FS embed.FS 9 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/011_add_environment_name.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE environments ADD COLUMN name TEXT DEFAULT ''; 2 | CREATE INDEX IF NOT EXISTS idx_environments_name ON environments(name); -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/copy-button/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Installed from @ieedan/shadcn-svelte-extras 3 | */ 4 | 5 | import CopyButton from './copy-button.svelte'; 6 | 7 | export { CopyButton }; 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💬 Discord 4 | url: https://discord.gg/WyXYpdyV3Z 5 | about: For help and chatting with the community 6 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/011_add_environment_name.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE environments ADD COLUMN IF NOT EXISTS name TEXT; 2 | CREATE INDEX IF NOT EXISTS idx_environments_name ON environments (name); -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/017_drop_unused_docker_tables.up.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS containers CASCADE; 2 | DROP TABLE IF EXISTS volumes CASCADE; 3 | DROP TABLE IF EXISTS networks CASCADE; 4 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /frontend/messages/en.json 3 | translation: /%original_path%/%two_letters_code%.json 4 | pull_request_title: 'chore(translations): update translations via Crowdin' 5 | -------------------------------------------------------------------------------- /tests/types/containers.type.ts: -------------------------------------------------------------------------------- 1 | export type ContainerSummary = { 2 | id: string; 3 | names?: string[]; 4 | image?: string; 5 | state: string; 6 | status?: string; 7 | created?: number; 8 | }; 9 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/013_stacks_to_projects.up.sql: -------------------------------------------------------------------------------- 1 | -- Rename stacks -> projects 2 | ALTER TABLE stacks RENAME TO projects; 3 | 4 | -- Remove legacy cache table 5 | DROP TABLE IF EXISTS project_cache; -------------------------------------------------------------------------------- /frontend/src/lib/components/tab-bar/types.ts: -------------------------------------------------------------------------------- 1 | export interface TabItem { 2 | value: string; 3 | label: string; 4 | icon?: any; 5 | badge?: string | number; 6 | disabled?: boolean; 7 | class?: string; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | import type { PageLoad } from './$types'; 3 | 4 | export const load: PageLoad = async () => { 5 | return redirect(302, '/login'); 6 | }; 7 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/003_remove_events_environment_fk.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE events ADD CONSTRAINT events_environment_id_fkey 2 | FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE SET NULL; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/007_add_user_oidc_tokens.down.sql: -------------------------------------------------------------------------------- 1 | -- SQLite cannot DROP COLUMN directly. No-op down migration. 2 | -- To rollback manually, recreate the users table without these columns and copy data back. -------------------------------------------------------------------------------- /types/env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | type Variable struct { 4 | Key string `json:"key"` 5 | Value string `json:"value"` 6 | } 7 | 8 | type Summary struct { 9 | Variables []Variable `json:"variables"` 10 | } 11 | -------------------------------------------------------------------------------- /backend/internal/utils/ptr_util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func Ptr[T any](v T) *T { 4 | return &v 5 | } 6 | 7 | func DerefString(p *string) string { 8 | if p == nil { 9 | return "" 10 | } 11 | return *p 12 | } 13 | -------------------------------------------------------------------------------- /backend/frontend/excluded.go: -------------------------------------------------------------------------------- 1 | //go:build exclude_frontend 2 | 3 | package frontend 4 | 5 | import "github.com/gin-gonic/gin" 6 | 7 | func RegisterFrontend(router *gin.Engine) error { 8 | return ErrFrontendNotIncluded 9 | } 10 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/024_add_api_keys.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS idx_api_keys_key_prefix; 2 | DROP INDEX IF EXISTS idx_api_keys_key_hash; 3 | DROP INDEX IF EXISTS idx_api_keys_user_id; 4 | DROP TABLE IF EXISTS api_keys; 5 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/024_add_api_keys.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS idx_api_keys_key_prefix; 2 | DROP INDEX IF EXISTS idx_api_keys_key_hash; 3 | DROP INDEX IF EXISTS idx_api_keys_user_id; 4 | DROP TABLE IF EXISTS api_keys; 5 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/responsive-dialog/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './responsive-dialog.svelte'; 2 | 3 | export { Root, Root as ResponsiveDialog }; 4 | 5 | export type { ResponsiveDialogProps } from './responsive-dialog.type.js'; 6 | -------------------------------------------------------------------------------- /backend/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/getarcaneapp/arcane/backend/cli" 5 | ) 6 | 7 | // @title Arcane API 8 | // @version 1.0 9 | // @description.markdown 10 | 11 | func main() { 12 | cli.Execute() 13 | } 14 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/007_add_user_oidc_tokens.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS users 2 | DROP COLUMN IF EXISTS oidc_access_token, 3 | DROP COLUMN IF EXISTS oidc_refresh_token, 4 | DROP COLUMN IF EXISTS oidc_access_token_expires_at; -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/013_stacks_to_projects.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | -- Rename stacks -> projects 3 | ALTER TABLE IF EXISTS stacks RENAME TO projects; 4 | 5 | -- Remove legacy cache table 6 | DROP TABLE IF EXISTS project_cache; 7 | COMMIT; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/007_add_user_oidc_tokens.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN oidc_access_token TEXT; 2 | ALTER TABLE users ADD COLUMN oidc_refresh_token TEXT; 3 | ALTER TABLE users ADD COLUMN oidc_access_token_expires_at DATETIME; -------------------------------------------------------------------------------- /backend/internal/utils/docker/network_utils.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | func IsDefaultNetwork(name string) bool { 4 | switch name { 5 | case "bridge", "host", "none", "ingress": 6 | return true 7 | default: 8 | return false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/004_project_cache.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS idx_project_cache_auto_update; 2 | DROP INDEX IF EXISTS idx_project_cache_cached_at; 3 | DROP INDEX IF EXISTS idx_project_cache_status; 4 | DROP TABLE IF EXISTS project_cache; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/data-table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FlexRender } from './flex-render.svelte'; 2 | export { renderComponent, renderSnippet } from './render-helpers.js'; 3 | export { createSvelteTable } from './data-table.svelte.js'; 4 | -------------------------------------------------------------------------------- /.devcontainer/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | arcane-dev-container: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | volumes: 7 | - ../:/workspace:cached 8 | command: sleep infinity 9 | working_dir: /workspace 10 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/004_project_cache.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS idx_project_cache_auto_update; 2 | DROP INDEX IF EXISTS idx_project_cache_cached_at; 3 | DROP INDEX IF EXISTS idx_project_cache_status; 4 | DROP TABLE IF EXISTS project_cache; -------------------------------------------------------------------------------- /frontend/src/lib/components/badges/index.ts: -------------------------------------------------------------------------------- 1 | export { default as StatusBadge } from './status-badge.svelte'; 2 | export { default as PortBadge } from './port-badge.svelte'; 3 | export { default as UiConfigDisabledTag } from './ui-config-disabled-tag.svelte'; 4 | -------------------------------------------------------------------------------- /frontend/src/routes/(auth)/+layout.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | {@render children()} 9 |
10 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/radio-group/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './radio-group.svelte'; 2 | import Item from './radio-group-item.svelte'; 3 | 4 | export { 5 | Root, 6 | Item, 7 | // 8 | Root as RadioGroup, 9 | Item as RadioGroupItem 10 | }; 11 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/015_rename_stacks_directory_setting.up.sql: -------------------------------------------------------------------------------- 1 | INSERT OR IGNORE INTO settings (key, value) 2 | SELECT 'projectsDirectory', value 3 | FROM settings 4 | WHERE key = 'stacksDirectory'; 5 | 6 | DELETE FROM settings WHERE key = 'stacksDirectory'; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/015_rename_stacks_directory_setting.down.sql: -------------------------------------------------------------------------------- 1 | INSERT OR IGNORE INTO settings (key, value) 2 | SELECT 'stacksDirectory', value 3 | FROM settings 4 | WHERE key = 'projectsDirectory'; 5 | 6 | DELETE FROM settings WHERE key = 'projectsDirectory'; -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/007_add_user_oidc_tokens.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS users 2 | ADD COLUMN IF NOT EXISTS oidc_access_token TEXT, 3 | ADD COLUMN IF NOT EXISTS oidc_refresh_token TEXT, 4 | ADD COLUMN IF NOT EXISTS oidc_access_token_expires_at TIMESTAMPTZ; -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/026_add_notification_sent_to_image_updates.down.sql: -------------------------------------------------------------------------------- 1 | -- Remove notification_sent column 2 | -- PostgreSQL automatically preserves existing indexes on other columns 3 | ALTER TABLE IF EXISTS image_updates 4 | DROP COLUMN notification_sent; 5 | -------------------------------------------------------------------------------- /cli/pkg/generate/generate.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var GenerateCmd = &cobra.Command{ 8 | Use: "generate", 9 | Aliases: []string{"gen", "g"}, 10 | Short: "Generate secrets for arcanes backend", 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tooltip/tooltip-portal.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "lib": ["ES2022"], 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "target": "ES2022", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/types/networks.type.ts: -------------------------------------------------------------------------------- 1 | export interface NetworkUsageCounts { 2 | inuse: number; 3 | unused: number; 4 | total: number; 5 | } 6 | 7 | export type NetworkSummary = { 8 | id: string; 9 | name: string; 10 | driver?: string; 11 | scope?: string; 12 | }; 13 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/022_unique_oidc_subject_index.up.sql: -------------------------------------------------------------------------------- 1 | -- Ensure OIDC subject is unique across users while allowing NULL values 2 | CREATE UNIQUE INDEX IF NOT EXISTS idx_users_oidc_subject_id_unique 3 | ON users (oidc_subject_id) 4 | WHERE oidc_subject_id IS NOT NULL; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/005_drop_user_sessions_table.up.sql: -------------------------------------------------------------------------------- 1 | -- Drop indexes first (safe if they exist), then drop the table 2 | DROP INDEX IF EXISTS idx_user_sessions_token; 3 | DROP INDEX IF EXISTS idx_user_sessions_user_id; 4 | 5 | DROP TABLE IF EXISTS user_sessions_table; -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 130, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tooltip/tooltip-provider.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/hooks/is-mobile.svelte.ts: -------------------------------------------------------------------------------- 1 | import { MediaQuery } from 'svelte/reactivity'; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export class IsMobile extends MediaQuery { 6 | constructor() { 7 | super(`max-width: ${MOBILE_BREAKPOINT - 1}px`); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /types/system/health.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | // HealthResponse contains the health status of the API. 4 | type HealthResponse struct { 5 | // Status indicates the health status (e.g., "UP", "DOWN"). 6 | // 7 | // Required: true 8 | Status string `json:"status"` 9 | } 10 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/005_drop_user_sessions_table.up.sql: -------------------------------------------------------------------------------- 1 | -- Drop indexes first (safe if they exist), then drop the table 2 | DROP INDEX IF EXISTS idx_user_sessions_token; 3 | DROP INDEX IF EXISTS idx_user_sessions_user_id; 4 | 5 | DROP TABLE IF EXISTS user_sessions_table; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/scroll-area/index.ts: -------------------------------------------------------------------------------- 1 | import Scrollbar from './scroll-area-scrollbar.svelte'; 2 | import Root from './scroll-area.svelte'; 3 | 4 | export { 5 | Root, 6 | Scrollbar, 7 | //, 8 | Root as ScrollArea, 9 | Scrollbar as ScrollAreaScrollbar 10 | }; 11 | -------------------------------------------------------------------------------- /tests/types/api-key.type.ts: -------------------------------------------------------------------------------- 1 | export type ApiKey = { 2 | id: string; 3 | name: string; 4 | description?: string; 5 | keyPrefix: string; 6 | userId: string; 7 | expiresAt?: string; 8 | lastUsedAt?: string; 9 | createdAt: string; 10 | updatedAt?: string; 11 | }; 12 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/009_add_image_update_auth_fields.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS image_updates 2 | DROP COLUMN IF EXISTS used_credential, 3 | DROP COLUMN IF EXISTS auth_registry, 4 | DROP COLUMN IF EXISTS auth_username, 5 | DROP COLUMN IF EXISTS auth_method; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/010_rework_environments.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE environments DROP COLUMN access_token; 2 | ALTER TABLE environments ADD COLUMN hostname TEXT NOT NULL DEFAULT ''; 3 | ALTER TABLE environments ADD COLUMN description TEXT; 4 | DROP INDEX IF EXISTS idx_environments_api_url; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/025_add_environment_api_key_pairing.down.sql: -------------------------------------------------------------------------------- 1 | -- Remove environment_id column from api_keys table 2 | ALTER TABLE api_keys DROP COLUMN environment_id; 3 | 4 | -- Remove api_key_id column from environments table 5 | ALTER TABLE environments DROP COLUMN api_key_id; 6 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/025_add_environment_api_key_pairing.down.sql: -------------------------------------------------------------------------------- 1 | -- Remove environment_id column from api_keys table 2 | ALTER TABLE api_keys DROP COLUMN environment_id; 3 | 4 | -- Remove api_key_id column from environments table 5 | ALTER TABLE environments DROP COLUMN api_key_id; 6 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/010_rework_environments.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE environments DROP COLUMN hostname; 2 | ALTER TABLE environments DROP COLUMN description; 3 | ALTER TABLE environments ADD COLUMN access_token TEXT; 4 | CREATE INDEX IF NOT EXISTS idx_environments_api_url ON environments(api_url); -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/026_add_notification_sent_to_image_updates.up.sql: -------------------------------------------------------------------------------- 1 | -- Add notification_sent column to track if updates have been sent 2 | -- Existing indexes on repository and tag are preserved automatically 3 | ALTER TABLE image_updates ADD COLUMN notification_sent INTEGER DEFAULT 0; 4 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tooltip/tooltip.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/internal/utils/registry/constants.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | const DefaultRegistry = "registry-1.docker.io" 4 | const DefaultRegistryDomain = "docker.io" 5 | const DefaultRegistryHost = "index.docker.io" 6 | const ContentDigestHeader = "Docker-Content-Digest" 7 | const ChallengeHeader = "WWW-Authenticate" 8 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/015_rename_stacks_directory_setting.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | INSERT INTO settings (key, value) 3 | SELECT 'projectsDirectory', value 4 | FROM settings 5 | WHERE key = 'stacksDirectory' 6 | ON CONFLICT (key) DO NOTHING; 7 | 8 | DELETE FROM settings WHERE key = 'stacksDirectory'; 9 | COMMIT; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/select/select-group.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/internal/utils/registry/client.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // Client provides helper methods for Docker/OCI registries. 8 | type Client struct { 9 | http *http.Client 10 | } 11 | 12 | func NewClient() *Client { 13 | return &Client{http: &http.Client{}} 14 | } 15 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/015_rename_stacks_directory_setting.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | INSERT INTO settings (key, value) 3 | SELECT 'stacksDirectory', value 4 | FROM settings 5 | WHERE key = 'projectsDirectory' 6 | ON CONFLICT (key) DO NOTHING; 7 | 8 | DELETE FROM settings WHERE key = 'projectsDirectory'; 9 | COMMIT; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/009_add_image_update_auth_fields.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE image_updates ADD COLUMN auth_method TEXT; 2 | ALTER TABLE image_updates ADD COLUMN auth_username TEXT; 3 | ALTER TABLE image_updates ADD COLUMN auth_registry TEXT; 4 | ALTER TABLE image_updates ADD COLUMN used_credential INTEGER DEFAULT 0; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/022_unique_oidc_subject_index.up.sql: -------------------------------------------------------------------------------- 1 | -- Ensure OIDC subject is unique across users while allowing NULL values (SQLite >= 3.8 supports partial indexes) 2 | CREATE UNIQUE INDEX IF NOT EXISTS idx_users_oidc_subject_id_unique 3 | ON users(oidc_subject_id) 4 | WHERE oidc_subject_id IS NOT NULL; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sheet/sheet-close.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/026_add_notification_sent_to_image_updates.up.sql: -------------------------------------------------------------------------------- 1 | -- Add notification_sent column to track if updates have been sent 2 | -- Existing indexes on repository and tag are preserved automatically 3 | ALTER TABLE IF EXISTS image_updates 4 | ADD COLUMN notification_sent BOOLEAN DEFAULT false; 5 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-close.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sheet/sheet-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #000000 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - frontend 3 | - tests 4 | - email-templates 5 | 6 | onlyBuiltDependencies: 7 | - '@tailwindcss/oxide' 8 | - esbuild 9 | - sharp 10 | 11 | overrides: 12 | devalue: ^5.3.2 13 | valibot: ^1.2.0 14 | validator: ^13.15.22 15 | cookie: ^0.7.0 16 | next: ^16.0.10 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer-close.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/009_add_image_update_auth_fields.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS image_updates 2 | ADD COLUMN IF NOT EXISTS auth_method TEXT, 3 | ADD COLUMN IF NOT EXISTS auth_username TEXT, 4 | ADD COLUMN IF NOT EXISTS auth_registry TEXT, 5 | ADD COLUMN IF NOT EXISTS used_credential BOOLEAN DEFAULT FALSE; -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/010_rework_environments.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE environments DROP COLUMN IF EXISTS access_token; 2 | ALTER TABLE environments ADD COLUMN IF NOT EXISTS hostname TEXT NOT NULL DEFAULT ''; 3 | ALTER TABLE environments ADD COLUMN IF NOT EXISTS description TEXT; 4 | DROP INDEX IF EXISTS idx_environments_api_url; -------------------------------------------------------------------------------- /email-templates/props.ts: -------------------------------------------------------------------------------- 1 | export const sharedPreviewProps = { 2 | logoURL: 'https://raw.githubusercontent.com/ofkm/arcane/main/backend/resources/images/logo-full.svg', 3 | appURL: 'http://localhost:3552', 4 | }; 5 | 6 | export const sharedTemplateProps = { 7 | logoURL: '{{.LogoURL}}', 8 | appURL: '{{.AppURL}}', 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/types/project.type.ts: -------------------------------------------------------------------------------- 1 | export type Project = { 2 | id?: string; 3 | name?: string; 4 | status?: string; 5 | serviceCount?: number; 6 | [key: string]: any; 7 | }; 8 | 9 | export interface ProjectStatusCounts { 10 | runningProjects: number; 11 | stoppedProjects: number; 12 | totalProjects: number; 13 | } 14 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/010_rework_environments.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE environments DROP COLUMN IF EXISTS hostname; 2 | ALTER TABLE environments DROP COLUMN IF EXISTS description; 3 | ALTER TABLE environments ADD COLUMN IF NOT EXISTS access_token TEXT; 4 | CREATE INDEX IF NOT EXISTS idx_environments_api_url ON environments (api_url); -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/routes/(app)/customize/variables/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | import { templateService } from '$lib/services/template-service'; 3 | 4 | export const load: PageLoad = async () => { 5 | const variables = await templateService.getGlobalVariables(); 6 | return { 7 | variables 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/avatar/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './avatar.svelte'; 2 | import Image from './avatar-image.svelte'; 3 | import Fallback from './avatar-fallback.svelte'; 4 | 5 | export { 6 | Root, 7 | Image, 8 | Fallback, 9 | // 10 | Root as Avatar, 11 | Image as AvatarImage, 12 | Fallback as AvatarFallback 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/constants.ts: -------------------------------------------------------------------------------- 1 | export const SIDEBAR_COOKIE_NAME = 'sidebar:state'; 2 | export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; 3 | export const SIDEBAR_WIDTH = '17rem'; 4 | export const SIDEBAR_WIDTH_MOBILE = '18rem'; 5 | export const SIDEBAR_WIDTH_ICON = '3rem'; 6 | export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; 7 | -------------------------------------------------------------------------------- /backend/internal/utils/pagination/types.go: -------------------------------------------------------------------------------- 1 | package pagination 2 | 3 | type Response struct { 4 | TotalPages int64 `json:"totalPages"` 5 | TotalItems int64 `json:"totalItems"` 6 | CurrentPage int `json:"currentPage"` 7 | ItemsPerPage int `json:"itemsPerPage"` 8 | GrandTotalItems int64 `json:"grandTotalItems,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tree-view/tree-view.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | {@render children?.()} 10 |
11 | -------------------------------------------------------------------------------- /frontend/src/lib/hooks/is-tablet.svelte.ts: -------------------------------------------------------------------------------- 1 | import { MediaQuery } from 'svelte/reactivity'; 2 | 3 | // Breakpoint for tablet/small desktop where sidebar should auto-collapse 4 | const TABLET_BREAKPOINT = 1024; 5 | 6 | export class IsTablet extends MediaQuery { 7 | constructor() { 8 | super(`max-width: ${TABLET_BREAKPOINT - 1}px`); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/collapsible/collapsible-content.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/collapsible/collapsible-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/003_remove_events_environment_fk.down.sql: -------------------------------------------------------------------------------- 1 | -- Create migration file: backend/resources/migrations/sqlite/003_remove_events_environment_fk.down.sql 2 | -- This would require recreating the table with the foreign key constraint 3 | -- For simplicity, we'll leave this empty since rolling back would be complex in SQLite 4 | DROP TABLE IF EXISTS events; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/collapsible/collapsible.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/setup/projects/test-project-static/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: nginx:alpine 4 | container_name: nginx_service 5 | env_file: 6 | - .env 7 | ports: 8 | - "8081:80" 9 | volumes: 10 | - nginx_data:/usr/share/nginx/html 11 | restart: unless-stopped 12 | 13 | volumes: 14 | nginx_data: 15 | driver: local 16 | -------------------------------------------------------------------------------- /docker/next-builds/Dockerfile-next-distroless: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/static-debian13 2 | 3 | ARG TARGETARCH 4 | ARG TARGETVARIANT 5 | 6 | WORKDIR /app 7 | 8 | COPY linux/${TARGETARCH}/${TARGETVARIANT}/arcane ./arcane 9 | 10 | ENV ENVIRONMENT=production 11 | ENV GIN_MODE=release 12 | ENV PORT=3552 13 | 14 | EXPOSE 3552 15 | VOLUME ["/app/data"] 16 | 17 | CMD ["./arcane"] 18 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/button-group/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './button-group.svelte'; 2 | import Text from './button-group-text.svelte'; 3 | import Separator from './button-group-separator.svelte'; 4 | 5 | export { 6 | Root, 7 | Text, 8 | Separator, 9 | // 10 | Root as ButtonGroup, 11 | Text as ButtonGroupText, 12 | Separator as ButtonGroupSeparator 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/collapsible/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './collapsible.svelte'; 2 | import Trigger from './collapsible-trigger.svelte'; 3 | import Content from './collapsible-content.svelte'; 4 | 5 | export { 6 | Root, 7 | Content, 8 | Trigger, 9 | // 10 | Root as Collapsible, 11 | Content as CollapsibleContent, 12 | Trigger as CollapsibleTrigger 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/app.d.ts: -------------------------------------------------------------------------------- 1 | import 'unplugin-icons/types/svelte'; 2 | 3 | declare global { 4 | namespace App { 5 | interface Error { 6 | message: string; 7 | status?: number; 8 | } 9 | // interface Locals { 10 | // user?: User | null; 11 | // } 12 | // interface PageData {} 13 | // interface PageState {} 14 | // interface Platform {} 15 | } 16 | } 17 | 18 | export {}; 19 | -------------------------------------------------------------------------------- /frontend/src/lib/components/arcane-tooltip/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './arcane-tooltip.svelte'; 2 | import Trigger from './arcane-tooltip-trigger.svelte'; 3 | import Content from './arcane-tooltip-content.svelte'; 4 | 5 | export { 6 | Root, 7 | Trigger, 8 | Content, 9 | // 10 | Root as ArcaneTooltip, 11 | Trigger as ArcaneTooltipTrigger, 12 | Content as ArcaneTooltipContent 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tree-view/index.ts: -------------------------------------------------------------------------------- 1 | import TreeView from './tree-view.svelte'; 2 | import TreeViewFile from './tree-view-file.svelte'; 3 | import TreeViewFolder from './tree-view-folder.svelte'; 4 | 5 | export { 6 | TreeView, 7 | TreeViewFile, 8 | TreeViewFolder, 9 | 10 | // ... 11 | TreeView as Root, 12 | TreeViewFile as File, 13 | TreeViewFolder as Folder 14 | }; 15 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "name": "arcane-tests", 4 | "packageManager": "pnpm@10.26.0", 5 | "engines": { 6 | "node": ">=25" 7 | }, 8 | "scripts": { 9 | "preinstall": "npx only-allow pnpm", 10 | "test": "playwright test" 11 | }, 12 | "devDependencies": { 13 | "@playwright/test": "^1.57.0", 14 | "@types/node": "^25.0.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/accordion/accordion-item.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/lib/components/arcane-table/arcane-table-checkbox.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/pagination/pagination-item.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
  • 9 | {@render children?.()} 10 |
  • 11 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/002_create_events_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS idx_events_timestamp; 2 | DROP INDEX IF EXISTS idx_events_environment_id; 3 | DROP INDEX IF EXISTS idx_events_user_id; 4 | DROP INDEX IF EXISTS idx_events_resource_id; 5 | DROP INDEX IF EXISTS idx_events_resource_type; 6 | DROP INDEX IF EXISTS idx_events_severity; 7 | DROP INDEX IF EXISTS idx_events_type; 8 | 9 | DROP TABLE IF EXISTS events; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './alert.svelte'; 2 | import Description from './alert-description.svelte'; 3 | import Title from './alert-title.svelte'; 4 | export { alertVariants, type AlertVariant } from './alert.svelte'; 5 | 6 | export { 7 | Root, 8 | Description, 9 | Title, 10 | // 11 | Root as Alert, 12 | Description as AlertDescription, 13 | Title as AlertTitle 14 | }; 15 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/popover/popover-trigger.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/lib/utils/utils.ts: -------------------------------------------------------------------------------- 1 | export function debounced void>(func: T, delay: number) { 2 | let debounceTimeout: ReturnType; 3 | 4 | return (...args: Parameters) => { 5 | if (debounceTimeout !== undefined) { 6 | clearTimeout(debounceTimeout); 7 | } 8 | 9 | debounceTimeout = setTimeout(() => { 10 | func(...args); 11 | }, delay); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /.github/matchers/deadcode-matcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "deadcode", 5 | "pattern": [ 6 | { 7 | "regexp": "^([^:]+):(\\d+):(\\d+):\\s+unreachable\\s+func:\\s+(.+)$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3, 11 | "message": 4 12 | } 13 | ], 14 | "severity": "warning" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/002_create_events_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS idx_events_timestamp; 2 | DROP INDEX IF EXISTS idx_events_environment_id; 3 | DROP INDEX IF EXISTS idx_events_user_id; 4 | DROP INDEX IF EXISTS idx_events_resource_id; 5 | DROP INDEX IF EXISTS idx_events_resource_type; 6 | DROP INDEX IF EXISTS idx_events_severity; 7 | DROP INDEX IF EXISTS idx_events_type; 8 | 9 | DROP TABLE IF EXISTS events; -------------------------------------------------------------------------------- /docker/next-builds/Dockerfile-agent-distroless: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/static-debian13 2 | 3 | ARG TARGETARCH 4 | ARG TARGETVARIANT 5 | 6 | WORKDIR /app 7 | 8 | COPY linux/${TARGETARCH}/${TARGETVARIANT}/arcane-agent ./arcane-agent 9 | 10 | ENV ENVIRONMENT=production \ 11 | AGENT_MODE=true \ 12 | GIN_MODE=release \ 13 | PORT=3553 14 | 15 | EXPOSE 3553 16 | VOLUME ["/app/data"] 17 | 18 | CMD ["./arcane-agent"] -------------------------------------------------------------------------------- /docker/next-builds/Dockerfile-static: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | ARG TARGETARCH 4 | ARG TARGETVARIANT 5 | 6 | WORKDIR /app 7 | RUN apk add --no-cache ca-certificates tzdata curl 8 | RUN mkdir -p /app/data 9 | 10 | COPY linux/${TARGETARCH}/${TARGETVARIANT}/arcane ./arcane 11 | 12 | ENV ENVIRONMENT=production 13 | ENV GIN_MODE=release 14 | ENV PORT=3552 15 | 16 | EXPOSE 3552 17 | VOLUME ["/app/data"] 18 | 19 | CMD ["./arcane"] -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/avatar/avatar-image.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/field-set/field-set-content.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 | {@render children?.()} 10 |
    11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sheet/sheet-title.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/023_add_apprise_settings.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS apprise_settings ( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT, 3 | api_url TEXT NOT NULL, 4 | enabled INTEGER DEFAULT 0, 5 | image_update_tag TEXT, 6 | container_update_tag TEXT, 7 | created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 9 | ); 10 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/025_add_environment_api_key_pairing.up.sql: -------------------------------------------------------------------------------- 1 | -- Add api_key_id column to environments table for API key-based pairing 2 | ALTER TABLE environments ADD COLUMN api_key_id TEXT REFERENCES api_keys(id) ON DELETE SET NULL; 3 | 4 | -- Add environment_id column to api_keys table to link API keys to environments 5 | ALTER TABLE api_keys ADD COLUMN environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE; 6 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-grid-body.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-grid-head.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/command/command-empty.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer-title.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/lib/layouts/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | default as ResourcePageLayout, 3 | type ActionButton, 4 | type StatCardConfig 5 | } from './resource-page-layout.svelte'; 6 | export { default as TabbedPageLayout } from './tabbed-page-layout.svelte'; 7 | export { 8 | default as SettingsPageLayout, 9 | type SettingsActionButton, 10 | type SettingsStatCard, 11 | type SettingsPageType 12 | } from './settings-page-layout.svelte'; 13 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/025_add_environment_api_key_pairing.up.sql: -------------------------------------------------------------------------------- 1 | -- Add api_key_id column to environments table for API key-based pairing 2 | ALTER TABLE environments ADD COLUMN api_key_id TEXT REFERENCES api_keys(id) ON DELETE SET NULL; 3 | 4 | -- Add environment_id column to api_keys table to link API keys to environments 5 | ALTER TABLE api_keys ADD COLUMN environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE; 6 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-svelte.com/schema.json", 3 | "style": "default", 4 | "tailwind": { 5 | "css": "src/app.css", 6 | "baseColor": "violet" 7 | }, 8 | "aliases": { 9 | "components": "$lib/components", 10 | "utils": "$lib/utils", 11 | "ui": "$lib/components/ui", 12 | "hooks": "$lib/hooks" 13 | }, 14 | "typescript": true, 15 | "registry": "https://shadcn-svelte.com/registry" 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-grid-row.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /backend/internal/utils/registry/helpers.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ref "go.podman.io/image/v5/docker/reference" 4 | 5 | func GetRegistryAddress(imageRef string) (string, error) { 6 | named, err := ref.ParseNormalizedNamed(imageRef) 7 | if err != nil { 8 | return "", err 9 | } 10 | addr := ref.Domain(named) 11 | if addr == DefaultRegistryDomain { 12 | return DefaultRegistryHost, nil 13 | } 14 | return addr, nil 15 | } 16 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/023_add_apprise_settings.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS apprise_settings ( 2 | id SERIAL PRIMARY KEY, 3 | api_url VARCHAR(512) NOT NULL, 4 | enabled BOOLEAN DEFAULT FALSE, 5 | image_update_tag VARCHAR(255), 6 | container_update_tag VARCHAR(255), 7 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 9 | ); 10 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/command/command-separator.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/field-set/field-set-footer.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 | {@render children?.()} 10 |
    11 | -------------------------------------------------------------------------------- /frontend/src/routes/auth/oidc/callback/+page.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | import type { PageLoad } from './$types'; 3 | 4 | // Temporary redirect to handle OIDC callback uniformly 5 | 6 | export const load: PageLoad = ({ url }) => { 7 | const searchParams = url.searchParams.toString(); 8 | const redirectUrl = searchParams ? `/oidc/callback?${searchParams}` : '/oidc/callback'; 9 | redirect(307, redirectUrl); 10 | }; 11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import Root, { 2 | type ButtonProps, 3 | type ButtonPropsWithoutHTML, 4 | type ButtonSize, 5 | type ButtonVariant, 6 | buttonVariants 7 | } from './button.svelte'; 8 | 9 | export { 10 | Root, 11 | type ButtonProps as Props, 12 | // 13 | Root as Button, 14 | buttonVariants, 15 | type ButtonProps, 16 | type ButtonSize, 17 | type ButtonVariant, 18 | type ButtonPropsWithoutHTML 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/empty/empty-title.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 | {@render children?.()} 10 |
    11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer-nested.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/setup/auth.setup.ts: -------------------------------------------------------------------------------- 1 | import { test as setup } from '@playwright/test'; 2 | import authUtil from '../utils/auth.util'; 3 | 4 | const authFile = '.auth/login.json'; 5 | 6 | setup('authenticate', async ({ page }) => { 7 | await authUtil.login(page); 8 | 9 | await page.waitForURL('/dashboard'); 10 | 11 | await authUtil.changeDefaultPassword(page, 'test-password-123'); 12 | 13 | await page.context().storageState({ path: authFile }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/radio-group/radio-group.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/006_rename_tables.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS users RENAME TO users_table; 2 | ALTER TABLE IF EXISTS image_updates RENAME TO image_update_table; 3 | ALTER TABLE IF EXISTS images RENAME TO images_table; 4 | ALTER TABLE IF EXISTS containers RENAME TO containers_table; 5 | ALTER TABLE IF EXISTS networks RENAME TO networks_table; 6 | ALTER TABLE IF EXISTS volumes RENAME TO volumes_table; 7 | ALTER TABLE IF EXISTS stacks RENAME TO stacks_table; -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/006_rename_tables.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS users_table RENAME TO users; 2 | ALTER TABLE IF EXISTS image_update_table RENAME TO image_updates; 3 | ALTER TABLE IF EXISTS images_table RENAME TO images; 4 | ALTER TABLE IF EXISTS containers_table RENAME TO containers; 5 | ALTER TABLE IF EXISTS networks_table RENAME TO networks; 6 | ALTER TABLE IF EXISTS volumes_table RENAME TO volumes; 7 | ALTER TABLE IF EXISTS stacks_table RENAME TO stacks; -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/empty/empty-header.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 | {@render children?.()} 10 |
    11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tabs/index.ts: -------------------------------------------------------------------------------- 1 | import { Tabs as TabsPrimitive } from 'bits-ui'; 2 | import Content from './tabs-content.svelte'; 3 | import List from './tabs-list.svelte'; 4 | import Trigger from './tabs-trigger.svelte'; 5 | 6 | const Root = TabsPrimitive.Root; 7 | 8 | export { 9 | Root, 10 | Content, 11 | List, 12 | Trigger, 13 | // 14 | Root as Tabs, 15 | Content as TabsContent, 16 | List as TabsList, 17 | Trigger as TabsTrigger 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/routes/(app)/settings/+layout.ts: -------------------------------------------------------------------------------- 1 | import { settingsService } from '$lib/services/settings-service'; 2 | 3 | export const load = async ({ parent }) => { 4 | try { 5 | const { settings } = await parent(); 6 | const oidcStatus = await settingsService.getOidcStatus(); 7 | 8 | return { 9 | settings, 10 | oidcStatus 11 | }; 12 | } catch (error) { 13 | console.error('Failed to load OIDC status:', error); 14 | throw error; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /.github/matchers/svelte-check-matcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "svelte-check", 5 | "pattern": [ 6 | { 7 | "regexp": "^([^\\s].*):(\\d+):(\\d+)$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3 11 | }, 12 | { 13 | "regexp": "^\\s*(Error|Warning):\\s*(.*)\\s+\\((?:ts|js|svelte)\\)$", 14 | "severity": 1, 15 | "message": 2, 16 | "loop": false 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/006_rename_tables.down.sql: -------------------------------------------------------------------------------- 1 | PRAGMA foreign_keys=ON; 2 | PRAGMA legacy_alter_table=ON; 3 | 4 | ALTER TABLE users RENAME TO users_table; 5 | ALTER TABLE image_updates RENAME TO image_update_table; 6 | ALTER TABLE images RENAME TO images_table; 7 | ALTER TABLE containers RENAME TO containers_table; 8 | ALTER TABLE networks RENAME TO networks_table; 9 | ALTER TABLE volumes RENAME TO volumes_table; 10 | ALTER TABLE stacks RENAME TO stacks_table; -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/006_rename_tables.up.sql: -------------------------------------------------------------------------------- 1 | PRAGMA foreign_keys=ON; 2 | PRAGMA legacy_alter_table=ON; 3 | 4 | ALTER TABLE users_table RENAME TO users; 5 | ALTER TABLE image_update_table RENAME TO image_updates; 6 | ALTER TABLE images_table RENAME TO images; 7 | ALTER TABLE containers_table RENAME TO containers; 8 | ALTER TABLE networks_table RENAME TO networks; 9 | ALTER TABLE volumes_table RENAME TO volumes; 10 | ALTER TABLE stacks_table RENAME TO stacks; -------------------------------------------------------------------------------- /frontend/jsrepo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/jsrepo@2.4.2/schemas/project-config.json", 3 | "repos": ["@ieedan/shadcn-svelte-extras"], 4 | "includeTests": false, 5 | "includeDocs": false, 6 | "watermark": false, 7 | "formatter": "prettier", 8 | "configFiles": {}, 9 | "paths": { 10 | "*": "$lib/ui/extras", 11 | "ui": "$lib/components/ui", 12 | "actions": "$lib/actions", 13 | "hooks": "$lib/hooks", 14 | "utils": "$lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/breadcrumb/breadcrumb.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-title.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /backend/internal/utils/pagination/pagination.go: -------------------------------------------------------------------------------- 1 | package pagination 2 | 3 | type PaginationParams struct { 4 | Start int 5 | Limit int 6 | } 7 | 8 | func paginateItemsFunction[T any](items []T, params PaginationParams) []T { 9 | if params.Limit <= 0 { 10 | return items 11 | } 12 | 13 | itemsCount := len(items) 14 | 15 | start := min(max(params.Start, 0), itemsCount) 16 | 17 | end := min(start+params.Limit, itemsCount) 18 | 19 | return items[start:end] 20 | } 21 | -------------------------------------------------------------------------------- /docker/next-builds/Dockerfile-agent-static: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | ARG TARGETARCH 4 | ARG TARGETVARIANT 5 | 6 | WORKDIR /app 7 | RUN apk add --no-cache ca-certificates tzdata curl 8 | RUN mkdir -p /app/data 9 | 10 | COPY linux/${TARGETARCH}/${TARGETVARIANT}/arcane-agent ./arcane-agent 11 | 12 | ENV ENVIRONMENT=production \ 13 | AGENT_MODE=true \ 14 | GIN_MODE=release \ 15 | PORT=3553 16 | 17 | EXPOSE 3553 18 | VOLUME ["/app/data"] 19 | 20 | CMD ["./arcane-agent"] -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/form/form-description.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/avatar/avatar.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/label/label.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/spinner/spinner.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tabs/tabs-list.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /frontend/src/lib/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { IsMobile } from './is-mobile.svelte'; 2 | export { IsTablet } from './is-tablet.svelte'; 3 | export { IsTouchDevice } from './is-touch-device.svelte'; 4 | export { UseClipboard } from './use-clipboard.svelte'; 5 | export { useEnvironmentRefresh } from './use-environment-refresh.svelte'; 6 | export { ScrollDirectionDetector, type ScrollDirection } from './use-scroll-direction.svelte'; 7 | export { UseSettingsForm } from './use-settings-form.svelte'; 8 | -------------------------------------------------------------------------------- /frontend/src/lib/types/event.type.ts: -------------------------------------------------------------------------------- 1 | export interface Event { 2 | id: string; 3 | type: string; 4 | severity: 'info' | 'warning' | 'error' | 'success'; 5 | title: string; 6 | description?: string; 7 | resourceType?: string; 8 | resourceId?: string; 9 | resourceName?: string; 10 | userId?: string; 11 | username?: string; 12 | environmentId?: string; 13 | metadata?: Record; 14 | timestamp: string; 15 | createdAt: string; 16 | updatedAt?: string; 17 | } 18 | -------------------------------------------------------------------------------- /docker/examples/compose.agent.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | arcane-agent: 3 | image: ghcr.io/getarcaneapp/arcane-headless:latest 4 | container_name: arcane-agent 5 | ports: 6 | - "3553:3553" 7 | environment: 8 | - AGENT_MODE=true 9 | - AGENT_BOOTSTRAP_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxx 10 | volumes: 11 | - /var/run/docker.sock:/var/run/docker.sock 12 | - agent-data:/app/data 13 | restart: unless-stopped 14 | 15 | volumes: 16 | agent-data: 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-heading.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sheet/sheet-description.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-grid.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-description.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/field-set/field-set.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
    10 | {@render children?.()} 11 |
    12 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/popover/index.ts: -------------------------------------------------------------------------------- 1 | import { Popover as PopoverPrimitive } from 'bits-ui'; 2 | import Content from './popover-content.svelte'; 3 | import Trigger from './popover-trigger.svelte'; 4 | const Root = PopoverPrimitive.Root; 5 | const Close = PopoverPrimitive.Close; 6 | 7 | export { 8 | Root, 9 | Content, 10 | Trigger, 11 | Close, 12 | // 13 | Root as Popover, 14 | Content as PopoverContent, 15 | Trigger as PopoverTrigger, 16 | Close as PopoverClose 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/lib/types/application-configuration.ts: -------------------------------------------------------------------------------- 1 | export interface AppVersionInformation { 2 | currentVersion: string; 3 | currentTag?: string; 4 | currentDigest?: string; 5 | displayVersion: string; 6 | revision: string; 7 | shortRevision: string; 8 | goVersion: string; 9 | buildTime?: string; 10 | isSemverVersion: boolean; 11 | newestVersion?: string; 12 | newestDigest?: string; 13 | updateAvailable?: boolean; 14 | releaseUrl?: string; 15 | releaseNotes?: string; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer-description.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "verbatimModuleSyntax": true, 6 | "allowJs": true, 7 | "checkJs": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "sourceMap": false, 13 | "strict": true, 14 | "moduleResolution": "bundler", 15 | "types": ["unplugin-icons/types/svelte"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/command/command-list.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /backend/internal/config/version.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "runtime" 4 | 5 | var ( 6 | Version = "dev" 7 | Revision = "unknown" 8 | BuildTime = "unknown" 9 | ) 10 | 11 | // ShortRevision returns the first 8 characters of the revision hash 12 | func ShortRevision() string { 13 | if len(Revision) > 8 { 14 | return Revision[:8] 15 | } 16 | return Revision 17 | } 18 | 19 | // GoVersion returns the Go runtime version 20 | func GoVersion() string { 21 | return runtime.Version() 22 | } 23 | -------------------------------------------------------------------------------- /backend/resources/migrations/postgres/016_drop_image_table.down.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS images_table ( 2 | id TEXT PRIMARY KEY, 3 | repo_tags TEXT, 4 | repo_digests TEXT, 5 | size BIGINT NOT NULL, 6 | virtual_size BIGINT NOT NULL DEFAULT 0, 7 | labels TEXT, 8 | created TIMESTAMP NOT NULL, 9 | repo TEXT, 10 | tag TEXT, 11 | in_use BOOLEAN NOT NULL DEFAULT false, 12 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 13 | updated_at TIMESTAMP 14 | ); -------------------------------------------------------------------------------- /backend/resources/migrations/sqlite/016_drop_image_table.down.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS images_table ( 2 | id TEXT PRIMARY KEY, 3 | repo_tags TEXT, 4 | repo_digests TEXT, 5 | size INTEGER NOT NULL, 6 | virtual_size INTEGER NOT NULL DEFAULT 0, 7 | labels TEXT, 8 | created DATETIME NOT NULL, 9 | repo TEXT, 10 | tag TEXT, 11 | in_use BOOLEAN NOT NULL DEFAULT false, 12 | created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 13 | updated_at DATETIME 14 | ); -------------------------------------------------------------------------------- /frontend/src/routes/(app)/settings/notifications/+page.ts: -------------------------------------------------------------------------------- 1 | import { notificationService } from '$lib/services/notification-service'; 2 | import type { PageLoad } from './$types'; 3 | 4 | export const load: PageLoad = async () => { 5 | try { 6 | const notificationSettings = await notificationService.getSettings(); 7 | 8 | return { 9 | notificationSettings 10 | }; 11 | } catch (error) { 12 | console.error('Failed to load notification settings:', error); 13 | throw error; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-month.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/types/system-stats.type.ts: -------------------------------------------------------------------------------- 1 | export interface SystemStats { 2 | cpuUsage: number; 3 | memoryUsage: number; 4 | memoryTotal: number; 5 | diskUsage?: number; 6 | diskTotal?: number; 7 | cpuCount: number; 8 | architecture: string; 9 | platform: string; 10 | hostname?: string; 11 | gpuCount: number; 12 | gpus?: GPUStats[]; 13 | } 14 | 15 | export interface GPUStats { 16 | name: string; 17 | index: number; 18 | memoryUsed: number; // in MB 19 | memoryTotal: number; // in MB 20 | } 21 | -------------------------------------------------------------------------------- /frontend/project.inlang/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://inlang.com/schema/project-settings", 3 | "baseLocale": "en", 4 | "locales": ["en", "el", "es", "nl", "zh", "fr", "eo", "de", "it", "pt-BR", "ru", "ja"], 5 | "modules": [ 6 | "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", 7 | "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js" 8 | ], 9 | "plugin.inlang.messageFormat": { 10 | "pathPattern": "./messages/{locale}.json" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/accordion/index.ts: -------------------------------------------------------------------------------- 1 | import { Accordion as AccordionPrimitive } from 'bits-ui'; 2 | import Content from './accordion-content.svelte'; 3 | import Item from './accordion-item.svelte'; 4 | import Trigger from './accordion-trigger.svelte'; 5 | const Root = AccordionPrimitive.Root; 6 | 7 | export { 8 | Root, 9 | Content, 10 | Item, 11 | Trigger, 12 | // 13 | Root as Accordion, 14 | Content as AccordionContent, 15 | Item as AccordionItem, 16 | Trigger as AccordionTrigger 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/avatar/avatar-fallback.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/empty/empty-content.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    13 | {@render children?.()} 14 |
    15 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/form/form-legend.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /backend/resources/email-templates/batch-image-updates_text.tmpl: -------------------------------------------------------------------------------- 1 | {{define "root"}}IMAGE UPDATES AVAILABLE 2 | 3 | {{.UpdateCount}} container images have updates available. 4 | 5 | Updates Available: 6 | 7 | {{.UpdateCount}} 8 | 9 | ---------------------------------------- 10 | 11 | Checked At: 12 | 13 | {{.CheckTime}} 14 | 15 | Images with Updates: 16 | 17 | {{range .ImageList}}• {{.}} 18 | {{end}} 19 | 20 | Log in to Arcane to view details and update your containers. 21 | 22 | Open Arcane Dashboard → {{.AppURL}}{{end}} -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
  • 10 | {@render children?.()} 11 |
  • 12 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/card/card-content.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /frontend/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | import packageJson from './package.json' with { type: 'json' }; 4 | 5 | const config = { 6 | preprocess: vitePreprocess(), 7 | kit: { 8 | adapter: adapter({ 9 | pages: process.env.BUILD_PATH ?? '../backend/frontend/dist', 10 | fallback: 'index.html' 11 | }), 12 | version: { 13 | name: packageJson.version 14 | } 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /backend/internal/utils/http/client.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | func NewHTTPClient() *http.Client { 9 | transport := &http.Transport{ 10 | Proxy: http.ProxyFromEnvironment, 11 | MaxIdleConns: 100, 12 | IdleConnTimeout: 90 * time.Second, 13 | TLSHandshakeTimeout: 5 * time.Second, 14 | ExpectContinueTimeout: 1 * time.Second, 15 | } 16 | return &http.Client{ 17 | Transport: transport, 18 | Timeout: 10 * time.Second, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/table/table-caption.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | {@render children?.()} 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/routes/(auth)/logout/+page.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | import type { PageLoad } from './$types'; 3 | import { authService } from '$lib/services/auth-service'; 4 | 5 | export const load: PageLoad = async ({ fetch }) => { 6 | try { 7 | await fetch('/api/auth/logout', { 8 | method: 'POST', 9 | credentials: 'include' 10 | }); 11 | } catch (error) { 12 | console.error('Logout error:', error); 13 | } 14 | 15 | authService.logout(); 16 | 17 | throw redirect(302, '/login'); 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/lib/utils/try-catch.ts: -------------------------------------------------------------------------------- 1 | type Success = { 2 | data: T; 3 | error: null; 4 | }; 5 | 6 | type Failure = { 7 | data: null; 8 | error: E; 9 | }; 10 | 11 | export type Result = Success | Failure; 12 | 13 | // Main wrapper function 14 | export async function tryCatch(promise: Promise): Promise> { 15 | try { 16 | const data = await promise; 17 | return { data, error: null }; 18 | } catch (error) { 19 | return { data: null, error: error as E }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/internal/utils/image/image_util.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | func GetImageMimeType(ext string) string { 4 | switch ext { 5 | case "jpg", "jpeg": 6 | return "image/jpeg" 7 | case "png": 8 | return "image/png" 9 | case "svg": 10 | return "image/svg+xml" 11 | case "ico": 12 | return "image/x-icon" 13 | case "gif": 14 | return "image/gif" 15 | case "webp": 16 | return "image/webp" 17 | case "avif": 18 | return "image/avif" 19 | case "heic": 20 | return "image/heic" 21 | default: 22 | return "" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/skeleton/skeleton.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
    13 | -------------------------------------------------------------------------------- /frontend/src/routes/(auth)/login/+page.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | 3 | export const load = async ({ parent, url }) => { 4 | const data = await parent(); 5 | 6 | if (data.user) { 7 | throw redirect(302, '/dashboard'); 8 | } 9 | 10 | const redirectTo = url.searchParams.get('redirect') || '/dashboard'; 11 | 12 | const error = url.searchParams.get('error'); 13 | 14 | return { 15 | settings: data.settings, 16 | redirectTo, 17 | error, 18 | versionInformation: data.versionInformation 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sheet/sheet-header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-head-cell.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer-header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/empty/empty-description.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', className)} 11 | {...restProps} 12 | > 13 | {@render children?.()} 14 |
    15 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/table/table-header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | {@render children?.()} 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tooltip/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./tooltip.svelte"; 2 | import Trigger from "./tooltip-trigger.svelte"; 3 | import Content from "./tooltip-content.svelte"; 4 | import Provider from "./tooltip-provider.svelte"; 5 | import Portal from "./tooltip-portal.svelte"; 6 | 7 | export { 8 | Root, 9 | Trigger, 10 | Content, 11 | Provider, 12 | Portal, 13 | // 14 | Root as Tooltip, 15 | Content as TooltipContent, 16 | Trigger as TooltipTrigger, 17 | Provider as TooltipProvider, 18 | Portal as TooltipPortal, 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-header.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/field-set/field-set-title.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    16 | {@render children?.()} 17 |
    18 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sheet/sheet-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/table/table-body.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | {@render children?.()} 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/card/card-description.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |

    14 | {@render children?.()} 15 |

    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/card/card-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/command/command.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/input-group/input-group-text.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | {@render children?.()} 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/item/item-separator.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tabs/tabs-content.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /backend/internal/bootstrap/db_bootstrap.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | 7 | "github.com/getarcaneapp/arcane/backend/internal/config" 8 | "github.com/getarcaneapp/arcane/backend/internal/database" 9 | ) 10 | 11 | func initializeDBAndMigrate(cfg *config.Config) (*database.DB, error) { 12 | db, err := database.Initialize(cfg.DatabaseURL) 13 | if err != nil { 14 | return nil, fmt.Errorf("failed to initialize database: %w", err) 15 | } 16 | 17 | slog.Info("Database initialized successfully") 18 | return db, nil 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/arcane-table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DataTableToolbar } from './arcane-table-toolbar.svelte'; 2 | export { default as DataTableViewOptions } from './arcane-table-view-options.svelte'; 3 | export { default as DataTableFacetedFilter } from './arcane-table-filter.svelte'; 4 | export type { ColumnSpec, FieldSpec, MobileFieldVisibility } from './arcane-table.types.svelte'; 5 | export { default as UniversalMobileCard } from './cards/universal-mobile-card.svelte'; 6 | export { usageFilters, imageUpdateFilters, severityFilters } from './data.js'; 7 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-months.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    18 | {@render children?.()} 19 |
    20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/select/select-separator.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "github.copilot.enable": { 3 | "*": false, 4 | "plaintext": false, 5 | "markdown": false, 6 | "scminput": false, 7 | "typescript": true 8 | }, 9 | "typescript.tsdk": "frontend/node_modules/typescript/lib", 10 | "go.buildTags": "playwright", 11 | "prettier.documentSelectors": ["**/*.svelte"], 12 | "tasks.statusbar.default.hide": true, 13 | "tasks.statusbar.limit": 8, 14 | "findUnusedExports.detectCircularImports": true, 15 | "github.copilot.chat.summarizeAgentConversationHistory.enabled": false 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert/alert-description.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
    15 | {@render children?.()} 16 |
    17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/empty/empty.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    16 | {@render children?.()} 17 |
    18 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/select/select-label.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-footer.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    15 | {@render children?.()} 16 |
    17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-header.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    15 | {@render children?.()} 16 |
    17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-separator.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/item/item-actions.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/arcane-tooltip/context.svelte.ts: -------------------------------------------------------------------------------- 1 | import { getContext, setContext } from 'svelte'; 2 | 3 | const CONTEXT_KEY = 'arcane-tooltip-context'; 4 | 5 | export interface ArcaneTooltipContext { 6 | isTouch: boolean; 7 | interactive: boolean; 8 | open: boolean; 9 | setOpen: (value: boolean) => void; 10 | } 11 | 12 | export function setArcaneTooltipContext(context: ArcaneTooltipContext) { 13 | setContext(CONTEXT_KEY, context); 14 | } 15 | 16 | export function getArcaneTooltipContext(): ArcaneTooltipContext { 17 | return getContext(CONTEXT_KEY); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/pagination/pagination-content.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
      15 | {@render children?.()} 16 |
    17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/table/table.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 | 10 | {@render children?.()} 11 |
    12 |
    13 | -------------------------------------------------------------------------------- /frontend/src/lib/stores/user-store.ts: -------------------------------------------------------------------------------- 1 | import type { User } from '$lib/types/user.type'; 2 | import { writable } from 'svelte/store'; 3 | import { setLocale } from '$lib/utils/locale.util'; 4 | 5 | const userStore = writable(null); 6 | 7 | const setUser = async (user: User) => { 8 | if (user.locale) { 9 | await setLocale(user.locale, false); 10 | } 11 | userStore.set(user); 12 | }; 13 | 14 | const clearUser = () => { 15 | userStore.set(null); 16 | }; 17 | 18 | export default { 19 | subscribe: userStore.subscribe, 20 | setUser, 21 | clearUser 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-group.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    15 | {@render children?.()} 16 |
    17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-input.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /frontend/src/hooks.client.ts: -------------------------------------------------------------------------------- 1 | import type { HandleClientError } from '@sveltejs/kit'; 2 | import { AxiosError } from 'axios'; 3 | 4 | export const handleError: HandleClientError = async ({ error, message, status }) => { 5 | if (error instanceof AxiosError) { 6 | message = error.response?.data.error || message; 7 | status = error.response?.status || status; 8 | console.error(`Axios error: ${error.request.path} - ${error.response?.data.error ?? error.message}`); 9 | } else { 10 | console.error(error); 11 | } 12 | 13 | return { 14 | message, 15 | status 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/calendar/calendar-nav.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/table/table-cell.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | {@render children?.()} 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/tree-view/tree-view-file.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/item/item-group.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    20 | {@render children?.()} 21 |
    22 | -------------------------------------------------------------------------------- /frontend/src/lib/utils/string.utils.ts: -------------------------------------------------------------------------------- 1 | export function capitalizeFirstLetter(string: string): string { 2 | if (!string) return ''; 3 | return string.charAt(0).toUpperCase() + string.slice(1); 4 | } 5 | 6 | export function shortId(id: string | undefined, length = 12): string { 7 | if (!id) return 'N/A'; 8 | return id.substring(0, length); 9 | } 10 | 11 | export function truncateString(str: string | undefined, maxLength: number): string { 12 | if (!str) return ''; 13 | if (str.length <= maxLength) { 14 | return str; 15 | } 16 | return str.substring(0, maxLength - 3) + '...'; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/button-group/button-group-separator.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/card/card-title.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/item/item-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/item/item-header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/types/api-key.type.ts: -------------------------------------------------------------------------------- 1 | export type ApiKey = { 2 | id: string; 3 | name: string; 4 | description?: string; 5 | keyPrefix: string; 6 | userId: string; 7 | expiresAt?: string; 8 | lastUsedAt?: string; 9 | createdAt: string; 10 | updatedAt?: string; 11 | }; 12 | 13 | export type ApiKeyCreated = ApiKey & { 14 | key: string; 15 | }; 16 | 17 | export type CreateApiKey = { 18 | name: string; 19 | description?: string; 20 | expiresAt?: string; 21 | }; 22 | 23 | export type UpdateApiKey = { 24 | name?: string; 25 | description?: string; 26 | expiresAt?: string; 27 | }; 28 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
      14 | {@render children?.()} 15 |
    16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-button/dropdown-button-separator.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/card/card-action.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/item/item-title.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/item/item-content.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/types/settings-search.type.ts: -------------------------------------------------------------------------------- 1 | export interface SettingMeta { 2 | key: string; 3 | label: string; 4 | type: string; 5 | keywords?: string[]; 6 | description?: string; 7 | } 8 | 9 | export interface SettingsCategory { 10 | id: string; 11 | title: string; 12 | description: string; 13 | icon: string; 14 | url: string; 15 | keywords: string[]; 16 | settings: SettingMeta[]; 17 | matchingSettings?: SettingMeta[]; 18 | relevanceScore?: number; 19 | } 20 | 21 | export interface SettingsSearchResponse { 22 | results: SettingsCategory[]; 23 | query: string; 24 | count: number; 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/command/command-shortcut.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | {@render children?.()} 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/drawer/drawer-overlay.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/empty/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './empty.svelte'; 2 | import Header from './empty-header.svelte'; 3 | import Media from './empty-media.svelte'; 4 | import Title from './empty-title.svelte'; 5 | import Description from './empty-description.svelte'; 6 | import Content from './empty-content.svelte'; 7 | 8 | export { 9 | Root, 10 | Header, 11 | Media, 12 | Title, 13 | Description, 14 | Content, 15 | // 16 | Root as Empty, 17 | Header as EmptyHeader, 18 | Media as EmptyMedia, 19 | Title as EmptyTitle, 20 | Description as EmptyDescription, 21 | Content as EmptyContent 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-group-content.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    20 | {@render children?.()} 21 |
    22 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/table/table-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | tr]:last:border-b-0', className)} 17 | {...restProps} 18 | > 19 | {@render children?.()} 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/lib/types/notification.type.ts: -------------------------------------------------------------------------------- 1 | export type NotificationProvider = 'discord' | 'email'; 2 | export type EmailTLSMode = 'none' | 'starttls' | 'ssl'; 3 | 4 | export interface NotificationSettings { 5 | provider: NotificationProvider; 6 | enabled: boolean; 7 | config?: Record; 8 | } 9 | 10 | export interface AppriseSettings { 11 | id?: number; 12 | apiUrl: string; 13 | enabled: boolean; 14 | imageUpdateTag: string; 15 | containerUpdateTag: string; 16 | } 17 | 18 | export interface TestNotificationResponse { 19 | success: boolean; 20 | message?: string; 21 | error?: string; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/routes/+error.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    19 | {@render children?.()} 20 |
    21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/separator/separator.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/table/table-row.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | {@render children?.()} 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sheet/sheet-overlay.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-menu-item.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
  • 20 | {@render children?.()} 21 |
  • 22 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
  • 20 | {@render children?.()} 21 |
  • 22 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sidebar/sidebar-menu.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
      20 | {@render children?.()} 21 |
    22 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-overlay.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | {@render children?.()} 20 | 21 | -------------------------------------------------------------------------------- /email-templates/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "allowSyntheticDefaultImports": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "noEmit": true, 14 | "jsx": "react-jsx", 15 | "types": ["node"] 16 | }, 17 | "include": [ 18 | "**/*.ts", 19 | "**/*.tsx" 20 | ], 21 | "exclude": [ 22 | "node_modules", 23 | "dist" 24 | ] 25 | } -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/input-group/input-group-textarea.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |