├── 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 |
22 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
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 |
18 |
--------------------------------------------------------------------------------
/frontend/src/lib/types/auth.type.ts:
--------------------------------------------------------------------------------
1 | import type { User } from '$lib/types/user.type';
2 |
3 | export interface OidcUserInfo {
4 | sub: string;
5 | email: string;
6 | name?: string;
7 | displayName?: string;
8 | preferred_username?: string;
9 | given_name?: string;
10 | family_name?: string;
11 | picture?: string;
12 | groups?: string[];
13 | }
14 |
15 | export interface LoginCredentials {
16 | username: string;
17 | password: string;
18 | }
19 |
20 | export type LoginResponseData = {
21 | token: string;
22 | refreshToken: string;
23 | expiresAt: string;
24 | user: User;
25 | requirePasswordChange?: boolean;
26 | };
27 |
--------------------------------------------------------------------------------
/frontend/src/lib/types/customize-search.type.ts:
--------------------------------------------------------------------------------
1 | export interface CustomizationMeta {
2 | key: string;
3 | label: string;
4 | type: string;
5 | keywords?: string[];
6 | description?: string;
7 | }
8 |
9 | export interface CustomizeCategory {
10 | id: string;
11 | title: string;
12 | description: string;
13 | icon: string;
14 | url: string;
15 | keywords: string[];
16 | customizations: CustomizationMeta[];
17 | matchingCustomizations?: CustomizationMeta[];
18 | relevanceScore?: number;
19 | }
20 |
21 | export interface CustomizeSearchResponse {
22 | results: CustomizeCategory[];
23 | query: string;
24 | count: number;
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/calendar/calendar-cell.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
20 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
17 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/pagination/pagination.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Output
4 | .output
5 | .vercel
6 | .netlify
7 | .wrangler
8 | .svelte-kit
9 | frontend/.svelte-kit
10 | frontend/build
11 | build
12 | data/
13 | dist/
14 | arcane
15 |
16 | # OS
17 | .DS_Store
18 | Thumbs.db
19 |
20 | # Env
21 | .env
22 | .env.*
23 | !.env.example
24 | !.env.test
25 |
26 | # Vite
27 | vite.config.js.timestamp-*
28 | vite.config.ts.timestamp-*
29 |
30 | #tests data
31 | tests/test-results/*
32 | tests/.report/*
33 | tests/.auth/
34 | backend/.bin
35 | backend/build-errors.log
36 | frontend/bun.lock
37 | tests/bun.lock
38 | tests/package-lock.json
39 | /.idea/**
40 | .pnpm-store/**
41 | .pnpm-store
42 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/input-group/index.ts:
--------------------------------------------------------------------------------
1 | import Root from './input-group.svelte';
2 | import Addon from './input-group-addon.svelte';
3 | import Button from './input-group-button.svelte';
4 | import Input from './input-group-input.svelte';
5 | import Text from './input-group-text.svelte';
6 | import Textarea from './input-group-textarea.svelte';
7 |
8 | export {
9 | Root,
10 | Addon,
11 | Button,
12 | Input,
13 | Text,
14 | Textarea,
15 | //
16 | Root as InputGroup,
17 | Addon as InputGroupAddon,
18 | Button as InputGroupButton,
19 | Input as InputGroupInput,
20 | Text as InputGroupText,
21 | Textarea as InputGroupTextarea
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/src/lib/utils/status.utils.ts:
--------------------------------------------------------------------------------
1 | export type StatusVariant = 'red' | 'purple' | 'green' | 'blue' | 'gray' | 'amber';
2 |
3 | const STATUS_VARIANT_MAP: Record = {
4 | running: 'green',
5 | deployed: 'green',
6 | stopped: 'red',
7 | failed: 'red',
8 | pending: 'amber',
9 | creating: 'blue',
10 | updating: 'blue',
11 | deleting: 'purple',
12 | exited: 'red'
13 | };
14 |
15 | export function getStatusVariant(status?: string | null): StatusVariant {
16 | if (!status) return 'gray';
17 | return STATUS_VARIANT_MAP[String(status).toLowerCase()] ?? 'gray';
18 | }
19 |
20 | export { STATUS_VARIANT_MAP as statusVariantMap };
21 |
--------------------------------------------------------------------------------
/frontend/src/routes/(app)/customize/templates/components/TemplatesBrowser.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
22 | {@render children?.()}
23 |
24 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/table/table-head.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
17 | {@render children?.()}
18 | |
19 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 | svg]:size-3.5', className)} bind:this={ref} {...restProps}>
11 | {#if children}
12 | {@render children?.()}
13 | {:else}
14 |
15 | {/if}
16 |
17 |
--------------------------------------------------------------------------------
/frontend/src/lib/types/user.type.ts:
--------------------------------------------------------------------------------
1 | import type { Locale } from '$lib/paraglide/runtime';
2 |
3 | export type User = {
4 | id: string;
5 | username: string;
6 | passwordHash?: string;
7 | displayName?: string;
8 | email?: string;
9 | roles: string[];
10 | createdAt: string;
11 | lastLogin?: string;
12 | updatedAt?: string;
13 | oidcSubjectId?: string;
14 | locale?: Locale;
15 | requiresPasswordChange?: boolean;
16 | };
17 |
18 | export type CreateUser = Omit<
19 | User,
20 | 'id' | 'createdAt' | 'updatedAt' | 'lastLogin' | 'oidcSubjectId' | 'passwordHash' | 'requiresPasswordChange' | 'roles'
21 | > & {
22 | password: string;
23 | roles?: string[];
24 | };
25 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/form/form-label.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | {#snippet child({ props })}
12 |
15 | {/snippet}
16 |
17 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-group-heading.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
20 | {@render children?.()}
21 |
22 |
--------------------------------------------------------------------------------
/tests/setup/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | arcane:
3 | image: arcane:playwright-tests
4 | user: root
5 | ports:
6 | - '3000:3552'
7 | environment:
8 | - ENVIRONMENT=testing
9 | - APP_ENV=test
10 | - ENCRYPTION_KEY=3JDIgolks2tJ9ymm1AdqzlYMWu0DUWyt
11 | - JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
12 | volumes:
13 | - /var/run/docker.sock:/var/run/docker.sock
14 | - arcane_data:/app/data
15 | - ./projects:/app/data/projects
16 | build:
17 | args:
18 | - BUILD_TAGS=playwright
19 | context: ../..
20 | dockerfile: docker/Dockerfile
21 |
22 | volumes:
23 | arcane_data:
24 |
--------------------------------------------------------------------------------
/backend/resources/migrations/postgres/014_containers_project_fk.down.sql:
--------------------------------------------------------------------------------
1 | BEGIN;
2 | -- Drop new FK
3 | ALTER TABLE containers
4 | DROP CONSTRAINT IF EXISTS containers_project_id_fkey;
5 |
6 | -- Rename column back
7 | ALTER TABLE containers
8 | RENAME COLUMN IF EXISTS project_id TO stack_id;
9 |
10 | -- Optionally restore FK to stacks if it exists
11 | DO $$
12 | BEGIN
13 | IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='stacks' AND table_schema = current_schema()) THEN
14 | ALTER TABLE containers
15 | ADD CONSTRAINT containers_stack_id_fkey
16 | FOREIGN KEY (stack_id) REFERENCES stacks(id) ON DELETE SET NULL;
17 | END IF;
18 | END$$;
19 | COMMIT;
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/card/index.ts:
--------------------------------------------------------------------------------
1 | import Root from './card.svelte';
2 | import Content from './card-content.svelte';
3 | import Description from './card-description.svelte';
4 | import Footer from './card-footer.svelte';
5 | import Header from './card-header.svelte';
6 | import Title from './card-title.svelte';
7 | import Action from './card-action.svelte';
8 |
9 | export {
10 | Root,
11 | Content,
12 | Description,
13 | Footer,
14 | Header,
15 | Title,
16 | Action,
17 | //
18 | Root as Card,
19 | Content as CardContent,
20 | Description as CardDescription,
21 | Footer as CardFooter,
22 | Header as CardHeader,
23 | Title as CardTitle,
24 | Action as CardAction
25 | };
26 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
23 | {@render children?.()}
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/lib/stores/config-store.ts:
--------------------------------------------------------------------------------
1 | import { settingsService } from '$lib/services/settings-service';
2 | import type { Settings } from '$lib/types/settings.type';
3 | import { applyAccentColor } from '$lib/utils/accent-color-util';
4 | import { writable } from 'svelte/store';
5 |
6 | const settingsStore = writable();
7 |
8 | const reload = async () => {
9 | const settings = await settingsService.getSettings();
10 | set(settings);
11 | };
12 |
13 | const set = (settings: Settings) => {
14 | applyAccentColor(settings.accentColor);
15 | settingsStore.set(settings);
16 | };
17 |
18 | export default {
19 | subscribe: settingsStore.subscribe,
20 | reload,
21 | set
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
9 | export type WithoutChild = T extends { child?: any } ? Omit : T;
10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
11 | export type WithoutChildren = T extends { children?: any } ? Omit : T;
12 | export type WithoutChildrenOrChild = WithoutChildren>;
13 | export type WithElementRef = T & { ref?: U | null };
14 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/alert/alert-title.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
24 | {@render children?.()}
25 |
26 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/input-group/input-group-input.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
17 |
--------------------------------------------------------------------------------
/frontend/src/lib/types/container-registry.type.ts:
--------------------------------------------------------------------------------
1 | export interface ContainerRegistryCreateDto {
2 | url: string;
3 | username: string;
4 | token: string;
5 | description?: string;
6 | insecure?: boolean;
7 | enabled?: boolean;
8 | }
9 |
10 | export interface ContainerRegistryUpdateDto {
11 | url?: string;
12 | username?: string;
13 | token?: string;
14 | description?: string;
15 | insecure?: boolean;
16 | enabled?: boolean;
17 | }
18 |
19 | export interface ContainerRegistry {
20 | id: string;
21 | url: string;
22 | username: string;
23 | token: string;
24 | description?: string;
25 | insecure?: boolean;
26 | enabled?: boolean;
27 | createdAt?: string;
28 | updatedAt?: string;
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/lib/utils/ansi.ts:
--------------------------------------------------------------------------------
1 | import Convert from 'ansi-to-html';
2 |
3 | const converter = new Convert({
4 | fg: '#e4e4e7',
5 | bg: '#000000',
6 | newline: false,
7 | escapeXML: true,
8 | stream: false,
9 | colors: {
10 | 0: '#18181b',
11 | 1: '#ef4444',
12 | 2: '#22c55e',
13 | 3: '#eab308',
14 | 4: '#3b82f6',
15 | 5: '#a855f7',
16 | 6: '#06b6d4',
17 | 7: '#f4f4f5',
18 | 8: '#71717a',
19 | 9: '#f87171',
20 | 10: '#4ade80',
21 | 11: '#facc15',
22 | 12: '#60a5fa',
23 | 13: '#c084fc',
24 | 14: '#22d3ee',
25 | 15: '#fafafa'
26 | }
27 | });
28 |
29 | export function ansiToHtml(text: string): string {
30 | if (!text) return '';
31 | return converter.toHtml(text);
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/pagination/pagination-ellipsis.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 | More pages
17 |
18 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/frontend/src/routes/(app)/environments/[id]/+page.ts:
--------------------------------------------------------------------------------
1 | import { environmentManagementService } from '$lib/services/env-mgmt-service';
2 | import { settingsService } from '$lib/services/settings-service';
3 | import type { PageLoad } from './$types';
4 |
5 | export const load: PageLoad = async ({ params }) => {
6 | try {
7 | const environment = await environmentManagementService.get(params.id);
8 |
9 | let settings = null;
10 | try {
11 | settings = await settingsService.getSettingsForEnvironment(params.id);
12 | } catch {}
13 |
14 | return {
15 | environment,
16 | settings
17 | };
18 | } catch (error) {
19 | console.error('Failed to load environment:', error);
20 | throw error;
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
23 |
--------------------------------------------------------------------------------
/backend/cli/version.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 |
7 | "github.com/spf13/cobra"
8 |
9 | "github.com/getarcaneapp/arcane/backend/internal/config"
10 | )
11 |
12 | var versionCmd = &cobra.Command{
13 | Use: "version",
14 | Short: "Print version information",
15 | Long: `Print detailed version information about Arcane.`,
16 | Run: func(cmd *cobra.Command, args []string) {
17 | fmt.Printf("Arcane version: %s\n", config.Version)
18 | fmt.Printf("Git revision: %s\n", config.Revision)
19 | fmt.Printf("Go version: %s\n", runtime.Version())
20 | fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
21 | },
22 | }
23 |
24 | func init() {
25 | rootCmd.AddCommand(versionCmd)
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/item/item-description.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 | a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
19 | className
20 | )}
21 | {...restProps}
22 | >
23 | {@render children?.()}
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/routes/(app)/settings/users/+page.ts:
--------------------------------------------------------------------------------
1 | import { userService } from '$lib/services/user-service';
2 | import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
3 | import { resolveInitialTableRequest } from '$lib/utils/table-persistence.util';
4 |
5 | export const load = async () => {
6 | const userRequestOptions = resolveInitialTableRequest('arcane-users-table', {
7 | pagination: {
8 | page: 1,
9 | limit: 20
10 | },
11 | sort: {
12 | column: 'Username',
13 | direction: 'asc'
14 | }
15 | } satisfies SearchPaginationSortRequest);
16 |
17 | const users = await userService.getUsers(userRequestOptions);
18 |
19 | return {
20 | users,
21 | userRequestOptions
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/backend/resources/migrations/sqlite/024_add_api_keys.up.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS api_keys (
2 | id TEXT PRIMARY KEY,
3 | name TEXT NOT NULL,
4 | description TEXT,
5 | key_hash TEXT NOT NULL,
6 | key_prefix TEXT NOT NULL,
7 | user_id TEXT NOT NULL,
8 | expires_at DATETIME,
9 | last_used_at DATETIME,
10 | created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
11 | updated_at DATETIME,
12 | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
13 | );
14 |
15 | CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id);
16 | CREATE INDEX IF NOT EXISTS idx_api_keys_key_hash ON api_keys(key_hash);
17 | CREATE INDEX IF NOT EXISTS idx_api_keys_key_prefix ON api_keys(key_prefix);
18 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/table/index.ts:
--------------------------------------------------------------------------------
1 | import Root from './table.svelte';
2 | import Body from './table-body.svelte';
3 | import Caption from './table-caption.svelte';
4 | import Cell from './table-cell.svelte';
5 | import Footer from './table-footer.svelte';
6 | import Head from './table-head.svelte';
7 | import Header from './table-header.svelte';
8 | import Row from './table-row.svelte';
9 |
10 | export {
11 | Root,
12 | Body,
13 | Caption,
14 | Cell,
15 | Footer,
16 | Head,
17 | Header,
18 | Row,
19 | //
20 | Root as Table,
21 | Body as TableBody,
22 | Caption as TableCaption,
23 | Cell as TableCell,
24 | Footer as TableFooter,
25 | Head as TableHead,
26 | Header as TableHeader,
27 | Row as TableRow
28 | };
29 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/tree-view/types.ts:
--------------------------------------------------------------------------------
1 | import type { WithChildren, WithoutChildren } from 'bits-ui';
2 | import type { Snippet } from 'svelte';
3 | import type { HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
4 |
5 | export type TreeViewRootProps = HTMLAttributes;
6 |
7 | export type TreeViewFolderProps = WithChildren<{
8 | name: string;
9 | open?: boolean;
10 | class?: string;
11 | icon?: Snippet<[{ name: string; open: boolean }]>;
12 | }>;
13 |
14 | export type TreeViewFilePropsWithoutHTML = WithChildren<{
15 | name: string;
16 | icon?: Snippet<[{ name: string }]>;
17 | }>;
18 |
19 | export type TreeViewFileProps = WithoutChildren & TreeViewFilePropsWithoutHTML;
20 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/breadcrumb/index.ts:
--------------------------------------------------------------------------------
1 | import Root from './breadcrumb.svelte';
2 | import Ellipsis from './breadcrumb-ellipsis.svelte';
3 | import Item from './breadcrumb-item.svelte';
4 | import Separator from './breadcrumb-separator.svelte';
5 | import Link from './breadcrumb-link.svelte';
6 | import List from './breadcrumb-list.svelte';
7 | import Page from './breadcrumb-page.svelte';
8 |
9 | export {
10 | Root,
11 | Ellipsis,
12 | Item,
13 | Separator,
14 | Link,
15 | List,
16 | Page,
17 | //
18 | Root as Breadcrumb,
19 | Ellipsis as BreadcrumbEllipsis,
20 | Item as BreadcrumbItem,
21 | Separator as BreadcrumbSeparator,
22 | Link as BreadcrumbLink,
23 | List as BreadcrumbList,
24 | Page as BreadcrumbPage
25 | };
26 |
--------------------------------------------------------------------------------
/frontend/src/lib/hooks/use-environment-refresh.svelte.ts:
--------------------------------------------------------------------------------
1 | import { environmentStore } from '$lib/stores/environment.store.svelte';
2 |
3 | /**
4 | * Creates an effect that triggers a callback when the environment changes.
5 | * Returns a cleanup function and tracks the last environment ID internally.
6 | */
7 | export function useEnvironmentRefresh(onRefresh: () => void | Promise) {
8 | let lastEnvId: string | null = $state(null);
9 |
10 | $effect(() => {
11 | const env = environmentStore.selected;
12 | if (!env) return;
13 |
14 | if (lastEnvId === null) {
15 | lastEnvId = env.id;
16 | return;
17 | }
18 |
19 | if (env.id !== lastEnvId) {
20 | lastEnvId = env.id;
21 | onRefresh();
22 | }
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/routes/(app)/networks/[networkId]/+page.ts:
--------------------------------------------------------------------------------
1 | import type { PageLoad } from './$types';
2 | import { error } from '@sveltejs/kit';
3 | import { networkService } from '$lib/services/network-service';
4 |
5 | export const load: PageLoad = async ({ params }) => {
6 | const { networkId } = params;
7 |
8 | try {
9 | const network = await networkService.getNetwork(networkId);
10 |
11 | if (!network) {
12 | throw error(404, 'Network not found');
13 | }
14 |
15 | return {
16 | network
17 | };
18 | } catch (err: any) {
19 | console.error('Failed to load network:', err);
20 | if (err.status === 404) {
21 | throw err;
22 | }
23 | throw error(500, err.message || 'Failed to load network details');
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/frontend/src/routes/(app)/projects/[projectId]/+page.ts:
--------------------------------------------------------------------------------
1 | import { projectService } from '$lib/services/project-service';
2 | import type { PageLoad } from './$types';
3 |
4 | export const load: PageLoad = async ({ params }) => {
5 | const project = await projectService.getProject(params.projectId);
6 |
7 | const editorState = {
8 | name: project.name || '',
9 | composeContent: project.composeContent || '',
10 | envContent: project.envContent || '',
11 | originalName: project.name || '',
12 | originalComposeContent: project.composeContent || '',
13 | originalEnvContent: project.envContent || ''
14 | };
15 |
16 | return {
17 | projectId: params.projectId,
18 | project,
19 | editorState,
20 | error: null
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/accordion/accordion-content.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
21 |
22 | {@render children?.()}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/backend/resources/migrations/postgres/024_add_api_keys.up.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS api_keys (
2 | id TEXT PRIMARY KEY,
3 | name TEXT NOT NULL,
4 | description TEXT,
5 | key_hash TEXT NOT NULL,
6 | key_prefix TEXT NOT NULL,
7 | user_id TEXT NOT NULL,
8 | expires_at TIMESTAMPTZ,
9 | last_used_at TIMESTAMPTZ,
10 | created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
11 | updated_at TIMESTAMPTZ,
12 | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
13 | );
14 |
15 | CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id);
16 | CREATE INDEX IF NOT EXISTS idx_api_keys_key_hash ON api_keys(key_hash);
17 | CREATE INDEX IF NOT EXISTS idx_api_keys_key_prefix ON api_keys(key_prefix);
18 |
--------------------------------------------------------------------------------
/frontend/src/lib/services/settings-search.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import type { SettingsSearchResponse, SettingsCategory } from '$lib/types/settings-search.type';
3 |
4 | export class SettingsSearchService {
5 | private baseUrl = '/api/settings';
6 |
7 | async search(query: string): Promise {
8 | const response = await axios.post(`${this.baseUrl}/search`, {
9 | query
10 | });
11 | return response.data;
12 | }
13 |
14 | async getCategories(): Promise {
15 | const response = await axios.get(`${this.baseUrl}/categories`);
16 | return response.data;
17 | }
18 | }
19 |
20 | export const settingsSearchService = new SettingsSearchService();
21 |
--------------------------------------------------------------------------------
/backend/resources/migrations/sqlite/013_stacks_to_projects.down.sql:
--------------------------------------------------------------------------------
1 | -- Recreate legacy cache table
2 | CREATE TABLE IF NOT EXISTS project_cache (
3 | id TEXT PRIMARY KEY,
4 | created_at DATETIME,
5 | updated_at DATETIME,
6 | deleted_at DATETIME,
7 | stack_id TEXT NOT NULL,
8 | name TEXT NOT NULL,
9 | status TEXT NOT NULL,
10 | service_count INTEGER NOT NULL DEFAULT 0,
11 | running_count INTEGER NOT NULL DEFAULT 0,
12 | auto_update INTEGER NOT NULL DEFAULT 0,
13 | last_modified DATETIME,
14 | compose_hash TEXT NOT NULL DEFAULT '',
15 | cached_at DATETIME
16 | );
17 | CREATE UNIQUE INDEX IF NOT EXISTS idx_project_cache_stack_id ON project_cache(stack_id);
18 |
19 | -- Rename projects -> stacks
20 | ALTER TABLE projects RENAME TO stacks;
--------------------------------------------------------------------------------
/frontend/src/routes/(app)/settings/api-keys/+page.ts:
--------------------------------------------------------------------------------
1 | import { apiKeyService } from '$lib/services/api-key-service';
2 | import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
3 | import { resolveInitialTableRequest } from '$lib/utils/table-persistence.util';
4 |
5 | export const load = async () => {
6 | const apiKeyRequestOptions = resolveInitialTableRequest('arcane-api-keys-table', {
7 | pagination: {
8 | page: 1,
9 | limit: 20
10 | },
11 | sort: {
12 | column: 'createdAt',
13 | direction: 'desc'
14 | }
15 | } satisfies SearchPaginationSortRequest);
16 |
17 | const apiKeys = await apiKeyService.getApiKeys(apiKeyRequestOptions);
18 |
19 | return {
20 | apiKeys,
21 | apiKeyRequestOptions
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/frontend/src/lib/services/customize-search.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import type { CustomizeSearchResponse, CustomizeCategory } from '$lib/types/customize-search.type';
3 |
4 | export class CustomizeSearchService {
5 | private baseUrl = '/api/customize';
6 |
7 | async search(query: string): Promise {
8 | const response = await axios.post(`${this.baseUrl}/search`, {
9 | query
10 | });
11 | return response.data;
12 | }
13 |
14 | async getCategories(): Promise {
15 | const response = await axios.get(`${this.baseUrl}/categories`);
16 | return response.data;
17 | }
18 | }
19 |
20 | export const customizeSearchService = new CustomizeSearchService();
21 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
21 |
22 | More
23 |
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/language-request.yml:
--------------------------------------------------------------------------------
1 | name: '🌐 Language request'
2 | description: "You want to contribute to a language that isn't on Crowdin yet?"
3 | title: '🌐 Language Request: '
4 | type: 'Language Request'
5 | body:
6 | - type: input
7 | id: language-name-native
8 | attributes:
9 | label: '🌐 Language Name (native)'
10 | placeholder: 'Schweizerdeutsch'
11 | validations:
12 | required: true
13 | - type: input
14 | id: language-code
15 | attributes:
16 | label: '🌐 ISO 639-1 Language Code'
17 | description: 'You can find your language code [here](https://www.andiamo.co.uk/resources/iso-language-codes/).'
18 | placeholder: 'de-CH'
19 | validations:
20 | required: true
21 |
--------------------------------------------------------------------------------
/frontend/src/lib/types/environment.type.ts:
--------------------------------------------------------------------------------
1 | export type EnvironmentStatus = 'online' | 'offline' | 'error' | 'pending';
2 |
3 | export type Environment = {
4 | id: string;
5 | name: string;
6 | apiUrl: string;
7 | status: EnvironmentStatus;
8 | enabled: boolean;
9 | lastSeen?: string;
10 | apiKey?: string;
11 | };
12 |
13 | export interface CreateEnvironmentDTO {
14 | apiUrl: string;
15 | name: string;
16 | bootstrapToken?: string;
17 | useApiKey?: boolean;
18 | }
19 |
20 | export interface UpdateEnvironmentDTO {
21 | apiUrl?: string;
22 | name?: string;
23 | enabled?: boolean;
24 | bootstrapToken?: string;
25 | regenerateApiKey?: boolean;
26 | }
27 |
28 | export interface DeploymentSnippets {
29 | dockerRun: string;
30 | dockerCompose: string;
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/src/routes/(app)/events/+page.ts:
--------------------------------------------------------------------------------
1 | import { eventService } from '$lib/services/event-service';
2 | import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
3 | import { resolveInitialTableRequest } from '$lib/utils/table-persistence.util';
4 | import type { PageLoad } from './$types';
5 |
6 | export const load: PageLoad = async () => {
7 | const eventRequestOptions = resolveInitialTableRequest('arcane-events-table', {
8 | pagination: {
9 | page: 1,
10 | limit: 20
11 | },
12 | sort: {
13 | column: 'timestamp',
14 | direction: 'desc'
15 | }
16 | } satisfies SearchPaginationSortRequest);
17 |
18 | const events = await eventService.getEvents(eventRequestOptions);
19 |
20 | return { events, eventRequestOptions };
21 | };
22 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/form/form-fieldset.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/setup/project.data.ts:
--------------------------------------------------------------------------------
1 | export const TEST_COMPOSE_YAML = `configs:
2 | some_content:
3 | content: |
4 | This is a test config file.
5 | It can contain multiple lines of text.
6 | Used for testing purposes.
7 |
8 | services:
9 | redis:
10 | image: redis:latest
11 | container_name: \${CONTAINER_NAME}
12 | configs:
13 | - source: some_content
14 | target: /etc/some_content.txt
15 | command: /bin/sh -c 'cat /etc/some_content.txt && redis-server'
16 | ports:
17 | - "8081:81"
18 | - "6379:6379"
19 | - "6378:6378"
20 | volumes:
21 | - redis_data:/data
22 |
23 | volumes:
24 | redis_data:
25 | driver: local
26 | `;
27 |
28 | export const TEST_ENV_FILE = `CONTAINER_NAME=test-redis-container
29 | `;
30 |
--------------------------------------------------------------------------------
/frontend/src/routes/(app)/customize/templates/[id]/+page.ts:
--------------------------------------------------------------------------------
1 | import { templateService } from '$lib/services/template-service';
2 | import { error } from '@sveltejs/kit';
3 | import type { Template, TemplateContentData } from '$lib/types/template.type';
4 |
5 | export const load = async ({
6 | params
7 | }): Promise<{
8 | templateData: TemplateContentData;
9 | allTemplates: Template[];
10 | }> => {
11 | try {
12 | const [templateData, allTemplates] = await Promise.all([
13 | templateService.getTemplateContent(params.id),
14 | templateService.getAllTemplates()
15 | ]);
16 |
17 | return {
18 | templateData,
19 | allTemplates
20 | };
21 | } catch (err) {
22 | console.error('Failed to load template:', err);
23 | throw error(404, 'Template not found');
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/pagination/index.ts:
--------------------------------------------------------------------------------
1 | import Root from './pagination.svelte';
2 | import Content from './pagination-content.svelte';
3 | import Item from './pagination-item.svelte';
4 | import Link from './pagination-link.svelte';
5 | import PrevButton from './pagination-prev-button.svelte';
6 | import NextButton from './pagination-next-button.svelte';
7 | import Ellipsis from './pagination-ellipsis.svelte';
8 |
9 | export {
10 | Root,
11 | Content,
12 | Item,
13 | Link,
14 | PrevButton,
15 | NextButton,
16 | Ellipsis,
17 | //
18 | Root as Pagination,
19 | Content as PaginationContent,
20 | Item as PaginationItem,
21 | Link as PaginationLink,
22 | PrevButton as PaginationPrevButton,
23 | NextButton as PaginationNextButton,
24 | Ellipsis as PaginationEllipsis
25 | };
26 |
--------------------------------------------------------------------------------
/types/search/search.go:
--------------------------------------------------------------------------------
1 | package search
2 |
3 | import "github.com/getarcaneapp/arcane/types/category"
4 |
5 | // Request represents a search request.
6 | type Request struct {
7 | // Query is the search query string.
8 | //
9 | // Required: true
10 | Query string `json:"query" binding:"required,min=1"`
11 | }
12 |
13 | // Response represents the results of a search.
14 | type Response struct {
15 | // Results is a list of categories matching the search query.
16 | //
17 | // Required: true
18 | Results []category.Category `json:"results"`
19 |
20 | // Query is the search query that was executed.
21 | //
22 | // Required: true
23 | Query string `json:"query"`
24 |
25 | // Count is the number of results returned.
26 | //
27 | // Required: true
28 | Count int `json:"count"`
29 | }
30 |
--------------------------------------------------------------------------------
/backend/resources/migrations/sqlite/005_drop_user_sessions_table.down.sql:
--------------------------------------------------------------------------------
1 | -- Recreate user_sessions_table (matches initial schema)
2 | CREATE TABLE IF NOT EXISTS user_sessions_table (
3 | id TEXT PRIMARY KEY,
4 | user_id TEXT NOT NULL,
5 | username TEXT NOT NULL,
6 | token TEXT NOT NULL UNIQUE,
7 | created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
8 | last_accessed DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
9 | expires_at DATETIME,
10 | is_active BOOLEAN NOT NULL DEFAULT true,
11 | updated_at DATETIME,
12 | FOREIGN KEY (user_id) REFERENCES users_table(id) ON DELETE CASCADE
13 | );
14 |
15 | CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions_table(user_id);
16 | CREATE INDEX IF NOT EXISTS idx_user_sessions_token ON user_sessions_table(token);
--------------------------------------------------------------------------------
/docker/examples/compose.basic.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | arcane:
3 | image: ghcr.io/getarcaneapp/arcane:latest
4 | container_name: arcane
5 | ports:
6 | - "3552:3552"
7 | volumes:
8 | - /var/run/docker.sock:/var/run/docker.sock
9 | - arcane-data:/app/data
10 | - /host/path/to/projects:/app/data/projects
11 | environment:
12 | - ENCRYPTION_KEY=xxxxxxxxxxxxxxxxxxxxxx
13 | - JWT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxx
14 | healthcheck:
15 | test:
16 | [
17 | "CMD-SHELL",
18 | "curl -fsS http://localhost:3552/api/health >/dev/null || exit 1",
19 | ]
20 | interval: 10s
21 | timeout: 3s
22 | retries: 5
23 | start_period: 15s
24 | restart: unless-stopped
25 |
26 | volumes:
27 | arcane-data:
28 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/pagination/pagination-next-button.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 | {#snippet Fallback()}
11 |
12 | {/snippet}
13 |
14 |
26 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/pagination/pagination-prev-button.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 | {#snippet Fallback()}
11 |
12 | {/snippet}
13 |
14 |
26 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
18 | {@render children?.()}
19 |
20 |
--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/frontend/src/lib/services/event-service.ts:
--------------------------------------------------------------------------------
1 | import BaseAPIService from './api-service';
2 | import type { SearchPaginationSortRequest, Paginated } from '$lib/types/pagination.type';
3 | import type { Event } from '$lib/types/event.type';
4 | import { transformPaginationParams } from '$lib/utils/params.util';
5 |
6 | export default class EventService extends BaseAPIService {
7 | async getEvents(options?: SearchPaginationSortRequest): Promise> {
8 | const params = transformPaginationParams(options);
9 | const res = await this.api.get('/events', { params });
10 | return res.data;
11 | }
12 |
13 | async delete(id: string): Promise {
14 | return this.handleResponse(this.api.delete(`/events/${id}`));
15 | }
16 | }
17 |
18 | export const eventService = new EventService();
19 |
--------------------------------------------------------------------------------