Full Code of Termix-SSH/Termix for AI

main 69eca2652bb9 cached
377 files
8.7 MB
2.3M tokens
1599 symbols
1 requests
Download .txt
Showing preview only (9,233K chars total). Download the full file or copy to clipboard to get everything.
Repository: Termix-SSH/Termix
Branch: main
Commit: 69eca2652bb9
Files: 377
Total size: 8.7 MB

Directory structure:
gitextract_m9ntu940/

├── .commitlintrc.json
├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── config.yml
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── docker.yml
│       ├── electron.yml
│       ├── openapi.yml
│       └── pr-check.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Casks/
│   └── termix.rb
├── LICENSE
├── README.md
├── SECURITY.md
├── build/
│   ├── Termix_Mac_App_Store.provisionprofile
│   ├── entitlements.mac.inherit.plist
│   ├── entitlements.mac.plist
│   ├── entitlements.mas.inherit.plist
│   ├── entitlements.mas.plist
│   └── notarize.cjs
├── chocolatey/
│   ├── termix-ssh.nuspec
│   └── tools/
│       ├── chocolateyinstall.ps1
│       └── chocolateyuninstall.ps1
├── components.json
├── crowdin.yml
├── docker/
│   ├── Dockerfile
│   ├── docker-compose.yml
│   ├── entrypoint.sh
│   ├── nginx-https.conf
│   └── nginx.conf
├── electron/
│   ├── main.cjs
│   └── preload.js
├── electron-builder.json
├── eslint.config.js
├── flatpak/
│   ├── com.karmaa.termix.desktop
│   ├── com.karmaa.termix.flatpakref
│   ├── com.karmaa.termix.metainfo.xml
│   ├── com.karmaa.termix.yml
│   └── flathub.json
├── index.html
├── package.json
├── public/
│   ├── icon.icns
│   ├── icons/
│   │   └── icon.icns
│   ├── manifest.json
│   └── sw.js
├── readme/
│   ├── README-AR.md
│   ├── README-CN.md
│   ├── README-DE.md
│   ├── README-ES.md
│   ├── README-FR.md
│   ├── README-HI.md
│   ├── README-IT.md
│   ├── README-JA.md
│   ├── README-KO.md
│   ├── README-PT.md
│   ├── README-RU.md
│   ├── README-TR.md
│   └── README-VI.md
├── src/
│   ├── backend/
│   │   ├── dashboard.ts
│   │   ├── database/
│   │   │   ├── database.ts
│   │   │   ├── db/
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   └── routes/
│   │   │       ├── alerts.ts
│   │   │       ├── credentials.ts
│   │   │       ├── host.ts
│   │   │       ├── network-topology.ts
│   │   │       ├── rbac.ts
│   │   │       ├── snippets.ts
│   │   │       ├── terminal.ts
│   │   │       └── users.ts
│   │   ├── guacamole/
│   │   │   ├── guacamole-server.ts
│   │   │   ├── routes.ts
│   │   │   └── token-service.ts
│   │   ├── scripts/
│   │   │   ├── enable-ssl.sh
│   │   │   └── setup-ssl.sh
│   │   ├── ssh/
│   │   │   ├── auth-manager.ts
│   │   │   ├── docker-console.ts
│   │   │   ├── docker.ts
│   │   │   ├── file-manager.ts
│   │   │   ├── host-key-verifier.ts
│   │   │   ├── opkssh-auth.ts
│   │   │   ├── server-stats.ts
│   │   │   ├── ssh-connection-pool.ts
│   │   │   ├── terminal-session-manager.ts
│   │   │   ├── terminal.ts
│   │   │   ├── tunnel.ts
│   │   │   └── widgets/
│   │   │       ├── common-utils.ts
│   │   │       ├── cpu-collector.ts
│   │   │       ├── disk-collector.ts
│   │   │       ├── firewall-collector.ts
│   │   │       ├── login-stats-collector.ts
│   │   │       ├── memory-collector.ts
│   │   │       ├── network-collector.ts
│   │   │       ├── ports-collector.ts
│   │   │       ├── processes-collector.ts
│   │   │       ├── system-collector.ts
│   │   │       └── uptime-collector.ts
│   │   ├── starter.ts
│   │   ├── swagger.ts
│   │   └── utils/
│   │       ├── auth-manager.ts
│   │       ├── auto-ssl-setup.ts
│   │       ├── credential-system-encryption-migration.ts
│   │       ├── data-crypto.ts
│   │       ├── database-file-encryption.ts
│   │       ├── database-migration.ts
│   │       ├── database-save-trigger.ts
│   │       ├── field-crypto.ts
│   │       ├── lazy-field-encryption.ts
│   │       ├── logger.ts
│   │       ├── login-rate-limiter.ts
│   │       ├── opkssh-binary-manager.ts
│   │       ├── permission-manager.ts
│   │       ├── proxy-agent.ts
│   │       ├── proxy-helper.ts
│   │       ├── request-origin.ts
│   │       ├── shared-credential-manager.ts
│   │       ├── simple-db-ops.ts
│   │       ├── socks5-helper.ts
│   │       ├── ssh-key-utils.ts
│   │       ├── system-crypto.ts
│   │       ├── user-agent-parser.ts
│   │       ├── user-crypto.ts
│   │       ├── user-data-export.ts
│   │       └── user-data-import.ts
│   ├── components/
│   │   ├── theme-provider.tsx
│   │   └── ui/
│   │       ├── accordion.tsx
│   │       ├── alert-dialog.tsx
│   │       ├── alert.tsx
│   │       ├── badge.tsx
│   │       ├── button-group.tsx
│   │       ├── button.tsx
│   │       ├── card.tsx
│   │       ├── chart.tsx
│   │       ├── checkbox.tsx
│   │       ├── command.tsx
│   │       ├── dialog.tsx
│   │       ├── dropdown-menu.tsx
│   │       ├── form.tsx
│   │       ├── input.tsx
│   │       ├── kbd.tsx
│   │       ├── label.tsx
│   │       ├── password-input.tsx
│   │       ├── popover.tsx
│   │       ├── progress.tsx
│   │       ├── resizable.tsx
│   │       ├── scroll-area.tsx
│   │       ├── select.tsx
│   │       ├── separator.tsx
│   │       ├── shadcn-io/
│   │       │   └── status/
│   │       │       └── index.tsx
│   │       ├── sheet.tsx
│   │       ├── sidebar.tsx
│   │       ├── skeleton.tsx
│   │       ├── slider.tsx
│   │       ├── sonner.tsx
│   │       ├── switch.tsx
│   │       ├── table.tsx
│   │       ├── tabs.tsx
│   │       ├── textarea.tsx
│   │       ├── tooltip.tsx
│   │       └── version-alert.tsx
│   ├── constants/
│   │   └── terminal-themes.ts
│   ├── hooks/
│   │   ├── use-confirmation.ts
│   │   ├── use-mobile.ts
│   │   └── use-service-worker.ts
│   ├── i18n/
│   │   └── i18n.ts
│   ├── index.css
│   ├── lib/
│   │   ├── base-path.ts
│   │   ├── clipboard-provider.ts
│   │   ├── db-health-monitor.ts
│   │   ├── frontend-logger.ts
│   │   ├── terminal-syntax-highlighter.ts
│   │   └── utils.ts
│   ├── locales/
│   │   ├── README.md
│   │   ├── en.json
│   │   └── translated/
│   │       ├── af_ZA.json
│   │       ├── ar_SA.json
│   │       ├── bg_BG.json
│   │       ├── bn_BD.json
│   │       ├── ca_ES.json
│   │       ├── cs_CZ.json
│   │       ├── da_DK.json
│   │       ├── de_DE.json
│   │       ├── el_GR.json
│   │       ├── es_ES.json
│   │       ├── fi_FI.json
│   │       ├── fr_FR.json
│   │       ├── he_IL.json
│   │       ├── hi_IN.json
│   │       ├── hu_HU.json
│   │       ├── id_ID.json
│   │       ├── it_IT.json
│   │       ├── ja_JP.json
│   │       ├── ko_KR.json
│   │       ├── nl_NL.json
│   │       ├── no_NO.json
│   │       ├── pl_PL.json
│   │       ├── pt_BR.json
│   │       ├── pt_PT.json
│   │       ├── ro_RO.json
│   │       ├── ru_RU.json
│   │       ├── sr_SP.json
│   │       ├── sv_SE.json
│   │       ├── th_TH.json
│   │       ├── tr_TR.json
│   │       ├── uk_UA.json
│   │       ├── vi_VN.json
│   │       ├── zh_CN.json
│   │       └── zh_TW.json
│   ├── main.tsx
│   ├── types/
│   │   ├── connection-log.ts
│   │   ├── electron.d.ts
│   │   ├── guacamole-common-js.d.ts
│   │   ├── index.ts
│   │   └── stats-widgets.ts
│   ├── ui/
│   │   ├── contexts/
│   │   │   └── ServerStatusContext.tsx
│   │   ├── desktop/
│   │   │   ├── DesktopApp.tsx
│   │   │   ├── apps/
│   │   │   │   ├── FullScreenAppWrapper.tsx
│   │   │   │   ├── admin/
│   │   │   │   │   ├── AdminSettings.tsx
│   │   │   │   │   ├── dialogs/
│   │   │   │   │   │   ├── CreateUserDialog.tsx
│   │   │   │   │   │   ├── LinkAccountDialog.tsx
│   │   │   │   │   │   └── UserEditDialog.tsx
│   │   │   │   │   └── tabs/
│   │   │   │   │       ├── DatabaseSecurityTab.tsx
│   │   │   │   │       ├── GeneralSettingsTab.tsx
│   │   │   │   │       ├── OIDCSettingsTab.tsx
│   │   │   │   │       ├── RolesTab.tsx
│   │   │   │   │       ├── SessionManagementTab.tsx
│   │   │   │   │       └── UserManagementTab.tsx
│   │   │   │   ├── command-palette/
│   │   │   │   │   └── CommandPalette.tsx
│   │   │   │   ├── dashboard/
│   │   │   │   │   ├── Dashboard.tsx
│   │   │   │   │   ├── apps/
│   │   │   │   │   │   ├── UpdateLog.tsx
│   │   │   │   │   │   └── alerts/
│   │   │   │   │   │       ├── AlertCard.tsx
│   │   │   │   │   │       └── AlertManager.tsx
│   │   │   │   │   ├── cards/
│   │   │   │   │   │   ├── NetworkGraphCard.tsx
│   │   │   │   │   │   ├── QuickActionsCard.tsx
│   │   │   │   │   │   ├── RecentActivityCard.tsx
│   │   │   │   │   │   ├── ServerOverviewCard.tsx
│   │   │   │   │   │   └── ServerStatsCard.tsx
│   │   │   │   │   ├── components/
│   │   │   │   │   │   └── DashboardSettingsDialog.tsx
│   │   │   │   │   └── hooks/
│   │   │   │   │       └── useDashboardPreferences.ts
│   │   │   │   ├── features/
│   │   │   │   │   ├── docker/
│   │   │   │   │   │   ├── DockerApp.tsx
│   │   │   │   │   │   ├── DockerManager.tsx
│   │   │   │   │   │   └── components/
│   │   │   │   │   │       ├── ConsoleTerminal.tsx
│   │   │   │   │   │       ├── ContainerCard.tsx
│   │   │   │   │   │       ├── ContainerDetail.tsx
│   │   │   │   │   │       ├── ContainerList.tsx
│   │   │   │   │   │       ├── ContainerStats.tsx
│   │   │   │   │   │       └── LogViewer.tsx
│   │   │   │   │   ├── file-manager/
│   │   │   │   │   │   ├── DragIndicator.tsx
│   │   │   │   │   │   ├── FileManager.tsx
│   │   │   │   │   │   ├── FileManagerApp.tsx
│   │   │   │   │   │   ├── FileManagerContextMenu.tsx
│   │   │   │   │   │   ├── FileManagerGrid.tsx
│   │   │   │   │   │   ├── FileManagerSidebar.tsx
│   │   │   │   │   │   ├── SudoPasswordDialog.tsx
│   │   │   │   │   │   ├── components/
│   │   │   │   │   │   │   ├── CompressDialog.tsx
│   │   │   │   │   │   │   ├── DiffViewer.tsx
│   │   │   │   │   │   │   ├── DiffWindow.tsx
│   │   │   │   │   │   │   ├── DraggableWindow.tsx
│   │   │   │   │   │   │   ├── FileViewer.tsx
│   │   │   │   │   │   │   ├── FileWindow.tsx
│   │   │   │   │   │   │   ├── PermissionsDialog.tsx
│   │   │   │   │   │   │   ├── TerminalWindow.tsx
│   │   │   │   │   │   │   └── WindowManager.tsx
│   │   │   │   │   │   └── hooks/
│   │   │   │   │   │       ├── useDragAndDrop.ts
│   │   │   │   │   │       └── useFileSelection.ts
│   │   │   │   │   ├── guacamole/
│   │   │   │   │   │   ├── GuacamoleApp.tsx
│   │   │   │   │   │   └── GuacamoleDisplay.tsx
│   │   │   │   │   ├── server-stats/
│   │   │   │   │   │   ├── ServerStats.tsx
│   │   │   │   │   │   ├── ServerStatsApp.tsx
│   │   │   │   │   │   └── widgets/
│   │   │   │   │   │       ├── CpuWidget.tsx
│   │   │   │   │   │       ├── DiskWidget.tsx
│   │   │   │   │   │       ├── FirewallWidget.tsx
│   │   │   │   │   │       ├── LoginStatsWidget.tsx
│   │   │   │   │   │       ├── MemoryWidget.tsx
│   │   │   │   │   │       ├── NetworkWidget.tsx
│   │   │   │   │   │       ├── PortsWidget.tsx
│   │   │   │   │   │       ├── ProcessesWidget.tsx
│   │   │   │   │   │       ├── SystemWidget.tsx
│   │   │   │   │   │       ├── UptimeWidget.tsx
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── terminal/
│   │   │   │   │   │   ├── SudoPasswordPopup.tsx
│   │   │   │   │   │   ├── Terminal.tsx
│   │   │   │   │   │   ├── TerminalApp.tsx
│   │   │   │   │   │   ├── TerminalPreview.tsx
│   │   │   │   │   │   └── command-history/
│   │   │   │   │   │       ├── CommandAutocomplete.tsx
│   │   │   │   │   │       └── CommandHistoryContext.tsx
│   │   │   │   │   └── tunnel/
│   │   │   │   │       ├── Tunnel.tsx
│   │   │   │   │       ├── TunnelApp.tsx
│   │   │   │   │       ├── TunnelManager.tsx
│   │   │   │   │       ├── TunnelObject.tsx
│   │   │   │   │       └── TunnelViewer.tsx
│   │   │   │   ├── host-manager/
│   │   │   │   │   ├── HostManagerApp.tsx
│   │   │   │   │   ├── credentials/
│   │   │   │   │   │   ├── CredentialEditor.tsx
│   │   │   │   │   │   ├── CredentialSelector.tsx
│   │   │   │   │   │   ├── CredentialViewer.tsx
│   │   │   │   │   │   ├── CredentialsManager.tsx
│   │   │   │   │   │   └── tabs/
│   │   │   │   │   │       ├── CredentialAuthenticationTab.tsx
│   │   │   │   │   │       ├── CredentialGeneralTab.tsx
│   │   │   │   │   │       └── shared/
│   │   │   │   │   │           └── tab-types.ts
│   │   │   │   │   ├── dialogs/
│   │   │   │   │   │   └── FolderEditDialog.tsx
│   │   │   │   │   └── hosts/
│   │   │   │   │       ├── HostManager.tsx
│   │   │   │   │       ├── HostManagerEditor.tsx
│   │   │   │   │       ├── HostManagerViewer.tsx
│   │   │   │   │       └── tabs/
│   │   │   │   │           ├── HostDockerTab.tsx
│   │   │   │   │           ├── HostFileManagerTab.tsx
│   │   │   │   │           ├── HostGeneralTab.tsx
│   │   │   │   │           ├── HostRemoteDesktopTab.tsx
│   │   │   │   │           ├── HostSharingTab.tsx
│   │   │   │   │           ├── HostStatisticsTab.tsx
│   │   │   │   │           ├── HostStatusTab.tsx
│   │   │   │   │           ├── HostTerminalTab.tsx
│   │   │   │   │           ├── HostTunnelTab.tsx
│   │   │   │   │           └── shared/
│   │   │   │   │               ├── JumpHostItem.tsx
│   │   │   │   │               ├── QuickActionItem.tsx
│   │   │   │   │               └── tab-types.ts
│   │   │   │   └── tools/
│   │   │   │       └── SSHToolsSidebar.tsx
│   │   │   ├── authentication/
│   │   │   │   ├── Auth.tsx
│   │   │   │   ├── ElectronLoginForm.tsx
│   │   │   │   └── ElectronServerConfig.tsx
│   │   │   ├── navigation/
│   │   │   │   ├── AppView.tsx
│   │   │   │   ├── LeftSidebar.tsx
│   │   │   │   ├── TopNavbar.tsx
│   │   │   │   ├── animations/
│   │   │   │   │   └── SimpleLoader.tsx
│   │   │   │   ├── connection-log/
│   │   │   │   │   ├── ConnectionLog.tsx
│   │   │   │   │   └── ConnectionLogContext.tsx
│   │   │   │   ├── dialogs/
│   │   │   │   │   ├── HostKeyVerificationDialog.tsx
│   │   │   │   │   ├── OPKSSHDialog.tsx
│   │   │   │   │   ├── QuickConnectDialog.tsx
│   │   │   │   │   ├── SSHAuthDialog.tsx
│   │   │   │   │   ├── TOTPDialog.tsx
│   │   │   │   │   └── WarpgateDialog.tsx
│   │   │   │   ├── hosts/
│   │   │   │   │   ├── FolderCard.tsx
│   │   │   │   │   └── Host.tsx
│   │   │   │   └── tabs/
│   │   │   │       ├── Tab.tsx
│   │   │   │       ├── TabContext.tsx
│   │   │   │       └── TabDropdown.tsx
│   │   │   └── user/
│   │   │       ├── ElectronVersionCheck.tsx
│   │   │       ├── LanguageSwitcher.tsx
│   │   │       ├── PasswordReset.tsx
│   │   │       ├── TOTPSetup.tsx
│   │   │       └── UserProfile.tsx
│   │   ├── hooks/
│   │   │   ├── useCommandHistory.ts
│   │   │   ├── useCommandTracker.ts
│   │   │   ├── useDragToDesktop.ts
│   │   │   └── useDragToSystemDesktop.ts
│   │   ├── main-axios.ts
│   │   └── mobile/
│   │       ├── MobileApp.tsx
│   │       ├── apps/
│   │       │   ├── navigation/
│   │       │   │   ├── BottomNavbar.tsx
│   │       │   │   ├── LeftSidebar.tsx
│   │       │   │   ├── hosts/
│   │       │   │   │   ├── FolderCard.tsx
│   │       │   │   │   └── Host.tsx
│   │       │   │   └── tabs/
│   │       │   │       └── TabContext.tsx
│   │       │   └── terminal/
│   │       │       ├── Terminal.tsx
│   │       │       ├── TerminalKeyboard.tsx
│   │       │       ├── kb-dark-theme.css
│   │       │       └── kb-light-theme.css
│   │       ├── authentication/
│   │       │   └── Auth.tsx
│   │       └── navigation/
│   │           ├── BottomNavbar.tsx
│   │           ├── LeftSidebar.tsx
│   │           ├── hosts/
│   │           │   ├── FolderCard.tsx
│   │           │   └── Host.tsx
│   │           └── tabs/
│   │               └── TabContext.tsx
│   └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .commitlintrc.json
================================================
{
  "extends": ["@commitlint/config-conventional"],
  "rules": {
    "type-enum": [
      2,
      "always",
      [
        "feat",
        "fix",
        "docs",
        "style",
        "refactor",
        "perf",
        "test",
        "chore",
        "revert"
      ]
    ],
    "subject-case": [0]
  }
}


================================================
FILE: .dockerignore
================================================
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*

dist
build
.next
.nuxt

.env.local
.env.development.local
.env.test.local
.env.production.local

.vscode
.idea
*.swp
*.swo
*~

.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

.git
.gitignore

README.md
CONTRIBUTING.md
LICENSE

repo-images/

uploads/

electron/
electron-builder.json

*.log
*.tmp
*.temp

logs
*.log

pids
*.pid
*.seed
*.pid.lock

coverage

.nyc_output

jspm_packages/

.npm

.eslintcache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

.node_repl_history

*.tgz

.yarn-integrity

.cache
.parcel-cache

.next

.nuxt

.vuepress/dist

.serverless

.fusebox/

.dynamodb/

.tern-port


================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{js,jsx,ts,tsx,json,css,scss,md,yml,yaml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .gitattributes
================================================
* text=auto eol=lf

*.js text eol=lf
*.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.json text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.html text eol=lf
*.md text eol=lf
*.yaml text eol=lf
*.yml text eol=lf

*.sh text eol=lf
*.bash text eol=lf

*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf

*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.svg binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary


================================================
FILE: .github/FUNDING.yml
================================================
github: [LukeGus]


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Support Center
    url: https://github.com/Termix-SSH/Support/issues
    about: Report any feature requests or bugs in the support center
  - name: Discord
    url: https://discord.gg/jVQGdvHDrf
    about: Official Termix Discord server for general discussion and quick support


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    groups:
      dev-patch-updates:
        dependency-type: "development"
        update-types:
          - "patch"
      dev-minor-updates:
        dependency-type: "development"
        update-types:
          - "minor"
      prod-patch-updates:
        dependency-type: "production"
        update-types:
          - "patch"
      prod-minor-updates:
        dependency-type: "production"
        update-types:
          - "minor"

  - package-ecosystem: "docker"
    directory: "/docker"
    schedule:
      interval: "daily"
    groups:
      patch-updates:
        update-types:
          - "patch"
      minor-updates:
        update-types:
          - "minor"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"


================================================
FILE: .github/pull_request_template.md
================================================
# Overview

_Short summary of what this PR does_

- [ ] Added: ...
- [ ] Updated: ...
- [ ] Removed: ...
- [ ] Fixed: ...

# Changes Made

_Detailed explanation of changes (if needed)_

- ...

# Related Issues

_Link any issues this PR addresses_

- Closes #ISSUE_NUMBER
- Related to #ISSUE_NUMBER

# Screenshots / Demos

_(Optional: add before/after screenshots, GIFs, or console output)_

# Checklist

- [ ] Code follows project style guidelines
- [ ] Supports mobile and desktop UI/app (if applicable)
- [ ] I have read [Contributing.md](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md)
- [ ] This is not a translation request. See [docs](https://docs.termix.site/translations)


================================================
FILE: .github/workflows/docker.yml
================================================
name: Build and Push Docker Image

on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version to build (e.g., 1.8.0)"
        required: true
      build_type:
        description: "Build type"
        required: true
        default: "Development"
        type: choice
        options:
          - Development
          - Production

jobs:
  build:
    runs-on: blacksmith-8vcpu-ubuntu-2404
    steps:
      - name: Checkout repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 1

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
        with:
          platforms: linux/amd64,linux/arm64,linux/arm/v7

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Determine tags
        id: tags
        run: |
          VERSION=${{ github.event.inputs.version }}
          BUILD_TYPE=${{ github.event.inputs.build_type }}

          TAGS=()
          ALL_TAGS=()

          if [ "$BUILD_TYPE" = "Production" ]; then
            TAGS+=("release-$VERSION" "latest")
            for tag in "${TAGS[@]}"; do
              ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
              ALL_TAGS+=("docker.io/bugattiguy527/termix:$tag")
            done
          else
            TAGS+=("dev-$VERSION")
            for tag in "${TAGS[@]}"; do
              ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
            done
          fi

          echo "ALL_TAGS=$(IFS=,; echo "${ALL_TAGS[*]}")" >> $GITHUB_ENV

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: lukegus
          password: ${{ secrets.GHCR_TOKEN }}

      - name: Login to Docker Hub (prod only)
        if: ${{ github.event.inputs.build_type == 'Production' }}
        uses: docker/login-action@v3
        with:
          username: bugattiguy527
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push multi-arch image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./docker/Dockerfile
          push: true
          platforms: linux/amd64,linux/arm64,linux/arm/v7
          tags: ${{ env.ALL_TAGS }}
          build-args: |
            BUILDKIT_INLINE_CACHE=1
            BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
          labels: |
            org.opencontainers.image.source=https://github.com/${{ github.repository }}
            org.opencontainers.image.revision=${{ github.sha }}
            org.opencontainers.image.created=${{ github.run_id }}
          outputs: type=registry,compression=gzip,compression-level=9

      - name: Cleanup Docker
        if: always()
        run: |
          docker image prune -af
          docker system prune -af --volumes


================================================
FILE: .github/workflows/electron.yml
================================================
name: Build and Push Electron App

on:
  workflow_dispatch:
    inputs:
      build_type:
        description: "Platform to build for"
        required: true
        default: "all"
        type: choice
        options:
          - all
          - windows
          - linux
          - macos
      artifact_destination:
        description: "What to do with the built app"
        required: true
        default: "file"
        type: choice
        options:
          - none
          - file
          - release
          - submit

jobs:
  build-windows:
    runs-on: windows-latest
    if: (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == '') && github.event.inputs.artifact_destination != 'submit'
    permissions:
      contents: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 1

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: |
          $maxAttempts = 3
          $attempt = 1
          while ($attempt -le $maxAttempts) {
            try {
              npm ci
              break
            } catch {
              if ($attempt -eq $maxAttempts) {
                Write-Error "npm ci failed after $maxAttempts attempts"
                exit 1
              }
              Start-Sleep -Seconds 10
              $attempt++
            }
          }

      - name: Get version
        id: package-version
        run: |
          $VERSION = (Get-Content package.json | ConvertFrom-Json).version
          echo "version=$VERSION" >> $env:GITHUB_OUTPUT

      - name: Build Windows (All Architectures)
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npm run build && npx electron-builder --win --x64 --ia32

      - name: Upload Windows x64 NSIS Installer
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_windows_x64_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_windows_x64_nsis
          path: release/termix_windows_x64_nsis.exe
          retention-days: 30

      - name: Upload Windows ia32 NSIS Installer
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_windows_ia32_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_windows_ia32_nsis
          path: release/termix_windows_ia32_nsis.exe
          retention-days: 30

      - name: Upload Windows x64 MSI Installer
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_windows_x64_msi.msi') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_windows_x64_msi
          path: release/termix_windows_x64_msi.msi
          retention-days: 30

      - name: Upload Windows ia32 MSI Installer
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_windows_ia32_msi.msi') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_windows_ia32_msi
          path: release/termix_windows_ia32_msi.msi
          retention-days: 30

      - name: Create Windows x64 Portable zip
        if: hashFiles('release/win-unpacked/*') != ''
        run: |
          Compress-Archive -Path "release\win-unpacked\*" -DestinationPath "termix_windows_x64_portable.zip"

      - name: Create Windows ia32 Portable zip
        if: hashFiles('release/win-ia32-unpacked/*') != ''
        run: |
          Compress-Archive -Path "release\win-ia32-unpacked\*" -DestinationPath "termix_windows_ia32_portable.zip"

      - name: Upload Windows x64 Portable
        uses: actions/upload-artifact@v4
        if: hashFiles('termix_windows_x64_portable.zip') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_windows_x64_portable
          path: termix_windows_x64_portable.zip
          retention-days: 30

      - name: Upload Windows ia32 Portable
        uses: actions/upload-artifact@v4
        if: hashFiles('termix_windows_ia32_portable.zip') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_windows_ia32_portable
          path: termix_windows_ia32_portable.zip
          retention-days: 30

  build-linux:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    if: (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == '') && github.event.inputs.artifact_destination != 'submit'
    permissions:
      contents: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 1

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install system dependencies for AppImage
        run: |
          sudo apt-get update
          sudo apt-get install -y libfuse2

      - name: Install dependencies
        run: |
          for i in 1 2 3;
          do
            if npm ci; then
              break
            else
              if [ $i -eq 3 ]; then
                exit 1
              fi
              sleep 10
            fi
          done
          npm install --force @rollup/rollup-linux-x64-gnu
          npm install --force @rollup/rollup-linux-arm64-gnu
          npm install --force @rollup/rollup-linux-arm-gnueabihf

      - name: Build Linux x64
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DEBUG: electron-builder
        run: npm run build && npx electron-builder --linux --x64

      - name: Build Linux arm64 and armv7l
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npx electron-builder --linux --arm64 --armv7l

      - name: Rename Linux artifacts for consistency
        run: |
          cd release

          if [ -f "termix_linux_amd64_deb.deb" ]; then
            mv "termix_linux_amd64_deb.deb" "termix_linux_x64_deb.deb"
          fi

          if [ -f "termix_linux_x86_64_appimage.AppImage" ]; then
            mv "termix_linux_x86_64_appimage.AppImage" "termix_linux_x64_appimage.AppImage"
          fi

          cd ..

      - name: Upload Linux x64 AppImage
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_x64_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_x64_appimage
          path: release/termix_linux_x64_appimage.AppImage
          retention-days: 30

      - name: Upload Linux arm64 AppImage
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_arm64_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_arm64_appimage
          path: release/termix_linux_arm64_appimage.AppImage
          retention-days: 30

      - name: Upload Linux armv7l AppImage
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_armv7l_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_armv7l_appimage
          path: release/termix_linux_armv7l_appimage.AppImage
          retention-days: 30

      - name: Upload Linux x64 DEB
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_x64_deb.deb') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_x64_deb
          path: release/termix_linux_x64_deb.deb
          retention-days: 30

      - name: Upload Linux arm64 DEB
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_arm64_deb.deb') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_arm64_deb
          path: release/termix_linux_arm64_deb.deb
          retention-days: 30

      - name: Upload Linux armv7l DEB
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_armv7l_deb.deb') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_armv7l_deb
          path: release/termix_linux_armv7l_deb.deb
          retention-days: 30

      - name: Upload Linux x64 tar.gz
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_x64_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_x64_portable
          path: release/termix_linux_x64_portable.tar.gz
          retention-days: 30

      - name: Upload Linux arm64 tar.gz
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_arm64_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_arm64_portable
          path: release/termix_linux_arm64_portable.tar.gz
          retention-days: 30

      - name: Upload Linux armv7l tar.gz
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_armv7l_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_armv7l_portable
          path: release/termix_linux_armv7l_portable.tar.gz
          retention-days: 30

      - name: Install Flatpak builder and dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y flatpak flatpak-builder imagemagick

      - name: Add Flathub repository
        run: |
          sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo

      - name: Install Flatpak runtime and SDK
        run: |
          sudo flatpak install -y flathub org.freedesktop.Platform//24.08
          sudo flatpak install -y flathub org.freedesktop.Sdk//24.08
          sudo flatpak install -y flathub org.electronjs.Electron2.BaseApp//24.08

      - name: Get version for Flatpak
        id: flatpak-version
        run: |
          VERSION=$(node -p "require('./package.json').version")
          RELEASE_DATE=$(date +%Y-%m-%d)
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT

      - name: Prepare Flatpak files
        run: |
          VERSION="${{ steps.flatpak-version.outputs.version }}"
          RELEASE_DATE="${{ steps.flatpak-version.outputs.release_date }}"

          CHECKSUM_X64=$(sha256sum "release/termix_linux_x64_appimage.AppImage" | awk '{print $1}')
          CHECKSUM_ARM64=$(sha256sum "release/termix_linux_arm64_appimage.AppImage" | awk '{print $1}')

          mkdir -p flatpak-build
          cp flatpak/com.karmaa.termix.yml flatpak-build/
          cp flatpak/com.karmaa.termix.desktop flatpak-build/
          cp flatpak/com.karmaa.termix.metainfo.xml flatpak-build/
          cp public/icon.svg flatpak-build/com.karmaa.termix.svg
          convert public/icon.png -resize 256x256 flatpak-build/icon-256.png
          convert public/icon.png -resize 128x128 flatpak-build/icon-128.png

          cd flatpak-build
          sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_x64_appimage.AppImage|file://$(realpath ../release/termix_linux_x64_appimage.AppImage)|g" com.karmaa.termix.yml
          sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_arm64_appimage.AppImage|file://$(realpath ../release/termix_linux_arm64_appimage.AppImage)|g" com.karmaa.termix.yml
          sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" com.karmaa.termix.yml
          sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" com.karmaa.termix.yml
          sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" com.karmaa.termix.metainfo.xml
          sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" com.karmaa.termix.metainfo.xml

      - name: Build Flatpak bundle
        run: |
          cd flatpak-build
          flatpak-builder --repo=repo --force-clean --disable-rofiles-fuse build-dir com.karmaa.termix.yml

          ARCH=$(uname -m)
          if [ "$ARCH" = "x86_64" ]; then
            FLATPAK_ARCH="x86_64"
          elif [ "$ARCH" = "aarch64" ]; then
            FLATPAK_ARCH="aarch64"
          else
            FLATPAK_ARCH="$ARCH"
          fi

          flatpak build-bundle repo ../release/termix_linux_flatpak.flatpak com.karmaa.termix --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo

      - name: Create flatpakref file
        run: |
          VERSION="${{ steps.flatpak-version.outputs.version }}"
          cp flatpak/com.karmaa.termix.flatpakref release/
          sed -i "s|VERSION_PLACEHOLDER|release-${VERSION}-tag|g" release/com.karmaa.termix.flatpakref

      - name: Upload Flatpak bundle
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_linux_flatpak.flatpak') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_flatpak
          path: release/termix_linux_flatpak.flatpak
          retention-days: 30

      - name: Upload Flatpakref
        uses: actions/upload-artifact@v4
        if: hashFiles('release/com.karmaa.termix.flatpakref') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_linux_flatpakref
          path: release/com.karmaa.termix.flatpakref
          retention-days: 30

  build-macos:
    runs-on: macos-latest
    if: (github.event.inputs.build_type == 'macos' || github.event.inputs.build_type == 'all') && github.event.inputs.artifact_destination != 'submit'
    needs: []
    permissions:
      contents: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 1

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: |
          for i in 1 2 3;
          do
            if npm ci; then
              break
            else
              if [ $i -eq 3 ]; then
                exit 1
              fi
              sleep 10
            fi
          done
          npm install --force @rollup/rollup-darwin-arm64
          npm install dmg-license

      - name: Check for Code Signing Certificates
        id: check_certs
        run: |
          if [ -n "${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.MAC_P12_PASSWORD }}" ]; then
            echo "has_certs=true" >> $GITHUB_OUTPUT
          fi

      - name: Import Code Signing Certificates
        if: steps.check_certs.outputs.has_certs == 'true'
        env:
          MAC_BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}
          MAC_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.MAC_INSTALLER_CERTIFICATE_BASE64 }}
          MAC_P12_PASSWORD: ${{ secrets.MAC_P12_PASSWORD }}
          MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
        run: |
          APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12
          INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.p12
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db

          echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH

          if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then
            echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH
          fi

          security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH

          if [ -f "$INSTALLER_CERT_PATH" ]; then
            security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          fi

          security list-keychain -d user -s $KEYCHAIN_PATH

          security find-identity -v -p codesigning $KEYCHAIN_PATH

      - name: Build macOS App Store Package
        if: steps.check_certs.outputs.has_certs == 'true'
        env:
          ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          CURRENT_VERSION=$(node -p "require('./package.json').version")
          BUILD_VERSION="${{ github.run_number }}"

          npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION"

      - name: Clean up MAS keychain before DMG build
        if: steps.check_certs.outputs.has_certs == 'true'
        run: |
          security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true

      - name: Check for Developer ID Certificates
        id: check_dev_id_certs
        run: |
          if [ -n "${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.DEVELOPER_ID_P12_PASSWORD }}" ]; then
            echo "has_dev_id_certs=true" >> $GITHUB_OUTPUT
          fi

      - name: Import Developer ID Certificates
        if: steps.check_dev_id_certs.outputs.has_dev_id_certs == 'true'
        env:
          DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}
          DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64 }}
          DEVELOPER_ID_P12_PASSWORD: ${{ secrets.DEVELOPER_ID_P12_PASSWORD }}
          MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
        run: |
          DEV_CERT_PATH=$RUNNER_TEMP/dev_certificate.p12
          DEV_INSTALLER_CERT_PATH=$RUNNER_TEMP/dev_installer_certificate.p12
          KEYCHAIN_PATH=$RUNNER_TEMP/dev-signing.keychain-db

          echo -n "$DEVELOPER_ID_CERTIFICATE_BASE64" | base64 --decode -o $DEV_CERT_PATH

          if [ -n "$DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64" ]; then
            echo -n "$DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $DEV_INSTALLER_CERT_PATH
          fi

          security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          security import $DEV_CERT_PATH -P "$DEVELOPER_ID_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH

          if [ -f "$DEV_INSTALLER_CERT_PATH" ]; then
            security import $DEV_INSTALLER_CERT_PATH -P "$DEVELOPER_ID_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          fi

          security list-keychain -d user -s $KEYCHAIN_PATH

          security find-identity -v -p codesigning $KEYCHAIN_PATH

      - name: Build macOS DMG
        env:
          ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
        run: |
          if [ "${{ steps.check_certs.outputs.has_certs }}" != "true" ]; then
            npm run build
          fi
          export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
          npx electron-builder --mac dmg --universal --x64 --arm64 --publish never

      - name: Upload macOS MAS PKG
        if: steps.check_certs.outputs.has_certs == 'true' && hashFiles('release/termix_macos_universal_mas.pkg') != '' && (github.event.inputs.artifact_destination == 'file' || github.event.inputs.artifact_destination == 'release' || github.event.inputs.artifact_destination == 'submit')
        uses: actions/upload-artifact@v4
        with:
          name: termix_macos_universal_mas
          path: release/termix_macos_universal_mas.pkg
          retention-days: 30
          if-no-files-found: warn

      - name: Upload macOS Universal DMG
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_macos_universal_dmg
          path: release/termix_macos_universal_dmg.dmg
          retention-days: 30

      - name: Upload macOS x64 DMG
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_macos_x64_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_macos_x64_dmg
          path: release/termix_macos_x64_dmg.dmg
          retention-days: 30

      - name: Upload macOS arm64 DMG
        uses: actions/upload-artifact@v4
        if: hashFiles('release/termix_macos_arm64_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none'
        with:
          name: termix_macos_arm64_dmg
          path: release/termix_macos_arm64_dmg.dmg
          retention-days: 30

      - name: Get version for Homebrew
        id: homebrew-version
        run: |
          VERSION=$(node -p "require('./package.json').version")
          echo "version=$VERSION" >> $GITHUB_OUTPUT

      - name: Generate Homebrew Cask
        if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && (github.event.inputs.artifact_destination == 'file' || github.event.inputs.artifact_destination == 'release')
        run: |
          VERSION="${{ steps.homebrew-version.outputs.version }}"
          DMG_PATH="release/termix_macos_universal_dmg.dmg"

          CHECKSUM=$(shasum -a 256 "$DMG_PATH" | awk '{print $1}')

          mkdir -p homebrew-generated
          cp Casks/termix.rb homebrew-generated/termix.rb

          sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-generated/termix.rb
          sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-generated/termix.rb
          sed -i '' "s|version \".*\"|version \"$VERSION\"|g" homebrew-generated/termix.rb
          sed -i '' "s|sha256 \".*\"|sha256 \"$CHECKSUM\"|g" homebrew-generated/termix.rb
          sed -i '' "s|release-[0-9.]*-tag|release-$VERSION-tag|g" homebrew-generated/termix.rb

      - name: Upload Homebrew Cask as artifact
        uses: actions/upload-artifact@v4
        if: hashFiles('homebrew-generated/termix.rb') != '' && github.event.inputs.artifact_destination == 'file'
        with:
          name: termix_macos_homebrew_cask
          path: homebrew-generated/termix.rb
          retention-days: 30

      - name: Upload Homebrew Cask to release
        if: hashFiles('homebrew-generated/termix.rb') != '' && github.event.inputs.artifact_destination == 'release'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ steps.homebrew-version.outputs.version }}"
          RELEASE_TAG="release-$VERSION-tag"

          gh release list --repo ${{ github.repository }} --limit 100 | grep -q "$RELEASE_TAG" || {
            echo "Release $RELEASE_TAG not found"
            exit 1
          }

          gh release upload "$RELEASE_TAG" homebrew-generated/termix.rb --repo ${{ github.repository }} --clobber

      - name: Clean up keychains
        if: always()
        run: |
          security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
          security delete-keychain $RUNNER_TEMP/dev-signing.keychain-db || true

  submit-to-chocolatey:
    runs-on: windows-latest
    if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == '')
    permissions:
      contents: read

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 1

      - name: Get version from package.json
        id: package-version
        run: |
          $VERSION = (Get-Content package.json | ConvertFrom-Json).version
          echo "version=$VERSION" >> $env:GITHUB_OUTPUT

      - name: Download and prepare MSI info from public release
        id: msi-info
        run: |
          $VERSION = "${{ steps.package-version.outputs.version }}"
          $MSI_NAME = "termix_windows_x64_msi.msi"
          $DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$($VERSION)-tag/$($MSI_NAME)"

          Write-Host "Downloading from $DOWNLOAD_URL"
          New-Item -ItemType Directory -Force -Path "release_asset"
          $DOWNLOAD_PATH = "release_asset\$MSI_NAME"

          try {
            Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $DOWNLOAD_PATH -UseBasicParsing
          } catch {
            Write-Error "Failed to download MSI from $DOWNLOAD_URL. Please ensure the release and asset exist."
            exit 1
          }

          $CHECKSUM = (Get-FileHash -Path $DOWNLOAD_PATH -Algorithm SHA256).Hash
          echo "msi_name=$MSI_NAME" >> $env:GITHUB_OUTPUT
          echo "checksum=$CHECKSUM" >> $env:GITHUB_OUTPUT

      - name: Prepare Chocolatey package
        run: |
          $VERSION = "${{ steps.package-version.outputs.version }}"
          $CHECKSUM = "${{ steps.msi-info.outputs.checksum }}"
          $MSI_NAME = "${{ steps.msi-info.outputs.msi_name }}"

          $DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$MSI_NAME"

          New-Item -ItemType Directory -Force -Path "choco-build"
          Copy-Item -Path "chocolatey\*" -Destination "choco-build" -Recurse -Force

          $installScript = Get-Content "choco-build\tools\chocolateyinstall.ps1" -Raw -Encoding UTF8
          $installScript = $installScript -replace 'DOWNLOAD_URL_PLACEHOLDER', $DOWNLOAD_URL
          $installScript = $installScript -replace 'CHECKSUM_PLACEHOLDER', $CHECKSUM
          [System.IO.File]::WriteAllText("$PWD\choco-build\tools\chocolateyinstall.ps1", $installScript, [System.Text.UTF8Encoding]::new($false))

          $nuspec = Get-Content "choco-build\termix-ssh.nuspec" -Raw -Encoding UTF8
          $nuspec = $nuspec -replace 'VERSION_PLACEHOLDER', $VERSION
          [System.IO.File]::WriteAllText("$PWD\choco-build\termix-ssh.nuspec", $nuspec, [System.Text.UTF8Encoding]::new($false))

      - name: Install Chocolatey
        run: |
          Set-ExecutionPolicy Bypass -Scope Process -Force
          [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
          iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

      - name: Pack Chocolatey package
        run: |
          cd choco-build
          choco pack termix-ssh.nuspec

          if ($LASTEXITCODE -ne 0) {
            throw "Chocolatey push failed with exit code $LASTEXITCODE"
          }

      - name: Check for Chocolatey API Key
        id: check_choco_key
        run: |
          if ("${{ secrets.CHOCOLATEY_API_KEY }}" -ne "") {
            echo "has_key=true" >> $env:GITHUB_OUTPUT
          }

      - name: Push to Chocolatey
        if: steps.check_choco_key.outputs.has_key == 'true'
        run: |
          $VERSION = "${{ steps.package-version.outputs.version }}"
          cd choco-build
          choco apikey --key "${{ secrets.CHOCOLATEY_API_KEY }}" --source https://push.chocolatey.org/

          try {
            choco push "termix-ssh.$VERSION.nupkg" --source https://push.chocolatey.org/
            if ($LASTEXITCODE -eq 0) {
            } else {
              throw "Chocolatey push failed with exit code $LASTEXITCODE"
            }
          } catch {
          }

      - name: Upload Chocolatey package as artifact
        uses: actions/upload-artifact@v4
        with:
          name: chocolatey-package
          path: choco-build/*.nupkg
          retention-days: 30

  submit-to-flatpak:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == '')
    needs: []
    permissions:
      contents: read

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 1

      - name: Get version from package.json
        id: package-version
        run: |
          VERSION=$(node -p "require('./package.json').version")
          RELEASE_DATE=$(date +%Y-%m-%d)
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT

      - name: Download and prepare AppImage info from public release
        id: appimage-info
        run: |
          VERSION="${{ steps.package-version.outputs.version }}"
          mkdir -p release_assets

          APPIMAGE_X64_NAME="termix_linux_x64_appimage.AppImage"
          URL_X64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_X64_NAME"
          PATH_X64="release_assets/$APPIMAGE_X64_NAME"
          echo "Downloading x64 AppImage from $URL_X64"
          curl -L -o "$PATH_X64" "$URL_X64"
          chmod +x "$PATH_X64"
          CHECKSUM_X64=$(sha256sum "$PATH_X64" | awk '{print $1}')

          APPIMAGE_ARM64_NAME="termix_linux_arm64_appimage.AppImage"
          URL_ARM64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_ARM64_NAME"
          PATH_ARM64="release_assets/$APPIMAGE_ARM64_NAME"
          echo "Downloading arm64 AppImage from $URL_ARM64"
          curl -L -o "$PATH_ARM64" "$URL_ARM64"
          chmod +x "$PATH_ARM64"
          CHECKSUM_ARM64=$(sha256sum "$PATH_ARM64" | awk '{print $1}')

          echo "appimage_x64_name=$APPIMAGE_X64_NAME" >> $GITHUB_OUTPUT
          echo "checksum_x64=$CHECKSUM_X64" >> $GITHUB_OUTPUT
          echo "appimage_arm64_name=$APPIMAGE_ARM64_NAME" >> $GITHUB_OUTPUT
          echo "checksum_arm64=$CHECKSUM_ARM64" >> $GITHUB_OUTPUT

      - name: Install ImageMagick for icon generation
        run: |
          sudo apt-get update
          sudo apt-get install -y imagemagick

      - name: Prepare Flatpak submission files
        run: |
          VERSION="${{ steps.package-version.outputs.version }}"
          CHECKSUM_X64="${{ steps.appimage-info.outputs.checksum_x64 }}"
          CHECKSUM_ARM64="${{ steps.appimage-info.outputs.checksum_arm64 }}"
          RELEASE_DATE="${{ steps.package-version.outputs.release_date }}"
          APPIMAGE_X64_NAME="${{ steps.appimage-info.outputs.appimage_x64_name }}"
          APPIMAGE_ARM64_NAME="${{ steps.appimage-info.outputs.appimage_arm64_name }}"

          mkdir -p flatpak-submission

          cp flatpak/com.karmaa.termix.yml flatpak-submission/
          cp flatpak/com.karmaa.termix.desktop flatpak-submission/
          cp flatpak/com.karmaa.termix.metainfo.xml flatpak-submission/
          cp flatpak/flathub.json flatpak-submission/

          cp public/icon.svg flatpak-submission/com.karmaa.termix.svg
          convert public/icon.png -resize 256x256 flatpak-submission/icon-256.png
          convert public/icon.png -resize 128x128 flatpak-submission/icon-128.png

          sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" flatpak-submission/com.karmaa.termix.yml
          sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" flatpak-submission/com.karmaa.termix.yml
          sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" flatpak-submission/com.karmaa.termix.yml

          sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" flatpak-submission/com.karmaa.termix.metainfo.xml
          sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" flatpak-submission/com.karmaa.termix.metainfo.xml

      - name: Upload Flatpak submission as artifact
        uses: actions/upload-artifact@v4
        with:
          name: flatpak-submission
          path: flatpak-submission/*
          retention-days: 30

  submit-to-homebrew:
    runs-on: macos-latest
    if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'macos')
    needs: []
    permissions:
      contents: read

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 1

      - name: Get version from package.json
        id: package-version
        run: |
          VERSION=$(node -p "require('./package.json').version")
          echo "version=$VERSION" >> $GITHUB_OUTPUT

      - name: Download and prepare DMG info from public release
        id: dmg-info
        run: |
          VERSION="${{ steps.package-version.outputs.version }}"
          DMG_NAME="termix_macos_universal_dmg.dmg"
          URL="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$DMG_NAME"

          mkdir -p release_asset
          DOWNLOAD_PATH="release_asset/$DMG_NAME"
          echo "Downloading DMG from $URL"

          if command -v curl &> /dev/null; then
            curl -L -o "$DOWNLOAD_PATH" "$URL"
          elif command -v wget &> /dev/null; then
            wget -O "$DOWNLOAD_PATH" "$URL"
          else
            echo "Neither curl nor wget is available, installing curl"
            brew install curl
            curl -L -o "$DOWNLOAD_PATH" "$URL"
          fi

          CHECKSUM=$(shasum -a 256 "$DOWNLOAD_PATH" | awk '{print $1}')

          echo "dmg_name=$DMG_NAME" >> $GITHUB_OUTPUT
          echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT

      - name: Prepare Homebrew submission files
        run: |
          VERSION="${{ steps.package-version.outputs.version }}"
          CHECKSUM="${{ steps.dmg-info.outputs.checksum }}"
          DMG_NAME="${{ steps.dmg-info.outputs.dmg_name }}"

          mkdir -p homebrew-submission/Casks/t

          cp Casks/termix.rb homebrew-submission/Casks/t/termix.rb

          sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-submission/Casks/t/termix.rb
          sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-submission/Casks/t/termix.rb

      - name: Verify Cask syntax
        run: |
          ruby -c homebrew-submission/Casks/t/termix.rb

      - name: Upload Homebrew submission as artifact
        uses: actions/upload-artifact@v4
        with:
          name: homebrew-submission
          path: homebrew-submission/*
          retention-days: 30

  upload-to-release:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    if: github.event.inputs.artifact_destination == 'release'
    needs: [build-windows, build-linux, build-macos]
    permissions:
      contents: write

    steps:
      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts

      - name: Get latest release tag
        id: get_release
        run: |
          echo "RELEASE_TAG=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName -q '.[0].tagName')" >> $GITHUB_ENV
        env:
          GH_TOKEN: ${{ github.token }}

      - name: Upload artifacts to latest release
        run: |
          cd artifacts
          for dir in */; do
            cd "$dir"
            for file in *;
            do
              if [ -f "$file" ]; then
                gh release upload "$RELEASE_TAG" "$file" --repo ${{ github.repository }} --clobber
              fi
            done
            cd ..
          done
        env:
          GH_TOKEN: ${{ github.token }}

  submit-to-testflight:
    runs-on: macos-latest
    if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'macos')
    needs: []
    permissions:
      contents: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 1

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: |
          for i in 1 2 3;
          do
            if npm ci; then
              break
            else
              if [ $i -eq 3 ]; then
                exit 1
              fi
              sleep 10
            fi
          done
          npm install --force @rollup/rollup-darwin-arm64
          npm install dmg-license

      - name: Check for Code Signing Certificates
        id: check_certs
        run: |
          if [ -n "${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.MAC_P12_PASSWORD }}" ]; then
            echo "has_certs=true" >> $GITHUB_OUTPUT
          fi

      - name: Import Code Signing Certificates
        if: steps.check_certs.outputs.has_certs == 'true'
        env:
          MAC_BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}
          MAC_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.MAC_INSTALLER_CERTIFICATE_BASE64 }}
          MAC_P12_PASSWORD: ${{ secrets.MAC_P12_PASSWORD }}
          MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
        run: |
          APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12
          INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.p12
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db

          echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH

          if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then
            echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH
          fi

          security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH

          if [ -f "$INSTALLER_CERT_PATH" ]; then
            security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          fi

          security list-keychain -d user -s $KEYCHAIN_PATH

          security find-identity -v -p codesigning $KEYCHAIN_PATH

      - name: Build macOS App Store Package
        if: steps.check_certs.outputs.has_certs == 'true'
        env:
          ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          CURRENT_VERSION=$(node -p "require('./package.json').version")
          BUILD_VERSION="${{ github.run_number }}"

          npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION"

      - name: Check for App Store Connect API credentials
        id: check_asc_creds
        run: |
          if [ -n "${{ secrets.APPLE_KEY_ID }}" ] && [ -n "${{ secrets.APPLE_ISSUER_ID }}" ] && [ -n "${{ secrets.APPLE_KEY_CONTENT }}" ]; then
            echo "has_credentials=true" >> $GITHUB_OUTPUT
          fi

      - name: Setup Ruby for Fastlane
        if: steps.check_asc_creds.outputs.has_credentials == 'true'
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: "3.2"
          bundler-cache: false

      - name: Install Fastlane
        if: steps.check_asc_creds.outputs.has_credentials == 'true'
        run: |
          gem install fastlane -N

      - name: Deploy to App Store Connect (TestFlight)
        if: steps.check_asc_creds.outputs.has_credentials == 'true'
        run: |
          PKG_FILE=$(find release -name "termix_macos_universal_mas.pkg" -type f | head -n 1)
          if [ -z "$PKG_FILE" ]; then
            echo "PKG file not found, exiting."
            exit 1
          fi

          mkdir -p ~/private_keys
          echo "${{ secrets.APPLE_KEY_CONTENT }}" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_KEY_ID }}.p8

          xcrun altool --upload-app -f "$PKG_FILE" \
            --type macos \
            --apiKey "${{ secrets.APPLE_KEY_ID }}" \
            --apiIssuer "${{ secrets.APPLE_ISSUER_ID }}"
        continue-on-error: true

      - name: Clean up keychains
        if: always()
        run: |
          security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true


================================================
FILE: .github/workflows/openapi.yml
================================================
name: Generate OpenAPI Specification

on:
  workflow_dispatch:

jobs:
  generate-openapi:
    name: Generate OpenAPI JSON
    runs-on: blacksmith-2vcpu-ubuntu-2404

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Generate OpenAPI specification
        run: npm run generate:openapi

      - name: Upload OpenAPI artifact
        uses: actions/upload-artifact@v4
        with:
          name: openapi-spec
          path: openapi.json
          retention-days: 90


================================================
FILE: .github/workflows/pr-check.yml
================================================
name: PR Check

on:
  pull_request:
    branches: [main, dev-*]

jobs:
  lint-and-build:
    runs-on: blacksmith-2vcpu-ubuntu-2404

    env:
      NODE_OPTIONS: "--max-old-space-size=4096"

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install dependencies
        run: |
          rm -rf node_modules package-lock.json
          npm install

      - name: Run ESLint
        run: npx eslint .

      - name: Run Prettier check
        run: npx prettier --check .

      - name: Type check
        run: npx tsc --noEmit

      - name: Build
        run: npm run build


================================================
FILE: .gitignore
================================================
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/db/
/release/
/.claude/
/ssl/
.env
/.mcp.json
/nul
/.vscode/
/CLAUDE.md


================================================
FILE: .husky/commit-msg
================================================
npx --no -- commitlint --edit $1


================================================
FILE: .husky/pre-commit
================================================
npx lint-staged


================================================
FILE: .nvmrc
================================================
20


================================================
FILE: .prettierignore
================================================
build
coverage
dist
dist-ssr
release

node_modules
package-lock.json
pnpm-lock.yaml
yarn.lock

db

.env

*.min.js
*.min.css
openapi.json


================================================
FILE: .prettierrc
================================================
{
  "semi": true,
  "singleQuote": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80,
  "arrowParens": "always",
  "endOfLine": "lf"
}


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

- The use of sexualized language or imagery, and sexual attention or
  advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
  address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
mail@termix.site.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

## Prerequisites

- [Node.js](https://nodejs.org/en/download/) (built with v24)
- [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
- [Git](https://git-scm.com/downloads)

## Installation

1. Clone the repository:
   ```sh
   git clone https://github.com/Termix-SSH/Termix
   ```
2. Install the dependencies:
   ```sh
   npm install
   ```

## Running the development server

Run the following commands:

```sh
npm run dev
npm run dev:backend
```

This will start the backend and the frontend Vite server. You can access Termix by going to `http://localhost:5174/`.

## Contributing

1. **Fork the repository**: Click the "Fork" button at the top right of
   the [repository page](https://github.com/Termix-SSH/Termix).
2. **Create a new branch**:
   ```sh
   git checkout -b feature/my-new-feature
   ```
3. **Make your changes**: Implement your feature, fix, or improvement.
4. **Commit your changes**:
   ```sh
   git commit -m "Feature request my new feature"
   ```
5. **Push to your fork**:
   ```sh
   git push origin feature/my-feature-request
   ```
6. **Open a pull request**: Go to the original repository and create a PR with a clear description.

## Guidelines

- Follow the existing code style. Use Tailwind CSS with shadcn components.
- Use the below color scheme with the respective CSS variable placed in the `className` of a div/component.
- Place all API routes in the `main-axios.ts` file. Updating the `openapi.json` is unneeded.
- Include meaningful commit messages.
- Link related issues when applicable.
- `MobileApp.tsx` renders when the users screen width is less than 768px, otherwise it loads the usual `DesktopApp.tsx`.

## Color Scheme

### Background Colors

| CSS Variable                  | Color Value | Usage                       | Description                              |
| ----------------------------- | ----------- | --------------------------- | ---------------------------------------- |
| `--color-dark-bg`             | `#18181b`   | Main dark background        | Primary dark background color            |
| `--color-dark-bg-darker`      | `#0e0e10`   | Darker backgrounds          | Darker variant for panels and containers |
| `--color-dark-bg-darkest`     | `#09090b`   | Darkest backgrounds         | Darkest background (terminal)            |
| `--color-dark-bg-light`       | `#141416`   | Light dark backgrounds      | Lighter variant of dark background       |
| `--color-dark-bg-very-light`  | `#101014`   | Very light dark backgrounds | Very light variant of dark background    |
| `--color-dark-bg-panel`       | `#1b1b1e`   | Panel backgrounds           | Background for panels and cards          |
| `--color-dark-bg-panel-hover` | `#232327`   | Panel hover states          | Background for panels on hover           |

### Element-Specific Backgrounds

| CSS Variable             | Color Value | Usage              | Description                                   |
| ------------------------ | ----------- | ------------------ | --------------------------------------------- |
| `--color-dark-bg-input`  | `#222225`   | Input fields       | Background for input fields and form elements |
| `--color-dark-bg-button` | `#23232a`   | Button backgrounds | Background for buttons and clickable elements |
| `--color-dark-bg-active` | `#1d1d1f`   | Active states      | Background for active/selected elements       |
| `--color-dark-bg-header` | `#131316`   | Header backgrounds | Background for headers and navigation bars    |

### Border Colors

| CSS Variable                 | Color Value | Usage           | Description                              |
| ---------------------------- | ----------- | --------------- | ---------------------------------------- |
| `--color-dark-border`        | `#303032`   | Default borders | Standard border color                    |
| `--color-dark-border-active` | `#2d2d30`   | Active borders  | Border color for active elements         |
| `--color-dark-border-hover`  | `#434345`   | Hover borders   | Border color on hover states             |
| `--color-dark-border-light`  | `#5a5a5d`   | Light borders   | Lighter border color for subtle elements |
| `--color-dark-border-medium` | `#373739`   | Medium borders  | Medium weight border color               |
| `--color-dark-border-panel`  | `#222224`   | Panel borders   | Border color for panels and cards        |

### Interactive States

| CSS Variable             | Color Value | Usage             | Description                                   |
| ------------------------ | ----------- | ----------------- | --------------------------------------------- |
| `--color-dark-hover`     | `#2d2d30`   | Hover states      | Background color for hover effects            |
| `--color-dark-active`    | `#2a2a2c`   | Active states     | Background color for active elements          |
| `--color-dark-pressed`   | `#1a1a1c`   | Pressed states    | Background color for pressed/clicked elements |
| `--color-dark-hover-alt` | `#2a2a2d`   | Alternative hover | Alternative hover state color                 |

## Support

If you need help or want to request a feature with Termix, visit the [Issues](https://github.com/Termix-SSH/Support/issues) page, log in, and press `New Issue`.
Please be as detailed as possible in your issue, preferably written in English. You can also join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support
channel, however, response times may be longer.


================================================
FILE: Casks/termix.rb
================================================
cask "termix" do
  version "2.0.0"
  sha256 "a752ad4f05b4991b8a2ef986da80a86b79a70c93fe1045c4399fcf348a28c1d0"

  url "https://github.com/Termix-SSH/Termix/releases/download/release-#{version}-tag/termix_macos_universal_dmg.dmg"
  name "Termix"
  desc "Web-based server management platform with SSH terminal, tunneling, and file editing"
  homepage "https://github.com/Termix-SSH/Termix"

  livecheck do
    url :url
    strategy :github_latest
  end

  app "Termix.app"

  zap trash: [
    "~/Library/Application Support/termix",
    "~/Library/Caches/com.karmaa.termix",
    "~/Library/Caches/com.karmaa.termix.ShipIt",
    "~/Library/Preferences/com.karmaa.termix.plist",
    "~/Library/Saved Application State/com.karmaa.termix.savedState",
  ]
end


================================================
FILE: LICENSE
================================================
Copyright 2025 Luke Gustafson

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

================================================
FILE: README.md
================================================
# Repo Stats

<p align="center">
  <img src="https://flagcdn.com/us.svg" alt="English" width="24" height="16"> English ·
  <a href="readme/README-CN.md"><img src="https://flagcdn.com/cn.svg" alt="中文" width="24" height="16"> 中文</a> ·
  <a href="readme/README-JA.md"><img src="https://flagcdn.com/jp.svg" alt="日本語" width="24" height="16"> 日本語</a> ·
  <a href="readme/README-KO.md"><img src="https://flagcdn.com/kr.svg" alt="한국어" width="24" height="16"> 한국어</a> ·
  <a href="readme/README-FR.md"><img src="https://flagcdn.com/fr.svg" alt="Français" width="24" height="16"> Français</a> ·
  <a href="readme/README-DE.md"><img src="https://flagcdn.com/de.svg" alt="Deutsch" width="24" height="16"> Deutsch</a> ·
  <a href="readme/README-ES.md"><img src="https://flagcdn.com/es.svg" alt="Español" width="24" height="16"> Español</a> ·
  <a href="readme/README-PT.md"><img src="https://flagcdn.com/br.svg" alt="Português" width="24" height="16"> Português</a> ·
  <a href="readme/README-RU.md"><img src="https://flagcdn.com/ru.svg" alt="Русский" width="24" height="16"> Русский</a> ·
  <a href="readme/README-AR.md"><img src="https://flagcdn.com/sa.svg" alt="العربية" width="24" height="16"> العربية</a> ·
  <a href="readme/README-HI.md"><img src="https://flagcdn.com/in.svg" alt="हिन्दी" width="24" height="16"> हिन्दी</a> ·
  <a href="readme/README-TR.md"><img src="https://flagcdn.com/tr.svg" alt="Türkçe" width="24" height="16"> Türkçe</a> ·
  <a href="readme/README-VI.md"><img src="https://flagcdn.com/vn.svg" alt="Tiếng Việt" width="24" height="16"> Tiếng Việt</a> ·
  <a href="readme/README-IT.md"><img src="https://flagcdn.com/it.svg" alt="Italiano" width="24" height="16"> Italiano</a>
</p>

![GitHub Repo stars](https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars)
![GitHub forks](https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks)
![GitHub Release](https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release)
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>

<p align="center">
  <img src="./repo-images/RepoOfTheDay.png" alt="Repo of the Day Achievement" style="width: 300px; height: auto;">
  <br>
  <small style="color: #666;">Achieved on September 1st, 2025</small>
</p>

<br />
<p align="center">
  <a href="https://github.com/Termix-SSH/Termix">
    <img alt="Termix Banner" src=./repo-images/HeaderImage.png style="width: auto; height: auto;">  </a>
</p>

If you would like, you can support the project here!\
[![GitHub Sponsor](https://img.shields.io/badge/Sponsor-LukeGus-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/sponsors/LukeGus)

# Overview

<p align="center">
  <a href="https://github.com/Termix-SSH/Termix">
    <img alt="Termix Banner" src=./public/icon.svg style="width: 250px; height: 250px;">  </a>
</p>

Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides a multi-platform
solution for managing your servers and infrastructure through a single, intuitive interface. Termix offers SSH terminal
access, remote desktop control (RDP, VNC, Telnet), SSH tunneling capabilities, remote SSH file management, and many other tools. Termix is the perfect
free and self-hosted alternative to Termius available for all platforms.

# Features

- **SSH Terminal Access** - Full-featured terminal with split-screen support (up to 4 panels) with a browser-like tab system. Includes support for customizing the terminal including common terminal themes, fonts, and other components.
- **Remote Desktop Access** - RDP, VNC, and Telnet support over the browser with complete customization and split screening
- **SSH Tunnel Management** - Create and manage SSH tunnels with automatic reconnection and health monitoring and support for -l or -r connections
- **Remote File Manager** - Manage files directly on remote servers with support for viewing and editing code, images, audio, and video. Upload, download, rename, delete, and move files seamlessly with sudo support.
- **Docker Management** - Start, stop, pause, remove containers. View container stats. Control container using docker exec terminal. It was not made to replace Portainer or Dockge but rather to simply manage your containers compared to creating them.
- **SSH Host Manager** - Save, organize, and manage your SSH connections with tags and folders, and easily save reusable login info while being able to automate the deployment of SSH keys
- **Server Stats** - View CPU, memory, and disk usage along with network, uptime, system information, firewall, port monitor, on most Linux based servers
- **Dashboard** - View server information at a glance on your dashboard
- **RBAC** - Create roles and share hosts across users/roles
- **User Authentication** - Secure user management with admin controls and OIDC (with access control) and 2FA (TOTP) support. View active user sessions across all platforms and revoke permissions. Link your OIDC/Local accounts together.
- **Database Encryption** - Backend stored as encrypted SQLite database files. View [docs](https://docs.termix.site/security) for more.
- **Data Export/Import** - Export and import SSH hosts, credentials, and file manager data
- **Automatic SSL Setup** - Built-in SSL certificate generation and management with HTTPS redirects
- **Modern UI** - Clean desktop/mobile-friendly interface built with React, Tailwind CSS, and Shadcn. Choose between dark or light mode based UI. Use URL routes to open any connection in full-screen.
- **Languages** - Built-in support ~30 languages (managed by [Crowdin](https://docs.termix.site/translations))
- **Platform Support** - Available as a web app, desktop application (Windows, Linux, and macOS, can be run standalone without Termix backend), PWA, and dedicated mobile/tablet app for iOS and Android.
- **SSH Tools** - Create reusable command snippets that execute with a single click. Run one command simultaneously across multiple open terminals.
- **Command History** - Auto-complete and view previously ran SSH commands
- **Quick Connect** - Connect to a server without having to save the connection data
- **Command Palette** - Double tap left shift to quickly access SSH connections with your keyboard
- **SSH Feature Rich** - Supports jump hosts, Warpgate, TOTP based connections, SOCKS5, host key verification, password autofill, [OPKSSH](https://github.com/openpubkey/opkssh), etc.
- **Network Graph** - Customize your Dashboard to visualize your homelab based off your SSH connections with status support
- **Persistent Tabs** - SSH sessions and tabs stay open across devices/refreshes if enabled in user profile

# Planned Features

See [Projects](https://github.com/orgs/Termix-SSH/projects/2) for all planned features. If you are looking to contribute, see [Contributing](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).

# Installation

Supported Devices:

- Website (any modern browser on any platform like Chrome, Safari, and Firefox) (includes PWA support)
- Windows (x64/ia32)
  - Portable
  - MSI Installer
  - Chocolatey Package Manager
- Linux (x64/ia32)
  - Portable
  - AUR
  - AppImage
  - Deb
  - Flatpak
- macOS (x64/ia32 on v12.0+)
  - Apple App Store
  - DMG
  - Homebrew
- iOS/iPadOS (v15.1+)
  - Apple App Store
  - IPA
- Android (v7.0+)
  - Google Play Store
  - APK

Visit the Termix [Docs](https://docs.termix.site/install) for more information on how to install Termix on all platforms. Otherwise, view
a sample Docker Compose file here (you can omit guacd and the network if you don't plan on using remote desktop features):

```yaml
services:
  termix:
    image: ghcr.io/lukegus/termix:latest
    container_name: termix
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - termix-data:/app/data
    environment:
      PORT: "8080"
    depends_on:
      - guacd
    networks:
      - termix-net

  guacd:
    image: guacamole/guacd:latest
    container_name: guacd
    restart: unless-stopped
    ports:
      - "4822:4822"
    networks:
      - termix-net

volumes:
  termix-data:
    driver: local

networks:
  termix-net:
    driver: bridge
```

# Sponsors

<p align="left">
  <a href="https://www.digitalocean.com/">
    <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="50" alt="DigitalOcean">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://crowdin.com/">
    <img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="50" alt="Crowdin">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://www.blacksmith.sh/">
    <img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="50" alt="Crowdin">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://www.cloudflare.com/">
    <img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="50" alt="Crowdin">
  </a>
</p>

# Support

If you need help or want to request a feature with Termix, visit the [Issues](https://github.com/Termix-SSH/Support/issues) page, log in, and press `New Issue`.
Please be as detailed as possible in your issue, preferably written in English. You can also join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support
channel, however, response times may be longer.

# Screenshots

[![YouTube](./repo-images/YouTube.jpg)](https://www.youtube.com/@TermixSSH/videos)

<p align="center">
  <img src="./repo-images/Image 1.png" width="400" alt="Termix Demo 1"/>
  <img src="./repo-images/Image 2.png" width="400" alt="Termix Demo 2"/>
</p>

<p align="center">
  <img src="./repo-images/Image 3.png" width="400" alt="Termix Demo 3"/>
  <img src="./repo-images/Image 4.png" width="400" alt="Termix Demo 4"/>
</p>

<p align="center">
  <img src="./repo-images/Image 5.png" width="400" alt="Termix Demo 5"/>
  <img src="./repo-images/Image 6.png" width="400" alt="Termix Demo 6"/>
</p>

<p align="center">
  <img src="./repo-images/Image 7.png" width="400" alt="Termix Demo 7"/>
  <img src="./repo-images/Image 8.png" width="400" alt="Termix Demo 8"/>
</p>

<p align="center">
  <img src="./repo-images/Image 9.png" width="400" alt="Termix Demo 9"/>
  <img src="./repo-images/Image 10.png" width="400" alt="Termix Demo 10"/>
</p>

<p align="center">
  <img src="./repo-images/Image 11.png" width="400" alt="Termix Demo 11"/>
  <img src="./repo-images/Image 12.png" width="400" alt="Termix Demo 12"/>
</p>

Some videos and images may be out of date or may not perfectly showcase features.

# License

Distributed under the Apache License Version 2.0. See LICENSE for more information.


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Reporting a Vulnerability

Please report any vulnerabilities to [GitHub Security](https://github.com/Termix-SSH/Termix/security/advisories).


================================================
FILE: build/entitlements.mac.inherit.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
	<true/>
	<key>com.apple.security.cs.disable-library-validation</key>
	<true/>
	<key>com.apple.security.cs.allow-dyld-environment-variables</key>
	<true/>
</dict>
</plist>


================================================
FILE: build/entitlements.mac.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
	<true/>
	<key>com.apple.security.cs.disable-library-validation</key>
	<true/>
	<key>com.apple.security.cs.allow-dyld-environment-variables</key>
	<true/>
</dict>
</plist>


================================================
FILE: build/entitlements.mas.inherit.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.inherit</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
	<true/>
	<key>com.apple.security.cs.disable-library-validation</key>
	<true/>
</dict>
</plist>


================================================
FILE: build/entitlements.mas.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.network.client</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
	<key>com.apple.security.files.user-selected.read-write</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
	<true/>
	<key>com.apple.security.cs.disable-library-validation</key>
	<true/>
</dict>
</plist>


================================================
FILE: build/notarize.cjs
================================================
const { notarize } = require('@electron/notarize');

exports.default = async function notarizing(context) {
  const { electronPlatformName, appOutDir } = context;

  if (electronPlatformName !== 'darwin') {
    return;
  }

  const appleId = process.env.APPLE_ID;
  const appleIdPassword = process.env.APPLE_ID_PASSWORD;
  const teamId = process.env.APPLE_TEAM_ID;

  if (!appleId || !appleIdPassword || !teamId) {
    return;
  }

  const appName = context.packager.appInfo.productFilename;

  try {
    await notarize({
      appBundleId: 'com.karmaa.termix',
      appPath: `${appOutDir}/${appName}.app`,
      appleId: appleId,
      appleIdPassword: appleIdPassword,
      teamId: teamId,
    });
  } catch (error) {
    console.error('Notarization failed:', error);
  }
};


================================================
FILE: chocolatey/termix-ssh.nuspec
================================================
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
  <metadata>
    <id>termix-ssh</id>
    <version>VERSION_PLACEHOLDER</version>
    <packageSourceUrl>https://github.com/Termix-SSH/Termix</packageSourceUrl>
    <owners>bugattiguy527</owners>
    <title>Termix SSH</title>
    <authors>bugattiguy527</authors>
    <projectUrl>https://github.com/Termix-SSH/Termix</projectUrl>
    <iconUrl>https://raw.githubusercontent.com/Termix-SSH/Termix/main/public/icon.png</iconUrl>
    <licenseUrl>https://raw.githubusercontent.com/Termix-SSH/Termix/refs/heads/main/LICENSE</licenseUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <projectSourceUrl>https://github.com/Termix-SSH/Termix</projectSourceUrl>
    <docsUrl>https://docs.termix.site/install</docsUrl>
    <bugTrackerUrl>https://github.com/Termix-SSH/Support/issues</bugTrackerUrl>
    <tags>docker ssh self-hosted file-management ssh-tunnel termix server-management terminal</tags>
    <summary>Termix is a web-based server management platform with SSH terminal, tunneling, and file editing capabilities.</summary>
    <description>
Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides a web-based solution for managing your servers and infrastructure through a single, intuitive interface.

Termix offers:
- SSH terminal access
- SSH tunneling capabilities
- Remote file management
- Server monitoring and management

This package installs the desktop application version of Termix.
    </description>
    <releaseNotes>https://github.com/Termix-SSH/Termix/releases</releaseNotes>
  </metadata>
  <files>
    <file src="tools\**" target="tools" />
  </files>
</package>


================================================
FILE: chocolatey/tools/chocolateyinstall.ps1
================================================
$ErrorActionPreference = 'Stop'

$packageName = 'termix-ssh'
$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$url64 = 'DOWNLOAD_URL_PLACEHOLDER'
$checksum64 = 'CHECKSUM_PLACEHOLDER'
$checksumType64 = 'sha256'

$packageArgs = @{
  packageName    = $packageName
  fileType       = 'msi'
  url64bit       = $url64
  softwareName   = 'Termix*'
  checksum64     = $checksum64
  checksumType64 = $checksumType64
  silentArgs     = "/qn /norestart /l*v `"$($env:TEMP)\$($packageName).$($env:chocolateyPackageVersion).MsiInstall.log`""
  validExitCodes = @(0, 3010, 1641)
}

Install-ChocolateyPackage @packageArgs


================================================
FILE: chocolatey/tools/chocolateyuninstall.ps1
================================================
$ErrorActionPreference = 'Stop'

$packageName = 'termix-ssh'
$softwareName = 'Termix*'
$installerType = 'msi'

$silentArgs = '/qn /norestart'
$validExitCodes = @(0, 3010, 1605, 1614, 1641)

[array]$key = Get-UninstallRegistryKey -SoftwareName $softwareName

if ($key.Count -eq 1) {
  $key | % {
    $file = "$($_.UninstallString)"

    if ($installerType -eq 'msi') {
      $silentArgs = "$($_.PSChildName) $silentArgs"
      $file = ''
    }

    Uninstall-ChocolateyPackage -PackageName $packageName `
                                 -FileType $installerType `
                                 -SilentArgs "$silentArgs" `
                                 -ValidExitCodes $validExitCodes `
                                 -File "$file"
  }
} elseif ($key.Count -eq 0) {
  Write-Warning "$packageName has already been uninstalled by other means."
} elseif ($key.Count -gt 1) {
  Write-Warning "$($key.Count) matches found!"
  Write-Warning "To prevent accidental data loss, no programs will be uninstalled."
  $key | % {Write-Warning "- $($_.DisplayName)"}
}


================================================
FILE: components.json
================================================
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/index.css",
    "baseColor": "zinc",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}


================================================
FILE: crowdin.yml
================================================
files:
  - source: /src/locales/en.json
    translation: /src/locales/translated/%locale_with_underscore%.json


================================================
FILE: docker/Dockerfile
================================================
# Stage 1: Install dependencies
FROM node:22-slim AS deps
WORKDIR /app

RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*

COPY package*.json ./

RUN npm ci --ignore-scripts --force && \
    npm cache clean --force

# Stage 2: Build frontend
FROM deps AS frontend-builder
WORKDIR /app

COPY . .

RUN find public/fonts -name "*.ttf" ! -name "*Regular.ttf" ! -name "*Bold.ttf" ! -name "*Italic.ttf" -delete

RUN npm cache clean --force && \
    NODE_OPTIONS="--max-old-space-size=3072" npm run build

# Stage 3: Build backend
FROM deps AS backend-builder
WORKDIR /app

COPY . .

RUN npm rebuild better-sqlite3 --force

RUN npm run build:backend

# Stage 4: Production dependencies only
FROM node:22-slim AS production-deps
WORKDIR /app

RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*

COPY package*.json ./

RUN npm ci --only=production --ignore-scripts --force && \
    npm rebuild better-sqlite3 bcryptjs --force && \
    npm cache clean --force

# Stage 5: Final optimized image
FROM node:22-slim
WORKDIR /app

ENV DATA_DIR=/app/data \
    PORT=8080 \
    NODE_ENV=production

RUN apt-get update && apt-get install -y nginx gettext-base openssl ca-certificates gosu wget && \
    update-ca-certificates && \
    rm -rf /var/lib/apt/lists/* && \
    mkdir -p /app/data /app/uploads /app/data/.opk /app/nginx /app/nginx/logs /app/nginx/cache /app/nginx/client_body && \
    chown -R node:node /app && \
    chmod 755 /app/data /app/uploads /app/data/.opk /app/nginx && \
    touch /app/nginx/nginx.conf && \
    chown node:node /app/nginx/nginx.conf

COPY docker/nginx.conf /app/nginx/nginx.conf.template
COPY docker/nginx-https.conf /app/nginx/nginx-https.conf.template

COPY --chown=node:node --from=frontend-builder /app/dist /app/html
COPY --chown=node:node --from=frontend-builder /app/src/locales /app/html/locales
COPY --chown=node:node --from=frontend-builder /app/public/fonts /app/html/fonts

COPY --chown=node:node --from=production-deps /app/node_modules /app/node_modules
COPY --chown=node:node --from=backend-builder /app/dist/backend ./dist/backend
COPY --chown=node:node package.json ./

VOLUME ["/app/data"]

EXPOSE ${PORT} 30001 30002 30003 30004 30005 30006

HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD wget -q -O /dev/null http://localhost:30001/health || exit 1

COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

CMD ["/entrypoint.sh"]


================================================
FILE: docker/docker-compose.yml
================================================
services:
  termix:
    image: ghcr.io/lukegus/termix:latest
    container_name: termix
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - termix-data:/app/data
    environment:
      PORT: "8080"
    depends_on:
      - guacd
    networks:
      - termix-net

  guacd:
    image: guacamole/guacd:latest
    container_name: guacd
    restart: unless-stopped
    ports:
      - "4822:4822"
    networks:
      - termix-net

volumes:
  termix-data:
    driver: local

networks:
  termix-net:
    driver: bridge


================================================
FILE: docker/entrypoint.sh
================================================
#!/bin/sh
set -e

PUID=${PUID:-1000}
PGID=${PGID:-1000}

if [ "$(id -u)" = "0" ]; then
    if [ "$PUID" = "0" ]; then
        echo "Running as root (PUID=0, PGID=$PGID)"
        chown -R root:root /app/data /app/uploads /app/nginx 2>/dev/null || true
    else
        echo "Setting up user permissions (PUID: $PUID, PGID: $PGID)..."

        groupmod -o -g "$PGID" node 2>/dev/null || true
        usermod -o -u "$PUID" node 2>/dev/null || true

        chown -R node:node /app/data /app/uploads /app/nginx 2>/dev/null || true

        echo "User node is now UID: $PUID, GID: $PGID"

        exec gosu node:node "$0" "$@"
    fi
fi

export PORT=${PORT:-8080}
export ENABLE_SSL=${ENABLE_SSL:-false}
export SSL_PORT=${SSL_PORT:-8443}
export SSL_CERT_PATH=${SSL_CERT_PATH:-/app/data/ssl/termix.crt}
export SSL_KEY_PATH=${SSL_KEY_PATH:-/app/data/ssl/termix.key}

echo "Configuring web UI to run on port: $PORT"

if [ "$ENABLE_SSL" = "true" ]; then
    echo "SSL enabled - using HTTPS configuration with redirect"
    NGINX_CONF_SOURCE="/app/nginx/nginx-https.conf.template"
else
    echo "SSL disabled - using HTTP-only configuration (default)"
    NGINX_CONF_SOURCE="/app/nginx/nginx.conf.template"
fi

envsubst '${PORT} ${SSL_PORT} ${SSL_CERT_PATH} ${SSL_KEY_PATH}' < $NGINX_CONF_SOURCE > /app/nginx/nginx.conf

mkdir -p /app/data /app/uploads /app/data/.opk
chmod 755 /app/data /app/uploads /app/data/.opk 2>/dev/null || true

if [ -w /app/data ]; then
    echo "Data directory is writable"
else
    echo "WARNING: Data directory is not writable. OPKSSH may fail."
    ls -ld /app/data
fi

if [ -w /app/data/.opk ]; then
    echo "OPKSSH directory is writable"
else
    echo "WARNING: OPKSSH directory is not writable. OPKSSH authentication will fail."
    ls -ld /app/data/.opk
fi

OPKSSH_DIR="${DATA_DIR:-/app/data}/opkssh"
if [ ! -d "$OPKSSH_DIR" ]; then
    echo "WARNING: OPKSSH binary directory not found at $OPKSSH_DIR"
    echo "OPKSSH will be downloaded automatically on first use."
else
    echo "OPKSSH binary directory found at $OPKSSH_DIR"
fi

if [ "$ENABLE_SSL" = "true" ]; then
    echo "Checking SSL certificate configuration..."
    mkdir -p /app/data/ssl
    chmod 755 /app/data/ssl 2>/dev/null || true

    DOMAIN=${SSL_DOMAIN:-localhost}
    
    if [ -f "/app/data/ssl/termix.crt" ] && [ -f "/app/data/ssl/termix.key" ]; then
        echo "SSL certificates found, checking validity..."
        
        if openssl x509 -in /app/data/ssl/termix.crt -checkend 2592000 -noout >/dev/null 2>&1; then
            echo "SSL certificates are valid and will be reused for domain: $DOMAIN"
        else
            echo "SSL certificate is expired or expiring soon, regenerating..."
            rm -f /app/data/ssl/termix.crt /app/data/ssl/termix.key
        fi
    else
        echo "SSL certificates not found, will generate new ones..."
    fi
    
    if [ ! -f "/app/data/ssl/termix.crt" ] || [ ! -f "/app/data/ssl/termix.key" ]; then
        echo "Generating SSL certificates for domain: $DOMAIN"

        cat > /app/data/ssl/openssl.conf << EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req

[dn]
C=US
ST=State
L=City
O=Termix
OU=IT Department
CN=$DOMAIN

[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = $DOMAIN
DNS.2 = localhost
DNS.3 = 127.0.0.1
IP.1 = 127.0.0.1
IP.2 = ::1
IP.3 = 0.0.0.0
EOF

        openssl genrsa -out /app/data/ssl/termix.key 2048

        openssl req -new -x509 -key /app/data/ssl/termix.key -out /app/data/ssl/termix.crt -days 365 -config /app/data/ssl/openssl.conf -extensions v3_req

        chmod 600 /app/data/ssl/termix.key
        chmod 644 /app/data/ssl/termix.crt

        rm -f /app/data/ssl/openssl.conf
        
        echo "SSL certificates generated successfully for domain: $DOMAIN"
    fi
fi

echo "Starting nginx..."
nginx -c /app/nginx/nginx.conf

echo "Starting backend services..."
cd /app
export NODE_ENV=production

if [ -f "package.json" ]; then
    VERSION=$(grep '"version"' package.json | sed 's/.*"version": *"\([^"]*\)".*/\1/')
    if [ -n "$VERSION" ]; then
        export VERSION
    else
        echo "Warning: Could not extract version from package.json"
    fi
else
    echo "Warning: package.json not found"
fi

node dist/backend/backend/starter.js

echo "All services started"

tail -f /dev/null

================================================
FILE: docker/nginx-https.conf
================================================
worker_processes 1;
master_process off;
pid /app/nginx/nginx.pid;
error_log /app/nginx/logs/error.log warn;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /app/nginx/logs/access.log;

    client_body_temp_path /app/nginx/client_body;
    proxy_temp_path /app/nginx/proxy_temp;
    fastcgi_temp_path /app/nginx/fastcgi_temp;
    uwsgi_temp_path /app/nginx/uwsgi_temp;
    scgi_temp_path /app/nginx/scgi_temp;

    sendfile on;
    keepalive_timeout 65;
    client_header_timeout 300s;

    set_real_ip_from 127.0.0.1;
    real_ip_header X-Forwarded-For;

    map $http_x_forwarded_proto $proxy_x_forwarded_proto {
        default $http_x_forwarded_proto;
        ''      $scheme;
    }

    map $http_x_forwarded_host $proxy_x_forwarded_host {
        default $http_x_forwarded_host;
        ''      $http_host;
    }

    map $http_x_forwarded_port $proxy_x_forwarded_port {
        default $http_x_forwarded_port;
        ''      '';
    }

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    server {
        listen ${PORT};
        server_name _;

        return 301 https://$host:${SSL_PORT}$request_uri;
    }

    server {
        listen ${SSL_PORT} ssl;
        server_name _;

        ssl_certificate ${SSL_CERT_PATH};
        ssl_certificate_key ${SSL_KEY_PATH};

        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Content-Type-Options nosniff always;
        add_header X-XSS-Protection "1; mode=block" always;

        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            root /app/html;
            expires 1y;
            add_header Cache-Control "public, immutable";
            try_files $uri =404;
        }

        location / {
            root /app/html;
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }

        location ~* \.map$ {
            return 404;
            access_log off;
            log_not_found off;
        }

        location ~ ^/users/sessions(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
        }

        location ~ ^/users(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
            proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
            proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
        }

        location ~ ^/version(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/releases(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/alerts(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/rbac(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/credentials(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;
        }

        location ~ ^/snippets(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/terminal(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/database(/.*)?$ {
            client_max_body_size 5G;
            client_body_timeout 300s;

            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;

            proxy_request_buffering off;
            proxy_buffering off;
        }

        location ~ ^/db(/.*)?$ {
            client_max_body_size 5G;
            client_body_timeout 300s;

            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;

            proxy_request_buffering off;
            proxy_buffering off;
        }

        location ~ ^/encryption(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/quick-connect {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $http_host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/host/opkssh-chooser(/.*)?$ {
            proxy_pass http://127.0.0.1:30001/host/opkssh-chooser$1$is_args$args;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
            proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;

            proxy_cache_bypass 1;
            proxy_no_cache 1;
            add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
        }

        location ~ ^/host/opkssh-callback(/.*)?$ {
            proxy_pass http://127.0.0.1:30001/host/opkssh-callback$1$is_args$args;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
            proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;

            proxy_cache_bypass 1;
            proxy_no_cache 1;
            add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
        }

        location /host/ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /ssh/websocket/ {
            proxy_pass http://127.0.0.1:30002/;
            proxy_http_version 1.1;

            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
            proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
            proxy_cache_bypass $http_upgrade;

            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_read_timeout 86400s;
            proxy_send_timeout 86400s;
            proxy_connect_timeout 10s;

            proxy_buffering off;
            proxy_request_buffering off;

            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        }

        location ^~ /guacamole/websocket/ {
            proxy_pass http://127.0.0.1:30008/;
            proxy_http_version 1.1;

            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_cache_bypass $http_upgrade;

            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;
            proxy_set_header X-Forwarded-Host $http_host;

            proxy_read_timeout 86400s;
            proxy_send_timeout 86400s;
            proxy_connect_timeout 10s;

            proxy_buffering off;
            proxy_request_buffering off;

            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        }

        location ~ ^/guacamole(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/tunnel/ {
            proxy_pass http://127.0.0.1:30003;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/file_manager/recent {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/file_manager/pinned {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/file_manager/shortcuts {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/file_manager/sudo-password {
            proxy_pass http://127.0.0.1:30004;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /ssh/file_manager/ {
            client_max_body_size 5G;
            client_body_timeout 300s;

            add_header Cache-Control "no-store, no-cache, must-revalidate" always;

            proxy_pass http://127.0.0.1:30004;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;

            proxy_request_buffering off;
            proxy_buffering off;
        }

        location /host/file_manager/ssh/ {
            client_max_body_size 5G;
            client_body_timeout 300s;

            add_header Cache-Control "no-store, no-cache, must-revalidate" always;

            proxy_pass http://127.0.0.1:30004;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;

            proxy_request_buffering off;
            proxy_buffering off;
        }

        location ~ ^/network-topology(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /health {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/status(/.*)?$ {
            proxy_pass http://127.0.0.1:30005;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/metrics(/.*)?$ {
            proxy_pass http://127.0.0.1:30005;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }

        location ~ ^/global-settings(/.*)?$ {
            proxy_pass http://127.0.0.1:30005;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/uptime(/.*)?$ {
            proxy_pass http://127.0.0.1:30006;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/activity(/.*)?$ {
            proxy_pass http://127.0.0.1:30006;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/dashboard/preferences(/.*)?$ {
            proxy_pass http://127.0.0.1:30006;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ^~ /docker/console/ {
            proxy_pass http://127.0.0.1:30009/;
            proxy_http_version 1.1;

            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_cache_bypass $http_upgrade;

            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;
            proxy_set_header X-Forwarded-Host $http_host;

            proxy_read_timeout 86400s;
            proxy_send_timeout 86400s;
            proxy_connect_timeout 10s;

            proxy_buffering off;
            proxy_request_buffering off;

            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        }

        location ~ ^/docker(/.*)?$ {
            proxy_pass http://127.0.0.1:30007;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root /app/html;
        }
    }
}


================================================
FILE: docker/nginx.conf
================================================
worker_processes 1;
master_process off;
pid /app/nginx/nginx.pid;
error_log /app/nginx/logs/error.log warn;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /app/nginx/logs/access.log;

    client_body_temp_path /app/nginx/client_body;
    proxy_temp_path /app/nginx/proxy_temp;
    fastcgi_temp_path /app/nginx/fastcgi_temp;
    uwsgi_temp_path /app/nginx/uwsgi_temp;
    scgi_temp_path /app/nginx/scgi_temp;

    sendfile on;
    keepalive_timeout 65;
    client_header_timeout 300s;

    set_real_ip_from 127.0.0.1;
    real_ip_header X-Forwarded-For;

    map $http_x_forwarded_proto $proxy_x_forwarded_proto {
        default $http_x_forwarded_proto;
        ''      $scheme;
    }

    map $http_x_forwarded_host $proxy_x_forwarded_host {
        default $http_x_forwarded_host;
        ''      $http_host;
    }

    map $http_x_forwarded_port $proxy_x_forwarded_port {
        default $http_x_forwarded_port;
        ''      '';
    }

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    server {
        listen ${PORT};
        server_name localhost;

        add_header X-Content-Type-Options nosniff always;
        add_header X-XSS-Protection "1; mode=block" always;

        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            root /app/html;
            expires 1y;
            add_header Cache-Control "public, immutable";
            try_files $uri =404;
        }

        location / {
            root /app/html;
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }

        location ~* \.map$ {
            return 404;
            access_log off;
            log_not_found off;
        }

        location ~ ^/users/sessions(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
        }

        location ~ ^/users(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
            proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
            proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
        }

        location ~ ^/version(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/releases(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/alerts(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/rbac(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/credentials(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;
        }

        location ~ ^/snippets(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/terminal(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/database(/.*)?$ {
            client_max_body_size 5G;
            client_body_timeout 300s;

            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;

            proxy_request_buffering off;
            proxy_buffering off;
        }

        location ~ ^/db(/.*)?$ {
            client_max_body_size 5G;
            client_body_timeout 300s;

            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;

            proxy_request_buffering off;
            proxy_buffering off;
        }

        location ~ ^/encryption(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/quick-connect {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $http_host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/host/opkssh-chooser(/.*)?$ {
            proxy_pass http://127.0.0.1:30001/host/opkssh-chooser$1$is_args$args;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
            proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;

            proxy_cache_bypass 1;
            proxy_no_cache 1;
            add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
        }

        location ~ ^/host/opkssh-callback(/.*)?$ {
            proxy_pass http://127.0.0.1:30001/host/opkssh-callback$1$is_args$args;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
            proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;

            proxy_cache_bypass 1;
            proxy_no_cache 1;
            add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
        }

        location /host/ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /ssh/websocket/ {
            proxy_pass http://127.0.0.1:30002/;
            proxy_http_version 1.1;

            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
            proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
            proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
            proxy_cache_bypass $http_upgrade;

            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_read_timeout 86400s;
            proxy_send_timeout 86400s;
            proxy_connect_timeout 10s;

            proxy_buffering off;
            proxy_request_buffering off;

            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        }

        location ^~ /guacamole/websocket/ {
            proxy_pass http://127.0.0.1:30008/;
            proxy_http_version 1.1;

            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_cache_bypass $http_upgrade;

            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;
            proxy_set_header X-Forwarded-Host $http_host;

            proxy_read_timeout 86400s;
            proxy_send_timeout 86400s;
            proxy_connect_timeout 10s;

            proxy_buffering off;
            proxy_request_buffering off;

            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        }

        location ~ ^/guacamole(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/tunnel/ {
            proxy_pass http://127.0.0.1:30003;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/file_manager/recent {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/file_manager/pinned {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/file_manager/shortcuts {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /host/file_manager/sudo-password {
            proxy_pass http://127.0.0.1:30004;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /ssh/file_manager/ {
            client_max_body_size 5G;
            client_body_timeout 300s;

            add_header Cache-Control "no-store, no-cache, must-revalidate" always;

            proxy_pass http://127.0.0.1:30004;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;

            proxy_request_buffering off;
            proxy_buffering off;
        }

        location /host/file_manager/ssh/ {
            client_max_body_size 5G;
            client_body_timeout 300s;

            add_header Cache-Control "no-store, no-cache, must-revalidate" always;

            proxy_pass http://127.0.0.1:30004;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;

            proxy_request_buffering off;
            proxy_buffering off;
        }

        location ~ ^/network-topology(/.*)?$ {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /health {
            proxy_pass http://127.0.0.1:30001;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/status(/.*)?$ {
            proxy_pass http://127.0.0.1:30005;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/metrics(/.*)?$ {
            proxy_pass http://127.0.0.1:30005;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }

        location ~ ^/global-settings(/.*)?$ {
            proxy_pass http://127.0.0.1:30005;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/uptime(/.*)?$ {
            proxy_pass http://127.0.0.1:30006;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/activity(/.*)?$ {
            proxy_pass http://127.0.0.1:30006;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ~ ^/dashboard/preferences(/.*)?$ {
            proxy_pass http://127.0.0.1:30006;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location ^~ /docker/console/ {
            proxy_pass http://127.0.0.1:30009/;
            proxy_http_version 1.1;

            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_cache_bypass $http_upgrade;

            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;
            proxy_set_header X-Forwarded-Host $http_host;

            proxy_read_timeout 86400s;
            proxy_send_timeout 86400s;
            proxy_connect_timeout 10s;

            proxy_buffering off;
            proxy_request_buffering off;

            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        }

        location ~ ^/docker(/.*)?$ {
            proxy_pass http://127.0.0.1:30007;
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root /app/html;
        }
    }
}


================================================
FILE: electron/main.cjs
================================================
const {
  app,
  BrowserWindow,
  shell,
  ipcMain,
  dialog,
  Menu,
  Tray,
} = require("electron");
const path = require("path");
const fs = require("fs");
const os = require("os");
const https = require("https");
const http = require("http");
const { URL } = require("url");
const { fork } = require("child_process");

const logFile = path.join(app.getPath("userData"), "termix-main.log");
function logToFile(...args) {
  const timestamp = new Date().toISOString();
  const msg = args
    .map((a) => (typeof a === "object" ? JSON.stringify(a) : String(a)))
    .join(" ");
  const line = `[${timestamp}] ${msg}\n`;
  try {
    fs.appendFileSync(logFile, line);
  } catch {
    // ignore
  }
  console.log(...args);
}

function httpFetch(url, options = {}) {
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);
    const isHttps = urlObj.protocol === "https:";
    const client = isHttps ? https : http;

    const requestOptions = {
      method: options.method || "GET",
      headers: options.headers || {},
      timeout: options.timeout || 10000,
    };

    if (isHttps) {
      requestOptions.rejectUnauthorized = false;
      requestOptions.agent = new https.Agent({
        rejectUnauthorized: false,
        checkServerIdentity: () => undefined,
      });
    }

    const req = client.request(url, requestOptions, (res) => {
      let data = "";
      res.on("data", (chunk) => (data += chunk));
      res.on("end", () => {
        resolve({
          ok: res.statusCode >= 200 && res.statusCode < 300,
          status: res.statusCode,
          text: () => Promise.resolve(data),
          json: () => Promise.resolve(JSON.parse(data)),
        });
      });
    });

    req.on("error", reject);
    req.on("timeout", () => {
      req.destroy();
      reject(new Error("Request timeout"));
    });

    if (options.body) {
      req.write(options.body);
    }
    req.end();
  });
}

if (process.platform === "linux") {
  app.commandLine.appendSwitch("--ozone-platform-hint=auto");

  app.commandLine.appendSwitch("--enable-features=VaapiVideoDecoder");
}

app.commandLine.appendSwitch("--ignore-certificate-errors");
app.commandLine.appendSwitch("--ignore-ssl-errors");
app.commandLine.appendSwitch("--ignore-certificate-errors-spki-list");
app.commandLine.appendSwitch("--enable-features=NetworkService");

let mainWindow = null;
let backendProcess = null;
let tray = null;
let isQuitting = false;

const isDev = process.env.NODE_ENV === "development" || !app.isPackaged;
const appRoot = isDev ? process.cwd() : path.join(__dirname, "..");

function getBackendEntryPath() {
  if (isDev) {
    return path.join(appRoot, "dist", "backend", "backend", "starter.js");
  }
  return path.join(appRoot, "dist", "backend", "backend", "starter.js");
}

function getBackendDataDir() {
  const userDataPath = app.getPath("userData");
  const dataDir = path.join(userDataPath, "server-data");
  if (!fs.existsSync(dataDir)) {
    fs.mkdirSync(dataDir, { recursive: true });
  }
  return dataDir;
}

function startBackendServer() {
  return new Promise((resolve) => {
    const entryPath = getBackendEntryPath();

    logToFile("isDev:", isDev, "appRoot:", appRoot);
    logToFile("app.isPackaged:", app.isPackaged);
    logToFile("process.env.NODE_ENV:", process.env.NODE_ENV);

    if (!fs.existsSync(entryPath)) {
      logToFile("Backend entry not found:", entryPath);
      resolve(false);
      return;
    }

    const dataDir = getBackendDataDir();
    logToFile("Starting embedded backend server...");
    logToFile("Backend entry:", entryPath);
    logToFile("Data directory:", dataDir);
    logToFile("Backend cwd:", appRoot);

    logToFile("Checking paths...");
    logToFile("  entryPath exists:", fs.existsSync(entryPath));
    logToFile("  dataDir exists:", fs.existsSync(dataDir));
    logToFile("  appRoot exists:", fs.existsSync(appRoot));

    const distPath = path.join(appRoot, "dist");
    if (fs.existsSync(distPath)) {
      logToFile("  dist directory contents:", fs.readdirSync(distPath));
      const backendPath = path.join(distPath, "backend");
      if (fs.existsSync(backendPath)) {
        logToFile("  dist/backend contents:", fs.readdirSync(backendPath));
      }
    }

    backendProcess = fork(entryPath, [], {
      cwd: appRoot,
      env: {
        ...process.env,
        DATA_DIR: dataDir,
        NODE_ENV: "production",
        ELECTRON_EMBEDDED: "true",
        PORT: "30001",
      },
      stdio: ["pipe", "pipe", "pipe", "ipc"],
    });

    logToFile("Backend process spawned, pid:", backendProcess.pid);

    let resolved = false;
    const readyTimeout = setTimeout(() => {
      if (!resolved) {
        resolved = true;
        logToFile("Backend ready timeout (15s), proceeding anyway...");
        resolve(true);
      }
    }, 15000);

    backendProcess.stdout.on("data", (data) => {
      const msg = data.toString().trim();
      logToFile("[backend]", msg);
      if (!resolved && msg.includes("started successfully")) {
        resolved = true;
        clearTimeout(readyTimeout);
        logToFile("Backend ready signal received");
        resolve(true);
      }
    });

    backendProcess.stderr.on("data", (data) => {
      logToFile("[backend:stderr]", data.toString().trim());
    });

    backendProcess.on("exit", (code, signal) => {
      logToFile(`Backend process exited with code ${code}, signal ${signal}`);
      backendProcess = null;
      if (!resolved) {
        resolved = true;
        clearTimeout(readyTimeout);
        resolve(false);
      }
    });

    backendProcess.on("error", (err) => {
      logToFile("Failed to start backend process:", err.message);
      backendProcess = null;
      if (!resolved) {
        resolved = true;
        clearTimeout(readyTimeout);
        resolve(false);
      }
    });
  });
}

function stopBackendServer() {
  if (!backendProcess) return;

  console.log("Stopping embedded backend server...");

  try {
    backendProcess.send({ type: "shutdown" });
  } catch {
    // IPC channel may already be closed
  }

  const forceKillTimeout = setTimeout(() => {
    if (backendProcess) {
      console.log("Force killing backend process...");
      backendProcess.kill("SIGKILL");
      backendProcess = null;
    }
  }, 5000);

  backendProcess.on("exit", () => {
    clearTimeout(forceKillTimeout);
    backendProcess = null;
  });
}

const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
  console.log("Another instance is already running, quitting...");
  app.quit();
  process.exit(0);
} else {
  app.on("second-instance", (event, commandLine, workingDirectory) => {
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore();
      mainWindow.focus();
      mainWindow.show();
    }
  });
}

function createTray() {
  try {
    const { nativeImage } = require("electron");

    let trayIcon;
    if (process.platform === "darwin") {
      const iconPath = path.join(appRoot, "public", "icons", "16x16.png");
      trayIcon = nativeImage.createFromPath(iconPath);
      trayIcon.setTemplateImage(true);
    } else if (process.platform === "win32") {
      trayIcon = path.join(appRoot, "public", "icon.ico");
    } else {
      trayIcon = path.join(appRoot, "public", "icons", "32x32.png");
    }

    tray = new Tray(trayIcon);
    tray.setToolTip("Termix");

    const contextMenu = Menu.buildFromTemplate([
      {
        label: "Show Window",
        click: () => {
          if (mainWindow) {
            mainWindow.show();
            mainWindow.focus();
          }
        },
      },
      {
        label: "Quit",
        click: () => {
          isQuitting = true;
          app.quit();
        },
      },
    ]);

    tray.setContextMenu(contextMenu);

    tray.on("click", () => {
      if (mainWindow) {
        if (mainWindow.isVisible()) {
          mainWindow.hide();
        } else {
          mainWindow.show();
          mainWindow.focus();
        }
      }
    });

    console.log("System tray created successfully");
  } catch (err) {
    console.error("Failed to create system tray:", err);
  }
}

function createWindow() {
  const appVersion = app.getVersion();
  const electronVersion = process.versions.electron;
  const platform =
    process.platform === "win32"
      ? "Windows"
      : process.platform === "darwin"
        ? "macOS"
        : "Linux";

  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    title: "Termix",
    icon: path.join(appRoot, "public", "icon.png"),
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      webSecurity: false,
      preload: path.join(__dirname, "preload.js"),
      partition: "persist:termix",
      allowRunningInsecureContent: true,
      webviewTag: true,
      offscreen: false,
    },
    show: true,
  });

  mainWindow.webContents.session.setPermissionRequestHandler(
    (webContents, permission, callback) => {
      if (
        permission === "clipboard-read" ||
        permission === "clipboard-sanitized-write"
      ) {
        callback(true);
        return;
      }
      callback(true);
    },
  );

  if (process.platform !== "darwin") {
    mainWindow.setMenuBarVisibility(false);
  }

  const customUserAgent = `Termix-Desktop/${appVersion} (${platform}; Electron/${electronVersion})`;
  mainWindow.webContents.setUserAgent(customUserAgent);

  mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
    (details, callback) => {
      details.requestHeaders["X-Electron-App"] = "true";

      details.requestHeaders["User-Agent"] = customUserAgent;

      callback({ requestHeaders: details.requestHeaders });
    },
  );

  if (isDev) {
    mainWindow.loadURL("http://localhost:5173");
    mainWindow.webContents.openDevTools();
  } else {
    const indexPath = path.join(appRoot, "dist", "index.html");
    mainWindow.loadFile(indexPath).catch((err) => {
      console.error("Failed to load file:", err);
    });
  }

  mainWindow.webContents.session.webRequest.onHeadersReceived(
    (details, callback) => {
      const headers = details.responseHeaders;

      if (headers) {
        delete headers["x-frame-options"];
        delete headers["X-Frame-Options"];

        if (headers["content-security-policy"]) {
          headers["content-security-policy"] = headers[
            "content-security-policy"
          ]
            .map((value) => value.replace(/frame-ancestors[^;]*/gi, ""))
            .filter((value) => value.trim().length > 0);

          if (headers["content-security-policy"].length === 0) {
            delete headers["content-security-policy"];
          }
        }
        if (headers["Content-Security-Policy"]) {
          headers["Content-Security-Policy"] = headers[
            "Content-Security-Policy"
          ]
            .map((value) => value.replace(/frame-ancestors[^;]*/gi, ""))
            .filter((value) => value.trim().length > 0);

          if (headers["Content-Security-Policy"].length === 0) {
            delete headers["Content-Security-Policy"];
          }
        }

        if (headers["set-cookie"]) {
          headers["set-cookie"] = headers["set-cookie"].map((cookie) => {
            let modified = cookie.replace(
              /;\s*SameSite=Strict/gi,
              "; SameSite=None",
            );
            modified = modified.replace(
              /;\s*SameSite=Lax/gi,
              "; SameSite=None",
            );
            if (!modified.includes("SameSite=")) {
              modified += "; SameSite=None";
            }
            if (
              !modified.includes("Secure") &&
              details.url.startsWith("https")
            ) {
              modified += "; Secure";
            }
            return modified;
          });
        }
      }

      callback({ responseHeaders: headers });
    },
  );

  mainWindow.once("ready-to-show", () => {
    mainWindow.show();
  });

  setTimeout(() => {
    if (mainWindow && !mainWindow.isVisible()) {
      mainWindow.show();
    }
  }, 3000);

  mainWindow.webContents.on(
    "did-fail-load",
    (event, errorCode, errorDescription, validatedURL) => {
      console.error(
        "Failed to load:",
        errorCode,
        errorDescription,
        validatedURL,
      );
    },
  );

  mainWindow.webContents.on("did-finish-load", () => {
    console.log("Frontend loaded successfully");
  });

  mainWindow.on("close", (event) => {
    if (!isQuitting && tray && !tray.isDestroyed()) {
      event.preventDefault();
      mainWindow.hide();
    }
  });

  mainWindow.on("closed", () => {
    mainWindow = null;
  });

  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
    shell.openExternal(url);
    return { action: "deny" };
  });
}

ipcMain.handle("get-app-version", () => {
  return app.getVersion();
});

const GITHUB_API_BASE = "https://api.github.com";
const REPO_OWNER = "Termix-SSH";
const REPO_NAME = "Termix";

const githubCache = new Map();
const CACHE_DURATION = 30 * 60 * 1000;

async function fetchGitHubAPI(endpoint, cacheKey) {
  const cached = githubCache.get(cacheKey);
  if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
    return {
      data: cached.data,
      cached: true,
      cache_age: Date.now() - cached.timestamp,
    };
  }

  try {
    const response = await httpFetch(`${GITHUB_API_BASE}${endpoint}`, {
      headers: {
        Accept: "application/vnd.github+json",
        "User-Agent": "TermixElectronUpdateChecker/1.0",
        "X-GitHub-Api-Version": "2022-11-28",
      },
      timeout: 10000,
    });

    if (!response.ok) {
      throw new Error(
        `GitHub API error: ${response.status} ${response.statusText}`,
      );
    }

    const data = await response.json();

    githubCache.set(cacheKey, {
      data,
      timestamp: Date.now(),
    });

    return {
      data: data,
      cached: false,
    };
  } catch (error) {
    console.error("Failed to fetch from GitHub API:", error);
    throw error;
  }
}

ipcMain.handle("check-electron-update", async () => {
  try {
    const localVersion = app.getVersion();

    const releaseData = await fetchGitHubAPI(
      `/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`,
      "latest_release_electron",
    );

    const rawTag = releaseData.data.tag_name || releaseData.data.name || "";
    const remoteVersionMatch = rawTag.match(/(\d+\.\d+(\.\d+)?)/);
    const remoteVersion = remoteVersionMatch ? remoteVersionMatch[1] : null;

    if (!remoteVersion) {
      return {
        success: false,
        error: "Remote version not found",
        localVersion,
      };
    }

    const isUpToDate = localVersion === remoteVersion;

    const result = {
      success: true,
      status: isUpToDate ? "up_to_date" : "requires_update",
      localVersion: localVersion,
      remoteVersion: remoteVersion,
      latest_release: {
        tag_name: releaseData.data.tag_name,
        name: releaseData.data.name,
        published_at: releaseData.data.published_at,
        html_url: releaseData.data.html_url,
        body: releaseData.data.body,
      },
      cached: releaseData.cached,
      cache_age: releaseData.cache_age,
    };

    return result;
  } catch (error) {
    return {
      success: false,
      error: error.message,
      localVersion: app.getVersion(),
    };
  }
});

ipcMain.handle("get-platform", () => {
  return process.platform;
});

ipcMain.handle("get-embedded-server-status", () => {
  return {
    running: backendProcess !== null && !backendProcess.killed,
    embedded: !isDev,
    dataDir: isDev ? null : getBackendDataDir(),
  };
});

ipcMain.handle("get-server-config", () => {
  try {
    const userDataPath = app.getPath("userData");
    const configPath = path.join(userDataPath, "server-config.json");

    if (fs.existsSync(configPath)) {
      const configData = fs.readFileSync(configPath, "utf8");
      return JSON.parse(configData);
    }
    return null;
  } catch (error) {
    console.error("Error reading server config:", error);
    return null;
  }
});

ipcMain.handle("save-server-config", (event, config) => {
  try {
    const userDataPath = app.getPath("userData");
    const configPath = path.join(userDataPath, "server-config.json");

    if (!fs.existsSync(userDataPath)) {
      fs.mkdirSync(userDataPath, { recursive: true });
    }

    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
    return { success: true };
  } catch (error) {
    console.error("Error saving server config:", error);
    return { success: false, error: error.message };
  }
});

ipcMain.handle("get-setting", (event, key) => {
  try {
    const userDataPath = app.getPath("userData");
    const settingsPath = path.join(userDataPath, "settings.json");

    if (!fs.existsSync(settingsPath)) {
      return null;
    }

    const settingsData = fs.readFileSync(settingsPath, "utf8");
    const settings = JSON.parse(settingsData);
    return settings[key] !== undefined ? settings[key] : null;
  } catch (error) {
    console.error("Error reading setting:", error);
    return null;
  }
});

ipcMain.handle("set-setting", (event, key, value) => {
  try {
    const userDataPath = app.getPath("userData");
    const settingsPath = path.join(userDataPath, "settings.json");

    if (!fs.existsSync(userDataPath)) {
      fs.mkdirSync(userDataPath, { recursive: true });
    }

    let settings = {};
    if (fs.existsSync(settingsPath)) {
      const settingsData = fs.readFileSync(settingsPath, "utf8");
      settings = JSON.parse(settingsData);
    }

    settings[key] = value;
    fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
    return { success: true };
  } catch (error) {
    console.error("Error saving setting:", error);
    return { success: false, error: error.message };
  }
});

ipcMain.handle("clear-session-cookies", async () => {
  try {
    const ses = mainWindow?.webContents?.session;
    if (ses) {
      const cookies = await ses.cookies.get({});
      for (const cookie of cookies) {
        const scheme = cookie.secure ? "https" : "http";
        const domain = cookie.domain?.startsWith(".")
          ? cookie.domain.slice(1)
          : cookie.domain || "localhost";
        const url = `${scheme}://${domain}${cookie.path || "/"}`;
        await ses.cookies.remove(url, cookie.name);
      }
    }
  } catch (error) {
    console.error("Failed to clear session cookies:", error);
  }
});

ipcMain.handle("test-server-connection", async (event, serverUrl) => {
  try {
    const normalizedServerUrl = serverUrl.replace(/\/$/, "");

    const healthUrl = `${normalizedServerUrl}/health`;

    try {
      const response = await httpFetch(healthUrl, {
        method: "GET",
        timeout: 10000,
      });

      if (response.ok) {
        const data = await response.text();

        if (
          data.includes("<html") ||
          data.includes("<!DOCTYPE") ||
          data.includes("<head>") ||
          data.includes("<body>")
        ) {
          return {
            success: false,
            error:
              "Server returned HTML instead of JSON. This does not appear to be a Termix server.",
          };
        }

        try {
          const healthData = JSON.parse(data);
          if (
            healthData &&
            (healthData.status === "ok" ||
              healthData.status === "healthy" ||
              healthData.healthy === true ||
              healthData.database === "connected")
          ) {
            return {
              success: true,
              status: response.status,
              testedUrl: healthUrl,
            };
          }
        } catch (parseError) {
          console.log("Health endpoint did not return valid JSON");
        }
      }
    } catch (urlError) {
      console.error("Health check failed:", urlError);
    }

    try {
      const versionUrl = `${normalizedServerUrl}/version`;
      const response = await httpFetch(versionUrl, {
        method: "GET",
        timeout: 10000,
      });

      if (response.ok) {
        const data = await response.text();

        if (
          data.includes("<html") ||
          data.includes("<!DOCTYPE") ||
          data.includes("<head>") ||
          data.includes("<body>")
        ) {
          return {
            success: false,
            error:
              "Server returned HTML instead of JSON. This does not appear to be a Termix server.",
          };
        }

        try {
          const versionData = JSON.parse(data);
          if (
            versionData &&
            (versionData.status === "up_to_date" ||
              versionData.status === "requires_update" ||
              (versionData.localVersion &&
                versionData.version &&
                versionData.latest_release))
          ) {
            return {
              success: true,
              status: response.status,
              testedUrl: versionUrl,
              warning:
                "Health endpoint not available, but server appears to be running",
            };
          }
        } catch (parseError) {
          console.log("Version endpoint did not return valid JSON");
        }
      }
    } catch (versionError) {
      console.error("Version check failed:", versionError);
    }

    return {
      success: false,
      error:
        "Server is not responding or does not appear to be a valid Termix server. Please ensure the server is running and accessible.",
    };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

function createMenu() {
  if (process.platform === "darwin") {
    const template = [
      {
        label: app.name,
        submenu: [
          { role: "about" },
          { type: "separator" },
          { role: "services" },
          { type: "separator" },
          { role: "hide" },
          { role: "hideOthers" },
          { role: "unhide" },
          { type: "separator" },
          { role: "quit" },
        ],
      },
      {
        label: "Edit",
        submenu: [
          { role: "undo" },
          { role: "redo" },
          { type: "separator" },
          { role: "cut" },
          { role: "copy" },
          { role: "paste" },
          { role: "selectAll" },
        ],
      },
      {
        label: "View",
        submenu: [
          { role: "reload" },
          { role: "forceReload" },
          { role: "toggleDevTools" },
          { type: "separator" },
          { role: "resetZoom" },
          { role: "zoomIn" },
          { role: "zoomOut" },
          { type: "separator" },
          { role: "togglefullscreen" },
        ],
      },
      {
        label: "Window",
        submenu: [
          { role: "minimize" },
          { role: "zoom" },
          { type: "separator" },
          { role: "front" },
          { type: "separator" },
          { role: "window" },
        ],
      },
    ];
    const menu = Menu.buildFromTemplate(template);
    Menu.setApplicationMenu(menu);
  }
}

app.whenReady().then(async () => {
  logToFile("=== App ready ===");
  logToFile(
    "isDev:",
    isDev,
    "platform:",
    process.platform,
    "arch:",
    process.arch,
  );
  createMenu();

  if (!isDev) {
    const result = await startBackendServer();
    logToFile("startBackendServer result:", result);
  } else {
    logToFile(
      "Skipping embedded backend (isDev=true) - expecting separate dev:backend process",
    );
  }

  createTray();
  createWindow();
  logToFile("=== Startup complete ===");
});

app.on("window-all-closed", () => {
  if (!tray || tray.isDestroyed()) {
    app.quit();
  }
});

app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

app.on("before-quit", () => {
  isQuitting = true;
});

app.on("will-quit", () => {
  console.log("App will quit...");
  stopBackendServer();
});

process.on("uncaughtException", (error) => {
  console.error("Uncaught Exception:", error);
});

process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled Rejection at:", promise, "reason:", reason);
});


================================================
FILE: electron/preload.js
================================================
const { contextBridge, ipcRenderer } = require("electron");
const { clipboard } = require("electron");

contextBridge.exposeInMainWorld("electronAPI", {
  getAppVersion: () => ipcRenderer.invoke("get-app-version"),

  removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel),
  isElectron: true,
  isDev: process.env.NODE_ENV === "development",

  getSetting: (key) => ipcRenderer.invoke("get-setting", key),
  setSetting: (key, value) => ipcRenderer.invoke("set-setting", key, value),

  clearSessionCookies: () => ipcRenderer.invoke("clear-session-cookies"),

  invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
});

contextBridge.exposeInMainWorld("electronClipboard", {
  writeText: (text) => clipboard.writeText(text),
  readText: () => clipboard.readText(),
});

window.IS_ELECTRON = true;


================================================
FILE: electron-builder.json
================================================
{
  "appId": "com.karmaa.termix",
  "productName": "Termix",
  "publish": null,
  "directories": {
    "output": "release"
  },
  "asar": false,
  "files": [
    "dist/**/*",
    "electron/**/*",
    "public/**/*",
    "!src/**/*",
    "!*.md",
    "!tsconfig*.json",
    "!vite.config.ts",
    "!eslint.config.js",
    "!node_modules/@napi-rs/canvas*/**/*",
    "!node_modules/@rollup/rollup-darwin-*/**/*",
    "!node_modules/@rollup/rollup-linux-*/**/*",
    "!node_modules/@rollup/rollup-win32-*/**/*",
    "!dist/icon-mac.png",
    "!public/icon-mac.png",
    "!dist/icon.ico",
    "!public/icon.ico",
    "!dist/icon.icns",
    "!public/icon.icns",
    "!dist/icons/**/*",
    "!public/icons/**/*"
  ],
  "extraMetadata": {
    "main": "electron/main.cjs"
  },
  "buildDependenciesFromSource": false,
  "nodeGypRebuild": false,
  "npmRebuild": true,
  "win": {
    "target": [
      {
        "target": "nsis",
        "arch": ["x64", "ia32"]
      },
      {
        "target": "msi",
        "arch": ["x64", "ia32"]
      }
    ],
    "icon": "public/icon.ico",
    "executableName": "Termix"
  },
  "nsis": {
    "oneClick": false,
    "allowToChangeInstallationDirectory": true,
    "artifactName": "termix_windows_${arch}_nsis.${ext}",
    "createDesktopShortcut": true,
    "createStartMenuShortcut": true,
    "shortcutName": "Termix",
    "uninstallDisplayName": "Termix"
  },
  "msi": {
    "artifactName": "termix_windows_${arch}_msi.${ext}"
  },
  "linux": {
    "artifactName": "termix_linux_${arch}_portable.${ext}",
    "target": [
      {
        "target": "AppImage",
        "arch": ["x64", "arm64", "armv7l"]
      },
      {
        "target": "deb",
        "arch": ["x64", "arm64", "armv7l"]
      },
      {
        "target": "tar.gz",
        "arch": ["x64", "arm64", "armv7l"]
      }
    ],
    "icon": "public/icon.png",
    "category": "Development",
    "executableName": "termix",
    "maintainer": "Termix <mail@termix.site>",
    "desktop": {
      "entry": {
        "Name": "Termix",
        "Comment": "A web-based server management platform",
        "Keywords": "terminal;ssh;server;management;",
        "StartupWMClass": "termix"
      }
    }
  },
  "appImage": {
    "artifactName": "termix_linux_${arch}_appimage.${ext}"
  },
  "deb": {
    "artifactName": "termix_linux_${arch}_deb.${ext}"
  },

  "mac": {
    "target": [
      {
        "target": "mas",
        "arch": "universal"
      },
      {
        "target": "dmg",
        "arch": ["universal", "x64", "arm64"]
      }
    ],
    "icon": "public/icon.icns",
    "category": "public.app-category.developer-tools",
    "hardenedRuntime": true,
    "gatekeeperAssess": false,
    "entitlements": "build/entitlements.mac.plist",
    "entitlementsInherit": "build/entitlements.mac.inherit.plist",
    "type": "distribution",
    "minimumSystemVersion": "10.15",
    "mergeASARs": false,
    "singleArchFiles": "**/*.node",
    "x64ArchFiles": "**/*.node"
  },
  "dmg": {
    "artifactName": "termix_macos_${arch}_dmg.${ext}",
    "sign": true
  },
  "afterSign": "build/notarize.cjs",
  "mas": {
    "provisioningProfile": "build/Termix_Mac_App_Store.provisionprofile",
    "entitlements": "build/entitlements.mas.plist",
    "entitlementsInherit": "build/entitlements.mas.inherit.plist",
    "hardenedRuntime": false,
    "gatekeeperAssess": false,
    "asar": false,
    "type": "distribution",
    "category": "public.app-category.developer-tools",
    "artifactName": "termix_macos_${arch}_mas.${ext}",
    "extendInfo": {
      "ITSAppUsesNonExemptEncryption": false,
      "NSAppleEventsUsageDescription": "Termix needs access to control other applications for terminal operations."
    }
  },
  "generateUpdatesFilesForAllChannels": true
}


================================================
FILE: eslint.config.js
================================================
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
import { globalIgnores } from "eslint/config";

export default tseslint.config([
  globalIgnores(["dist", "release", "Mobile"]),
  {
    files: ["**/*.{ts,tsx}"],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
      reactHooks.configs["recommended-latest"],
      reactRefresh.configs.vite,
    ],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    rules: {
      "@typescript-eslint/no-unused-vars": "warn",
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-unused-expressions": "warn",
      "no-empty": "warn",
      "no-control-regex": "off",
      "react-refresh/only-export-components": "warn",
    },
  },
]);


================================================
FILE: flatpak/com.karmaa.termix.desktop
================================================
[Desktop Entry]
Name=Termix
Comment=Web-based server management platform with SSH terminal, tunneling, and file editing
Exec=run.sh %U
Icon=com.karmaa.termix
Terminal=false
Type=Application
Categories=Development;Network;System;
Keywords=ssh;terminal;server;management;tunnel;
StartupWMClass=termix
StartupNotify=true


================================================
FILE: flatpak/com.karmaa.termix.flatpakref
================================================
[Flatpak Ref]
Name=Termix
Branch=stable
Title=Termix - SSH Server Management Platform
IsRuntime=false
Url=https://github.com/Termix-SSH/Termix/releases/download/VERSION_PLACEHOLDER/termix_linux_flatpak.flatpak
RuntimeRepo=https://flathub.org/repo/flathub.flatpakrepo
Comment=Web-based server management platform with SSH terminal, tunneling, and file editing
Description=Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides SSH terminal access, tunneling capabilities, and remote file management.
Icon=https://raw.githubusercontent.com/Termix-SSH/Termix/main/public/icon.png
Homepage=https://github.com/Termix-SSH/Termix


================================================
FILE: flatpak/com.karmaa.termix.metainfo.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
  <id>com.karmaa.termix</id>
  <name>Termix</name>
  <summary>Web-based server management platform with SSH terminal, tunneling, and file editing</summary>

  <metadata_license>CC0-1.0</metadata_license>
  <project_license>Apache-2.0</project_license>

  <developer_name>bugattiguy527</developer_name>

  <description>
    <p>
      Termix is an open-source, forever-free, self-hosted all-in-one server management platform.
      It provides a web-based solution for managing your servers and infrastructure through a single, intuitive interface.
    </p>
    <p>Features:</p>
    <ul>
      <li>SSH terminal access with full terminal emulation</li>
      <li>SSH tunneling capabilities for secure port forwarding</li>
      <li>Remote file management with editor support</li>
      <li>Server monitoring and management tools</li>
      <li>Self-hosted solution - keep your data private</li>
      <li>Modern, intuitive web interface</li>
    </ul>
  </description>

  <launchable type="desktop-id">com.karmaa.termix.desktop</launchable>

  <screenshots>
    <screenshot type="default">
      <image>https://raw.githubusercontent.com/Termix-SSH/Termix/main/public/screenshots/terminal.png</image>
      <caption>SSH Terminal Interface</caption>
    </screenshot>
  </screenshots>

  <url type="homepage">https://github.com/Termix-SSH/Termix</url>
  <url type="bugtracker">https://github.com/Termix-SSH/Support/issues</url>
  <url type="help">https://docs.termix.site</url>
  <url type="vcs-browser">https://github.com/Termix-SSH/Termix</url>

  <content_rating type="oars-1.1">
    <content_attribute id="social-info">moderate</content_attribute>
  </content_rating>

  <releases>
    <release version="VERSION_PLACEHOLDER" date="DATE_PLACEHOLDER">
      <description>
        <p>Latest release of Termix</p>
      </description>
      <url>https://github.com/Termix-SSH/Termix/releases</url>
    </release>
  </releases>

  <categories>
    <category>Development</category>
    <category>Network</category>
    <category>System</category>
  </categories>

  <keywords>
    <keyword>ssh</keyword>
    <keyword>terminal</keyword>
    <keyword>server</keyword>
    <keyword>management</keyword>
    <keyword>tunnel</keyword>
    <keyword>file-manager</keyword>
  </keywords>

  <provides>
    <binary>termix</binary>
  </provides>

  <requires>
    <internet>always</internet>
  </requires>
</component>


================================================
FILE: flatpak/com.karmaa.termix.yml
================================================
app-id: com.karmaa.termix
runtime: org.freedesktop.Platform
runtime-version: "24.08"
sdk: org.freedesktop.Sdk
base: org.electronjs.Electron2.BaseApp
base-version: "24.08"
command: run.sh
separate-locales: false

finish-args:
  - --socket=x11
  - --socket=wayland
  - --socket=pulseaudio
  - --share=network
  - --share=ipc
  - --device=dri
  - --filesystem=home
  - --socket=ssh-auth
  - --socket=session-bus
  - --talk-name=org.freedesktop.secrets
  - --env=ELECTRON_TRASH=gio
  - --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons
  - --env=ELECTRON_OZONE_PLATFORM_HINT=auto

modules:
  - name: termix
    buildsystem: simple
    build-commands:
      - chmod +x termix.AppImage
      - ./termix.AppImage --appimage-extract

      - install -Dm755 squashfs-root/termix /app/bin/termix
      - cp -r squashfs-root/resources /app/bin/
      - cp -r squashfs-root/locales /app/bin/ || true

      - cp squashfs-root/*.so /app/bin/ || true
      - cp squashfs-root/*.pak /app/bin/ || true
      - cp squashfs-root/*.bin /app/bin/ || true
      - cp squashfs-root/*.dat /app/bin/ || true
      - cp squashfs-root/*.json /app/bin/ || true

      - |
        cat > run.sh << 'EOF'
        #!/bin/bash
        export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID"
        exec zypak-wrapper /app/bin/termix "$@"
        EOF
      - chmod +x run.sh
      - install -Dm755 run.sh /app/bin/run.sh

      - install -Dm644 com.karmaa.termix.desktop /app/share/applications/com.karmaa.termix.desktop

      - install -Dm644 com.karmaa.termix.metainfo.xml /app/share/metainfo/com.karmaa.termix.metainfo.xml

      - install -Dm644 com.karmaa.termix.svg /app/share/icons/hicolor/scalable/apps/com.karmaa.termix.svg
      - install -Dm644 icon-256.png /app/share/icons/hicolor/256x256/apps/com.karmaa.termix.png || true
      - install -Dm644 icon-128.png /app/share/icons/hicolor/128x128/apps/com.karmaa.termix.png || true

    sources:
      - type: file
        url: https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_x64_appimage.AppImage
        sha256: CHECKSUM_X64_PLACEHOLDER
        dest-filename: termix.AppImage
        only-arches:
          - x86_64

      - type: file
        url: https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_arm64_appimage.AppImage
        sha256: CHECKSUM_ARM64_PLACEHOLDER
        dest-filename: termix.AppImage
        only-arches:
          - aarch64

      - type: file
        path: com.karmaa.termix.desktop

      - type: file
        path: com.karmaa.termix.metainfo.xml

      - type: file
        path: com.karmaa.termix.svg

      - type: file
        path: icon-256.png

      - type: file
        path: icon-128.png


================================================
FILE: flatpak/flathub.json
================================================
{
  "only-arches": ["x86_64", "aarch64"],
  "skip-icons-check": false,
  "skip-appstream-check": false
}


================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <meta name="theme-color" content="#09090b" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta
      name="apple-mobile-web-app-status-bar-style"
      content="black-translucent"
    />
    <meta name="apple-mobile-web-app-title" content="Termix" />
    <link rel="apple-touch-icon" href="/icons/512x512.png" />
    <link rel="manifest" href="/manifest.json" />
    <title>Termix</title>
    <style>
      .hide-scrollbar {
        scrollbar-width: none;
        -ms-overflow-style: none;
      }

      .hide-scrollbar::-webkit-scrollbar {
        display: none;
      }

      .skinny-scrollbar {
        scrollbar-width: thin;
        scrollbar-color: #4a4a4a #1e1e21;
      }

      .skinny-scrollbar::-webkit-scrollbar {
        width: 6px;
        height: 6px;
      }

      .skinny-scrollbar::-webkit-scrollbar-track {
        background: #1e1e21;
      }

      .skinny-scrollbar::-webkit-scrollbar-thumb {
        background-color: #4a4a4a;
        border-radius: 3px;
        border: 1px solid #1e1e21;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "termix",
  "private": true,
  "version": "2.0.0",
  "description": "A web-based server management platform with SSH terminal, tunneling, and file editing capabilities",
  "author": "Karmaa",
  "main": "electron/main.cjs",
  "type": "module",
  "scripts": {
    "clean": "npx prettier . --write",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "lint": "eslint .",
    "lint:fix": "eslint --fix .",
    "type-check": "tsc --noEmit",
    "dev": "vite",
    "build": "vite build && tsc -p tsconfig.node.json",
    "build:backend": "tsc -p tsconfig.node.json",
    "dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/backend/starter.js",
    "dev:docker": "docker stop termix-dev 2>nul & docker rm termix-dev 2>nul & docker build -f docker/Dockerfile -t termix:dev --no-cache . && docker run -d --name termix-dev -p 3000:3000 -p 8080:8080 -p 30001-30006:30001-30006 -v \"%cd%\\db\\data:/app/data\" termix:dev",
    "dev:docker:restart": "docker stop termix-dev 2>nul & docker rm termix-dev 2>nul & docker run -d --name termix-dev -p 8080:8080 -p 30001-30006:30001-30006 -v \"%cd%\\db\\data:/app/data\" termix:dev",
    "generate:openapi": "tsc -p tsconfig.node.json && node ./dist/backend/backend/swagger.js",
    "preview": "vite preview",
    "electron:dev": "concurrently \"npm run dev\" \"powershell -c \\\"Start-Sleep -Seconds 5\\\" && electron .\"",
    "electron:rebuild": "electron-rebuild -f -w better-sqlite3",
    "build:win-portable": "npm run build && npm run electron:rebuild && electron-builder --win --dir",
    "build:win-installer": "npm run build && npm run electron:rebuild && electron-builder --win --publish=never",
    "build:linux-portable": "npm run build && npm run electron:rebuild && electron-builder --linux --dir",
    "build:linux-appimage": "npm run build && npm run electron:rebuild && electron-builder --linux AppImage",
    "build:linux-targz": "npm run build && npm run electron:rebuild && electron-builder --linux tar.gz",
    "build:mac": "npm run build && npm run electron:rebuild && electron-builder --mac --universal"
  },
  "dependencies": {
    "@codemirror/autocomplete": "^6.18.7",
    "@codemirror/commands": "^6.3.3",
    "@codemirror/search": "^6.5.11",
    "@codemirror/theme-one-dark": "^6.1.2",
    "@codemirror/view": "^6.23.1",
    "@hookform/resolvers": "^5.1.1",
    "@monaco-editor/react": "^4.7.0",
    "@radix-ui/react-accordion": "^1.2.11",
    "@radix-ui/react-alert-dialog": "^1.1.15",
    "@radix-ui/react-checkbox": "^1.3.2",
    "@radix-ui/react-dialog": "^1.1.15",
    "@radix-ui/react-dropdown-menu": "^2.1.15",
    "@radix-ui/react-label": "^2.1.7",
    "@radix-ui/react-popover": "^1.1.14",
    "@radix-ui/react-progress": "^1.1.7",
    "@radix-ui/react-scroll-area": "^1.2.9",
    "@radix-ui/react-select": "^2.2.5",
    "@radix-ui/react-separator": "^1.1.7",
    "@radix-ui/react-slider": "^1.3.6",
    "@radix-ui/react-slot": "^1.2.4",
    "@radix-ui/react-switch": "^1.2.5",
    "@radix-ui/react-tabs": "^1.1.12",
    "@radix-ui/react-tooltip": "^1.2.8",
    "@tailwindcss/vite": "^4.1.14",
    "@types/bcryptjs": "^2.4.6",
    "@types/cookie-parser": "^1.4.9",
    "@types/cytoscape": "^3.21.9",
    "@types/guacamole-common-js": "^1.5.5",
    "@types/jszip": "^3.4.0",
    "@types/multer": "^2.0.0",
    "@types/qrcode": "^1.5.5",
    "@types/react-grid-layout": "^1.3.6",
    "@types/speakeasy": "^2.0.10",
    "@uiw/codemirror-extensions-langs": "^4.24.1",
    "@uiw/codemirror-theme-github": "^4.25.4",
    "@uiw/react-codemirror": "^4.24.1",
    "@xterm/addon-clipboard": "^0.2.0",
    "@xterm/addon-fit": "^0.10.0",
    "@xterm/addon-unicode11": "^0.8.0",
    "@xterm/addon-web-links": "^0.11.0",
    "@xterm/xterm": "^5.5.0",
    "axios": "^1.10.0",
    "bcryptjs": "^3.0.2",
    "better-sqlite3": "^12.2.0",
    "body-parser": "^1.20.2",
    "chalk": "^4.1.2",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "cookie-parser": "^1.4.7",
    "cors": "^2.8.5",
    "cytoscape": "^3.33.1",
    "dotenv": "^17.2.0",
    "drizzle-orm": "^0.44.3",
    "express": "^5.1.0",
    "guacamole-common-js": "^1.5.0",
    "guacamole-lite": "^1.2.0",
    "https-proxy-agent": "^7.0.6",
    "i18n-auto-translation": "^2.2.3",
    "i18next": "^25.4.2",
    "i18next-browser-languagedetector": "^8.2.0",
    "jose": "^5.2.3",
    "jsonwebtoken": "^9.0.2",
    "jszip": "^3.10.1",
    "lucide-react": "^0.525.0",
    "multer": "^2.0.2",
    "nanoid": "^5.1.5",
    "next-themes": "^0.4.6",
    "node-fetch": "^3.3.2",
    "qrcode": "^1.5.4",
    "react": "^19.1.0",
    "react-cytoscapejs": "^2.0.0",
    "react-dom": "^19.1.0",
    "react-grid-layout": "^2.2.2",
    "react-h5-audio-player": "^3.10.1",
    "react-hook-form": "^7.60.0",
    "react-i18next": "^15.7.3",
    "react-icons": "^5.5.0",
    "react-markdown": "^10.1.0",
    "react-pdf": "^10.1.0",
    "react-photo-view": "^1.2.7",
    "react-player": "^3.3.3",
    "react-resizable-panels": "^3.0.3",
    "react-simple-keyboard": "^3.8.120",
    "react-syntax-highlighter": "^15.6.6",
    "react-xtermjs": "^1.0.10",
    "recharts": "^3.2.1",
    "remark-gfm": "^4.0.1",
    "socks": "^2.8.7",
    "sonner": "^2.0.7",
    "speakeasy": "^2.0.0",
    "ssh2": "^1.16.0",
    "tailwind-merge": "^3.3.1",
    "tailwindcss": "^4.1.14",
    "wait-on": "^9.0.1",
    "ws": "^8.18.3",
    "zod": "^4.0.5"
  },
  "devDependencies": {
    "@commitlint/cli": "^20.1.0",
    "@commitlint/config-conventional": "^20.0.0",
    "@electron/notarize": "^2.5.0",
    "@electron/rebuild": "^3.7.2",
    "@eslint/js": "^9.34.0",
    "@types/better-sqlite3": "^7.6.13",
    "@types/cors": "^2.8.19",
    "@types/express": "^5.0.3",
    "@types/jsonwebtoken": "^9.0.10",
    "@types/node": "^24.3.0",
    "@types/react": "^19.1.8",
    "@types/react-dom": "^19.1.6",
    "@types/ssh2": "^1.15.5",
    "@types/ws": "^8.18.1",
    "@vitejs/plugin-react": "^4.3.4",
    "concurrently": "^9.2.1",
    "electron": "^38.0.0",
    "electron-builder": "^26.0.12",
    "eslint": "^9.34.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.20",
    "globals": "^16.3.0",
    "husky": "^9.1.7",
    "lint-staged": "^16.2.3",
    "prettier": "3.6.2",
    "swagger-jsdoc": "^6.2.8",
    "typescript": "~5.9.2",
    "typescript-eslint": "^8.40.0",
    "vite": "^7.1.5"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "prettier --write"
    ],
    "*.{json,css,md}": [
      "prettier --write"
    ]
  }
}


================================================
FILE: public/manifest.json
================================================
{
  "name": "Termix",
  "short_name": "Termix",
  "description": "A web-based server management platform with SSH terminal, tunneling, and file editing capabilities",
  "theme_color": "#09090b",
  "background_color": "#09090b",
  "display": "standalone",
  "orientation": "any",
  "scope": "./",
  "start_url": "./",
  "icons": [
    {
      "src": "./icons/48x48.png",
      "sizes": "48x48",
      "type": "image/png"
    },
    {
      "src": "./icons/64x64.png",
      "sizes": "64x64",
      "type": "image/png"
    },
    {
      "src": "./icons/128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "./icons/256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    },
    {
      "src": "./icons/512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ],
  "categories": ["utilities", "developer", "productivity"]
}


================================================
FILE: public/sw.js
================================================
const CACHE_NAME = "termix-v1";
const STATIC_ASSETS = [
  "/",
  "/index.html",
  "/manifest.json",
  "/favicon.ico",
  "/icons/48x48.png",
  "/icons/128x128.png",
  "/icons/256x256.png",
  "/icons/512x512.png",
];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches
      .open(CACHE_NAME)
      .then((cache) => {
        return cache.addAll(STATIC_ASSETS);
      })
      .then(() => {
        return self.skipWaiting();
      }),
  );
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches
      .keys()
      .then((cacheNames) => {
        return Promise.all(
          cacheNames
            .filter((name) => name !== CACHE_NAME)
            .map((name) => {
              return caches.delete(name);
            }),
        );
      })
      .then(() => {
        return self.clients.claim();
      }),
  );
});

self.addEventListener("fetch", (event) => {
  const { request } = event;
  const url = new URL(request.url);

  if (request.method !== "GET") {
    return;
  }

  if (url.pathname.startsWith("/api/") || url.pathname.startsWith("/ws")) {
    return;
  }

  if (
    url.pathname.startsWith("/ssh/opkssh-chooser/") ||
    url.pathname.startsWith("/ssh/opkssh-callback/")
  ) {
    return;
  }

  if (url.origin !== self.location.origin) {
    return;
  }

  if (request.mode === "navigate") {
    event.respondWith(
      fetch(request).catch(() => {
        return caches.match("/index.html");
      }),
    );
    return;
  }

  const isStaticAsset = STATIC_ASSETS.some((asset) => {
    if (asset === "/") return url.pathname === "/";
    return url.pathname === asset || url.pathname.startsWith("/assets/");
  });

  if (!isStaticAsset) {
    return;
  }

  event.respondWith(
    caches.match(request).then((cachedResponse) => {
      if (cachedResponse) {
        return cachedResponse;
      }

      return fetch(request).then((response) => {
        if (!response || response.status !== 200 || response.type !== "basic") {
          return response;
        }

        const responseClone = response.clone();
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(request, responseClone);
        });

        return response;
      });
    }),
  );
});


================================================
FILE: readme/README-AR.md
================================================
# إحصائيات المستودع

<p align="center">
  <a href="../README.md"><img src="https://flagcdn.com/us.svg" alt="English" width="24" height="16"> English</a> ·
  <a href="README-CN.md"><img src="https://flagcdn.com/cn.svg" alt="中文" width="24" height="16"> 中文</a> ·
  <a href="README-JA.md"><img src="https://flagcdn.com/jp.svg" alt="日本語" width="24" height="16"> 日本語</a> ·
  <a href="README-KO.md"><img src="https://flagcdn.com/kr.svg" alt="한국어" width="24" height="16"> 한국어</a> ·
  <a href="README-FR.md"><img src="https://flagcdn.com/fr.svg" alt="Français" width="24" height="16"> Français</a> ·
  <a href="README-DE.md"><img src="https://flagcdn.com/de.svg" alt="Deutsch" width="24" height="16"> Deutsch</a> ·
  <a href="README-ES.md"><img src="https://flagcdn.com/es.svg" alt="Español" width="24" height="16"> Español</a> ·
  <a href="README-PT.md"><img src="https://flagcdn.com/br.svg" alt="Português" width="24" height="16"> Português</a> ·
  <a href="README-RU.md"><img src="https://flagcdn.com/ru.svg" alt="Русский" width="24" height="16"> Русский</a> ·
  <img src="https://flagcdn.com/sa.svg" alt="العربية" width="24" height="16"> العربية ·
  <a href="README-HI.md"><img src="https://flagcdn.com/in.svg" alt="हिन्दी" width="24" height="16"> हिन्दी</a> ·
  <a href="README-TR.md"><img src="https://flagcdn.com/tr.svg" alt="Türkçe" width="24" height="16"> Türkçe</a> ·
  <a href="README-VI.md"><img src="https://flagcdn.com/vn.svg" alt="Tiếng Việt" width="24" height="16"> Tiếng Việt</a> ·
  <a href="README-IT.md"><img src="https://flagcdn.com/it.svg" alt="Italiano" width="24" height="16"> Italiano</a>
</p>

![GitHub Repo stars](https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars)
![GitHub forks](https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks)
![GitHub Release](https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release)
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>

<p align="center">
  <img src="../repo-images/RepoOfTheDay.png" alt="Repo of the Day Achievement" style="width: 300px; height: auto;">
  <br>
  <small style="color: #666;">تم تحقيقه في 1 سبتمبر 2025</small>
</p>

<br />
<p align="center">
  <a href="https://github.com/Termix-SSH/Termix">
    <img alt="Termix Banner" src=../repo-images/HeaderImage.png style="width: auto; height: auto;">  </a>
</p>

إذا كنت ترغب في ذلك، يمكنك دعم المشروع هنا!\
[![GitHub Sponsor](https://img.shields.io/badge/Sponsor-LukeGus-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/sponsors/LukeGus)

# نظرة عامة

<p align="center">
  <a href="https://github.com/Termix-SSH/Termix">
    <img alt="Termix Banner" src=../public/icon.svg style="width: 250px; height: 250px;">  </a>
</p>

Termix هي منصة مفتوحة المصدر ومجانية للأبد وذاتية الاستضافة لإدارة الخوادم بشكل شامل. توفر حلاً متعدد المنصات لإدارة خوادمك وبنيتك التحتية من خلال واجهة واحدة وسهلة الاستخدام. يوفر Termix الوصول إلى طرفية SSH، وقدرات إنشاء أنفاق SSH، وإدارة الملفات عن بُعد، والعديد من الأدوات الأخرى. يُعد Termix البديل المثالي المجاني وذاتي الاستضافة لـ Termius المتاح لجميع المنصات.

# الميزات

- **الوصول إلى طرفية SSH** - طرفية كاملة الميزات مع دعم تقسيم الشاشة (حتى 4 لوحات) مع نظام علامات تبويب شبيه بالمتصفح. يتضمن دعم تخصيص الطرفية بما في ذلك السمات الشائعة والخطوط والمكونات الأخرى
- **الوصول إلى سطح المكتب البعيد** - دعم RDP و VNC و Telnet عبر المتصفح مع تخصيص كامل وتقسيم الشاشة
- **إدارة أنفاق SSH** - إنشاء وإدارة أنفاق SSH مع إعادة الاتصال التلقائي ومراقبة الحالة ودعم اتصالات -l أو -r
- **مدير الملفات عن بُعد** - إدارة الملفات مباشرة على الخوادم البعيدة مع دعم عرض وتحرير الكود والصور والصوت والفيديو. رفع وتنزيل وإعادة تسمية وحذف ونقل الملفات بسلاسة مع دعم sudo
- **إدارة Docker** - تشغيل وإيقاف وتعليق وحذف الحاويات. عرض إحصائيات الحاويات. التحكم في الحاوية باستخدام طرفية docker exec. لم يُصمم ليحل محل Portainer أو Dockge بل لإدارة حاوياتك ببساطة مقارنة بإنشائها
- **مدير مضيفات SSH** - حفظ وتنظيم وإدارة اتصالات SSH الخاصة بك باستخدام العلامات والمجلدات، وحفظ بيانات تسجيل الدخول القابلة لإعادة الاستخدام بسهولة مع إمكانية أتمتة نشر مفاتيح SSH
- **إحصائيات الخادم** - عرض استخدام المعالج والذاكرة والقرص إلى جانب الشبكة ووقت التشغيل ومعلومات النظام وجدار الحماية ومراقب المنافذ على معظم الخوادم المبنية على Linux
- **لوحة التحكم** - عرض معلومات الخادم بنظرة واحدة على لوحة التحكم
- **RBAC** - إنشاء الأدوار ومشاركة المضيفات عبر المستخدمين/الأدوار
- **مصادقة المستخدمين** - إدارة آمنة للمستخدمين مع ضوابط إدارية ودعم OIDC و 2FA (TOTP). عرض جلسات المستخدمين النشطة عبر جميع المنصات وإلغاء الصلاحيات. ربط حسابات OIDC/المحلية معاً
- **تشفير قاعدة البيانات** - يُخزَّن الخادم الخلفي كملفات قاعدة بيانات SQLite مشفرة. اطلع على [الوثائق](https://docs.termix.site/security) لمزيد من المعلومات
- **تصدير/استيراد البيانات** - تصدير واستيراد مضيفات SSH وبيانات الاعتماد وبيانات مدير الملفات
- **إعداد SSL تلقائي** - إنشاء وإدارة شهادات SSL مدمجة مع إعادة التوجيه إلى HTTPS
- **واجهة مستخدم حديثة** - واجهة نظيفة متوافقة مع سطح المكتب والهاتف المحمول مبنية بـ React و Tailwind CSS و Shadcn. الاختيار بين الوضع الداكن أو الفاتح. استخدام مسارات URL لفتح أي اتصال في وضع ملء الشاشة
- **اللغات** - دعم مدمج لحوالي 30 لغة (تُدار بواسطة [Crowdin](https://docs.termix.site/translations))
- **دعم المنصات** - متاح كتطبيق ويب، وتطبيق سطح مكتب (Windows و Linux و macOS)، و PWA، وتطبيق مخصص للهاتف المحمول/الجهاز اللوحي لـ iOS و Android
- **أدوات SSH** - إنشاء مقتطفات أوامر قابلة لإعادة الاستخدام تُنفَّذ بنقرة واحدة. تشغيل أمر واحد في وقت واحد عبر عدة طرفيات مفتوحة
- **سجل الأوامر** - الإكمال التلقائي وعرض أوامر SSH التي تم تنفيذها سابقاً
- **الاتصال السريع** - الاتصال بخادم دون الحاجة إلى حفظ بيانات الاتصال
- **لوحة الأوامر** - اضغط مرتين على Shift الأيسر للوصول السريع إلى اتصالات SSH باستخدام لوحة المفاتيح
- **ميزات SSH الغنية** - دعم مضيفات القفز، Warpgate، الاتصالات المبنية على TOTP، SOCKS5، التحقق من مفتاح المضيف، الملء التلقائي لكلمة المرور، [OPKSSH](https://github.com/openpubkey/opkssh)، وغيرها
- **الرسم البياني للشبكة** - تخصيص لوحة التحكم لتصور مختبرك المنزلي بناءً على اتصالات SSH مع دعم الحالة
- **علامات التبويب الدائمة** - تبقى جلسات SSH وعلامات التبويب مفتوحة عبر الأجهزة/التحديثات إذا تم تفعيلها في ملف تعريف المستخدم

# الميزات المخططة

راجع [المشاريع](https://github.com/orgs/Termix-SSH/projects/2) لعرض جميع الميزات المخططة. إذا كنت تتطلع للمساهمة، راجع [المساهمة](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).

# التثبيت

الأجهزة المدعومة:

- الموقع الإلكتروني (أي متصفح حديث على أي منصة مثل Chrome و Safari و Firefox) (يتضمن دعم PWA)
- Windows (x64/ia32)
  - نسخة محمولة
  - مثبت MSI
  - مدير حزم Chocolatey
- Linux (x64/ia32)
  - نسخة محمولة
  - AUR
  - AppImage
  - Deb
  - Flatpak
- macOS (x64/ia32 على الإصدار 12.0+)
  - Apple App Store
  - DMG
  - Homebrew
- iOS/iPadOS (الإصدار 15.1+)
  - Apple App Store
  - IPA
- Android (الإصدار 7.0+)
  - Google Play Store
  - APK

قم بزيارة [وثائق](https://docs.termix.site/install) Termix للحصول على مزيد من المعلومات حول كيفية تثبيت Termix على جميع المنصات. بخلاف ذلك، يمكنك الاطلاع على نموذج ملف Docker Compose هنا:

```yaml
services:
  termix:
    image: ghcr.io/lukegus/termix:latest
    container_name: termix
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - termix-data:/app/data
    environment:
      PORT: "8080"
    depends_on:
      - guacd
    networks:
      - termix-net

  guacd:
    image: guacamole/guacd:latest
    container_name: guacd
    restart: unless-stopped
    ports:
      - "4822:4822"
    networks:
      - termix-net

volumes:
  termix-data:
    driver: local

networks:
  termix-net:
    driver: bridge
```

# الرعاة

<p align="left">
  <a href="https://www.digitalocean.com/">
    <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="50" alt="DigitalOcean">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://crowdin.com/">
    <img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="50" alt="Crowdin">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://www.blacksmith.sh/">
    <img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="50" alt="Crowdin">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://www.cloudflare.com/">
    <img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="50" alt="Crowdin">
  </a>
</p>

# الدعم

إذا كنت بحاجة إلى مساعدة أو ترغب في طلب ميزة لـ Termix، قم بزيارة صفحة [المشكلات](https://github.com/Termix-SSH/Support/issues)، وسجل الدخول، واضغط على `New Issue`.
يرجى أن تكون مفصلاً قدر الإمكان في مشكلتك، ويُفضَّل كتابتها باللغة الإنجليزية. يمكنك أيضاً الانضمام إلى خادم [Discord](https://discord.gg/jVQGdvHDrf) وزيارة قناة الدعم، ومع ذلك قد تكون أوقات الاستجابة أطول.

# لقطات الشاشة

[![YouTube](../repo-images/YouTube.jpg)](https://www.youtube.com/@TermixSSH/videos)

<p align="center">
  <img src="../repo-images/Image%201.png" width="400" alt="Termix Demo 1"/>
  <img src="../repo-images/Image%202.png" width="400" alt="Termix Demo 2"/>
</p>

<p align="center">
  <img src="../repo-images/Image%203.png" width="400" alt="Termix Demo 3"/>
  <img src="../repo-images/Image%204.png" width="400" alt="Termix Demo 4"/>
</p>

<p align="center">
  <img src="../repo-images/Image%205.png" width="400" alt="Termix Demo 5"/>
  <img src="../repo-images/Image%206.png" width="400" alt="Termix Demo 6"/>
</p>

<p align="center">
  <img src="../repo-images/Image%207.png" width="400" alt="Termix Demo 7"/>
  <img src="../repo-images/Image%208.png" width="400" alt="Termix Demo 8"/>
</p>

<p align="center">
  <img src="../repo-images/Image%209.png" width="400" alt="Termix Demo 9"/>
  <img src="../repo-images/Image%2010.png" width="400" alt="Termix Demo 10"/>
</p>

<p align="center">
  <img src="../repo-images/Image%2011.png" width="400" alt="Termix Demo 11"/>
  <img src="../repo-images/Image%2012.png" width="400" alt="Termix Demo 12"/>
</p>

قد تكون بعض مقاطع الفيديو والصور قديمة أو قد لا تعرض الميزات بشكل مثالي.

# الترخيص

موزع بموجب رخصة Apache License الإصدار 2.0. راجع ملف LICENSE لمزيد من المعلومات.


================================================
FILE: readme/README-CN.md
================================================
# 仓库统计

<p align="center">
  <a href="../README.md"><img src="https://flagcdn.com/us.svg" alt="English" width="24" height="16"> English</a> ·
  <img src="https://flagcdn.com/cn.svg" alt="中文" width="24" height="16"> 中文 ·
  <a href="README-JA.md"><img src="https://flagcdn.com/jp.svg" alt="日本語" width="24" height="16"> 日本語</a> ·
  <a href="README-KO.md"><img src="https://flagcdn.com/kr.svg" alt="한국어" width="24" height="16"> 한국어</a> ·
  <a href="README-FR.md"><img src="https://flagcdn.com/fr.svg" alt="Français" width="24" height="16"> Français</a> ·
  <a href="README-DE.md"><img src="https://flagcdn.com/de.svg" alt="Deutsch" width="24" height="16"> Deutsch</a> ·
  <a href="README-ES.md"><img src="https://flagcdn.com/es.svg" alt="Español" width="24" height="16"> Español</a> ·
  <a href="README-PT.md"><img src="https://flagcdn.com/br.svg" alt="Português" width="24" height="16"> Português</a> ·
  <a href="README-RU.md"><img src="https://flagcdn.com/ru.svg" alt="Русский" width="24" height="16"> Русский</a> ·
  <a href="README-AR.md"><img src="https://flagcdn.com/sa.svg" alt="العربية" width="24" height="16"> العربية</a> ·
  <a href="README-HI.md"><img src="https://flagcdn.com/in.svg" alt="हिन्दी" width="24" height="16"> हिन्दी</a> ·
  <a href="README-TR.md"><img src="https://flagcdn.com/tr.svg" alt="Türkçe" width="24" height="16"> Türkçe</a> ·
  <a href="README-VI.md"><img src="https://flagcdn.com/vn.svg" alt="Tiếng Việt" width="24" height="16"> Tiếng Việt</a> ·
  <a href="README-IT.md"><img src="https://flagcdn.com/it.svg" alt="Italiano" width="24" height="16"> Italiano</a>
</p>

![GitHub Repo stars](https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars)
![GitHub forks](https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks)
![GitHub Release](https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release)
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>

<p align="center">
  <img src="../repo-images/RepoOfTheDay.png" alt="Repo of the Day Achievement" style="width: 300px; height: auto;">
  <br>
  <small style="color: #666;">2025年9月1日获得</small>
</p>

<br />
<p align="center">
  <a href="https://github.com/Termix-SSH/Termix">
    <img alt="Termix Banner" src=../repo-images/HeaderImage.png style="width: auto; height: auto;">  </a>
</p>

如果你愿意,可以在这里支持这个项目!\
[![GitHub Sponsor](https://img.shields.io/badge/Sponsor-LukeGus-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/sponsors/LukeGus)

# 概览

<p align="center">
  <a href="https://github.com/Termix-SSH/Termix">
    <img alt="Termix Banner" src=../public/icon.svg style="width: 250px; height: 250px;">  </a>
</p>

Termix 是一个开源、永久免费、自托管的一体化服务器管理平台。它提供了一个多平台解决方案,通过一个直观的界面管理你的服务器和基础设施。Termix
提供 SSH 终端访问、远程桌面控制(RDP、VNC、Telnet)、SSH 隧道功能以及远程文件管理,还会陆续添加更多工具。Termix 是适用于所有平台的完美免费自托管 Termius 替代品。

# 功能

- **SSH 终端访问** - 功能齐全的终端,具有分屏支持(最多 4 个面板)和类似浏览器的选项卡系统。包括对自定义终端的支持,包括常见终端主题、字体和其他组件
- **远程桌面访问** - 通过浏览器支持 RDP、VNC 和 Telnet,具有完整的自定义和分屏功能
- **SSH 隧道管理** - 创建和管理 SSH 隧道,具有自动重新连接和健康监控功能,支持 -l 或 -r 连接
- **远程文件管理器** - 直接在远程服务器上管理文件,支持查看和编辑代码、图像、音频和视频。无缝上传、下载、重命名、删除和移动文件,支持 sudo
- **Docker 管理** - 启动、停止、暂停、删除容器。查看容器统计信息。使用 docker exec 终端控制容器。它不是用来替代 Portainer 或 Dockge,而是用于简单管理你的容器而不是创建它们
- **SSH 主机管理器** - 保存、组织和管理您的 SSH 连接,支持标签和文件夹,并轻松保存可重用的登录信息,同时能够自动部署 SSH 密钥
- **服务器统计** - 在大多数 Linux 服务器上查看 CPU、内存和磁盘使用情况以及网络、正常运行时间、系统信息、防火墙、端口监控
- **仪表板** - 在仪表板上一目了然地查看服务器信息
- **RBAC** - 创建角色并在用户/角色之间共享主机
- **用户认证** - 安全的用户管理,具有管理员控制以及 OIDC 和 2FA (TOTP) 支持。查看所有平台上的活动用户会话并撤销权限。将您的 OIDC/本地帐户链接在一起
- **数据库加密** - 后端存储为加密的 SQLite 数据库文件。查看[文档](https://docs.termix.site/security)了解更多信息
- **数据导出/导入** - 导出和导入 SSH 主机、凭据和文件管理器数据
- **自动 SSL 设置** - 内置 SSL 证书生成和管理,支持 HTTPS 重定向
- **现代用户界面** - 使用 React、Tailwind CSS 和 Shadcn 构建的简洁的桌面/移动设备友好界面。可选择基于深色或浅色模式的用户界面。使用 URL 路由以全屏方式打开任何连接
- **语言** - 内置支持约 30 种语言(由 [Crowdin](https://docs.termix.site/translations) 管理)
- **平台支持** - 可作为 Web 应用程序、桌面应用程序(Windows、Linux 和 macOS)、PWA 以及适用于 iOS 和 Android 的专用移动/平板电脑应用程序
- **SSH 工具** - 创建可重用的命令片段,单击即可执行。在多个打开的终端上同时运行一个命令
- **命令历史** - 自动完成并查看以前运行的 SSH 命令
- **快速连接** - 无需保存连接数据即可连接到服务器
- **命令面板** - 双击左 Shift 键可快速使用键盘访问 SSH 连接
- **SSH 功能丰富** - 支持跳板机、Warpgate、基于 TOTP 的连接、SOCKS5、主机密钥验证、密码自动填充、[OPKSSH](https://github.com/openpubkey/opkssh)等
- **网络图** - 自定义您的仪表板,根据您的 SSH 连接可视化您的家庭实验室,支持状态显示
- **持久标签页** - 如果在用户配置文件中启用,SSH 会话和标签页在设备/刷新后保持打开状态

# 计划功能

查看 [项目](https://github.com/orgs/Termix-SSH/projects/2) 了解所有计划功能。如果你想贡献代码,请参阅 [贡献指南](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md)。

# 安装

支持的设备:

- 网站(任何平台上的任何现代浏览器,如 Chrome、Safari 和 Firefox)(包括 PWA 支持)
- Windows(x64/ia32)
  - 便携版
  - MSI 安装程序
  - Chocolatey 软件包管理器
- Linux(x64/ia32)
  - 便携版
  - AUR
  - AppImage
  - Deb
  - Flatpak
- macOS(x64/ia32 on v12.0+)
  - Apple App Store
  - DMG
  - Homebrew
- iOS/iPadOS(v15.1+)
  - Apple App Store
  - IPA
- Android(v7.0+)
  - Google Play 商店
  - APK

访问 Termix [文档](https://docs.termix.site/install) 了解有关如何在所有平台上安装 Termix 的更多信息。或者,在此处查看示例 Docker Compose 文件(如果不打算使用远程桌面功能,可以省略 guacd 和 network):

```yaml
services:
  termix:
    image: ghcr.io/lukegus/termix:latest
    container_name: termix
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - termix-data:/app/data
    environment:
      PORT: "8080"
    depends_on:
      - guacd
    networks:
      - termix-net

  guacd:
    image: guacamole/guacd:latest
    container_name: guacd
    restart: unless-stopped
    ports:
      - "4822:4822"
    networks:
      - termix-net

volumes:
  termix-data:
    driver: local

networks:
  termix-net:
    driver: bridge
```

# 赞助商

<p align="left">
  <a href="https://www.digitalocean.com/">
    <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="50" alt="DigitalOcean">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://crowdin.com/">
    <img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="50" alt="Crowdin">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://www.blacksmith.sh/">
    <img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="50" alt="Blacksmith">
  </a>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="https://www.cloudflare.com/">
    <img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="50" alt="Cloudflare">
  </a>
</p>

# 支持

如果你需要 Termix 的帮助或想要请求功能,请访问 [Issues](https://github.com/Termix-SSH/Support/issues) 页面,登录并点击 `New Issue`。
请尽可能详细地描述你的问题,最好使用英语。你也可以加入 [Discord](https://discord.gg/jVQGdvHDrf) 服务器并访问支持
频道,但响应时间可能较长。

# 展示

[![YouTube](../repo-images/YouTube.jpg)](https://www.youtube.com/@TermixSSH/videos)

<p align="center">
  <img src="../repo-images/Image%201.png" width="400" alt="Termix Demo 1"/>
  <img src="../repo-images/Image%202.png" width="400" alt="Termix Demo 2"/>
</p>

<p align="center">
  <img src="../repo-images/Image%203.png" width="400" alt="Termix Demo 3"/>
  <img src="../repo-images/Image%204.png" width="400" alt="Termix Demo 4"/>
</p>

<p align="center">
  <img src="../repo-images/Image%205.png" width="400" alt="Termix Demo 5"/>
  <img src="../repo-images/Image%206.png" width="400" alt="Termix Demo 6"/>
</p>

<p align="center">
  <img src="../repo-images/Image%207.png" width="400" alt="Termix Demo 7"/>
  <img src="../repo-images/Image%208.png" width="400" alt="Termix Demo 8"/>
</p>

<p align="center">
  <img src="../repo-images/Image%209.png" width="400" alt="Termix Demo 9"/>
  <img src="../repo-images/Image%2010.png" width="400" alt="Termix Demo 10"/>
</p>

<p align="center">
  <img src="../repo-images/Image%2011.png" width="400" alt="Termix Demo 11"/>
  <img src="../repo-images/Image%2012.png" width="400" alt="Termix Demo 12"/>
</p>

某些视频和图像可能已过时或可能无法完美展示功能。

# 许可证

根据 Apache License Version 2.0 发布。更多信息请参见 LICENSE。


================================================
FILE: readme/README-DE.md
================================================
# Repo-Statistiken

<p align="center">
  <a href="../README.md"><img src="https://flagcdn.com/us.svg" alt="English" width="24" height="16"> English</a> ·
  <a href="README-CN.md"><img src="https://flagcdn.com/cn.svg" alt="中文" width="24" height="16"> 中文</a> ·
  <a href="README-JA.md"><img src="https://flagcdn.com/jp.svg" alt="日本語" width="24" height="16"> 日本語</a> ·
  <a href="README-KO.md"><img src="https://flagcdn.com/kr.svg" alt="한국어" width="24" height="16"> 한국어</a> ·
  <a href="README-FR.md"><img src="https://flagcdn.com/fr.svg" alt="Français" width="24" height="16"> Français</a> ·
  <img src="https://flagcdn.com/de.svg" alt="Deutsch" width="24" height="16"> Deutsch ·
  <a href="README-ES.md"><img src="https://flagcdn.com/es.svg" alt="Español" width="24" height="16"> Español</a> ·
  <a href="README-PT.md"><img src="https://flagcdn.com/br.svg" alt="Português" width="24" height="16"> Português</a> ·
  <a href="README-RU.md"><img src="https://flagcdn.com/ru.svg" alt="Русский" width="24" height="16"> Русский</a> ·
  <a href="README-AR.md"><img src="https://flagcdn.com/sa.svg" alt="العربية" width="24" height="16"> العربية</a> ·
  <a href="README-HI.md"><img src="https://flagcdn.com/in.svg" alt="हिन्दी" width="24" height="16"> हिन्दी</a> ·
  <a href="README-TR.md"><img src="https://flagcdn.com/tr.svg" alt="Türkçe" width="24" height="16"> Türkçe</a> ·
  <a href="README-VI.md"><img src="https://flagcdn.com/vn.svg" alt="Tiếng Việt" width="24" height="16"> Tiếng Việt</a> ·
  <a href="README-IT.md"><img src="https://flagcdn.com/it.svg" alt="Italiano" width="24" height="16"> Italiano</a>
</p>

![GitHub Repo stars](https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars)
![GitHub forks](https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks)
![GitHub Release](https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release)
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>

<p align="center">
  <img src="../repo-images/RepoOfTheDay.png" alt="Repo of the Day Achievement" style="width: 300px; height: auto;">
  <br>
  <small style="color: #666;">Erreicht am 1. September 2025</small>
</p>

<br />
<p align="center">
  <a href="https:/
Download .txt
gitextract_m9ntu940/

├── .commitlintrc.json
├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── config.yml
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── docker.yml
│       ├── electron.yml
│       ├── openapi.yml
│       └── pr-check.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Casks/
│   └── termix.rb
├── LICENSE
├── README.md
├── SECURITY.md
├── build/
│   ├── Termix_Mac_App_Store.provisionprofile
│   ├── entitlements.mac.inherit.plist
│   ├── entitlements.mac.plist
│   ├── entitlements.mas.inherit.plist
│   ├── entitlements.mas.plist
│   └── notarize.cjs
├── chocolatey/
│   ├── termix-ssh.nuspec
│   └── tools/
│       ├── chocolateyinstall.ps1
│       └── chocolateyuninstall.ps1
├── components.json
├── crowdin.yml
├── docker/
│   ├── Dockerfile
│   ├── docker-compose.yml
│   ├── entrypoint.sh
│   ├── nginx-https.conf
│   └── nginx.conf
├── electron/
│   ├── main.cjs
│   └── preload.js
├── electron-builder.json
├── eslint.config.js
├── flatpak/
│   ├── com.karmaa.termix.desktop
│   ├── com.karmaa.termix.flatpakref
│   ├── com.karmaa.termix.metainfo.xml
│   ├── com.karmaa.termix.yml
│   └── flathub.json
├── index.html
├── package.json
├── public/
│   ├── icon.icns
│   ├── icons/
│   │   └── icon.icns
│   ├── manifest.json
│   └── sw.js
├── readme/
│   ├── README-AR.md
│   ├── README-CN.md
│   ├── README-DE.md
│   ├── README-ES.md
│   ├── README-FR.md
│   ├── README-HI.md
│   ├── README-IT.md
│   ├── README-JA.md
│   ├── README-KO.md
│   ├── README-PT.md
│   ├── README-RU.md
│   ├── README-TR.md
│   └── README-VI.md
├── src/
│   ├── backend/
│   │   ├── dashboard.ts
│   │   ├── database/
│   │   │   ├── database.ts
│   │   │   ├── db/
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   └── routes/
│   │   │       ├── alerts.ts
│   │   │       ├── credentials.ts
│   │   │       ├── host.ts
│   │   │       ├── network-topology.ts
│   │   │       ├── rbac.ts
│   │   │       ├── snippets.ts
│   │   │       ├── terminal.ts
│   │   │       └── users.ts
│   │   ├── guacamole/
│   │   │   ├── guacamole-server.ts
│   │   │   ├── routes.ts
│   │   │   └── token-service.ts
│   │   ├── scripts/
│   │   │   ├── enable-ssl.sh
│   │   │   └── setup-ssl.sh
│   │   ├── ssh/
│   │   │   ├── auth-manager.ts
│   │   │   ├── docker-console.ts
│   │   │   ├── docker.ts
│   │   │   ├── file-manager.ts
│   │   │   ├── host-key-verifier.ts
│   │   │   ├── opkssh-auth.ts
│   │   │   ├── server-stats.ts
│   │   │   ├── ssh-connection-pool.ts
│   │   │   ├── terminal-session-manager.ts
│   │   │   ├── terminal.ts
│   │   │   ├── tunnel.ts
│   │   │   └── widgets/
│   │   │       ├── common-utils.ts
│   │   │       ├── cpu-collector.ts
│   │   │       ├── disk-collector.ts
│   │   │       ├── firewall-collector.ts
│   │   │       ├── login-stats-collector.ts
│   │   │       ├── memory-collector.ts
│   │   │       ├── network-collector.ts
│   │   │       ├── ports-collector.ts
│   │   │       ├── processes-collector.ts
│   │   │       ├── system-collector.ts
│   │   │       └── uptime-collector.ts
│   │   ├── starter.ts
│   │   ├── swagger.ts
│   │   └── utils/
│   │       ├── auth-manager.ts
│   │       ├── auto-ssl-setup.ts
│   │       ├── credential-system-encryption-migration.ts
│   │       ├── data-crypto.ts
│   │       ├── database-file-encryption.ts
│   │       ├── database-migration.ts
│   │       ├── database-save-trigger.ts
│   │       ├── field-crypto.ts
│   │       ├── lazy-field-encryption.ts
│   │       ├── logger.ts
│   │       ├── login-rate-limiter.ts
│   │       ├── opkssh-binary-manager.ts
│   │       ├── permission-manager.ts
│   │       ├── proxy-agent.ts
│   │       ├── proxy-helper.ts
│   │       ├── request-origin.ts
│   │       ├── shared-credential-manager.ts
│   │       ├── simple-db-ops.ts
│   │       ├── socks5-helper.ts
│   │       ├── ssh-key-utils.ts
│   │       ├── system-crypto.ts
│   │       ├── user-agent-parser.ts
│   │       ├── user-crypto.ts
│   │       ├── user-data-export.ts
│   │       └── user-data-import.ts
│   ├── components/
│   │   ├── theme-provider.tsx
│   │   └── ui/
│   │       ├── accordion.tsx
│   │       ├── alert-dialog.tsx
│   │       ├── alert.tsx
│   │       ├── badge.tsx
│   │       ├── button-group.tsx
│   │       ├── button.tsx
│   │       ├── card.tsx
│   │       ├── chart.tsx
│   │       ├── checkbox.tsx
│   │       ├── command.tsx
│   │       ├── dialog.tsx
│   │       ├── dropdown-menu.tsx
│   │       ├── form.tsx
│   │       ├── input.tsx
│   │       ├── kbd.tsx
│   │       ├── label.tsx
│   │       ├── password-input.tsx
│   │       ├── popover.tsx
│   │       ├── progress.tsx
│   │       ├── resizable.tsx
│   │       ├── scroll-area.tsx
│   │       ├── select.tsx
│   │       ├── separator.tsx
│   │       ├── shadcn-io/
│   │       │   └── status/
│   │       │       └── index.tsx
│   │       ├── sheet.tsx
│   │       ├── sidebar.tsx
│   │       ├── skeleton.tsx
│   │       ├── slider.tsx
│   │       ├── sonner.tsx
│   │       ├── switch.tsx
│   │       ├── table.tsx
│   │       ├── tabs.tsx
│   │       ├── textarea.tsx
│   │       ├── tooltip.tsx
│   │       └── version-alert.tsx
│   ├── constants/
│   │   └── terminal-themes.ts
│   ├── hooks/
│   │   ├── use-confirmation.ts
│   │   ├── use-mobile.ts
│   │   └── use-service-worker.ts
│   ├── i18n/
│   │   └── i18n.ts
│   ├── index.css
│   ├── lib/
│   │   ├── base-path.ts
│   │   ├── clipboard-provider.ts
│   │   ├── db-health-monitor.ts
│   │   ├── frontend-logger.ts
│   │   ├── terminal-syntax-highlighter.ts
│   │   └── utils.ts
│   ├── locales/
│   │   ├── README.md
│   │   ├── en.json
│   │   └── translated/
│   │       ├── af_ZA.json
│   │       ├── ar_SA.json
│   │       ├── bg_BG.json
│   │       ├── bn_BD.json
│   │       ├── ca_ES.json
│   │       ├── cs_CZ.json
│   │       ├── da_DK.json
│   │       ├── de_DE.json
│   │       ├── el_GR.json
│   │       ├── es_ES.json
│   │       ├── fi_FI.json
│   │       ├── fr_FR.json
│   │       ├── he_IL.json
│   │       ├── hi_IN.json
│   │       ├── hu_HU.json
│   │       ├── id_ID.json
│   │       ├── it_IT.json
│   │       ├── ja_JP.json
│   │       ├── ko_KR.json
│   │       ├── nl_NL.json
│   │       ├── no_NO.json
│   │       ├── pl_PL.json
│   │       ├── pt_BR.json
│   │       ├── pt_PT.json
│   │       ├── ro_RO.json
│   │       ├── ru_RU.json
│   │       ├── sr_SP.json
│   │       ├── sv_SE.json
│   │       ├── th_TH.json
│   │       ├── tr_TR.json
│   │       ├── uk_UA.json
│   │       ├── vi_VN.json
│   │       ├── zh_CN.json
│   │       └── zh_TW.json
│   ├── main.tsx
│   ├── types/
│   │   ├── connection-log.ts
│   │   ├── electron.d.ts
│   │   ├── guacamole-common-js.d.ts
│   │   ├── index.ts
│   │   └── stats-widgets.ts
│   ├── ui/
│   │   ├── contexts/
│   │   │   └── ServerStatusContext.tsx
│   │   ├── desktop/
│   │   │   ├── DesktopApp.tsx
│   │   │   ├── apps/
│   │   │   │   ├── FullScreenAppWrapper.tsx
│   │   │   │   ├── admin/
│   │   │   │   │   ├── AdminSettings.tsx
│   │   │   │   │   ├── dialogs/
│   │   │   │   │   │   ├── CreateUserDialog.tsx
│   │   │   │   │   │   ├── LinkAccountDialog.tsx
│   │   │   │   │   │   └── UserEditDialog.tsx
│   │   │   │   │   └── tabs/
│   │   │   │   │       ├── DatabaseSecurityTab.tsx
│   │   │   │   │       ├── GeneralSettingsTab.tsx
│   │   │   │   │       ├── OIDCSettingsTab.tsx
│   │   │   │   │       ├── RolesTab.tsx
│   │   │   │   │       ├── SessionManagementTab.tsx
│   │   │   │   │       └── UserManagementTab.tsx
│   │   │   │   ├── command-palette/
│   │   │   │   │   └── CommandPalette.tsx
│   │   │   │   ├── dashboard/
│   │   │   │   │   ├── Dashboard.tsx
│   │   │   │   │   ├── apps/
│   │   │   │   │   │   ├── UpdateLog.tsx
│   │   │   │   │   │   └── alerts/
│   │   │   │   │   │       ├── AlertCard.tsx
│   │   │   │   │   │       └── AlertManager.tsx
│   │   │   │   │   ├── cards/
│   │   │   │   │   │   ├── NetworkGraphCard.tsx
│   │   │   │   │   │   ├── QuickActionsCard.tsx
│   │   │   │   │   │   ├── RecentActivityCard.tsx
│   │   │   │   │   │   ├── ServerOverviewCard.tsx
│   │   │   │   │   │   └── ServerStatsCard.tsx
│   │   │   │   │   ├── components/
│   │   │   │   │   │   └── DashboardSettingsDialog.tsx
│   │   │   │   │   └── hooks/
│   │   │   │   │       └── useDashboardPreferences.ts
│   │   │   │   ├── features/
│   │   │   │   │   ├── docker/
│   │   │   │   │   │   ├── DockerApp.tsx
│   │   │   │   │   │   ├── DockerManager.tsx
│   │   │   │   │   │   └── components/
│   │   │   │   │   │       ├── ConsoleTerminal.tsx
│   │   │   │   │   │       ├── ContainerCard.tsx
│   │   │   │   │   │       ├── ContainerDetail.tsx
│   │   │   │   │   │       ├── ContainerList.tsx
│   │   │   │   │   │       ├── ContainerStats.tsx
│   │   │   │   │   │       └── LogViewer.tsx
│   │   │   │   │   ├── file-manager/
│   │   │   │   │   │   ├── DragIndicator.tsx
│   │   │   │   │   │   ├── FileManager.tsx
│   │   │   │   │   │   ├── FileManagerApp.tsx
│   │   │   │   │   │   ├── FileManagerContextMenu.tsx
│   │   │   │   │   │   ├── FileManagerGrid.tsx
│   │   │   │   │   │   ├── FileManagerSidebar.tsx
│   │   │   │   │   │   ├── SudoPasswordDialog.tsx
│   │   │   │   │   │   ├── components/
│   │   │   │   │   │   │   ├── CompressDialog.tsx
│   │   │   │   │   │   │   ├── DiffViewer.tsx
│   │   │   │   │   │   │   ├── DiffWindow.tsx
│   │   │   │   │   │   │   ├── DraggableWindow.tsx
│   │   │   │   │   │   │   ├── FileViewer.tsx
│   │   │   │   │   │   │   ├── FileWindow.tsx
│   │   │   │   │   │   │   ├── PermissionsDialog.tsx
│   │   │   │   │   │   │   ├── TerminalWindow.tsx
│   │   │   │   │   │   │   └── WindowManager.tsx
│   │   │   │   │   │   └── hooks/
│   │   │   │   │   │       ├── useDragAndDrop.ts
│   │   │   │   │   │       └── useFileSelection.ts
│   │   │   │   │   ├── guacamole/
│   │   │   │   │   │   ├── GuacamoleApp.tsx
│   │   │   │   │   │   └── GuacamoleDisplay.tsx
│   │   │   │   │   ├── server-stats/
│   │   │   │   │   │   ├── ServerStats.tsx
│   │   │   │   │   │   ├── ServerStatsApp.tsx
│   │   │   │   │   │   └── widgets/
│   │   │   │   │   │       ├── CpuWidget.tsx
│   │   │   │   │   │       ├── DiskWidget.tsx
│   │   │   │   │   │       ├── FirewallWidget.tsx
│   │   │   │   │   │       ├── LoginStatsWidget.tsx
│   │   │   │   │   │       ├── MemoryWidget.tsx
│   │   │   │   │   │       ├── NetworkWidget.tsx
│   │   │   │   │   │       ├── PortsWidget.tsx
│   │   │   │   │   │       ├── ProcessesWidget.tsx
│   │   │   │   │   │       ├── SystemWidget.tsx
│   │   │   │   │   │       ├── UptimeWidget.tsx
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── terminal/
│   │   │   │   │   │   ├── SudoPasswordPopup.tsx
│   │   │   │   │   │   ├── Terminal.tsx
│   │   │   │   │   │   ├── TerminalApp.tsx
│   │   │   │   │   │   ├── TerminalPreview.tsx
│   │   │   │   │   │   └── command-history/
│   │   │   │   │   │       ├── CommandAutocomplete.tsx
│   │   │   │   │   │       └── CommandHistoryContext.tsx
│   │   │   │   │   └── tunnel/
│   │   │   │   │       ├── Tunnel.tsx
│   │   │   │   │       ├── TunnelApp.tsx
│   │   │   │   │       ├── TunnelManager.tsx
│   │   │   │   │       ├── TunnelObject.tsx
│   │   │   │   │       └── TunnelViewer.tsx
│   │   │   │   ├── host-manager/
│   │   │   │   │   ├── HostManagerApp.tsx
│   │   │   │   │   ├── credentials/
│   │   │   │   │   │   ├── CredentialEditor.tsx
│   │   │   │   │   │   ├── CredentialSelector.tsx
│   │   │   │   │   │   ├── CredentialViewer.tsx
│   │   │   │   │   │   ├── CredentialsManager.tsx
│   │   │   │   │   │   └── tabs/
│   │   │   │   │   │       ├── CredentialAuthenticationTab.tsx
│   │   │   │   │   │       ├── CredentialGeneralTab.tsx
│   │   │   │   │   │       └── shared/
│   │   │   │   │   │           └── tab-types.ts
│   │   │   │   │   ├── dialogs/
│   │   │   │   │   │   └── FolderEditDialog.tsx
│   │   │   │   │   └── hosts/
│   │   │   │   │       ├── HostManager.tsx
│   │   │   │   │       ├── HostManagerEditor.tsx
│   │   │   │   │       ├── HostManagerViewer.tsx
│   │   │   │   │       └── tabs/
│   │   │   │   │           ├── HostDockerTab.tsx
│   │   │   │   │           ├── HostFileManagerTab.tsx
│   │   │   │   │           ├── HostGeneralTab.tsx
│   │   │   │   │           ├── HostRemoteDesktopTab.tsx
│   │   │   │   │           ├── HostSharingTab.tsx
│   │   │   │   │           ├── HostStatisticsTab.tsx
│   │   │   │   │           ├── HostStatusTab.tsx
│   │   │   │   │           ├── HostTerminalTab.tsx
│   │   │   │   │           ├── HostTunnelTab.tsx
│   │   │   │   │           └── shared/
│   │   │   │   │               ├── JumpHostItem.tsx
│   │   │   │   │               ├── QuickActionItem.tsx
│   │   │   │   │               └── tab-types.ts
│   │   │   │   └── tools/
│   │   │   │       └── SSHToolsSidebar.tsx
│   │   │   ├── authentication/
│   │   │   │   ├── Auth.tsx
│   │   │   │   ├── ElectronLoginForm.tsx
│   │   │   │   └── ElectronServerConfig.tsx
│   │   │   ├── navigation/
│   │   │   │   ├── AppView.tsx
│   │   │   │   ├── LeftSidebar.tsx
│   │   │   │   ├── TopNavbar.tsx
│   │   │   │   ├── animations/
│   │   │   │   │   └── SimpleLoader.tsx
│   │   │   │   ├── connection-log/
│   │   │   │   │   ├── ConnectionLog.tsx
│   │   │   │   │   └── ConnectionLogContext.tsx
│   │   │   │   ├── dialogs/
│   │   │   │   │   ├── HostKeyVerificationDialog.tsx
│   │   │   │   │   ├── OPKSSHDialog.tsx
│   │   │   │   │   ├── QuickConnectDialog.tsx
│   │   │   │   │   ├── SSHAuthDialog.tsx
│   │   │   │   │   ├── TOTPDialog.tsx
│   │   │   │   │   └── WarpgateDialog.tsx
│   │   │   │   ├── hosts/
│   │   │   │   │   ├── FolderCard.tsx
│   │   │   │   │   └── Host.tsx
│   │   │   │   └── tabs/
│   │   │   │       ├── Tab.tsx
│   │   │   │       ├── TabContext.tsx
│   │   │   │       └── TabDropdown.tsx
│   │   │   └── user/
│   │   │       ├── ElectronVersionCheck.tsx
│   │   │       ├── LanguageSwitcher.tsx
│   │   │       ├── PasswordReset.tsx
│   │   │       ├── TOTPSetup.tsx
│   │   │       └── UserProfile.tsx
│   │   ├── hooks/
│   │   │   ├── useCommandHistory.ts
│   │   │   ├── useCommandTracker.ts
│   │   │   ├── useDragToDesktop.ts
│   │   │   └── useDragToSystemDesktop.ts
│   │   ├── main-axios.ts
│   │   └── mobile/
│   │       ├── MobileApp.tsx
│   │       ├── apps/
│   │       │   ├── navigation/
│   │       │   │   ├── BottomNavbar.tsx
│   │       │   │   ├── LeftSidebar.tsx
│   │       │   │   ├── hosts/
│   │       │   │   │   ├── FolderCard.tsx
│   │       │   │   │   └── Host.tsx
│   │       │   │   └── tabs/
│   │       │   │       └── TabContext.tsx
│   │       │   └── terminal/
│   │       │       ├── Terminal.tsx
│   │       │       ├── TerminalKeyboard.tsx
│   │       │       ├── kb-dark-theme.css
│   │       │       └── kb-light-theme.css
│   │       ├── authentication/
│   │       │   └── Auth.tsx
│   │       └── navigation/
│   │           ├── BottomNavbar.tsx
│   │           ├── LeftSidebar.tsx
│   │           ├── hosts/
│   │           │   ├── FolderCard.tsx
│   │           │   └── Host.tsx
│   │           └── tabs/
│   │               └── TabContext.tsx
│   └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
Download .txt
SYMBOL INDEX (1599 symbols across 253 files)

FILE: electron/main.cjs
  function logToFile (line 19) | function logToFile(...args) {
  function httpFetch (line 33) | function httpFetch(url, options = {}) {
  function getBackendEntryPath (line 98) | function getBackendEntryPath() {
  function getBackendDataDir (line 105) | function getBackendDataDir() {
  function startBackendServer (line 114) | function startBackendServer() {
  function stopBackendServer (line 208) | function stopBackendServer() {
  function createTray (line 248) | function createTray() {
  function createWindow (line 304) | function createWindow() {
  constant GITHUB_API_BASE (line 480) | const GITHUB_API_BASE = "https://api.github.com";
  constant REPO_OWNER (line 481) | const REPO_OWNER = "Termix-SSH";
  constant REPO_NAME (line 482) | const REPO_NAME = "Termix";
  constant CACHE_DURATION (line 485) | const CACHE_DURATION = 30 * 60 * 1000;
  function fetchGitHubAPI (line 487) | async function fetchGitHubAPI(endpoint, cacheKey) {
  function createMenu (line 795) | function createMenu() {

FILE: public/sw.js
  constant CACHE_NAME (line 1) | const CACHE_NAME = "termix-v1";
  constant STATIC_ASSETS (line 2) | const STATIC_ASSETS = [

FILE: src/backend/dashboard.ts
  constant RATE_LIMIT_MS (line 23) | const RATE_LIMIT_MS = 1000;
  constant PORT (line 514) | const PORT = 30006;

FILE: src/backend/database/database.ts
  class GitHubCache (line 122) | class GitHubCache {
    method set (line 126) | set<T>(key: string, data: T): void {
    method get (line 135) | get<T>(key: string): T | null {
  constant GITHUB_API_BASE (line 152) | const GITHUB_API_BASE = "https://api.github.com";
  constant REPO_OWNER (line 153) | const REPO_OWNER = "Termix-SSH";
  constant REPO_NAME (line 154) | const REPO_NAME = "Termix";
  function fetchGitHubAPI (line 156) | async function fetchGitHubAPI<T>(
  constant HTTP_PORT (line 1815) | const HTTP_PORT = 30001;
  function initializeSecurity (line 1817) | async function initializeSecurity() {

FILE: src/backend/database/db/index.ts
  function initializeDatabaseAsync (line 27) | async function initializeDatabaseAsync(): Promise<void> {
  function initializeCompleteDatabase (line 129) | async function initializeCompleteDatabase(): Promise<void> {
  function saveMemoryDatabaseToFile (line 1258) | async function saveMemoryDatabaseToFile() {
  function handlePostInitFileEncryption (line 1294) | async function handlePostInitFileEncryption() {
  function initializeDatabase (line 1333) | async function initializeDatabase(): Promise<void> {
  function cleanupDatabase (line 1340) | async function cleanupDatabase() {
  function getDb (line 1417) | function getDb(): ReturnType<typeof drizzle<typeof schema>> {
  function getSqlite (line 1426) | function getSqlite(): Database.Database {

FILE: src/backend/database/routes/alerts.ts
  class AlertCache (line 15) | class AlertCache {
    method set (line 19) | set<T>(key: string, data: T): void {
    method get (line 28) | get<T>(key: string): T | null {
  constant GITHUB_RAW_BASE (line 45) | const GITHUB_RAW_BASE = "https://raw.githubusercontent.com";
  constant REPO_OWNER (line 46) | const REPO_OWNER = "Termix-SSH";
  constant REPO_NAME (line 47) | const REPO_NAME = "Docs";
  constant ALERTS_FILE (line 48) | const ALERTS_FILE = "main/termix-alerts.json";
  function fetchAlertsFromGitHub (line 50) | async function fetchAlertsFromGitHub(): Promise<TermixAlert[]> {

FILE: src/backend/database/routes/credentials.ts
  function generateSSHKeyPair (line 27) | function generateSSHKeyPair(
  function isNonEmptyString (line 79) | function isNonEmptyString(val: unknown): val is string {
  function formatCredentialOutput (line 933) | function formatCredentialOutput(
  function formatSSHHostOutput (line 959) | function formatSSHHostOutput(
  function deploySSHKeyToHost (line 1497) | async function deploySSHKeyToHost(

FILE: src/backend/database/routes/host.ts
  function isNonEmptyString (line 44) | function isNonEmptyString(value: unknown): value is string {
  function isValidPort (line 48) | function isValidPort(port: unknown): port is number {
  function transformHostResponse (line 52) | function transformHostResponse(
  function resolveHostCredentials (line 2469) | async function resolveHostCredentials(
  function rewriteOPKSSHHtml (line 3885) | function rewriteOPKSSHHtml(

FILE: src/backend/database/routes/rbac.ts
  function isNonEmptyString (line 25) | function isNonEmptyString(value: unknown): value is string {

FILE: src/backend/database/routes/snippets.ts
  function isNonEmptyString (line 12) | function isNonEmptyString(val: unknown): val is string {

FILE: src/backend/database/routes/terminal.ts
  function isNonEmptyString (line 12) | function isNonEmptyString(val: unknown): val is string {

FILE: src/backend/database/routes/users.ts
  function getOIDCConfigFromEnv (line 51) | function getOIDCConfigFromEnv(): {
  function isOIDCUserAllowed (line 93) | function isOIDCUserAllowed(
  function verifyOIDCToken (line 123) | async function verifyOIDCToken(
  function isNonEmptyString (line 220) | function isNonEmptyString(val: unknown): val is string {
  function deleteUserAndRelatedData (line 227) | async function deleteUserAndRelatedData(userId: string): Promise<void> {

FILE: src/backend/guacamole/guacamole-server.ts
  function parseGuacUrl (line 12) | function parseGuacUrl(url: string): { host: string; port: number } {
  function readGuacdOptions (line 20) | function readGuacdOptions(): { host: string; port: number } {
  constant GUAC_WS_PORT (line 39) | const GUAC_WS_PORT = 30008;
  function createGuacServer (line 98) | function createGuacServer(): GuacamoleLite {
  function restartGuacServer (line 144) | async function restartGuacServer(): Promise<void> {

FILE: src/backend/guacamole/token-service.ts
  type GuacamoleConnectionSettings (line 4) | interface GuacamoleConnectionSettings {
  type GuacamoleToken (line 28) | interface GuacamoleToken {
  constant CIPHER (line 32) | const CIPHER = "aes-256-cbc";
  constant KEY_LENGTH (line 33) | const KEY_LENGTH = 32;
  class GuacamoleTokenService (line 35) | class GuacamoleTokenService {
    method constructor (line 39) | private constructor() {
    method getInstance (line 43) | static getInstance(): GuacamoleTokenService {
    method initializeKey (line 50) | private initializeKey(): Buffer {
    method getEncryptionKey (line 78) | getEncryptionKey(): Buffer {
    method encryptToken (line 82) | encryptToken(tokenObject: GuacamoleToken): string {
    method decryptToken (line 101) | decryptToken(token: string): GuacamoleToken | null {
    method createRdpToken (line 119) | createRdpToken(
    method createVncToken (line 142) | createVncToken(
    method createTelnetToken (line 161) | createTelnetToken(

FILE: src/backend/ssh/auth-manager.ts
  type ResolvedCredentials (line 7) | interface ResolvedCredentials {
  type HostConfig (line 15) | interface HostConfig {
  type AuthContext (line 30) | interface AuthContext {
  class SSHAuthManager (line 44) | class SSHAuthManager {
    method constructor (line 47) | constructor(context: AuthContext) {
    method resolveCredentials (line 51) | async resolveCredentials(
    method handleKeyboardInteractive (line 105) | handleKeyboardInteractive(
    method handleWarpgateAuth (line 146) | private handleWarpgateAuth(
    method handleTotpAuth (line 196) | private handleTotpAuth(
    method handlePasswordAuth (line 278) | private handlePasswordAuth(
    method sendLog (line 351) | sendLog(
    method cleanup (line 370) | cleanup(): void {

FILE: src/backend/ssh/docker-console.ts
  type SSHSession (line 14) | interface SSHSession {
  function detectShell (line 51) | async function detectShell(
  function createJumpHostChain (line 94) | async function createJumpHostChain(

FILE: src/backend/ssh/docker.ts
  function createConnectionLog (line 22) | function createConnectionLog(
  type SSHSession (line 36) | interface SSHSession {
  type PendingTOTPSession (line 45) | interface PendingTOTPSession {
  constant SESSION_IDLE_TIMEOUT (line 66) | const SESSION_IDLE_TIMEOUT = 60 * 60 * 1000;
  function cleanupSession (line 83) | function cleanupSession(sessionId: string) {
  function scheduleSessionCleanup (line 109) | function scheduleSessionCleanup(sessionId: string) {
  type JumpHostConfig (line 120) | interface JumpHostConfig {
  function resolveJumpHost (line 134) | async function resolveJumpHost(
  function createJumpHostChain (line 193) | async function createJumpHostChain(
  function executeDockerCommand (line 347) | async function executeDockerCommand(
  constant PORT (line 3190) | const PORT = 30007;

FILE: src/backend/ssh/file-manager.ts
  function createConnectionLog (line 20) | function createConnectionLog(
  function isExecutableFile (line 34) | function isExecutableFile(permissions: string, fileName: string): boolean {
  function modeToPermissions (line 66) | function modeToPermissions(mode: number): string {
  function formatMtime (line 89) | function formatMtime(mtime: number): string {
  type JumpHostConfig (line 163) | interface JumpHostConfig {
  function resolveJumpHost (line 177) | async function resolveJumpHost(
  function createJumpHostChain (line 236) | async function createJumpHostChain(
  type SSHSession (line 393) | interface SSHSession {
  type PendingTOTPSession (line 404) | interface PendingTOTPSession {
  function execWithSudo (line 425) | function execWithSudo(
  function getSessionSftp (line 463) | function getSessionSftp(
  function cleanupSession (line 486) | function cleanupSession(sessionId: string) {
  function scheduleSessionCleanup (line 520) | function scheduleSessionCleanup(sessionId: string) {
  function getMimeType (line 534) | function getMimeType(fileName: string): string {
  function detectBinary (line 554) | function detectBinary(buffer: Buffer): boolean {
  constant PORT (line 5590) | const PORT = 30004;

FILE: src/backend/ssh/host-key-verifier.ts
  type HostKeyVerificationData (line 7) | interface HostKeyVerificationData {
  type VerificationResponse (line 19) | interface VerificationResponse {
  class SSHHostKeyVerifier (line 23) | class SSHHostKeyVerifier {
    method createHostVerifier (line 24) | static async createHostVerifier(
    method storeHostKey (line 272) | private static async storeHostKey(
    method updateHostKey (line 290) | private static async updateHostKey(
    method promptUserForNewKey (line 309) | private static async promptUserForNewKey(
    method promptUserForChangedKey (line 369) | private static async promptUserForChangedKey(
    method getKeyType (line 433) | private static getKeyType(key: Buffer): string {

FILE: src/backend/ssh/opkssh-auth.ts
  constant AUTH_TIMEOUT (line 17) | const AUTH_TIMEOUT = 60 * 1000;
  type OPKSSHAuthSession (line 19) | interface OPKSSHAuthSession {
  function getOPKConfigPath (line 52) | function getOPKConfigPath(): string {
  function ensureOPKConfigDir (line 58) | async function ensureOPKConfigDir(): Promise<void> {
  function createTemplateConfig (line 64) | async function createTemplateConfig(): Promise<void> {
  function checkOPKConfigExists (line 81) | async function checkOPKConfigExists(): Promise<{
  function startOPKSSHAuth (line 141) | async function startOPKSSHAuth(
  function handleOPKSSHOutput (line 334) | function handleOPKSSHOutput(requestId: string, output: string): void {
  function storeOPKSSHToken (line 448) | async function storeOPKSSHToken(session: OPKSSHAuthSession): Promise<voi...
  function getOPKSSHToken (line 548) | async function getOPKSSHToken(
  function deleteOPKSSHToken (line 615) | async function deleteOPKSSHToken(
  function invalidateOPKSSHToken (line 627) | async function invalidateOPKSSHToken(
  function handleOAuthCallback (line 649) | async function handleOAuthCallback(
  function cleanupAuthSession (line 679) | async function cleanupAuthSession(requestId: string): Promise<void> {
  function cancelAuthSession (line 729) | function cancelAuthSession(requestId: string): void {
  function getActiveAuthSession (line 736) | function getActiveAuthSession(
  function getActiveSessionsForUser (line 742) | function getActiveSessionsForUser(userId: string): OPKSSHAuthSession[] {
  function getActiveSessionsAll (line 752) | function getActiveSessionsAll(): OPKSSHAuthSession[] {
  function getUserIdFromRequest (line 756) | async function getUserIdFromRequest(req: {

FILE: src/backend/ssh/server-stats.ts
  function supportsMetrics (line 32) | function supportsMetrics(host: SSHHostWithCredentials): boolean {
  function isTcpPingEnabled (line 37) | function isTcpPingEnabled(statsConfig: StatsConfig): boolean {
  function createConnectionLog (line 41) | function createConnectionLog(
  type JumpHostConfig (line 55) | interface JumpHostConfig {
  function resolveJumpHost (line 69) | async function resolveJumpHost(
  function createJumpHostChain (line 128) | async function createJumpHostChain(
  type MetricsSession (line 285) | interface MetricsSession {
  type PendingTOTPSession (line 295) | interface PendingTOTPSession {
  type MetricsViewer (line 309) | interface MetricsViewer {
  function cleanupMetricsSession (line 319) | function cleanupMetricsSession(sessionId: string) {
  function scheduleMetricsSessionCleanup (line 345) | function scheduleMetricsSessionCleanup(sessionId: string) {
  function getSessionKey (line 359) | function getSessionKey(hostId: number, userId: string): string {
  class RequestQueue (line 363) | class RequestQueue {
    method queueRequest (line 368) | async queueRequest<T>(hostId: number, request: () => Promise<T>): Prom...
    method processQueue (line 399) | private async processQueue(hostId: number): Promise<void> {
  type CachedMetrics (line 424) | interface CachedMetrics {
  class MetricsCache (line 430) | class MetricsCache {
    method get (line 434) | get(hostId: number): unknown | null {
    method set (line 442) | set(hostId: number, data: unknown): void {
    method clear (line 450) | clear(hostId?: number): void {
  type AuthFailureRecord (line 459) | interface AuthFailureRecord {
  class AuthFailureTracker (line 466) | class AuthFailureTracker {
    method recordFailure (line 471) | recordFailure(
    method shouldSkip (line 492) | shouldSkip(hostId: number): boolean {
    method getSkipReason (line 510) | getSkipReason(hostId: number): string | null {
    method reset (line 537) | reset(hostId: number): void {
    method cleanup (line 541) | cleanup(): void {
  class PollingBackoff (line 553) | class PollingBackoff {
    method recordFailure (line 559) | recordFailure(hostId: number): void {
    method shouldSkip (line 571) | shouldSkip(hostId: number): boolean {
    method getBackoffInfo (line 582) | getBackoffInfo(hostId: number): string | null {
    method reset (line 599) | reset(hostId: number): void {
    method cleanup (line 603) | cleanup(): void {
  type HostStatus (line 622) | type HostStatus = "online" | "offline";
  type SSHHostWithCredentials (line 624) | interface SSHHostWithCredentials {
  type StatusEntry (line 659) | type StatusEntry = {
  type StatsConfig (line 664) | interface StatsConfig {
  constant DEFAULT_STATS_CONFIG (line 675) | const DEFAULT_STATS_CONFIG: StatsConfig = {
  type HostPollingConfig (line 683) | interface HostPollingConfig {
  class PollingManager (line 691) | class PollingManager {
    method constructor (line 705) | constructor() {
    method getGlobalDefaults (line 711) | private getGlobalDefaults(): {
    method parseStatsConfig (line 746) | parseStatsConfig(statsConfigStr?: string | StatsConfig): StatsConfig {
    method startPollingForHost (line 789) | async startPollingForHost(
    method pollHostStatus (line 878) | private async pollHostStatus(
    method pollHostMetrics (line 908) | private async pollHostMetrics(
    method stopPollingForHost (line 960) | stopPollingForHost(hostId: number, clearData = true): void {
    method stopMetricsOnly (line 980) | stopMetricsOnly(hostId: number): void {
    method getStatus (line 988) | getStatus(hostId: number): StatusEntry | undefined {
    method getAllStatuses (line 992) | getAllStatuses(): Map<number, StatusEntry> {
    method getMetrics (line 996) | getMetrics(
    method initializePolling (line 1004) | async initializePolling(userId: string): Promise<void> {
    method refreshHostPolling (line 1012) | async refreshHostPolling(userId: string): Promise<void> {
    method refreshAllPolling (line 1032) | async refreshAllPolling(): Promise<void> {
    method registerViewer (line 1060) | registerViewer(hostId: number, sessionId: string, userId: string): void {
    method updateHeartbeat (line 1078) | updateHeartbeat(sessionId: string): boolean {
    method unregisterViewer (line 1087) | unregisterViewer(hostId: number, sessionId: string): void {
    method startMetricsForHost (line 1100) | private async startMetricsForHost(
    method stopMetricsForHost (line 1118) | private stopMetricsForHost(hostId: number): void {
    method cleanupInactiveViewers (line 1122) | private cleanupInactiveViewers(): void {
    method destroy (line 1133) | destroy(): void {
  function validateHostId (line 1143) | function validateHostId(
  function fetchAllHosts (line 1197) | async function fetchAllHosts(
  function fetchHostById (line 1228) | async function fetchHostById(
  function resolveHostCredentials (line 1270) | async function resolveHostCredentials(
  function addLegacyCredentials (line 1422) | function addLegacyCredentials(
  function buildSshConfig (line 1432) | async function buildSshConfig(
  function getPoolKey (line 1577) | function getPoolKey(host: SSHHostWithCredentials): string {
  function createSshFactory (line 1584) | function createSshFactory(host: SSHHostWithCredentials): () => Promise<C...
  function withSshConnection (line 1732) | async function withSshConnection<T>(
  function collectMetrics (line 1741) | async function collectMetrics(host: SSHHostWithCredentials): Promise<{
  function tcpPing (line 1937) | function tcpPing(
  constant PORT (line 3278) | const PORT = 30005;

FILE: src/backend/ssh/ssh-connection-pool.ts
  type PooledConnection (line 4) | interface PooledConnection {
  class SSHConnectionPool (line 11) | class SSHConnectionPool {
    method constructor (line 16) | constructor() {
    method isConnectionHealthy (line 25) | private isConnectionHealthy(client: Client): boolean {
    method getConnection (line 41) | async getConnection(
    method releaseConnection (line 128) | releaseConnection(key: string, client: Client): void {
    method removeConnection (line 137) | private removeConnection(key: string, client: Client): void {
    method clearKeyConnections (line 148) | clearKeyConnections(key: string): void {
    method cleanup (line 160) | private cleanup(): void {
    method clearAllConnections (line 193) | clearAllConnections(): void {
    method destroy (line 206) | destroy(): void {
  function withConnection (line 214) | async function withConnection<T>(

FILE: src/backend/ssh/terminal-session-manager.ts
  constant MAX_BUFFER_BYTES (line 6) | const MAX_BUFFER_BYTES = 512 * 1024;
  constant DEFAULT_TIMEOUT_MINUTES (line 7) | const DEFAULT_TIMEOUT_MINUTES = 30;
  constant HEALTH_CHECK_INTERVAL_MS (line 8) | const HEALTH_CHECK_INTERVAL_MS = 60_000;
  constant MAX_SESSIONS_PER_USER (line 9) | const MAX_SESSIONS_PER_USER = 10;
  type TerminalSession (line 11) | interface TerminalSession {
  class TerminalSessionManager (line 37) | class TerminalSessionManager {
    method constructor (line 42) | private constructor() {
    method getInstance (line 49) | static getInstance(): TerminalSessionManager {
    method createSession (line 56) | createSession(
    method getSession (line 125) | getSession(sessionId: string | null): TerminalSession | null {
    method setSSHState (line 130) | setSSHState(
    method attachWs (line 147) | attachWs(
    method detachWs (line 262) | detachWs(sessionId: string): void {
    method destroySession (line 293) | destroySession(sessionId: string): void {
    method getUserSessions (line 349) | getUserSessions(userId: string): TerminalSession[] {
    method bufferOutput (line 359) | bufferOutput(sessionId: string, data: string): void {
    method flushBuffer (line 375) | flushBuffer(session: TerminalSession): string | null {
    method getBuffer (line 383) | getBuffer(session: TerminalSession): string | null {
    method getTimeoutMs (line 388) | private getTimeoutMs(): number {
    method healthCheck (line 408) | private healthCheck(): void {
    method cleanupOpksshFiles (line 452) | private async cleanupOpksshFiles(tempFiles: {
    method destroyAll (line 478) | destroyAll(): void {

FILE: src/backend/ssh/terminal.ts
  type ConnectToHostData (line 21) | interface ConnectToHostData {
  type ResizeData (line 55) | interface ResizeData {
  type TOTPResponseData (line 60) | interface TOTPResponseData {
  type WebSocketMessage (line 64) | interface WebSocketMessage {
  type JumpHostConfig (line 76) | interface JumpHostConfig {
  function resolveJumpHost (line 90) | async function resolveJumpHost(
  function createJumpHostChain (line 154) | async function createJumpHostChain(
  function resetConnectionState (line 465) | function resetConnectionState() {
  function handleConnectToHost (line 934) | async function handleConnectToHost(data: ConnectToHostData) {
  function handleResize (line 2188) | function handleResize(data: ResizeData) {
  function cleanupAuthState (line 2204) | function cleanupAuthState(timeoutId?: NodeJS.Timeout) {

FILE: src/backend/ssh/tunnel.ts
  function broadcastTunnelStatus (line 90) | function broadcastTunnelStatus(tunnelName: string, status: TunnelStatus)...
  function getAllTunnelStatus (line 108) | function getAllTunnelStatus(): Record<string, TunnelStatus> {
  function classifyError (line 116) | function classifyError(errorMessage: string): ErrorType {
  function getTunnelMarker (line 162) | function getTunnelMarker(tunnelName: string) {
  function normalizeTunnelName (line 166) | function normalizeTunnelName(
  function parseTunnelName (line 177) | function parseTunnelName(tunnelName: string): {
  function validateTunnelConfig (line 212) | function validateTunnelConfig(
  function cleanupTunnelResources (line 231) | async function cleanupTunnelResources(
  function resetRetryState (line 332) | function resetRetryState(tunnelName: string): void {
  function handleDisconnect (line 357) | async function handleDisconnect(
  function setupPingInterval (line 498) | function setupPingInterval(tunnelName: string): void {
  function connectSSHTunnel (line 526) | async function connectSSHTunnel(
  function killRemoteTunnelByMarker (line 1309) | async function killRemoteTunnelByMarker(
  function initializeAutoStartTunnels (line 2024) | async function initializeAutoStartTunnels(): Promise<void> {
  constant PORT (line 2158) | const PORT = 30003;

FILE: src/backend/ssh/widgets/common-utils.ts
  function execCommand (line 3) | function execCommand(
  function toFixedNum (line 91) | function toFixedNum(
  function kibToGiB (line 99) | function kibToGiB(kib: number): number {

FILE: src/backend/ssh/widgets/cpu-collector.ts
  function parseCpuLine (line 4) | function parseCpuLine(
  function collectCpuMetrics (line 19) | async function collectCpuMetrics(client: Client): Promise<{

FILE: src/backend/ssh/widgets/disk-collector.ts
  function collectDiskMetrics (line 4) | async function collectDiskMetrics(client: Client): Promise<{

FILE: src/backend/ssh/widgets/firewall-collector.ts
  function parseIptablesRule (line 9) | function parseIptablesRule(line: string): FirewallRule | null {
  function parseIptablesOutput (line 68) | function parseIptablesOutput(output: string): FirewallChain[] {
  function parseNftablesOutput (line 104) | function parseNftablesOutput(output: string): FirewallChain[] {
  function collectFirewallMetrics (line 201) | async function collectFirewallMetrics(

FILE: src/backend/ssh/widgets/login-stats-collector.ts
  type LoginRecord (line 4) | interface LoginRecord {
  type LoginStats (line 11) | interface LoginStats {
  function collectLoginStats (line 18) | async function collectLoginStats(client: Client): Promise<LoginStats> {

FILE: src/backend/ssh/widgets/memory-collector.ts
  function collectMemoryMetrics (line 4) | async function collectMemoryMetrics(client: Client): Promise<{

FILE: src/backend/ssh/widgets/network-collector.ts
  function collectNetworkMetrics (line 4) | async function collectNetworkMetrics(client: Client): Promise<{

FILE: src/backend/ssh/widgets/ports-collector.ts
  function parseSsOutput (line 8) | function parseSsOutput(output: string): ListeningPort[] {
  function parseNetstatOutput (line 57) | function parseNetstatOutput(output: string): ListeningPort[] {
  function collectPortsMetrics (line 116) | async function collectPortsMetrics(

FILE: src/backend/ssh/widgets/processes-collector.ts
  function collectProcessesMetrics (line 4) | async function collectProcessesMetrics(client: Client): Promise<{

FILE: src/backend/ssh/widgets/system-collector.ts
  function collectSystemMetrics (line 4) | async function collectSystemMetrics(client: Client): Promise<{

FILE: src/backend/ssh/widgets/uptime-collector.ts
  function collectUptimeMetrics (line 4) | async function collectUptimeMetrics(client: Client): Promise<{

FILE: src/backend/swagger.ts
  function generateOpenAPISpec (line 127) | async function generateOpenAPISpec() {

FILE: src/backend/utils/auth-manager.ts
  type AuthenticationResult (line 13) | interface AuthenticationResult {
  type JWTPayload (line 24) | interface JWTPayload {
  type AuthenticatedRequest (line 32) | interface AuthenticatedRequest extends Request {
  type RequestWithHeaders (line 38) | interface RequestWithHeaders extends Request {
  class AuthManager (line 44) | class AuthManager {
    method constructor (line 49) | private constructor() {
    method getInstance (line 73) | static getInstance(): AuthManager {
    method initialize (line 80) | async initialize(): Promise<void> {
    method registerUser (line 84) | async registerUser(userId: string, password: string): Promise<void> {
    method registerOIDCUser (line 88) | async registerOIDCUser(
    method authenticateOIDCUser (line 95) | async authenticateOIDCUser(
    method authenticateUser (line 116) | async authenticateUser(
    method convertToOIDCEncryption (line 139) | async convertToOIDCEncryption(userId: string): Promise<void> {
    method performLazyEncryptionMigration (line 143) | private async performLazyEncryptionMigration(userId: string): Promise<...
    method generateJWTToken (line 197) | async generateJWTToken(
    method parseExpiresIn (line 278) | private parseExpiresIn(expiresIn: string): number {
    method verifyJWTToken (line 299) | async verifyJWTToken(token: string): Promise<JWTPayload | null> {
    method invalidateJWTToken (line 345) | invalidateJWTToken(_token: string): void {
    method invalidateUserTokens (line 350) | invalidateUserTokens(_userId: string): void {
    method revokeSession (line 354) | async revokeSession(sessionId: string): Promise<boolean> {
    method revokeAllUserSessions (line 388) | async revokeAllUserSessions(
    method cleanupExpiredSessions (line 446) | async cleanupExpiredSessions(): Promise<number> {
    method getAllSessions (line 498) | async getAllSessions(): Promise<Record<string, unknown>[]> {
    method getUserSessions (line 510) | async getUserSessions(userId: string): Promise<Record<string, unknown>...
    method getSecureCookieOptions (line 526) | getSecureCookieOptions(
    method getClearCookieOptions (line 539) | getClearCookieOptions(req: RequestWithHeaders) {
    method createAuthMiddleware (line 548) | createAuthMiddleware() {
    method createDataAccessMiddleware (line 676) | createDataAccessMiddleware() {
    method createAdminMiddleware (line 690) | createAdminMiddleware() {
    method logoutUser (line 749) | async logoutUser(userId: string, sessionId?: string): Promise<void> {
    method getUserDataKey (line 792) | getUserDataKey(userId: string): Buffer | null {
    method isUserUnlocked (line 796) | isUserUnlocked(userId: string): boolean {
    method changeUserPassword (line 800) | async changeUserPassword(
    method resetUserPasswordWithPreservedDEK (line 812) | async resetUserPasswordWithPreservedDEK(
    method isTrustedDevice (line 822) | async isTrustedDevice(
    method addTrustedDevice (line 867) | async addTrustedDevice(
    method removeTrustedDevice (line 914) | async removeTrustedDevice(

FILE: src/backend/utils/auto-ssl-setup.ts
  class AutoSSLSetup (line 6) | class AutoSSLSetup {
    method initialize (line 19) | static async initialize(): Promise<void> {
    method isSSLConfigured (line 64) | private static async isSSLConfigured(): Promise<boolean> {
    method generateSSLCertificates (line 100) | private static async generateSSLCertificates(): Promise<void> {
    method logCertificateInfo (line 178) | private static async logCertificateInfo(): Promise<void> {
    method setupEnvironmentVariables (line 221) | private static async setupEnvironmentVariables(): Promise<void> {
    method getSSLConfig (line 272) | static getSSLConfig() {

FILE: src/backend/utils/credential-system-encryption-migration.ts
  class CredentialSystemEncryptionMigration (line 9) | class CredentialSystemEncryptionMigration {
    method migrateUserCredentials (line 10) | async migrateUserCredentials(userId: string): Promise<{

FILE: src/backend/utils/data-crypto.ts
  type DatabaseInstance (line 6) | interface DatabaseInstance {
  type DatabaseRecord (line 14) | interface DatabaseRecord {
  class DataCrypto (line 19) | class DataCrypto {
    method initialize (line 22) | static initialize() {
    method encryptRecord (line 26) | static encryptRecord<T extends Record<string, unknown>>(
    method decryptRecord (line 49) | static decryptRecord<T extends Record<string, unknown>>(
    method decryptRecords (line 74) | static decryptRecords<T extends Record<string, unknown>>(
    method migrateUserSensitiveFields (line 86) | static async migrateUserSensitiveFields(
    method getUserDataKey (line 237) | static getUserDataKey(userId: string): Buffer | null {
    method reencryptUserDataAfterPasswordReset (line 241) | static async reencryptUserDataAfterPasswordReset(
    method validateUserAccess (line 413) | static validateUserAccess(userId: string): Buffer {
    method encryptRecordForUser (line 421) | static encryptRecordForUser<T extends Record<string, unknown>>(
    method decryptRecordForUser (line 430) | static decryptRecordForUser<T extends Record<string, unknown>>(
    method decryptRecordsForUser (line 439) | static decryptRecordsForUser<T extends Record<string, unknown>>(
    method canUserAccessData (line 448) | static canUserAccessData(userId: string): boolean {
    method testUserEncryption (line 452) | static testUserEncryption(userId: string): boolean {
    method encryptRecordWithSystemKey (line 477) | static async encryptRecordWithSystemKey<T extends Record<string, unkno...

FILE: src/backend/utils/database-file-encryption.ts
  type EncryptedFileMetadata (line 7) | interface EncryptedFileMetadata {
  class DatabaseFileEncryption (line 18) | class DatabaseFileEncryption {
    method encryptDatabaseFromBuffer (line 25) | static async encryptDatabaseFromBuffer(
    method encryptDatabaseFile (line 109) | static async encryptDatabaseFile(
    method decryptDatabaseToBuffer (line 204) | static async decryptDatabaseToBuffer(encryptedPath: string): Promise<B...
    method decryptDatabaseFile (line 373) | static async decryptDatabaseFile(
    method isEncryptedDatabaseFile (line 405) | static isEncryptedDatabaseFile(filePath: string): boolean {
    method getEncryptedFileInfo (line 451) | static getEncryptedFileInfo(encryptedPath: string): {
    method getDiagnosticInfo (line 502) | static getDiagnosticInfo(encryptedPath: string): {
    method createEncryptedBackup (line 623) | static async createEncryptedBackup(
    method restoreFromEncryptedBackup (line 656) | static async restoreFromEncryptedBackup(
    method cleanupTempFiles (line 681) | static cleanupTempFiles(basePath: string): void {

FILE: src/backend/utils/database-migration.ts
  type MigrationResult (line 7) | interface MigrationResult {
  type MigrationStatus (line 16) | interface MigrationStatus {
  class DatabaseMigration (line 24) | class DatabaseMigration {
    method constructor (line 29) | constructor(dataDir: string) {
    method checkMigrationStatus (line 35) | checkMigrationStatus(): MigrationStatus {
    method createBackup (line 97) | private createBackup(): string {
    method verifyMigration (line 126) | private async verifyMigration(
    method migrateDatabase (line 200) | async migrateDatabase(): Promise<MigrationResult> {
    method cleanupOldBackups (line 347) | cleanupOldBackups(): void {

FILE: src/backend/utils/database-save-trigger.ts
  class DatabaseSaveTrigger (line 3) | class DatabaseSaveTrigger {
    method initialize (line 10) | static initialize(saveFunction: () => Promise<void>): void {
    method isDirty (line 15) | static get isDirty(): boolean {
    method markClean (line 19) | static markClean(): void {
    method triggerSave (line 23) | static async triggerSave(
    method forceSave (line 62) | static async forceSave(reason: string = "critical_operation"): Promise...
    method getStatus (line 99) | static getStatus(): {
    method cleanup (line 111) | static cleanup(): void {

FILE: src/backend/utils/field-crypto.ts
  type EncryptedData (line 3) | interface EncryptedData {
  class FieldCrypto (line 11) | class FieldCrypto {
    method encryptField (line 44) | static encryptField(
    method decryptField (line 80) | static decryptField(
    method shouldEncryptField (line 114) | static shouldEncryptField(tableName: string, fieldName: string): boole...

FILE: src/backend/utils/lazy-field-encryption.ts
  type DatabaseInstance (line 4) | interface DatabaseInstance {
  class LazyFieldEncryption (line 12) | class LazyFieldEncryption {
    method isPlaintextField (line 33) | static isPlaintextField(value: string): boolean {
    method safeGetFieldValue (line 55) | static safeGetFieldValue(
    method migrateFieldToEncrypted (line 117) | static migrateFieldToEncrypted(
    method migrateRecordSensitiveFields (line 192) | static migrateRecordSensitiveFields(
    method getSensitiveFieldsForTable (line 252) | static getSensitiveFieldsForTable(tableName: string): string[] {
    method propertyToColumn (line 276) | static propertyToColumn(propertyName: string): string {
    method fieldNeedsMigration (line 280) | static fieldNeedsMigration(
    method checkUserNeedsMigration (line 314) | static async checkUserNeedsMigration(

FILE: src/backend/utils/logger.ts
  type LogLevel (line 3) | type LogLevel = "debug" | "info" | "warn" | "error" | "success";
  type LogContext (line 5) | interface LogContext {
  constant SENSITIVE_FIELDS (line 17) | const SENSITIVE_FIELDS = [
  constant TRUNCATE_FIELDS (line 37) | const TRUNCATE_FIELDS = ["data", "content", "body", "response", "request"];
  class Logger (line 39) | class Logger {
    method constructor (line 47) | constructor(serviceName: string, serviceIcon: string, serviceColor: st...
    method getTimeStamp (line 53) | private getTimeStamp(): string {
    method sanitizeContext (line 57) | private sanitizeContext(context: LogContext): LogContext {
    method formatMessage (line 88) | private formatMessage(
    method getLevelColor (line 125) | private getLevelColor(level: LogLevel): chalk.Chalk {
    method shouldLog (line 142) | private shouldLog(level: LogLevel, message: string): boolean {
    method debug (line 168) | debug(message: string, context?: LogContext): void {
    method info (line 173) | info(message: string, context?: LogContext): void {
    method warn (line 178) | warn(message: string, context?: LogContext): void {
    method error (line 183) | error(message: string, error?: unknown, context?: LogContext): void {
    method success (line 191) | success(message: string, context?: LogContext): void {
    method auth (line 196) | auth(message: string, context?: LogContext): void {
    method db (line 200) | db(message: string, context?: LogContext): void {
    method ssh (line 204) | ssh(message: string, context?: LogContext): void {
    method tunnel (line 208) | tunnel(message: string, context?: LogContext): void {
    method file (line 212) | file(message: string, context?: LogContext): void {
    method api (line 216) | api(message: string, context?: LogContext): void {
    method request (line 220) | request(message: string, context?: LogContext): void {
    method response (line 224) | response(message: string, context?: LogContext): void {
    method connection (line 228) | connection(message: string, context?: LogContext): void {
    method disconnect (line 235) | disconnect(message: string, context?: LogContext): void {
    method retry (line 242) | retry(message: string, context?: LogContext): void {

FILE: src/backend/utils/login-rate-limiter.ts
  type LoginAttempt (line 1) | interface LoginAttempt {
  class LoginRateLimiter (line 7) | class LoginRateLimiter {
    method constructor (line 25) | constructor() {
    method cleanup (line 29) | private cleanup(): void {
    method recordFailedAttempt (line 77) | recordFailedAttempt(ip: string, username?: string): void {
    method resetAttempts (line 119) | resetAttempts(ip: string, username?: string): void {
    method isLocked (line 126) | isLocked(
    method getRemainingAttempts (line 153) | getRemainingAttempts(ip: string, username?: string): number {
    method recordFailedTOTPAttempt (line 177) | recordFailedTOTPAttempt(userId: string): void {
    method resetTOTPAttempts (line 199) | resetTOTPAttempts(userId: string): void {
    method isTOTPLocked (line 203) | isTOTPLocked(userId: string): { locked: boolean; remainingTime?: numbe...
    method getRemainingTOTPAttempts (line 217) | getRemainingTOTPAttempts(userId: string): number {
    method recordResetCodeAttempt (line 228) | recordResetCodeAttempt(username: string): void {
    method resetResetCodeAttempts (line 250) | resetResetCodeAttempts(username: string): void {
    method isResetCodeLocked (line 254) | isResetCodeLocked(username: string): {
    method getRemainingResetCodeAttempts (line 271) | getRemainingResetCodeAttempts(username: string): number {

FILE: src/backend/utils/opkssh-binary-manager.ts
  constant OPKSSH_REPO (line 7) | const OPKSSH_REPO = "openpubkey/opkssh";
  function getBinaryDir (line 9) | function getBinaryDir(): string {
  function getVersionFile (line 15) | function getVersionFile(): string {
  type GitHubAsset (line 19) | interface GitHubAsset {
  type GitHubRelease (line 24) | interface GitHubRelease {
  class OPKSSHBinaryManager (line 29) | class OPKSSHBinaryManager {
    method ensureBinary (line 32) | static async ensureBinary(): Promise<string> {
    method downloadBinary (line 62) | static async downloadBinary(): Promise<void> {
    method getBinaryPath (line 113) | static getBinaryPath(): string {
    method checkForUpdate (line 122) | private static async checkForUpdate(): Promise<boolean> {
    method getLatestRelease (line 149) | private static async getLatestRelease(): Promise<GitHubRelease> {
    method findMatchingAsset (line 164) | private static findMatchingAsset(assets: GitHubAsset[]): GitHubAsset |...
    method getBinaryName (line 205) | private static getBinaryName(): string {

FILE: src/backend/utils/permission-manager.ts
  type AuthenticatedRequest (line 13) | interface AuthenticatedRequest extends Request {
  type HostAccessInfo (line 18) | interface HostAccessInfo {
  type PermissionCheckResult (line 26) | interface PermissionCheckResult {
  class PermissionManager (line 31) | class PermissionManager {
    method constructor (line 39) | private constructor() {
    method getInstance (line 59) | static getInstance(): PermissionManager {
    method cleanupExpiredAccess (line 66) | private async cleanupExpiredAccess(): Promise<void> {
    method clearPermissionCache (line 84) | private clearPermissionCache(): void {
    method invalidateUserPermissionCache (line 88) | invalidateUserPermissionCache(userId: string): void {
    method getUserPermissions (line 92) | async getUserPermissions(userId: string): Promise<string[]> {
    method hasPermission (line 140) | async hasPermission(userId: string, permission: string): Promise<boole...
    method canAccessHost (line 162) | async canAccessHost(
    method isAdmin (line 279) | async isAdmin(userId: string): Promise<boolean> {
    method requirePermission (line 312) | requirePermission(permission: string) {
    method requireHostAccess (line 344) | requireHostAccess(
    method requireAdmin (line 392) | requireAdmin() {

FILE: src/backend/utils/proxy-agent.ts
  function getProxyAgent (line 4) | function getProxyAgent(targetUrl?: string): Agent | undefined {

FILE: src/backend/utils/proxy-helper.ts
  type SOCKS5Config (line 7) | interface SOCKS5Config {
  function createProxyConnection (line 16) | async function createProxyConnection(
  function createSingleProxyConnection (line 45) | async function createSingleProxyConnection(
  function createHttpConnectConnection (line 82) | async function createHttpConnectConnection(
  function createMixedProxyChainConnection (line 164) | async function createMixedProxyChainConnection(
  function createPureSocksChainConnection (line 182) | async function createPureSocksChainConnection(
  function createHopByHopConnection (line 216) | async function createHopByHopConnection(
  function testProxyConnectivity (line 280) | async function testProxyConnectivity(options: {

FILE: src/backend/utils/request-origin.ts
  function getRequestOrigin (line 4) | function getRequestOrigin(req: Request | IncomingMessage): string {
  function getRequestOriginWithForceHTTPS (line 55) | function getRequestOriginWithForceHTTPS(

FILE: src/backend/utils/shared-credential-manager.ts
  type CredentialData (line 14) | interface CredentialData {
  class SharedCredentialManager (line 23) | class SharedCredentialManager {
    method constructor (line 26) | private constructor() {}
    method getInstance (line 28) | static getInstance(): SharedCredentialManager {
    method createSharedCredentialForUser (line 35) | async createSharedCredentialForUser(
    method createSharedCredentialsForRole (line 114) | async createSharedCredentialsForRole(
    method getSharedCredentialForUser (line 161) | async getSharedCredentialForUser(
    method updateSharedCredentialsForOriginal (line 227) | async updateSharedCredentialsForOriginal(
    method deleteSharedCredentialsForOriginal (line 303) | async deleteSharedCredentialsForOriginal(
    method reEncryptPendingCredentialsForUser (line 318) | async reEncryptPendingCredentialsForUser(userId: string): Promise<void> {
    method getDecryptedCredential (line 346) | private async getDecryptedCredential(
    method getDecryptedCredentialViaSystemKey (line 389) | private async getDecryptedCredentialViaSystemKey(
    method encryptCredentialForUser (line 441) | private encryptCredentialForUser(
    method decryptSharedCredential (line 492) | private decryptSharedCredential(
    method decryptField (line 534) | private decryptField(
    method createPendingSharedCredential (line 557) | private async createPendingSharedCredential(
    method reEncryptSharedCredential (line 578) | private async reEncryptSharedCredential(

FILE: src/backend/utils/simple-db-ops.ts
  type TableName (line 6) | type TableName =
  class SimpleDBOps (line 13) | class SimpleDBOps {
    method insert (line 14) | static async insert<T extends Record<string, unknown>>(
    method select (line 67) | static async select<T extends Record<string, unknown>>(
    method selectOne (line 89) | static async selectOne<T extends Record<string, unknown>>(
    method update (line 112) | static async update<T extends Record<string, unknown>>(
    method delete (line 160) | static async delete(
    method healthCheck (line 175) | static async healthCheck(userId: string): Promise<boolean> {
    method isUserDataUnlocked (line 179) | static isUserDataUnlocked(userId: string): boolean {
    method selectEncrypted (line 183) | static async selectEncrypted(query: unknown): Promise<unknown[]> {

FILE: src/backend/utils/ssh-key-utils.ts
  function detectKeyTypeFromContent (line 4) | function detectKeyTypeFromContent(keyContent: string): string {
  function detectPublicKeyTypeFromContent (line 103) | function detectPublicKeyTypeFromContent(publicKeyContent: string): string {
  type KeyInfo (line 184) | interface KeyInfo {
  type PublicKeyInfo (line 192) | interface PublicKeyInfo {
  type KeyPairValidationResult (line 199) | interface KeyPairValidationResult {
  function parseSSHKey (line 207) | function parseSSHKey(
  function parsePublicKey (line 292) | function parsePublicKey(publicKeyData: string): PublicKeyInfo {
  function detectKeyType (line 314) | function detectKeyType(privateKeyData: string): string {
  function getFriendlyKeyTypeName (line 326) | function getFriendlyKeyTypeName(keyType: string): string {
  function validateKeyPair (line 342) | function validateKeyPair(

FILE: src/backend/utils/system-crypto.ts
  class SystemCrypto (line 6) | class SystemCrypto {
    method constructor (line 13) | private constructor() {}
    method getInstance (line 15) | static getInstance(): SystemCrypto {
    method initializeJWTSecret (line 22) | async initializeJWTSecret(): Promise<void> {
    method getJWTSecret (line 68) | async getJWTSecret(): Promise<string> {
    method initializeDatabaseKey (line 75) | async initializeDatabaseKey(): Promise<void> {
    method getDatabaseKey (line 110) | async getDatabaseKey(): Promise<Buffer> {
    method initializeInternalAuthToken (line 117) | async initializeInternalAuthToken(): Promise<void> {
    method getInternalAuthToken (line 149) | async getInternalAuthToken(): Promise<string> {
    method initializeCredentialSharingKey (line 156) | async initializeCredentialSharingKey(): Promise<void> {
    method getCredentialSharingKey (line 193) | async getCredentialSharingKey(): Promise<Buffer> {
    method generateAndGuideUser (line 200) | private async generateAndGuideUser(): Promise<void> {
    method generateAndGuideDatabaseKey (line 216) | private async generateAndGuideDatabaseKey(): Promise<void> {
    method generateAndGuideInternalAuthToken (line 233) | private async generateAndGuideInternalAuthToken(): Promise<void> {
    method generateAndGuideCredentialSharingKey (line 252) | private async generateAndGuideCredentialSharingKey(): Promise<void> {
    method validateJWTSecret (line 272) | async validateJWTSecret(): Promise<boolean> {
    method getSystemKeyStatus (line 293) | async getSystemKeyStatus() {
    method updateEnvFile (line 312) | private async updateEnvFile(key: string, value: string): Promise<void> {

FILE: src/backend/utils/user-agent-parser.ts
  type DeviceType (line 4) | type DeviceType = "web" | "desktop" | "mobile";
  type DeviceInfo (line 6) | interface DeviceInfo {
  function detectPlatform (line 14) | function detectPlatform(req: Request): DeviceType {
  function parseUserAgent (line 44) | function parseUserAgent(req: Request): DeviceInfo {
  function parseElectronUserAgent (line 59) | function parseElectronUserAgent(userAgent: string): DeviceInfo {
  function parseMobileUserAgent (line 93) | function parseMobileUserAgent(userAgent: string): DeviceInfo {
  function parseWebUserAgent (line 147) | function parseWebUserAgent(userAgent: string): DeviceInfo {
  function parseWindowsVersion (line 211) | function parseWindowsVersion(userAgent: string): string {
  function parseMacVersion (line 231) | function parseMacVersion(userAgent: string): string {
  function generateDeviceFingerprint (line 257) | function generateDeviceFingerprint(deviceInfo: DeviceInfo): string {

FILE: src/backend/utils/user-crypto.ts
  type KEKSalt (line 7) | interface KEKSalt {
  type EncryptedDEK (line 14) | interface EncryptedDEK {
  type UserSession (line 22) | interface UserSession {
  class UserCrypto (line 28) | class UserCrypto {
    method constructor (line 37) | private constructor() {
    method getInstance (line 46) | static getInstance(): UserCrypto {
    method setSessionExpiredCallback (line 53) | setSessionExpiredCallback(callback: (userId: string) => void): void {
    method setupUserEncryption (line 57) | async setupUserEncryption(userId: string, password: string): Promise<v...
    method setupOIDCUserEncryption (line 70) | async setupOIDCUserEncryption(
    method authenticateUser (line 114) | async authenticateUser(
    method authenticateOIDCUser (line 167) | async authenticateOIDCUser(
    method getUserDataKey (line 248) | getUserDataKey(userId: string): Buffer | null {
    method logoutUser (line 268) | logoutUser(userId: string): void {
    method isUserUnlocked (line 276) | isUserUnlocked(userId: string): boolean {
    method changeUserPassword (line 280) | async changeUserPassword(
    method resetUserPasswordWithPreservedDEK (line 325) | async resetUserPasswordWithPreservedDEK(
    method convertToOIDCEncryption (line 365) | async convertToOIDCEncryption(userId: string): Promise<void> {
    method validatePassword (line 432) | private async validatePassword(
    method cleanupExpiredSessions (line 455) | private cleanupExpiredSessions(): void {
    method generateKEKSalt (line 471) | private async generateKEKSalt(): Promise<KEKSalt> {
    method deriveKEK (line 480) | private deriveKEK(password: string, kekSalt: KEKSalt): Buffer {
    method deriveOIDCSystemKey (line 490) | private deriveOIDCSystemKey(userId: string): Buffer {
    method encryptDEK (line 503) | private encryptDEK(dek: Buffer, kek: Buffer): EncryptedDEK {
    method decryptDEK (line 520) | private decryptDEK(encryptedDEK: EncryptedDEK, kek: Buffer): Buffer {
    method storeKEKSalt (line 534) | private async storeKEKSalt(userId: string, kekSalt: KEKSalt): Promise<...
    method getKEKSalt (line 553) | private async getKEKSalt(userId: string): Promise<KEKSalt | null> {
    method storeEncryptedDEK (line 571) | private async storeEncryptedDEK(
    method getEncryptedDEK (line 593) | private async getEncryptedDEK(userId: string): Promise<EncryptedDEK | ...
    method getOIDCEncryptedDEK (line 611) | private async getOIDCEncryptedDEK(

FILE: src/backend/utils/user-data-export.ts
  type UserExportData (line 15) | interface UserExportData {
  class UserDataExport (line 37) | class UserDataExport {
    method exportUserData (line 40) | static async exportUserData(
    method exportUserDataToJSON (line 174) | static async exportUserDataToJSON(
    method validateExportData (line 188) | static validateExportData(data: unknown): {
    method getExportStats (line 256) | static getExportStats(data: UserExportData): {

FILE: src/backend/utils/user-data-import.ts
  type ImportOptions (line 16) | interface ImportOptions {
  type ImportResult (line 23) | interface ImportResult {
  class UserDataImport (line 36) | class UserDataImport {
    method importUserData (line 37) | static async importUserData(
    method importSshHosts (line 160) | private static async importSshHosts(
    method importSshCredentials (line 241) | private static async importSshCredentials(
    method importFileManagerData (line 327) | private static async importFileManagerData(
    method importDismissedAlerts (line 414) | private static async importDismissedAlerts(
    method importUserDataFromJSON (line 475) | static async importUserDataFromJSON(

FILE: src/components/theme-provider.tsx
  type Theme (line 4) | type Theme = "dark" | "light" | "system";
  type ThemeProviderProps (line 6) | type ThemeProviderProps = {
  type ThemeProviderState (line 12) | type ThemeProviderState = {
  function ThemeProvider (line 24) | function ThemeProvider({

FILE: src/components/ui/accordion.tsx
  function Accordion (line 7) | function Accordion({
  function AccordionItem (line 13) | function AccordionItem({
  function AccordionTrigger (line 26) | function AccordionTrigger({
  function AccordionContent (line 48) | function AccordionContent({

FILE: src/components/ui/alert-dialog.tsx
  function AlertDialog (line 7) | function AlertDialog({
  function AlertDialogTrigger (line 13) | function AlertDialogTrigger({
  function AlertDialogPortal (line 21) | function AlertDialogPortal({
  function AlertDialogOverlay (line 29) | function AlertDialogOverlay({
  function AlertDialogContent (line 45) | function AlertDialogContent({
  function AlertDialogHeader (line 64) | function AlertDialogHeader({
  function AlertDialogFooter (line 77) | function AlertDialogFooter({
  function AlertDialogTitle (line 93) | function AlertDialogTitle({
  function AlertDialogDescription (line 106) | function AlertDialogDescription({
  function AlertDialogAction (line 119) | function AlertDialogAction({
  function AlertDialogCancel (line 131) | function AlertDialogCancel({

FILE: src/components/ui/alert.tsx
  function Alert (line 22) | function Alert({
  function AlertTitle (line 37) | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
  function AlertDescription (line 50) | function AlertDescription({

FILE: src/components/ui/badge.tsx
  function Badge (line 29) | function Badge({

FILE: src/components/ui/button-group.tsx
  type ButtonGroupProps (line 6) | interface ButtonGroupProps {

FILE: src/components/ui/button.tsx
  type ButtonProps (line 39) | interface ButtonProps
  function Button (line 44) | function Button({

FILE: src/components/ui/card.tsx
  function Card (line 5) | function Card({ className, ...props }: React.ComponentProps<"div">) {
  function CardHeader (line 18) | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  function CardTitle (line 31) | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  function CardDescription (line 41) | function CardDescription({ className, ...props }: React.ComponentProps<"...
  function CardAction (line 51) | function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  function CardContent (line 64) | function CardContent({ className, ...props }: React.ComponentProps<"div"...
  function CardFooter (line 74) | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {

FILE: src/components/ui/checkbox.tsx
  function Checkbox (line 7) | function Checkbox({

FILE: src/components/ui/command.tsx
  function Command (line 16) | function Command({
  function CommandDialog (line 32) | function CommandDialog({
  function CommandInput (line 63) | function CommandInput({
  function CommandList (line 85) | function CommandList({
  function CommandEmpty (line 101) | function CommandEmpty({
  function CommandGroup (line 113) | function CommandGroup({
  function CommandSeparator (line 129) | function CommandSeparator({
  function CommandItem (line 142) | function CommandItem({
  function CommandShortcut (line 158) | function CommandShortcut({

FILE: src/components/ui/dialog.tsx
  function Dialog (line 7) | function Dialog({
  function DialogTrigger (line 13) | function DialogTrigger({
  function DialogPortal (line 19) | function DialogPortal({
  function DialogClose (line 25) | function DialogClose({
  function DialogOverlay (line 31) | function DialogOverlay({
  function DialogContent (line 47) | function DialogContent({
  function DialogHeader (line 81) | function DialogHeader({ className, ...props }: React.ComponentProps<"div...
  function DialogFooter (line 91) | function DialogFooter({ className, ...props }: React.ComponentProps<"div...
  function DialogTitle (line 104) | function DialogTitle({
  function DialogDescription (line 117) | function DialogDescription({

FILE: src/components/ui/form.tsx
  type FormFieldContextValue (line 20) | type FormFieldContextValue<
  type FormItemContextValue (line 67) | type FormItemContextValue = {
  function FormItem (line 75) | function FormItem({ className, ...props }: React.ComponentProps<"div">) {
  function FormLabel (line 89) | function FormLabel({
  function FormControl (line 106) | function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
  function FormDescription (line 125) | function FormDescription({ className, ...props }: React.ComponentProps<"...
  function FormMessage (line 138) | function FormMessage({ className, ...props }: React.ComponentProps<"p">) {

FILE: src/components/ui/input.tsx
  function Input (line 5) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...

FILE: src/components/ui/kbd.tsx
  function Kbd (line 3) | function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
  function KbdGroup (line 18) | function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {

FILE: src/components/ui/label.tsx
  function Label (line 6) | function Label({

FILE: src/components/ui/password-input.tsx
  type PasswordInputProps (line 8) | type PasswordInputProps = React.InputHTMLAttributes<HTMLInputElement>;

FILE: src/components/ui/popover.tsx
  function Popover (line 6) | function Popover({
  function PopoverTrigger (line 12) | function PopoverTrigger({
  function PopoverContent (line 18) | function PopoverContent({
  function PopoverAnchor (line 40) | function PopoverAnchor({

FILE: src/components/ui/progress.tsx
  function Progress (line 6) | function Progress({

FILE: src/components/ui/resizable.tsx
  function ResizablePanelGroup (line 7) | function ResizablePanelGroup({
  function ResizablePanel (line 23) | function ResizablePanel({
  function ResizableHandle (line 29) | function ResizableHandle({

FILE: src/components/ui/scroll-area.tsx
  function ScrollArea (line 6) | function ScrollArea({
  function ScrollBar (line 29) | function ScrollBar({

FILE: src/components/ui/select.tsx
  function Select (line 7) | function Select({
  function SelectGroup (line 13) | function SelectGroup({
  function SelectValue (line 19) | function SelectValue({
  function SelectTrigger (line 25) | function SelectTrigger({
  function SelectContent (line 51) | function SelectContent({
  function SelectLabel (line 86) | function SelectLabel({
  function SelectItem (line 99) | function SelectItem({
  function SelectSeparator (line 123) | function SelectSeparator({
  function SelectScrollUpButton (line 136) | function SelectScrollUpButton({
  function SelectScrollDownButton (line 154) | function SelectScrollDownButton({

FILE: src/components/ui/separator.tsx
  function Separator (line 8) | function Separator({

FILE: src/components/ui/shadcn-io/status/index.tsx
  type StatusProps (line 6) | type StatusProps = ComponentProps<typeof Badge> & {
  type StatusIndicatorProps (line 18) | type StatusIndicatorProps = HTMLAttributes<HTMLSpanElement>;
  type StatusLabelProps (line 43) | type StatusLabelProps = HTMLAttributes<HTMLSpanElement>;

FILE: src/components/ui/sheet.tsx
  function Sheet (line 7) | function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive....
  function SheetTrigger (line 11) | function SheetTrigger({
  function SheetClose (line 17) | function SheetClose({
  function SheetPortal (line 23) | function SheetPortal({
  function SheetOverlay (line 29) | function SheetOverlay({
  function SheetContent (line 45) | function SheetContent({
  function SheetHeader (line 82) | function SheetHeader({ className, ...props }: React.ComponentProps<"div"...
  function SheetFooter (line 92) | function SheetFooter({ className, ...props }: React.ComponentProps<"div"...
  function SheetTitle (line 102) | function SheetTitle({
  function SheetDescription (line 115) | function SheetDescription({

FILE: src/components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 20) | const SIDEBAR_COOKIE_NAME = "sidebar_state";
  constant SIDEBAR_COOKIE_MAX_AGE (line 21) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
  constant SIDEBAR_WIDTH (line 22) | const SIDEBAR_WIDTH = "16rem";
  constant SIDEBAR_WIDTH_ICON (line 23) | const SIDEBAR_WIDTH_ICON = "3rem";
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 24) | const SIDEBAR_KEYBOARD_SHORTCUT = "b";
  type SidebarContextProps (line 26) | type SidebarContextProps = {
  function useSidebar (line 38) | function useSidebar() {
  function SidebarProvider (line 47) | function SidebarProvider({
  function Sidebar (line 145) | function Sidebar({
  function SidebarTrigger (line 247) | function SidebarTrigger({
  function SidebarRail (line 273) | function SidebarRail({ className, ...props }: React.ComponentProps<"butt...
  function SidebarInset (line 298) | function SidebarInset({ className, ...props }: React.ComponentProps<"mai...
  function SidebarInput (line 312) | function SidebarInput({
  function SidebarHeader (line 326) | function SidebarHeader({ className, ...props }: React.ComponentProps<"di...
  function SidebarFooter (line 337) | function SidebarFooter({ className, ...props }: React.ComponentProps<"di...
  function SidebarSeparator (line 348) | function SidebarSeparator({
  function SidebarContent (line 362) | function SidebarContent({ className, ...props }: React.ComponentProps<"d...
  function SidebarGroup (line 376) | function SidebarGroup({ className, ...props }: React.ComponentProps<"div...
  function SidebarGroupLabel (line 387) | function SidebarGroupLabel({
  function SidebarGroupAction (line 408) | function SidebarGroupAction({
  function SidebarGroupContent (line 431) | function SidebarGroupContent({
  function SidebarMenu (line 445) | function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
  function SidebarMenuItem (line 456) | function SidebarMenuItem({ className, ...props }: React.ComponentProps<"...
  function SidebarMenuButton (line 489) | function SidebarMenuButton({
  function SidebarMenuAction (line 539) | function SidebarMenuAction({
  function SidebarMenuBadge (line 571) | function SidebarMenuBadge({
  function SidebarMenuSkeleton (line 593) | function SidebarMenuSkeleton({
  function SidebarMenuSub (line 631) | function SidebarMenuSub({ className, ...props }: React.ComponentProps<"u...
  function SidebarMenuSubItem (line 646) | function SidebarMenuSubItem({
  function SidebarMenuSubButton (line 660) | function SidebarMenuSubButton({

FILE: src/components/ui/skeleton.tsx
  function Skeleton (line 3) | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

FILE: src/components/ui/slider.tsx
  function Slider (line 6) | function Slider({

FILE: src/components/ui/switch.tsx
  function Switch (line 6) | function Switch({

FILE: src/components/ui/table.tsx
  function Table (line 5) | function Table({ className, ...props }: React.ComponentProps<"table">) {
  function TableHeader (line 20) | function TableHeader({ className, ...props }: React.ComponentProps<"thea...
  function TableBody (line 30) | function TableBody({ className, ...props }: React.ComponentProps<"tbody"...
  function TableFooter (line 40) | function TableFooter({ className, ...props }: React.ComponentProps<"tfoo...
  function TableRow (line 53) | function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
  function TableHead (line 66) | function TableHead({ className, ...props }: React.ComponentProps<"th">) {
  function TableCell (line 79) | function TableCell({ className, ...props }: React.ComponentProps<"td">) {
  function TableCaption (line 92) | function TableCaption({

FILE: src/components/ui/tabs.tsx
  function Tabs (line 6) | function Tabs({
  function TabsList (line 19) | function TabsList({
  function TabsTrigger (line 35) | function TabsTrigger({
  function TabsContent (line 51) | function TabsContent({

FILE: src/components/ui/textarea.tsx
  type TextareaProps (line 5) | type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;

FILE: src/components/ui/tooltip.tsx
  function TooltipProvider (line 8) | function TooltipProvider({
  function Tooltip (line 21) | function Tooltip({
  function TooltipTrigger (line 31) | function TooltipTrigger({
  function TooltipContent (line 37) | function TooltipContent({

FILE: src/components/ui/version-alert.tsx
  type VersionAlertProps (line 7) | interface VersionAlertProps {
  function VersionAlert (line 27) | function VersionAlert({ updateInfo, onDownload }: VersionAlertProps) {

FILE: src/constants/terminal-themes.ts
  type TerminalTheme (line 1) | interface TerminalTheme {
  constant TERMINAL_THEMES (line 30) | const TERMINAL_THEMES: Record<string, TerminalTheme> = {
  constant TERMINAL_FONTS (line 676) | const TERMINAL_FONTS = [
  constant CURSOR_STYLES (line 723) | const CURSOR_STYLES = [
  constant BELL_STYLES (line 729) | const BELL_STYLES = [
  constant FAST_SCROLL_MODIFIERS (line 736) | const FAST_SCROLL_MODIFIERS = [
  constant DEFAULT_TERMINAL_CONFIG (line 742) | const DEFAULT_TERMINAL_CONFIG = {
  type TerminalConfigType (line 769) | type TerminalConfigType = typeof DEFAULT_TERMINAL_CONFIG;

FILE: src/hooks/use-confirmation.ts
  type ConfirmationOptions (line 4) | interface ConfirmationOptions {
  type ToastConfirmOptions (line 12) | interface ToastConfirmOptions {
  function useConfirmation (line 17) | function useConfirmation() {

FILE: src/hooks/use-mobile.ts
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768;
  function useIsMobile (line 5) | function useIsMobile() {

FILE: src/hooks/use-service-worker.ts
  type ServiceWorkerState (line 5) | interface ServiceWorkerState {
  function useServiceWorker (line 11) | function useServiceWorker(): ServiceWorkerState {

FILE: src/lib/base-path.ts
  function getBasePath (line 1) | function getBasePath(): string {

FILE: src/lib/clipboard-provider.ts
  class RobustClipboardProvider (line 6) | class RobustClipboardProvider implements IClipboardProvider {
    method constructor (line 10) | constructor() {
    method dispose (line 23) | dispose(): void {
    method readText (line 29) | readText(selection: ClipboardSelectionType): string {
    method writeText (line 33) | async writeText(

FILE: src/lib/db-health-monitor.ts
  type EventListener (line 1) | type EventListener = (...args: any[]) => void;
  class DatabaseHealthMonitor (line 3) | class DatabaseHealthMonitor {
    method constructor (line 12) | private constructor() {}
    method getInstance (line 14) | static getInstance(): DatabaseHealthMonitor {
    method on (line 21) | on(event: string, listener: EventListener): void {
    method off (line 28) | off(event: string, listener: EventListener): void {
    method emit (line 38) | private emit(event: string, ...args: any[]): void {
    method reportSessionExpired (line 45) | reportSessionExpired() {
    method reportDatabaseError (line 49) | reportDatabaseError(error: any, _wasAuthenticated: boolean = false) {
    method reportDatabaseSuccess (line 86) | reportDatabaseSuccess() {
    method clearErrorTimer (line 100) | private clearErrorTimer(): void {
    method isDatabaseHealthy (line 107) | isDatabaseHealthy(): boolean {
    method reset (line 111) | reset() {

FILE: src/lib/frontend-logger.ts
  type LogLevel (line 1) | type LogLevel = "debug" | "info" | "warn" | "error" | "success";
  type LogContext (line 3) | interface LogContext {
  class FrontendLogger (line 23) | class FrontendLogger {
    method constructor (line 29) | constructor(serviceName: string, serviceIcon: string, serviceColor: st...
    method getTimeStamp (line 36) | private getTimeStamp(): string {
    method formatMessage (line 41) | private formatMessage(
    method getLevelTag (line 70) | private getLevelTag(level: LogLevel): string {
    method getServiceTag (line 81) | private getServiceTag(): string {
    method shouldLog (line 85) | private shouldLog(level: LogLevel): boolean {
    method log (line 92) | private log(
    method debug (line 124) | debug(message: string, context?: LogContext): void {
    method info (line 128) | info(message: string, context?: LogContext): void {
    method warn (line 132) | warn(message: string, context?: LogContext): void {
    method error (line 136) | error(message: string, error?: unknown, context?: LogContext): void {
    method success (line 140) | success(message: string, context?: LogContext): void {
    method api (line 144) | api(message: string, context?: LogContext): void {
    method request (line 148) | request(message: string, context?: LogContext): void {
    method response (line 152) | response(message: string, context?: LogContext): void {
    method auth (line 156) | auth(message: string, context?: LogContext): void {
    method ssh (line 160) | ssh(message: string, context?: LogContext): void {
    method tunnel (line 164) | tunnel(message: string, context?: LogContext): void {
    method file (line 168) | file(message: string, context?: LogContext): void {
    method connection (line 172) | connection(message: string, context?: LogContext): void {
    method disconnect (line 179) | disconnect(message: string, context?: LogContext): void {
    method retry (line 186) | retry(message: string, context?: LogContext): void {
    method performance (line 190) | performance(message: string, context?: LogContext): void {
    method security (line 197) | security(message: string, context?: LogContext): void {
    method requestStart (line 201) | requestStart(method: string, url: string, context?: LogContext): void {
    method requestSuccess (line 213) | requestSuccess(
    method requestError (line 237) | requestError(
    method networkError (line 259) | networkError(
    method authError (line 277) | authError(method: string, url: string, context?: LogContext): void {
    method retryAttempt (line 289) | retryAttempt(
    method apiOperation (line 306) | apiOperation(operation: string, details: string, context?: LogContext)...
    method requestSummary (line 313) | requestSummary(
    method getShortUrl (line 332) | private getShortUrl(url: string): string {
    method getStatusIcon (line 343) | private getStatusIcon(status: number): string {
    method getPerformanceIcon (line 351) | private getPerformanceIcon(responseTime: number): string {
    method sanitizeUrl (line 359) | private sanitizeUrl(url: string): string {

FILE: src/lib/terminal-syntax-highlighter.ts
  constant ANSI_CODES (line 1) | const ANSI_CODES = {
  type HighlightPattern (line 28) | interface HighlightPattern {
  type MatchResult (line 36) | interface MatchResult {
  constant MAX_LINE_LENGTH (line 43) | const MAX_LINE_LENGTH = 5000;
  constant MAX_ANSI_CODES (line 44) | const MAX_ANSI_CODES = 10;
  constant PATTERNS (line 46) | const PATTERNS: HighlightPattern[] = [
  function hasExistingAnsiCodes (line 113) | function hasExistingAnsiCodes(text: string): boolean {
  function hasIncompleteAnsiSequence (line 122) | function hasIncompleteAnsiSequence(text: string): boolean {
  type TextSegment (line 126) | interface TextSegment {
  function parseAnsiSegments (line 131) | function parseAnsiSegments(text: string): TextSegment[] {
  function highlightPlainText (line 163) | function highlightPlainText(text: string): string {
  function highlightTerminalOutput (line 227) | function highlightTerminalOutput(text: string): string {
  function isSyntaxHighlightingEnabled (line 257) | function isSyntaxHighlightingEnabled(): boolean {

FILE: src/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {

FILE: src/main.tsx
  function useWindowWidth (line 47) | function useWindowWidth() {
  function RootApp (line 91) | function RootApp() {

FILE: src/types/connection-log.ts
  type ConnectionStage (line 1) | type ConnectionStage =
  type LogEntry (line 29) | type LogEntry = {
  type ConnectionLogResponse (line 38) | interface ConnectionLogResponse {

FILE: src/types/electron.d.ts
  type ServerConfig (line 1) | interface ServerConfig {
  type ConnectionTestResult (line 6) | interface ConnectionTestResult {
  type DialogOptions (line 12) | interface DialogOptions {
  type DialogResult (line 21) | interface DialogResult {
  type ElectronAPI (line 28) | interface ElectronAPI {
  type Window (line 88) | interface Window {

FILE: src/types/guacamole-common-js.d.ts
  class Client (line 3) | class Client {
  class Display (line 17) | class Display {
  class Tunnel (line 25) | class Tunnel {
  class WebSocketTunnel (line 30) | class WebSocketTunnel extends Tunnel {
  class Mouse (line 34) | class Mouse {
  class State (line 43) | class State {
  class Keyboard (line 72) | class Keyboard {
  class Status (line 78) | class Status {
  class InputStream (line 84) | class InputStream {
  class OutputStream (line 89) | class OutputStream {
  class StringReader (line 94) | class StringReader {
  class StringWriter (line 100) | class StringWriter {

FILE: src/types/index.ts
  type ConnectionType (line 8) | type ConnectionType = "ssh" | "rdp" | "vnc" | "telnet";
  type SSHAuthType (line 9) | type SSHAuthType = "password" | "key" | "credential" | "none" | "opkssh";
  type GuacamoleAuthType (line 10) | type GuacamoleAuthType = "password" | "credential";
  type HostFeatureFlags (line 12) | interface HostFeatureFlags {
  type JumpHost (line 20) | interface JumpHost {
  type QuickAction (line 24) | interface QuickAction {
  type Host (line 29) | interface Host {
  type JumpHostData (line 91) | interface JumpHostData {
  type QuickActionData (line 95) | interface QuickActionData {
  type ProxyNode (line 100) | interface ProxyNode {
  type HostData (line 108) | interface HostData {
  type SSHHost (line 157) | type SSHHost = Host;
  type SSHHostData (line 158) | type SSHHostData = HostData;
  type SSHFolder (line 160) | interface SSHFolder {
  type Credential (line 174) | interface Credential {
  type CredentialBackend (line 193) | interface CredentialBackend {
  type CredentialData (line 215) | interface CredentialData {
  type TunnelConnection (line 233) | interface TunnelConnection {
  type TunnelConfig (line 250) | interface TunnelConfig {
  type TunnelStatus (line 296) | interface TunnelStatus {
  type Tab (line 318) | interface Tab {
  type FileManagerFile (line 330) | interface FileManagerFile {
  type FileManagerShortcut (line 338) | interface FileManagerShortcut {
  type FileItem (line 343) | interface FileItem {
  type ShortcutItem (line 358) | interface ShortcutItem {
  type SSHConnection (line 363) | interface SSHConnection {
  type HostInfo (line 376) | interface HostInfo {
  type TermixAlert (line 388) | interface TermixAlert {
  type TerminalConfig (line 403) | interface TerminalConfig {
  type TabContextTab (line 434) | interface TabContextTab {
  type SplitLayout (line 458) | type SplitLayout = "2h" | "2v" | "3l" | "3r" | "3t" | "4grid";
  type SplitConfiguration (line 460) | interface SplitConfiguration {
  type SplitLayoutOption (line 465) | interface SplitLayoutOption {
  constant CONNECTION_STATES (line 477) | const CONNECTION_STATES = {
  type ConnectionState (line 489) | type ConnectionState =
  type ErrorType (line 492) | type ErrorType =
  type AuthType (line 503) | type AuthType = "password" | "key" | "credential" | "none" | "opkssh";
  type KeyType (line 505) | type KeyType = "rsa" | "ecdsa" | "ed25519";
  type ApiResponse (line 511) | interface ApiResponse<T = unknown> {
  type CredentialsManagerProps (line 522) | interface CredentialsManagerProps {
  type CredentialEditorProps (line 527) | interface CredentialEditorProps {
  type CredentialViewerProps (line 533) | interface CredentialViewerProps {
  type CredentialSelectorProps (line 539) | interface CredentialSelectorProps {
  type HostManagerProps (line 544) | interface HostManagerProps {
  type SSHManagerHostEditorProps (line 556) | interface SSHManagerHostEditorProps {
  type SSHManagerHostViewerProps (line 561) | interface SSHManagerHostViewerProps {
  type HostProps (line 566) | interface HostProps {
  type SSHTunnelProps (line 571) | interface SSHTunnelProps {
  type SSHTunnelViewerProps (line 575) | interface SSHTunnelViewerProps {
  type FileManagerProps (line 593) | interface FileManagerProps {
  type AlertCardProps (line 599) | interface AlertCardProps {
  type AlertManagerProps (line 604) | interface AlertManagerProps {
  type SSHTunnelObjectProps (line 610) | interface SSHTunnelObjectProps {
  type FolderStats (line 624) | interface FolderStats {
  type Snippet (line 636) | interface Snippet {
  type SnippetData (line 648) | interface SnippetData {
  type SnippetFolder (line 656) | interface SnippetFolder {
  type HostConfig (line 670) | interface HostConfig {
  type VerificationData (line 675) | interface VerificationData {
  type Optional (line 687) | type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
  type RequiredFields (line 689) | type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
  type PartialExcept (line 691) | type PartialExcept<T, K extends keyof T> = Partial<T> & Pick<T, K>;
  type AuthenticatedRequest (line 697) | interface AuthenticatedRequest extends Request {
  type GitHubAsset (line 710) | interface GitHubAsset {
  type GitHubRelease (line 718) | interface GitHubRelease {
  type GitHubAPIResponse (line 730) | interface GitHubAPIResponse<T> {
  type CacheEntry (line 741) | interface CacheEntry<T = unknown> {
  type ExportSummary (line 751) | interface ExportSummary {
  type ImportResult (line 762) | interface ImportResult {
  type ExportRequestBody (line 767) | interface ExportRequestBody {
  type ImportRequestBody (line 771) | interface ImportRequestBody {
  type ExportPreviewBody (line 775) | interface ExportPreviewBody {
  type RestoreRequestBody (line 780) | interface RestoreRequestBody {
  type DockerContainer (line 789) | interface DockerContainer {
  type DockerStats (line 810) | interface DockerStats {
  type DockerLogOptions (line 822) | interface DockerLogOptions {
  type DockerValidation (line 830) | interface DockerValidation {

FILE: src/types/stats-widgets.ts
  type WidgetType (line 1) | type WidgetType =
  type ListeningPort (line 13) | interface ListeningPort {
  type PortsMetrics (line 22) | interface PortsMetrics {
  type FirewallRule (line 27) | interface FirewallRule {
  type FirewallChain (line 40) | interface FirewallChain {
  type FirewallMetrics (line 46) | interface FirewallMetrics {
  type StatsConfig (line 52) | interface StatsConfig {
  constant DEFAULT_STATS_CONFIG (line 63) | const DEFAULT_STATS_CONFIG: StatsConfig = {

FILE: src/ui/contexts/ServerStatusContext.tsx
  type StatusValue (line 13) | type StatusValue = "online" | "offline" | "degraded";
  type ServerStatusEntry (line 15) | interface ServerStatusEntry {
  type ServerStatusContextType (line 20) | interface ServerStatusContextType {
  constant POLL_INTERVAL (line 29) | const POLL_INTERVAL = 30000;
  function ServerStatusProvider (line 31) | function ServerStatusProvider({
  function useServerStatus (line 197) | function useServerStatus() {
  function useHostStatus (line 207) | function useHostStatus(

FILE: src/ui/desktop/DesktopApp.tsx
  function AppContent (line 32) | function AppContent({
  class TabErrorBoundary (line 630) | class TabErrorBoundary extends Component<
    method constructor (line 634) | constructor(props: { children: ReactNode }) {
    method getDerivedStateFromError (line 639) | static getDerivedStateFromError(error: Error) {
    method componentDidCatch (line 646) | componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    method render (line 658) | render() {
  function DesktopApp (line 666) | function DesktopApp() {

FILE: src/ui/desktop/apps/FullScreenAppWrapper.tsx
  type FullScreenAppWrapperProps (line 10) | interface FullScreenAppWrapperProps {

FILE: src/ui/desktop/apps/admin/AdminSettings.tsx
  type AdminSettingsProps (line 35) | interface AdminSettingsProps {
  function AdminSettings (line 41) | function AdminSettings({

FILE: src/ui/desktop/apps/admin/dialogs/CreateUserDialog.tsx
  type CreateUserDialogProps (line 20) | interface CreateUserDialogProps {
  function CreateUserDialog (line 26) | function CreateUserDialog({

FILE: src/ui/desktop/apps/admin/dialogs/LinkAccountDialog.tsx
  type LinkAccountDialogProps (line 19) | interface LinkAccountDialogProps {
  function LinkAccountDialog (line 26) | function LinkAccountDialog({

FILE: src/ui/desktop/apps/admin/dialogs/UserEditDialog.tsx
  type User (line 42) | interface User {
  type UserEditDialogProps (line 50) | interface UserEditDialogProps {
  function UserEditDialog (line 59) | function UserEditDialog({

FILE: src/ui/desktop/apps/admin/tabs/DatabaseSecurityTab.tsx
  type DatabaseSecurityTabProps (line 11) | interface DatabaseSecurityTabProps {
  function DatabaseSecurityTab (line 17) | function DatabaseSecurityTab({

FILE: src/ui/desktop/apps/admin/tabs/GeneralSettingsTab.tsx
  type GeneralSettingsTabProps (line 25) | interface GeneralSettingsTabProps {
  function GeneralSettingsTab (line 41) | function GeneralSettingsTab({

FILE: src/ui/desktop/apps/admin/tabs/OIDCSettingsTab.tsx
  type OIDCSettingsTabProps (line 13) | interface OIDCSettingsTabProps {
  function OIDCSettingsTab (line 43) | function OIDCSettingsTab({

FILE: src/ui/desktop/apps/admin/tabs/RolesTab.tsx
  function RolesTab (line 35) | function RolesTab(): React.ReactElement {

FILE: src/ui/desktop/apps/admin/tabs/SessionManagementTab.tsx
  type Session (line 21) | interface Session {
  type SessionManagementTabProps (line 34) | interface SessionManagementTabProps {
  function SessionManagementTab (line 40) | function SessionManagementTab({

FILE: src/ui/desktop/apps/admin/tabs/UserManagementTab.tsx
  type User (line 17) | interface User {
  type UserManagementTabProps (line 25) | interface UserManagementTabProps {
  function UserManagementTab (line 36) | function UserManagementTab({

FILE: src/ui/desktop/apps/command-palette/CommandPalette.tsx
  type SSHHost (line 51) | interface SSHHost {
  function CommandPalette (line 86) | function CommandPalette({

FILE: src/ui/desktop/apps/dashboard/Dashboard.tsx
  type DashboardProps (line 36) | interface DashboardProps {
  function Dashboard (line 51) | function Dashboard({

FILE: src/ui/desktop/apps/dashboard/apps/UpdateLog.tsx
  type UpdateLogProps (line 16) | interface UpdateLogProps extends React.ComponentProps<"div"> {
  type ReleaseItem (line 20) | interface ReleaseItem {
  type RSSResponse (line 37) | interface RSSResponse {
  type VersionResponse (line 50) | interface VersionResponse {
  function UpdateLog (line 62) | function UpdateLog({ loggedIn }: UpdateLogProps) {

FILE: src/ui/desktop/apps/dashboard/apps/alerts/AlertCard.tsx
  type AlertCardProps (line 22) | interface AlertCardProps {
  function AlertCard (line 70) | function AlertCard({

FILE: src/ui/desktop/apps/dashboard/apps/alerts/AlertManager.tsx
  type AlertManagerProps (line 9) | interface AlertManagerProps {
  function AlertManager (line 14) | function AlertManager({

FILE: src/ui/desktop/apps/dashboard/cards/NetworkGraphCard.tsx
  constant AVAILABLE_COLORS (line 77) | const AVAILABLE_COLORS = [
  type HostMap (line 88) | interface HostMap {
  type ContextMenuState (line 92) | interface ContextMenuState {
  type NetworkGraphCardProps (line 100) | interface NetworkGraphCardProps {
  function NetworkGraphCard (line 107) | function NetworkGraphCard({

FILE: src/ui/desktop/apps/dashboard/cards/QuickActionsCard.tsx
  type QuickActionsCardProps (line 6) | interface QuickActionsCardProps {
  function QuickActionsCard (line 14) | function QuickActionsCard({

FILE: src/ui/desktop/apps/dashboard/cards/RecentActivityCard.tsx
  type RecentActivityCardProps (line 19) | interface RecentActivityCardProps {
  function RecentActivityCard (line 26) | function RecentActivityCard({

FILE: src/ui/desktop/apps/dashboard/cards/ServerOverviewCard.tsx
  type ServerOverviewCardProps (line 14) | interface ServerOverviewCardProps {
  function ServerOverviewCard (line 26) | function ServerOverviewCard({

FILE: src/ui/desktop/apps/dashboard/cards/ServerStatsCard.tsx
  type ServerStat (line 6) | interface ServerStat {
  type ServerStatsCardProps (line 13) | interface ServerStatsCardProps {
  function ServerStatsCard (line 19) | function ServerStatsCard({

FILE: src/ui/desktop/apps/dashboard/components/DashboardSettingsDialog.tsx
  type DashboardSettingsDialogProps (line 16) | interface DashboardSettingsDialogProps {
  function DashboardSettingsDialog (line 24) | function DashboardSettingsDialog({

FILE: src/ui/desktop/apps/dashboard/hooks/useDashboardPreferences.ts
  constant DEFAULT_LAYOUT (line 8) | const DEFAULT_LAYOUT: DashboardLayout = {
  function useDashboardPreferences (line 18) | function useDashboardPreferences(enabled: boolean = true) {

FILE: src/ui/desktop/apps/features/docker/DockerApp.tsx
  type DockerAppProps (line 5) | interface DockerAppProps {

FILE: src/ui/desktop/apps/features/docker/DockerManager.tsx
  type DockerManagerProps (line 37) | interface DockerManagerProps {
  type TabData (line 46) | interface TabData {
  function DockerManagerInner (line 52) | function DockerManagerInner({
  function DockerManager (line 756) | function DockerManager(props: DockerManagerProps): React.ReactElement {

FILE: src/ui/desktop/apps/features/docker/components/ConsoleTerminal.tsx
  type ConsoleTerminalProps (line 24) | interface ConsoleTerminalProps {
  function ConsoleTerminal (line 32) | function ConsoleTerminal({

FILE: src/ui/desktop/apps/features/docker/components/ContainerCard.tsx
  type ContainerCardProps (line 37) | interface ContainerCardProps {
  function ContainerCard (line 45) | function ContainerCard({

FILE: src/ui/desktop/apps/features/docker/components/ContainerDetail.tsx
  type ContainerDetailProps (line 17) | interface ContainerDetailProps {
  function ContainerDetail (line 25) | function ContainerDetail({

FILE: src/ui/desktop/apps/features/docker/components/ContainerList.tsx
  type ContainerListProps (line 15) | interface ContainerListProps {
  function ContainerList (line 23) | function ContainerList({

FILE: src/ui/desktop/apps/features/docker/components/ContainerStats.tsx
  type ContainerStatsProps (line 15) | interface ContainerStatsProps {
  function ContainerStats (line 22) | function ContainerStats({

FILE: src/ui/desktop/apps/features/docker/components/LogViewer.tsx
  type LogViewerProps (line 19) | interface LogViewerProps {
  function LogViewer (line 25) | function LogViewer({

FILE: src/ui/desktop/apps/features/file-manager/DragIndicator.tsx
  type DragIndicatorProps (line 13) | interface DragIndicatorProps {
  function DragIndicator (line 24) | function DragIndicator({

FILE: src/ui/desktop/apps/features/file-manager/FileManager.tsx
  type FileManagerProps (line 89) | interface FileManagerProps {
  type CreateIntent (line 94) | interface CreateIntent {
  function formatFileSize (line 101) | function formatFileSize(bytes?: number): string {
  function FileManagerContent (line 119) | function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
  function FileManagerInner (line 2557) | function FileManagerInner({ initialHost, onClose }: FileManagerProps) {
  function FileManager (line 2565) | function FileManager(props: FileManagerProps) {

FILE: src/ui/desktop/apps/features/file-manager/FileManagerApp.tsx
  type FileManagerAppProps (line 5) | interface FileManagerAppProps {

FILE: src/ui/desktop/apps/features/file-manager/FileManagerContextMenu.tsx
  type FileItem (line 25) | interface FileItem {
  type ContextMenuProps (line 37) | interface ContextMenuProps {
  type MenuItem (line 69) | interface MenuItem {
  function FileManagerContextMenu (line 79) | function FileManagerContextMenu({

FILE: src/ui/desktop/apps/features/file-manager/FileManagerGrid.tsx
  type CreateIntent (line 30) | interface CreateIntent {
  function formatFileSize (line 37) | function formatFileSize(bytes?: number): string {
  type DragState (line 57) | interface DragState {
  type FileManagerGridProps (line 66) | interface FileManagerGridProps {
  function FileManagerGrid (line 191) | function FileManagerGrid({
  function CreateIntentGridItem (line 1384) | function CreateIntentGridItem({
  function CreateIntentListItem (line 1441) | function CreateIntentListItem({

FILE: src/ui/desktop/apps/features/file-manager/FileManagerSidebar.tsx
  type RecentFileData (line 26) | interface RecentFileData {
  type PinnedFileData (line 34) | interface PinnedFileData {
  type ShortcutData (line 41) | interface ShortcutData {
  type DirectoryItemData (line 48) | interface DirectoryItemData {
  type SidebarItem (line 55) | interface SidebarItem {
  type FileManagerSidebarProps (line 65) | interface FileManagerSidebarProps {
  function FileManagerSidebar (line 74) | function FileManagerSidebar({

FILE: src/ui/desktop/apps/features/file-manager/SudoPasswordDialog.tsx
  type SudoPasswordDialogProps (line 15) | interface SudoPasswordDialogProps {
  function SudoPasswordDialog (line 21) | function SudoPasswordDialog({

FILE: src/ui/desktop/apps/features/file-manager/components/CompressDialog.tsx
  type CompressDialogProps (line 22) | interface CompressDialogProps {
  function CompressDialog (line 29) | function CompressDialog({

FILE: src/ui/desktop/apps/features/file-manager/components/DiffViewer.tsx
  type DiffViewerProps (line 22) | interface DiffViewerProps {
  function DiffViewer (line 31) | function DiffViewer({

FILE: src/ui/desktop/apps/features/file-manager/components/DiffWindow.tsx
  type DiffWindowProps (line 8) | interface DiffWindowProps {
  function DiffWindow (line 18) | function DiffWindow({

FILE: src/ui/desktop/apps/features/file-manager/components/DraggableWindow.tsx
  type DraggableWindowProps (line 6) | interface DraggableWindowProps {
  function DraggableWindow (line 25) | function DraggableWindow({

FILE: src/ui/desktop/apps/features/file-manager/components/FileViewer.tsx
  type FileItem (line 65) | interface FileItem {
  type FileViewerProps (line 76) | interface FileViewerProps {
  function getLanguageIcon (line 92) | function getLanguageIcon(filename: string): React.ReactNode {
  function getFileType (line 141) | function getFileType(filename: string): {
  function getLanguageExtension (line 238) | function getLanguageExtension(filename: string) {
  function formatFileSize (line 284) | function formatFileSize(bytes?: number, t?: (key: string) => string): st...
  function FileViewer (line 291) | function FileViewer({

FILE: src/ui/desktop/apps/features/file-manager/components/FileWindow.tsx
  type FileItem (line 15) | interface FileItem {
  type SSHHost (line 26) | interface SSHHost {
  type FileWindowProps (line 40) | interface FileWindowProps {
  function isDisplayableText (line 50) | function isDisplayableText(str: string): boolean {
  function FileWindow (line 66) | function FileWindow({

FILE: src/ui/desktop/apps/features/file-manager/components/PermissionsDialog.tsx
  type FileItem (line 17) | interface FileItem {
  type PermissionsDialogProps (line 26) | interface PermissionsDialogProps {
  function PermissionsDialog (line 69) | function PermissionsDialog({

FILE: src/ui/desktop/apps/features/file-manager/components/TerminalWindow.tsx
  type SSHHost (line 7) | interface SSHHost {
  type TerminalWindowProps (line 21) | interface TerminalWindowProps {
  function TerminalWindow (line 30) | function TerminalWindow({

FILE: src/ui/desktop/apps/features/file-manager/components/WindowManager.tsx
  type WindowInstance (line 3) | interface WindowInstance {
  type WindowManagerProps (line 16) | interface WindowManagerProps {
  type WindowManagerContextType (line 20) | interface WindowManagerContextType {
  function WindowManager (line 33) | function WindowManager({ children }: WindowManagerProps) {
  function useWindowManager (line 132) | function useWindowManager() {

FILE: src/ui/desktop/apps/features/file-manager/hooks/useDragAndDrop.ts
  type DragAndDropState (line 3) | interface DragAndDropState {
  type UseDragAndDropProps (line 9) | interface UseDragAndDropProps {
  function useDragAndDrop (line 16) | function useDragAndDrop({

FILE: src/ui/desktop/apps/features/file-manager/hooks/useFileSelection.ts
  type FileItem (line 3) | interface FileItem {
  function useFileSelection (line 14) | function useFileSelection() {

FILE: src/ui/desktop/apps/features/guacamole/GuacamoleApp.tsx
  type GuacamoleAppProps (line 7) | interface GuacamoleAppProps {
  type GuacamoleAppInnerProps (line 47) | interface GuacamoleAppInnerProps {

FILE: src/ui/desktop/apps/features/guacamole/GuacamoleDisplay.tsx
  type GuacamoleConnectionType (line 14) | type GuacamoleConnectionType = "rdp" | "vnc" | "telnet";
  type GuacamoleConnectionConfig (line 16) | interface GuacamoleConnectionConfig {
  type GuacamoleDisplayHandle (line 31) | interface GuacamoleDisplayHandle {
  type GuacamoleDisplayProps (line 38) | interface GuacamoleDisplayProps {

FILE: src/ui/desktop/apps/features/server-stats/ServerStats.tsx
  type QuickAction (line 47) | interface QuickAction {
  type HostConfig (line 52) | interface HostConfig {
  type TabData (line 65) | interface TabData {
  type ServerProps (line 73) | interface ServerProps {
  function ServerStatsInner (line 81) | function ServerStatsInner({
  function ServerStats (line 869) | function ServerStats(props: ServerProps): React.ReactElement {

FILE: src/ui/desktop/apps/features/server-stats/ServerStatsApp.tsx
  type ServerStatsAppProps (line 5) | interface ServerStatsAppProps {

FILE: src/ui/desktop/apps/features/server-stats/widgets/CpuWidget.tsx
  type CpuWidgetProps (line 17) | interface CpuWidgetProps {
  function CpuWidget (line 22) | function CpuWidget({ metrics, metricsHistory }: CpuWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/DiskWidget.tsx
  type DiskWidgetProps (line 17) | interface DiskWidgetProps {
  function DiskWidget (line 22) | function DiskWidget({ metrics, metricsHistory }: DiskWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/FirewallWidget.tsx
  type FirewallWidgetProps (line 11) | interface FirewallWidgetProps {
  function RuleRow (line 16) | function RuleRow({ rule }: { rule: FirewallRule }) {
  function ChainSection (line 76) | function ChainSection({ chain }: { chain: FirewallChain }) {
  function FirewallWidget (line 143) | function FirewallWidget({ metrics }: FirewallWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/LoginStatsWidget.tsx
  type LoginRecord (line 5) | interface LoginRecord {
  type LoginStatsMetrics (line 12) | interface LoginStatsMetrics {
  type ServerMetrics (line 19) | interface ServerMetrics {
  type LoginStatsWidgetProps (line 23) | interface LoginStatsWidgetProps {
  function LoginStatsWidget (line 28) | function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/MemoryWidget.tsx
  type MemoryWidgetProps (line 17) | interface MemoryWidgetProps {
  function MemoryWidget (line 22) | function MemoryWidget({ metrics, metricsHistory }: MemoryWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/NetworkWidget.tsx
  type NetworkWidgetProps (line 6) | interface NetworkWidgetProps {
  function NetworkWidget (line 11) | function NetworkWidget({ metrics }: NetworkWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/PortsWidget.tsx
  type PortsWidgetProps (line 7) | interface PortsWidgetProps {
  function PortRow (line 12) | function PortRow({ port }: { port: ListeningPort }) {
  function PortsWidget (line 43) | function PortsWidget({ metrics }: PortsWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/ProcessesWidget.tsx
  type ProcessesWidgetProps (line 6) | interface ProcessesWidgetProps {
  function ProcessesWidget (line 11) | function ProcessesWidget({ metrics }: ProcessesWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/SystemWidget.tsx
  type SystemWidgetProps (line 6) | interface SystemWidgetProps {
  function SystemWidget (line 11) | function SystemWidget({ metrics }: SystemWidgetProps) {

FILE: src/ui/desktop/apps/features/server-stats/widgets/UptimeWidget.tsx
  type UptimeWidgetProps (line 6) | interface UptimeWidgetProps {
  function UptimeWidget (line 11) | function UptimeWidget({ metrics }: UptimeWidgetProps) {

FILE: src/ui/desktop/apps/features/terminal/SudoPasswordPopup.tsx
  type SudoPasswordPopupProps (line 6) | interface SudoPasswordPopupProps {
  function SudoPasswordPopup (line 14) | function SudoPasswordPopup({

FILE: src/ui/desktop/apps/features/terminal/Terminal.tsx
  type HostConfig (line 51) | interface HostConfig {
  type TerminalHandle (line 67) | interface TerminalHandle {
  type SSHTerminalProps (line 75) | interface SSHTerminalProps {
  function hardRefresh (line 382) | function hardRefresh() {
  function performFit (line 399) | function performFit() {
  function handleTotpSubmit (line 435) | function handleTotpSubmit(code: string) {
  function handleTotpCancel (line 453) | function handleTotpCancel() {
  function handleWarpgateContinue (line 463) | function handleWarpgateContinue() {
  function handleWarpgateCancel (line 481) | function handleWarpgateCancel() {
  function handleWarpgateOpenUrl (line 492) | function handleWarpgateOpenUrl() {
  function handleAuthDialogSubmit (line 498) | function handleAuthDialogSubmit(credentials: {
  function handleAuthDialogCancel (line 527) | function handleAuthDialogCancel() {
  function scheduleNotify (line 532) | function scheduleNotify(cols: number, rows: number) {
  function getUseRightClickCopyPaste (line 614) | function getUseRightClickCopyPaste() {
  function attemptReconnection (line 618) | function attemptReconnection() {
  function connectToHost (line 709) | function connectToHost(cols: number, rows: number) {
  function setupWebSocketListeners (line 785) | function setupWebSocketListeners(
  function writeTextToClipboard (line 1470) | async function writeTextToClipboard(text: string): Promise<boolean> {
  function readTextFromClipboard (line 1496) | async function readTextFromClipboard(): Promise<string> {

FILE: src/ui/desktop/apps/features/terminal/TerminalApp.tsx
  type TerminalAppProps (line 5) | interface TerminalAppProps {

FILE: src/ui/desktop/apps/features/terminal/TerminalPreview.tsx
  type TerminalPreviewProps (line 8) | interface TerminalPreviewProps {
  function TerminalPreview (line 18) | function TerminalPreview({

FILE: src/ui/desktop/apps/features/terminal/command-history/CommandAutocomplete.tsx
  type CommandAutocompleteProps (line 4) | interface CommandAutocompleteProps {
  function CommandAutocomplete (line 12) | function CommandAutocomplete({

FILE: src/ui/desktop/apps/features/terminal/command-history/CommandHistoryContext.tsx
  type CommandHistoryContextType (line 9) | interface CommandHistoryContextType {
  function CommandHistoryProvider (line 26) | function CommandHistoryProvider({ children }: { children: ReactNode }) {
  function useCommandHistory (line 77) | function useCommandHistory() {

FILE: src/ui/desktop/apps/features/tunnel/Tunnel.tsx
  function Tunnel (line 19) | function Tunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement {

FILE: src/ui/desktop/apps/features/tunnel/TunnelApp.tsx
  type TunnelAppProps (line 5) | interface TunnelAppProps {

FILE: src/ui/desktop/apps/features/tunnel/TunnelManager.tsx
  type HostConfig (line 8) | interface HostConfig {
  type TunnelManagerProps (line 19) | interface TunnelManagerProps {
  function TunnelManager (line 27) | function TunnelManager({

FILE: src/ui/desktop/apps/features/tunnel/TunnelObject.tsx
  function TunnelObject (line 25) | function TunnelObject({

FILE: src/ui/desktop/apps/features/tunnel/TunnelViewer.tsx
  type SSHTunnelViewerProps (line 6) | interface SSHTunnelViewerProps {
  function TunnelViewer (line 17) | function TunnelViewer({

FILE: src/ui/desktop/apps/host-manager/credentials/CredentialEditor.tsx
  function CredentialEditor (line 40) | function CredentialEditor({

FILE: src/ui/desktop/apps/host-manager/credentials/CredentialSelector.tsx
  type CredentialSelectorProps (line 10) | interface CredentialSelectorProps {
  function CredentialSelector (line 16) | function CredentialSelector({

FILE: src/ui/desktop/apps/host-manager/credentials/CredentialsManager.tsx
  function CredentialsManager (line 67) | function CredentialsManager({

FILE: src/ui/desktop/apps/host-manager/credentials/tabs/CredentialAuthenticationTab.tsx
  function CredentialAuthenticationTab (line 27) | function CredentialAuthenticationTab({

FILE: src/ui/desktop/apps/host-manager/credentials/tabs/CredentialGeneralTab.tsx
  function CredentialGeneralTab (line 13) | function CredentialGeneralTab({

FILE: src/ui/desktop/apps/host-manager/credentials/tabs/shared/tab-types.ts
  type CredentialGeneralTabProps (line 4) | interface CredentialGeneralTabProps {
  type CredentialAuthenticationTabProps (line 17) | interface CredentialAuthenticationTabProps {

FILE: src/ui/desktop/apps/host-manager/dialogs/FolderEditDialog.tsx
  type FolderEditDialogProps (line 26) | interface FolderEditDialogProps {
  constant AVAILABLE_COLORS (line 35) | const AVAILABLE_COLORS = [
  constant AVAILABLE_ICONS (line 46) | const AVAILABLE_ICONS = [
  function FolderEditDialog (line 59) | function FolderEditDialog({

FILE: src/ui/desktop/apps/host-manager/hosts/HostManager.tsx
  function HostManager (line 17) | function HostManager({

FILE: src/ui/desktop/apps/host-manager/hosts/HostManagerEditor.tsx
  type User (line 139) | interface User {
  type SSHManagerHostEditorProps (line 145) | interface SSHManagerHostEditorProps {
  function HostManagerEditor (line 151) | function HostManagerEditor({

FILE: src/ui/desktop/apps/host-manager/hosts/HostManagerViewer.tsx
  constant INITIAL_HOSTS_PER_FOLDER (line 103) | const INITIAL_HOSTS_PER_FOLDER = 12;
  function HostManagerViewer (line 105) | function HostManagerViewer({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostDockerTab.tsx
  function HostDockerTab (line 12) | function HostDockerTab({ form, t }: HostDockerTabProps) {

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostFileManagerTab.tsx
  function HostFileManagerTab (line 12) | function HostFileManagerTab({ form, t }: HostFileManagerTabProps) {

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostGeneralTab.tsx
  function HostGeneralTab (line 53) | function HostGeneralTab({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostRemoteDesktopTab.tsx
  function GuacField (line 26) | function GuacField({
  function GuacSelect (line 116) | function GuacSelect({
  function HostRemoteDesktopTab (line 161) | function HostRemoteDesktopTab({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostSharingTab.tsx
  type User (line 61) | interface User {
  type HostSharingTabProps (line 67) | interface HostSharingTabProps {
  function HostSharingTab (line 72) | function HostSharingTab({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostStatisticsTab.tsx
  function HostStatisticsTab (line 24) | function HostStatisticsTab({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostStatusTab.tsx
  function HostStatusTab (line 19) | function HostStatusTab({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostTerminalTab.tsx
  function HostTerminalTab (line 51) | function HostTerminalTab({ form, snippets, t }: HostTerminalTabProps) {

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/HostTunnelTab.tsx
  function HostTunnelTab (line 14) | function HostTunnelTab({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/shared/JumpHostItem.tsx
  function JumpHostItem (line 19) | function JumpHostItem({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/shared/QuickActionItem.tsx
  function QuickActionItem (line 20) | function QuickActionItem({

FILE: src/ui/desktop/apps/host-manager/hosts/tabs/shared/tab-types.ts
  type HostGeneralTabProps (line 5) | interface HostGeneralTabProps {
  type HostTerminalTabProps (line 36) | interface HostTerminalTabProps {
  type HostDockerTabProps (line 42) | interface HostDockerTabProps {
  type HostTunnelTabProps (line 47) | interface HostTunnelTabProps {
  type HostFileManagerTabProps (line 64) | interface HostFileManagerTabProps {
  type HostStatisticsTabProps (line 69) | interface HostStatisticsTabProps {
  type HostRemoteDesktopTabProps (line 79) | interface HostRemoteDesktopTabProps {
  type HostSharingTabProps (line 85) | interface HostSharingTabProps {
  type JumpHostItemProps (line 90) | interface JumpHostItemProps {
  type QuickActionItemProps (line 100) | interface QuickActionItemProps {

FILE: src/ui/desktop/apps/tools/SSHToolsSidebar.tsx
  type TabData (line 86) | interface TabData {
  type SSHToolsSidebarProps (line 102) | interface SSHToolsSidebarProps {
  constant AVAILABLE_COLORS (line 112) | const AVAILABLE_COLORS = [
  constant AVAILABLE_ICONS (line 123) | const AVAILABLE_ICONS = [
  function SSHToolsSidebar (line 136) | function SSHToolsSidebar({

FILE: src/ui/desktop/authentication/Auth.tsx
  function getCookie (line 41) | function getCookie(name: string): string | undefined {
  type ExtendedWindow (line 47) | interface ExtendedWindow extends Window {
  type AuthProps (line 51) | interface AuthProps extends React.ComponentProps<"div"> {
  function Auth (line 66) | function Auth({

FILE: src/ui/desktop/authentication/ElectronLoginForm.tsx
  type ElectronLoginFormProps (line 8) | interface ElectronLoginFormProps {
  function ElectronLoginForm (line 14) | function ElectronLoginForm({

FILE: src/ui/desktop/authentication/ElectronServerConfig.tsx
  type ServerConfigProps (line 17) | interface ServerConfigProps {
  function ElectronServerConfig (line 24) | function ElectronServerConfig({

FILE: src/ui/desktop/navigation/AppView.tsx
  type TabData (line 30) | interface TabData {
  type LayoutNode (line 46) | type LayoutNode =
  constant SPLIT_LAYOUTS (line 50) | const SPLIT_LAYOUTS: Record<number, LayoutNode> = {
  type TerminalViewProps (line 79) | interface TerminalViewProps {
  function AppView (line 85) | function AppView({

FILE: src/ui/desktop/navigation/LeftSidebar.tsx
  type SidebarProps (line 41) | interface SidebarProps {
  function handleLogout (line 49) | async function handleLogout() {
  function LeftSidebar (line 64) | function LeftSidebar({

FILE: src/ui/desktop/navigation/TopNavbar.tsx
  type TabData (line 14) | interface TabData {
  type TopNavbarProps (line 26) | interface TopNavbarProps {
  function TopNavbar (line 33) | function TopNavbar({

FILE: src/ui/desktop/navigation/animations/SimpleLoader.tsx
  type SimpleLoaderProps (line 4) | interface SimpleLoaderProps {
  function SimpleLoader (line 11) | function SimpleLoader({

FILE: src/ui/desktop/navigation/connection-log/ConnectionLog.tsx
  type ConnectionLogProps (line 16) | interface ConnectionLogProps {
  function ConnectionLog (line 23) | function ConnectionLog({

FILE: src/ui/desktop/navigation/connection-log/ConnectionLogContext.tsx
  type ConnectionLogContextType (line 4) | interface ConnectionLogContextType {
  function ConnectionLogProvider (line 18) | function ConnectionLogProvider({
  function useConnectionLog (line 81) | function useConnectionLog() {

FILE: src/ui/desktop/navigation/dialogs/HostKeyVerificationDialog.tsx
  type HostKeyVerificationDialogProps (line 14) | interface HostKeyVerificationDialogProps {
  function HostKeyVerificationDialog (line 30) | function HostKeyVerificationDialog({

FILE: src/ui/desktop/navigation/dialogs/OPKSSHDialog.tsx
  type OPKSSHDialogProps (line 9) | interface OPKSSHDialogProps {
  function OPKSSHDialog (line 20) | function OPKSSHDialog({

FILE: src/ui/desktop/navigation/dialogs/QuickConnectDialog.tsx
  type QuickConnectDialogProps (line 43) | interface QuickConnectDialogProps {
  function QuickConnectDialog (line 60) | function QuickConnectDialog({

FILE: src/ui/desktop/navigation/dialogs/SSHAuthDialog.tsx
  type SSHAuthDialogProps (line 26) | interface SSHAuthDialogProps {
  function SSHAuthDialog (line 44) | function SSHAuthDialog({

FILE: src/ui/desktop/navigation/dialogs/TOTPDialog.tsx
  type TOTPDialogProps (line 8) | interface TOTPDialogProps {
  function TOTPDialog (line 16) | function TOTPDialog({

FILE: src/ui/desktop/navigation/dialogs/WarpgateDialog.tsx
  type WarpgateDialogProps (line 9) | interface WarpgateDialogProps {
  function WarpgateDialog (line 19) | function WarpgateDialog({

FILE: src/ui/desktop/navigation/hosts/FolderCard.tsx
  type SSHHost (line 20) | interface SSHHost {
  type FolderCardProps (line 50) | interface FolderCardProps {
  function FolderCard (line 59) | function FolderCard({

FILE: src/ui/desktop/navigation/hosts/Host.tsx
  function Host (line 31) | function Host({ host: initialHost }: HostProps): React.ReactElement {

FILE: src/ui/desktop/navigation/tabs/Tab.tsx
  type TabProps (line 23) | interface TabProps {
  function Tab (line 43) | function Tab({

FILE: src/ui/desktop/navigation/tabs/TabContext.tsx
  type Tab (line 14) | type Tab = TabContextTab;
  type TabContextType (line 16) | interface TabContextType {
  function useTabs (line 41) | function useTabs() {
  type TabProviderProps (line 49) | interface TabProviderProps {
  function clearTermixSessionStorage (line 53) | function clearTermixSessionStorage() {
  function TabProvider (line 66) | function TabProvider({ children }: TabProviderProps) {

FILE: src/ui/desktop/navigation/tabs/TabDropdown.tsx
  function TabDropdown (line 26) | function TabDropdown(): React.ReactElement {

FILE: src/ui/desktop/user/ElectronVersionCheck.tsx
  type VersionCheckModalProps (line 8) | interface VersionCheckModalProps {
  function ElectronVersionCheck (line 13) | function ElectronVersionCheck({

FILE: src/ui/desktop/user/LanguageSwitcher.tsx
  function LanguageSwitcher (line 58) | function LanguageSwitcher() {

FILE: src/ui/desktop/user/PasswordReset.tsx
  type PasswordResetProps (line 18) | interface PasswordResetProps {
  function PasswordReset (line 27) | function PasswordReset({ userInfo }: PasswordResetProps) {

FILE: src/ui/desktop/user/TOTPSetup.tsx
  type TOTPSetupProps (line 36) | interface TOTPSetupProps {
  function TOTPSetup (line 41) | function TOTPSetup({

FILE: src/ui/desktop/user/UserProfile.tsx
  type UserProfileProps (line 47) | interface UserProfileProps {
  function handleLogout (line 53) | async function handleLogout() {
  function UserProfile (line 93) | function UserProfile({

FILE: src/ui/hooks/useCommandHistory.ts
  type UseCommandHistoryOptions (line 4) | interface UseCommandHistoryOptions {
  type CommandHistoryResult (line 9) | interface CommandHistoryResult {
  function useCommandHistory (line 17) | function useCommandHistory({

FILE: src/ui/hooks/useCommandTracker.ts
  type UseCommandTrackerOptions (line 4) | interface UseCommandTrackerOptions {
  type CommandTrackerResult (line 10) | interface CommandTrackerResult {
  function useCommandTracker (line 17) | function useCommandTracker({

FILE: src/ui/hooks/useDragToDesktop.ts
  type DragToDesktopState (line 6) | interface DragToDesktopState {
  type UseDragToDesktopProps (line 13) | interface UseDragToDesktopProps {
  type DragToDesktopOptions (line 18) | interface DragToDesktopOptions {
  function useDragToDesktop (line 24) | function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {

FILE: src/ui/hooks/useDragToSystemDesktop.ts
  type DragToSystemState (line 6) | interface DragToSystemState {
  type UseDragToSystemProps (line 13) | interface UseDragToSystemProps {
  type DragToSystemOptions (line 18) | interface DragToSystemOptions {
  function useDragToSystemDesktop (line 24) | function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) {

FILE: src/ui/main-axios.ts
  type Role (line 24) | interface Role {
  type UserRole (line 35) | interface UserRole {
  type AccessRecord (line 45) | interface AccessRecord {
  type FileManagerOperation (line 72) | interface FileManagerOperation {
  type ServerStatus (line 80) | type ServerStatus = {
  type SSHHostWithStatus (line 85) | type SSHHostWithStatus = SSHHost & {
  type CpuMetrics (line 89) | interface CpuMetrics {
  type MemoryMetrics (line 95) | interface MemoryMetrics {
  type DiskMetrics (line 101) | interface DiskMetrics {
  type ServerMetrics (line 108) | type ServerMetrics = {
  type AuthResponse (line 115) | interface AuthResponse {
  type UserInfo (line 129) | interface UserInfo {
  type UserCount (line 139) | interface UserCount {
  type OIDCAuthorize (line 143) | interface OIDCAuthorize {
  function isElectron (line 151) | function isElectron(): boolean {
  function getLoggerForService (line 176) | function getLoggerForService(serviceName: string) {
  function setCookie (line 225) | function setCookie(name: string, value: string, days = 7): void {
  function getCookie (line 255) | function getCookie(name: string): string | undefined {
  function createApiInstance (line 284) | function createApiInstance(
  function isDev (line 507) | function isDev(): boolean {
  type ServerConfig (line 526) | interface ServerConfig {
  type AxiosRequestConfigExtended (line 531) | interface AxiosRequestConfigExtended extends AxiosRequestConfig {
  type AxiosResponseExtended (line 536) | interface AxiosResponseExtended extends AxiosResponse {
  type AxiosErrorExtended (line 540) | interface AxiosErrorExtended extends AxiosError {
  function getServerConfig (line 544) | async function getServerConfig(): Promise<ServerConfig | null> {
  function saveServerConfig (line 563) | async function saveServerConfig(config: ServerConfig): Promise<boolean> {
  function getConfiguredServerUrl (line 595) | function getConfiguredServerUrl(): string | null {
  type AxiosRequestConfigExtended (line 599) | interface AxiosRequestConfigExtended extends AxiosRequestConfig {
  type AxiosResponseExtended (line 604) | interface AxiosResponseExtended extends AxiosResponse {
  type AxiosErrorExtended (line 608) | interface AxiosErrorExtended extends AxiosError {
  function testServerConnection (line 612) | async function testServerConnection(
  function checkElectronUpdate (line 634) | async function checkElectronUpdate(): Promise<{
  function getEmbeddedServerStatus (line 669) | async function getEmbeddedServerStatus(): Promise<{
  function isEmbeddedMode (line 696) | function isEmbeddedMode(): boolean {
  function setEmbeddedMode (line 700) | function setEmbeddedMode(value: boolean): void {
  function getApiUrl (line 708) | function getApiUrl(path: string, defaultPort: number): string {
  function initializeApiInstances (line 733) | function initializeApiInstances() {
  function initializeApp (line 789) | function initializeApp() {
  function updateApiInstances (line 831) | function updateApiInstances() {
  class ApiError (line 858) | class ApiError extends Error {
    method constructor (line 859) | constructor(
  function handleApiError (line 869) | function handleApiError(error: unknown, operation: string): never {
  function getSSHHosts (line 1006) | async function getSSHHosts(): Promise<SSHHostWithStatus[]> {
  function createSSHHost (line 1023) | async function createSSHHost(hostData: SSHHostData): Promise<SSHHost> {
  function updateSSHHost (line 1107) | async function updateSSHHost(
  function bulkImportSSHHosts (line 1193) | async function bulkImportSSHHosts(
  function bulkUpdateSSHHosts (line 1215) | async function bulkUpdateSSHHosts(
  function deleteSSHHost (line 1230) | async function deleteSSHHost(
  function getSSHHostById (line 1241) | async function getSSHHostById(hostId: number): Promise<SSHHost> {
  function exportSSHHostWithCredentials (line 1250) | async function exportSSHHostWithCredentials(
  function enableAutoStart (line 1265) | async function enableAutoStart(
  function disableAutoStart (line 1278) | async function disableAutoStart(
  function getAutoStartStatus (line 1291) | async function getAutoStartStatus(): Promise<{
  function testProxyConnection (line 1313) | async function testProxyConnection(options: {
  function getTunnelStatuses (line 1339) | async function getTunnelStatuses(): Promise<
  function getTunnelStatusByName (line 1350) | async function getTunnelStatusByName(
  function connectTunnel (line 1357) | async function connectTunnel(
  function disconnectTunnel (line 1368) | async function disconnectTunnel(
  function cancelTunnel (line 1379) | async function cancelTunnel(
  function getFileManagerRecent (line 1394) | async function getFileManagerRecent(
  function addFileManagerRecent (line 1407) | async function addFileManagerRecent(
  function removeFileManagerRecent (line 1418) | async function removeFileManagerRecent(
  function getFileManagerPinned (line 1431) | async function getFileManagerPinned(
  function addFileManagerPinned (line 1444) | async function addFileManagerPinned(
  function removeFileManagerPinned (line 1455) | async function removeFileManagerPinned(
  function getFileManagerShortcuts (line 1468) | async function getFileManagerShortcuts(
  function addFileManagerShortcut (line 1481) | async function addFileManagerShortcut(
  function removeFileManagerShortcut (line 1492) | async function removeFileManagerShortcut(
  function connectSSH (line 1509) | async function connectSSH(
  function disconnectSSH (line 1568) | async function disconnectSSH(
  function verifySSHTOTP (line 1581) | async function verifySSHTOTP(
  function verifySSHWarpgate (line 1596) | async function verifySSHWarpgate(
  function quickConnect (line 1675) | async function quickConnect(
  function getSSHStatus (line 1686) | async function getSSHStatus(
  function keepSSHAlive (line 1699) | async function keepSSHAlive(
  function listSSHFiles (line 1712) | async function listSSHFiles(
  function identifySSHSymlink (line 1727) | async function identifySSHSymlink(
  function resolveSSHPath (line 1741) | async function resolveSSHPath(
  function readSSHFile (line 1755) | async function readSSHFile(
  function writeSSHFile (line 1783) | async function writeSSHFile(
  function uploadSSHFile (line 1813) | async function uploadSSHFile(
  function downloadSSHFile (line 1836) | async function downloadSSHFile(
  function createSSHFile (line 1855) | async function createSSHFile(
  function createSSHFolder (line 1878) | async function createSSHFolder(
  function deleteSSHItem (line 1899) | async function deleteSSHItem(
  function setSudoPassword (line 1922) | async function setSudoPassword(
  function copySSHItem (line 1936) | async function copySSHItem(
  function renameSSHItem (line 1964) | async function renameSSHItem(
  function moveSSHItem (line 1986) | async function moveSSHItem(
  function changeSSHPermissions (line 2014) | async function changeSSHPermissions(
  function extractSSHArchive (line 2059) | async function extractSSHArchive(
  function compressSSHFiles (line 2104) | async function compressSSHFiles(
  function getRecentFiles (line 2157) | async function getRecentFiles(
  function addRecentFile (line 2171) | async function addRecentFile(
  function removeRecentFile (line 2189) | async function removeRecentFile(
  function getPinnedFiles (line 2204) | async function getPinnedFiles(
  function addPinnedFile (line 2218) | async function addPinnedFile(
  function removePinnedFile (line 2236) | async function removePinnedFile(
  function getFolderShortcuts (line 2251) | async function getFolderShortcuts(
  function addFolderShortcut (line 2265) | async function addFolderShortcut(
  function removeFolderShortcut (line 2283) | async function removeFolderShortcut(
  function getAllServerStatuses (line 2302) | async function getAllServerStatuses(): Promise<
  function getServerStatusById (line 2313) | async function getServerStatusById(id: number): Promise<ServerStatus> {
  function getServerMetricsById (line 2323) | async function getServerMetricsById(id: number): Promise<ServerMetrics> {
  function startMetricsPolling (line 2333) | async function startMetricsPolling(hostId: number): Promise<{
  function stopMetricsPolling (line 2358) | async function stopMetricsPolling(
  function sendMetricsHeartbeat (line 2370) | async function sendMetricsHeartbeat(
  function registerMetricsViewer (line 2381) | async function registerMetricsViewer(
  function unregisterMetricsViewer (line 2395) | async function unregisterMetricsViewer(
  function submitMetricsTOTP (line 2410) | async function submitMetricsTOTP(
  function refreshServerPolling (line 2429) | async function refreshServerPolling(): Promise<void> {
  function notifyHostCreatedOrUpdated (line 2437) | async function notifyHostCreatedOrUpdated(
  function getGlobalMonitoringSettings (line 2451) | async function getGlobalMonitoringSettings(): Promise<{
  function updateGlobalMonitoringSettings (line 2463) | async function updateGlobalMonitoringSettings(settings: {
  function getGuacamoleSettings (line 2478) | async function getGuacamoleSettings(): Promise<{
  function updateGuacamoleSettings (line 2490) | async function updateGuacamoleSettings(settings: {
  function registerUser (line 2505) | async function registerUser(
  function loginUser (line 2520) | async function loginUser(
  function logoutUser (line 2577) | async function logoutUser(): Promise<{
  function getUserInfo (line 2627) | async function getUserInfo(): Promise<UserInfo> {
  function unlockUserData (line 2636) | async function unlockUserData(
  function getRegistrationAllowed (line 2647) | async function getRegistrationAllowed(): Promise<{ allowed: boolean }> {
  function getPasswordLoginAllowed (line 2656) | async function getPasswordLoginAllowed(): Promise<{ allowed: boolean }> {
  function getOIDCConfig (line 2665) | async function getOIDCConfig(): Promise<Record<string, unknown>> {
  function getAdminOIDCConfig (line 2678) | async function getAdminOIDCConfig(): Promise<Record<string, unknown>> {
  function getSetupRequired (line 2687) | async function getSetupRequired(): Promise<{ setup_required: boolean }> {
  function getUserCount (line 2696) | async function getUserCount(): Promise<UserCount> {
  function initiatePasswordReset (line 2705) | async function initiatePasswordReset(
  function verifyPasswordResetCode (line 2716) | async function verifyPasswordResetCode(
  function completePasswordReset (line 2731) | async function completePasswordReset(
  function changePassword (line 2748) | async function changePassword(oldPassword: string, newPassword: string) {
  function getOIDCAuthorizeUrl (line 2760) | async function getOIDCAuthorizeUrl(
  function getUserList (line 2777) | async function getUserList(): Promise<{ users: UserInfo[] }> {
  function getSessions (line 2786) | async function getSessions(): Promise<{
  function revokeSession (line 2808) | async function revokeSession(
  function revokeAllUserSessions (line 2819) | async function revokeAllUserSessions(
  function makeUserAdmin (line 2833) | async function makeUserAdmin(
  function removeAdminStatus (line 2844) | async function removeAdminStatus(
  function deleteUser (line 2855) | async function deleteUser(
  function deleteAccount (line 2868) | async function deleteAccount(
  function updateRegistrationAllowed (line 2881) | async function updateRegistrationAllowed(
  function updatePasswordLoginAllowed (line 2894) | async function updatePasswordLoginAllowed(
  function getPasswordResetAllowed (line 2907) | async function getPasswordResetAllowed(): Promise<boolean> {
  function updatePasswordResetAllowed (line 2916) | async function updatePasswordResetAllowed(
  function updateOIDCConfig (line 2929) | async function updateOIDCConfig(
  function disableOIDCConfig (line 2940) | async function disableOIDCConfig(): Promise<Record<string, unknown>> {
  function setupTOTP (line 2953) | async function setupTOTP(): Promise<{
  function enableTOTP (line 2966) | async function enableTOTP(
  function disableTOTP (line 2978) | async function disableTOTP(
  function verifyTOTPLogin (line 2994) | async function verifyTOTPLogin(
  function generateBackupCodes (line 3041) | async function generateBackupCodes(
  function getUserAlerts (line 3057) | async function getUserAlerts(): Promise<{
  function dismissAlert (line 3068) | async function dismissAlert(
  function getReleasesRSS (line 3083) | async function getReleasesRSS(
  function getVersionInfo (line 3094) | async function getVersionInfo(): Promise<Record<string, unknown>> {
  function getDatabaseHealth (line 3107) | async function getDatabaseHealth(): Promise<Record<string, unknown>> {
  function getCredentials (line 3120) | async function getCredentials(): Promise<Record<string, unknown>> {
  function getCredentialDetails (line 3129) | async function getCredentialDetails(
  function createCredential (line 3140) | async function createCredential(
  function updateCredential (line 3151) | async function updateCredential(
  function deleteCredential (line 3166) | async function deleteCredential(
  function getCredentialHosts (line 3177) | async function getCredentialHosts(
  function getCredentialFolders (line 3188) | async function getCredentialFolders(): Promise<Record<string, unknown>> {
  function getSSHHostWithCredentials (line 3197) | async function getSSHHostWithCredentials(
  function applyCredentialToHost (line 3210) | async function applyCredentialToHost(
  function removeCredentialFromHost (line 3225) | async function removeCredentialFromHost(
  function migrateHostToCredential (line 3236) | async function migrateHostToCredential(
  function getFoldersWithStats (line 3255) | async function getFoldersWithStats(): Promise<Record<string, unknown>> {
  function renameFolder (line 3264) | async function renameFolder(
  function getSSHFolders (line 3279) | async function getSSHFolders(): Promise<SSHFolder[]> {
  function updateFolderMetadata (line 3302) | async function updateFolderMetadata(
  function deleteAllHostsInFolder (line 3335) | async function deleteAllHostsInFolder(
  function renameCredentialFolder (line 3365) | async function renameCredentialFolder(
  function detectKeyType (line 3380) | async function detectKeyType(
  function detectPublicKeyType (line 3395) | async function detectPublicKeyType(
  function validateKeyPair (line 3408) | async function validateKeyPair(
  function generatePublicKeyFromPrivate (line 3425) | async function generatePublicKeyFromPrivate(
  function generateKeyPair (line 3440) | async function generateKeyPair(
  function deployCredentialToHost (line 3457) | async function deployCredentialToHost(
  function getSnippets (line 3476) | async function getSnippets(): Promise<Record<string, unknown>> {
  function createSnippet (line 3485) | async function createSnippet(
  function updateSnippet (line 3496) | async function updateSnippet(
  function deleteSnippet (line 3508) | async function deleteSnippet(
  function executeSnippet (line 3519) | async function executeSnippet(
  type NetworkTopologyData (line 3538) | interface NetworkTopologyData {
  function getNetworkTopology (line 3543) | async function getNetworkTopology(): Promise<NetworkTopologyData | null> {
  function saveNetworkTopology (line 3552) | async function saveNetworkTopology(
  function getSnippetFolders (line 3563) | async function getSnippetFolders(): Promise<Record<string, unknown>> {
  function createSnippetFolder (line 3572) | async function createSnippetFolder(folderData: {
  function updateSnippetFolderMetadata (line 3585) | async function updateSnippetFolderMetadata(
  function renameSnippetFolder (line 3600) | async function renameSnippetFolder(
  function deleteSnippetFolder (line 3615) | async function deleteSnippetFolder(
  function reorderSnippets (line 3628) | async function reorderSnippets(
  type UptimeInfo (line 3643) | interface UptimeInfo {
  type RecentActivityItem (line 3649) | interface RecentActivityItem {
  function getUptime (line 3666) | async function getUptime(): Promise<UptimeInfo> {
  function getRecentActivity (line 3675) | async function getRecentActivity(
  function logActivity (line 3688) | async function logActivity(
  function resetRecentActivity (line 3713) | async function resetRecentActivity(): Promise<{ message: string }> {
  function saveCommandToHistory (line 3726) | async function saveCommandToHistory(
  function getCommandHistory (line 3741) | async function getCommandHistory(
  function deleteCommandFromHistory (line 3755) | async function deleteCommandFromHistory(
  function clearCommandHistory (line 3770) | async function clearCommandHistory(
  function linkOIDCToPasswordAccount (line 3787) | async function linkOIDCToPasswordAccount(
  function unlinkOIDCFromPasswordAccount (line 3802) | async function unlinkOIDCFromPasswordAccount(
  type GuacamoleTokenRequest (line 3815) | interface GuacamoleTokenRequest {
  type GuacamoleTokenResponse (line 3885) | interface GuacamoleTokenResponse {
  function toGuacamoleParams (line 3889) | function toGuacamoleParams(
  function getGuacamoleToken (line 3964) | async function getGuacamoleToken(
  function getGuacamoleTokenFromHost (line 3987) | async function getGuacamoleTokenFromHost(
  function getRoles (line 4002) | async function getRoles(): Promise<{ roles: Role[] }> {
  function createRole (line 4011) | async function createRole(roleData: {
  function updateRole (line 4024) | async function updateRole(
  function deleteRole (line 4039) | async function deleteRole(
  function getUserRoles (line 4050) | async function getUserRoles(
  function assignRoleToUser (line 4061) | async function assignRoleToUser(
  function removeRoleFromUser (line 4075) | async function removeRoleFromUser(
  function shareHost (line 4089) | async function shareHost(
  function getHostAccess (line 4110) | async function getHostAccess(
  function revokeHostAccess (line 4121) | async function revokeHostAccess(
  function connectDockerSession (line 4139) | async function connectDockerSession(
  function verifyDockerTOTP (line 4198) | async function verifyDockerTOTP(
  function verifyDockerWarpgate (line 4213) | async function verifyDockerWarpgate(
  function disconnectDockerSession (line 4226) | async function disconnectDockerSession(
  function keepaliveDockerSession (line 4239) | async function keepaliveDockerSession(
  function getDockerSessionStatus (line 4252) | async function getDockerSessionStatus(
  function validateDockerAvailability (line 4265) | async function validateDockerAvailability(
  function listDockerContainers (line 4276) | async function listDockerContainers(
  function getDockerContainerDetails (line 4290) | async function getDockerContainerDetails(
  function startDockerContainer (line 4304) | async function startDockerContainer(
  function stopDockerContainer (line 4318) | async function stopDockerContainer(
  function restartDockerContainer (line 4332) | async function restartDockerContainer(
  function pauseDockerContainer (line 4346) | async function pauseDockerContainer(
  function unpauseDockerContainer (line 4360) | async function unpauseDockerContainer(
  function removeDockerContainer (line 4374) | async function removeDockerContainer(
  function getContainerLogs (line 4392) | async function getContainerLogs(
  function downloadContainerLogs (line 4410) | async function downloadContainerLogs(
  function getContainerStats (line 4429) | async function getContainerStats(
  type DashboardLayout (line 4443) | interface DashboardLayout {
  function getDashboardPreferences (line 4447) | async function getDashboardPreferences(): Promise<DashboardLayout> {
  function saveDashboardPreferences (line 4452) | async function saveDashboardPreferences(

FILE: src/ui/mobile/MobileApp.tsx
  function isReactNativeWebView (line 22) | function isReactNativeWebView(): boolean {
  function handleKeyboardInput (line 119) | function handleKeyboardInput(input: string) {
  class TabErrorBoundary (line 236) | class TabErrorBoundary extends Component<
    method constructor (line 240) | constructor(props: { children: ReactNode }) {
    method getDerivedStateFromError (line 245) | static getDerivedStateFromError(error: Error) {
    method componentDidCatch (line 252) | componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    method render (line 264) | render() {

FILE: src/ui/mobile/apps/navigation/BottomNavbar.tsx
  type MenuProps (line 6) | interface MenuProps {
  function BottomNavbar (line 10) | function BottomNavbar({ onSidebarOpenClick }: MenuProps) {

FILE: src/ui/mobile/apps/navigation/LeftSidebar.tsx
  type SSHHost (line 27) | interface SSHHost {
  type LeftSidebarProps (line 57) | interface LeftSidebarProps {
  function handleLogout (line 65) | async function handleLogout() {
  function LeftSidebar (line 76) | function LeftSidebar({

FILE: src/ui/mobile/apps/navigation/hosts/FolderCard.tsx
  type SSHHost (line 8) | interface SSHHost {
  type FolderCardProps (line 38) | interface FolderCardProps {
  function FolderCard (line 44) | function FolderCard({

FILE: src/ui/mobile/apps/navigation/hosts/Host.tsx
  function Host (line 10) | function Host({ host, onHostConnect }: HostProps): React.ReactElement {

FILE: src/ui/mobile/apps/navigation/tabs/TabContext.tsx
  type Tab (line 10) | type Tab = TabContextTab;
  type TabContextType (line 12) | interface TabContextType {
  function useTabs (line 23) | function useTabs() {
  type TabProviderProps (line 31) | interface TabProviderProps {
  function TabProvider (line 35) | function TabProvider({ children }: TabProviderProps) {

FILE: src/ui/mobile/apps/terminal/Terminal.tsx
  type HostConfig (line 35) | interface HostConfig {
  type TerminalHandle (line 51) | interface TerminalHandle {
  type SSHTerminalProps (line 59) | interface SSHTerminalProps {
  function hardRefresh (line 173) | function hardRefresh() {
  function performFit (line 190) | function performFit() {
  function scheduleNotify (line 215) | function scheduleNotify(cols: number, rows: number) {
  function handleTotpSubmit (line 233) | function handleTotpSubmit(code: string) {
  function handleTotpCancel (line 251) | function handleTotpCancel() {
  function handleWarpgateContinue (line 261) | function handleWarpgateContinue() {
  function handleWarpgateCancel (line 279) | function handleWarpgateCancel() {
  function handleWarpgateOpenUrl (line 290) | function handleWarpgateOpenUrl() {
  function handleAuthDialogSubmit (line 296) | function handleAuthDialogSubmit(credentials: {
  function handleAuthDialogCancel (line 325) | function handleAuthDialogCancel() {
  function attemptReconnection (line 386) | function attemptReconnection() {
  function connectToHost (line 475) | function connectToHost(cols: number, rows: number) {
  function setupWebSocketListeners (line 549) | function setupWebSocketListeners(

FILE: src/ui/mobile/apps/terminal/TerminalKeyboard.tsx
  type TerminalKeyboardProps (line 8) | interface TerminalKeyboardProps {
  function TerminalKeyboard (line 13) | function TerminalKeyboard({

FILE: src/ui/mobile/authentication/Auth.tsx
  function isReactNativeWebView (line 31) | function isReactNativeWebView(): boolean {
  function postJWTToWebView (line 35) | function postJWTToWebView() {
  type AuthProps (line 61) | interface AuthProps extends React.ComponentProps<"div"> {
  function Auth (line 77) | function Auth({

FILE: src/ui/mobile/navigation/BottomNavbar.tsx
  type MenuProps (line 6) | interface MenuProps {
  function BottomNavbar (line 10) | function BottomNavbar({ onSidebarOpenClick }: MenuProps) {

FILE: src/ui/mobile/navigation/LeftSidebar.tsx
  type SSHHost (line 29) | interface SSHHost {
  type LeftSidebarProps (line 60) | interface LeftSidebarProps {
  function handleLogout (line 68) | async function handleLogout() {
  function LeftSidebar (line 78) | function LeftSidebar({

FILE: src/ui/mobile/navigation/hosts/FolderCard.tsx
  type SSHHost (line 8) | interface SSHHost {
  type FolderCardProps (line 38) | interface FolderCardProps {
  function FolderCard (line 44) | function FolderCard({

FILE: src/ui/mobile/navigation/hosts/Host.tsx
  function Host (line 11) | function Host({ host, onHostConnect }: HostProps): React.ReactElement {

FILE: src/ui/mobile/navigation/tabs/TabContext.tsx
  type Tab (line 10) | type Tab = TabContextTab;
  type TabContextType (line 12) | interface TabContextType {
  function useTabs (line 23) | function useTabs() {
  type TabProviderProps (line 31) | interface TabProviderProps {
  function TabProvider (line 35) | function TabProvider({ children }: TabProviderProps) {
Condensed preview — 377 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (9,876K chars).
[
  {
    "path": ".commitlintrc.json",
    "chars": 312,
    "preview": "{\n  \"extends\": [\"@commitlint/config-conventional\"],\n  \"rules\": {\n    \"type-enum\": [\n      2,\n      \"always\",\n      [\n   "
  },
  {
    "path": ".dockerignore",
    "chars": 698,
    "preview": "node_modules\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\ndist\nbuild\n.next\n.nuxt\n\n.env.local\n.env.development.local\n."
  },
  {
    "path": ".editorconfig",
    "chars": 235,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{js,js"
  },
  {
    "path": ".gitattributes",
    "chars": 452,
    "preview": "* text=auto eol=lf\n\n*.js text eol=lf\n*.jsx text eol=lf\n*.ts text eol=lf\n*.tsx text eol=lf\n*.json text eol=lf\n*.css text "
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 18,
    "preview": "github: [LukeGus]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 331,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Support Center\n    url: https://github.com/Termix-SSH/Support/issue"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 875,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    groups:\n  "
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 696,
    "preview": "# Overview\n\n_Short summary of what this PR does_\n\n- [ ] Added: ...\n- [ ] Updated: ...\n- [ ] Removed: ...\n- [ ] Fixed: .."
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 2755,
    "preview": "name: Build and Push Docker Image\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Version to "
  },
  {
    "path": ".github/workflows/electron.yml",
    "chars": 40108,
    "preview": "name: Build and Push Electron App\n\non:\n  workflow_dispatch:\n    inputs:\n      build_type:\n        description: \"Platform"
  },
  {
    "path": ".github/workflows/openapi.yml",
    "chars": 696,
    "preview": "name: Generate OpenAPI Specification\n\non:\n  workflow_dispatch:\n\njobs:\n  generate-openapi:\n    name: Generate OpenAPI JSO"
  },
  {
    "path": ".github/workflows/pr-check.yml",
    "chars": 718,
    "preview": "name: PR Check\n\non:\n  pull_request:\n    branches: [main, dev-*]\n\njobs:\n  lint-and-build:\n    runs-on: blacksmith-2vcpu-u"
  },
  {
    "path": ".gitignore",
    "chars": 288,
    "preview": "logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 33,
    "preview": "npx --no -- commitlint --edit $1\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 16,
    "preview": "npx lint-staged\n"
  },
  {
    "path": ".nvmrc",
    "chars": 3,
    "preview": "20\n"
  },
  {
    "path": ".prettierignore",
    "chars": 137,
    "preview": "build\ncoverage\ndist\ndist-ssr\nrelease\n\nnode_modules\npackage-lock.json\npnpm-lock.yaml\nyarn.lock\n\ndb\n\n.env\n\n*.min.js\n*.min."
  },
  {
    "path": ".prettierrc",
    "chars": 154,
    "preview": "{\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"all\",\n  \"printWidth\": 80,\n  \"arrowParens\""
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5217,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 5494,
    "preview": "# Contributing\n\n## Prerequisites\n\n- [Node.js](https://nodejs.org/en/download/) (built with v24)\n- [NPM](https://docs.npm"
  },
  {
    "path": "Casks/termix.rb",
    "chars": 753,
    "preview": "cask \"termix\" do\n  version \"2.0.0\"\n  sha256 \"a752ad4f05b4991b8a2ef986da80a86b79a70c93fe1045c4399fcf348a28c1d0\"\n\n  url \"h"
  },
  {
    "path": "LICENSE",
    "chars": 554,
    "preview": "Copyright 2025 Luke Gustafson\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file"
  },
  {
    "path": "README.md",
    "chars": 10905,
    "preview": "# Repo Stats\n\n<p align=\"center\">\n  <img src=\"https://flagcdn.com/us.svg\" alt=\"English\" width=\"24\" height=\"16\"> English ·"
  },
  {
    "path": "SECURITY.md",
    "chars": 163,
    "preview": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease report any vulnerabilities to [GitHub Security](https://github.c"
  },
  {
    "path": "build/entitlements.mac.inherit.plist",
    "chars": 463,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "build/entitlements.mac.plist",
    "chars": 463,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "build/entitlements.mas.inherit.plist",
    "chars": 487,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "build/entitlements.mas.plist",
    "chars": 620,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "build/notarize.cjs",
    "chars": 779,
    "preview": "const { notarize } = require('@electron/notarize');\n\nexports.default = async function notarizing(context) {\n  const { el"
  },
  {
    "path": "chocolatey/termix-ssh.nuspec",
    "chars": 1759,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<package xmlns=\"http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd\">\n  <me"
  },
  {
    "path": "chocolatey/tools/chocolateyinstall.ps1",
    "chars": 649,
    "preview": "$ErrorActionPreference = 'Stop'\r\n\r\n$packageName = 'termix-ssh'\r\n$toolsDir = \"$(Split-Path -parent $MyInvocation.MyComman"
  },
  {
    "path": "chocolatey/tools/chocolateyuninstall.ps1",
    "chars": 1094,
    "preview": "$ErrorActionPreference = 'Stop'\r\n\r\n$packageName = 'termix-ssh'\r\n$softwareName = 'Termix*'\r\n$installerType = 'msi'\r\n\r\n$si"
  },
  {
    "path": "components.json",
    "chars": 423,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": "
  },
  {
    "path": "crowdin.yml",
    "chars": 111,
    "preview": "files:\n  - source: /src/locales/en.json\n    translation: /src/locales/translated/%locale_with_underscore%.json\n"
  },
  {
    "path": "docker/Dockerfile",
    "chars": 2495,
    "preview": "# Stage 1: Install dependencies\nFROM node:22-slim AS deps\nWORKDIR /app\n\nRUN apt-get update && apt-get install -y python3"
  },
  {
    "path": "docker/docker-compose.yml",
    "chars": 538,
    "preview": "services:\n  termix:\n    image: ghcr.io/lukegus/termix:latest\n    container_name: termix\n    restart: unless-stopped\n    "
  },
  {
    "path": "docker/entrypoint.sh",
    "chars": 4431,
    "preview": "#!/bin/sh\nset -e\n\nPUID=${PUID:-1000}\nPGID=${PGID:-1000}\n\nif [ \"$(id -u)\" = \"0\" ]; then\n    if [ \"$PUID\" = \"0\" ]; then\n  "
  },
  {
    "path": "docker/nginx-https.conf",
    "chars": 19774,
    "preview": "worker_processes 1;\nmaster_process off;\npid /app/nginx/nginx.pid;\nerror_log /app/nginx/logs/error.log warn;\n\nevents {\n  "
  },
  {
    "path": "docker/nginx.conf",
    "chars": 19469,
    "preview": "worker_processes 1;\nmaster_process off;\npid /app/nginx/nginx.pid;\nerror_log /app/nginx/logs/error.log warn;\n\nevents {\n  "
  },
  {
    "path": "electron/main.cjs",
    "chars": 24148,
    "preview": "const {\n  app,\n  BrowserWindow,\n  shell,\n  ipcMain,\n  dialog,\n  Menu,\n  Tray,\n} = require(\"electron\");\nconst path = requ"
  },
  {
    "path": "electron/preload.js",
    "chars": 830,
    "preview": "const { contextBridge, ipcRenderer } = require(\"electron\");\nconst { clipboard } = require(\"electron\");\n\ncontextBridge.ex"
  },
  {
    "path": "electron-builder.json",
    "chars": 3749,
    "preview": "{\n  \"appId\": \"com.karmaa.termix\",\n  \"productName\": \"Termix\",\n  \"publish\": null,\n  \"directories\": {\n    \"output\": \"releas"
  },
  {
    "path": "eslint.config.js",
    "chars": 932,
    "preview": "import js from \"@eslint/js\";\nimport globals from \"globals\";\nimport reactHooks from \"eslint-plugin-react-hooks\";\nimport r"
  },
  {
    "path": "flatpak/com.karmaa.termix.desktop",
    "chars": 318,
    "preview": "[Desktop Entry]\nName=Termix\nComment=Web-based server management platform with SSH terminal, tunneling, and file editing\n"
  },
  {
    "path": "flatpak/com.karmaa.termix.flatpakref",
    "chars": 671,
    "preview": "[Flatpak Ref]\nName=Termix\nBranch=stable\nTitle=Termix - SSH Server Management Platform\nIsRuntime=false\nUrl=https://github"
  },
  {
    "path": "flatpak/com.karmaa.termix.metainfo.xml",
    "chars": 2480,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n  <id>com.karmaa.termix</id>\n  <name>Termi"
  },
  {
    "path": "flatpak/com.karmaa.termix.yml",
    "chars": 2764,
    "preview": "app-id: com.karmaa.termix\nruntime: org.freedesktop.Platform\nruntime-version: \"24.08\"\nsdk: org.freedesktop.Sdk\nbase: org."
  },
  {
    "path": "flatpak/flathub.json",
    "chars": 105,
    "preview": "{\n  \"only-arches\": [\"x86_64\", \"aarch64\"],\n  \"skip-icons-check\": false,\n  \"skip-appstream-check\": false\n}\n"
  },
  {
    "path": "index.html",
    "chars": 1397,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "package.json",
    "chars": 6534,
    "preview": "{\n  \"name\": \"termix\",\n  \"private\": true,\n  \"version\": \"2.0.0\",\n  \"description\": \"A web-based server management platform "
  },
  {
    "path": "public/manifest.json",
    "chars": 925,
    "preview": "{\n  \"name\": \"Termix\",\n  \"short_name\": \"Termix\",\n  \"description\": \"A web-based server management platform with SSH termin"
  },
  {
    "path": "public/sw.js",
    "chars": 2249,
    "preview": "const CACHE_NAME = \"termix-v1\";\nconst STATIC_ASSETS = [\n  \"/\",\n  \"/index.html\",\n  \"/manifest.json\",\n  \"/favicon.ico\",\n  "
  },
  {
    "path": "readme/README-AR.md",
    "chars": 10344,
    "preview": "# إحصائيات المستودع\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" wid"
  },
  {
    "path": "readme/README-CN.md",
    "chars": 8066,
    "preview": "# 仓库统计\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" width=\"24\" heigh"
  },
  {
    "path": "readme/README-DE.md",
    "chars": 11567,
    "preview": "# Repo-Statistiken\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" widt"
  },
  {
    "path": "readme/README-ES.md",
    "chars": 11430,
    "preview": "# Estadísticas del Repositorio\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"E"
  },
  {
    "path": "readme/README-FR.md",
    "chars": 11723,
    "preview": "# Statistiques du dépôt\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\""
  },
  {
    "path": "readme/README-HI.md",
    "chars": 10732,
    "preview": "# रिपॉजिटरी आँकड़े\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" widt"
  },
  {
    "path": "readme/README-IT.md",
    "chars": 11324,
    "preview": "# Statistiche Repo\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" widt"
  },
  {
    "path": "readme/README-JA.md",
    "chars": 8585,
    "preview": "# リポジトリ統計\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" width=\"24\" he"
  },
  {
    "path": "readme/README-KO.md",
    "chars": 8617,
    "preview": "# 리포지토리 통계\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" width=\"24\" h"
  },
  {
    "path": "readme/README-PT.md",
    "chars": 11433,
    "preview": "# Estatísticas do Repositório\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"En"
  },
  {
    "path": "readme/README-RU.md",
    "chars": 11469,
    "preview": "# Статистика репозитория\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English"
  },
  {
    "path": "readme/README-TR.md",
    "chars": 11336,
    "preview": "# Repo İstatistikleri\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" w"
  },
  {
    "path": "readme/README-VI.md",
    "chars": 10873,
    "preview": "# Thống Kê Repo\n\n<p align=\"center\">\n  <a href=\"../README.md\"><img src=\"https://flagcdn.com/us.svg\" alt=\"English\" width=\""
  },
  {
    "path": "src/backend/dashboard.ts",
    "chars": 14528,
    "preview": "import express from \"express\";\nimport cors from \"cors\";\nimport cookieParser from \"cookie-parser\";\nimport { getDb, Databa"
  },
  {
    "path": "src/backend/database/database.ts",
    "chars": 60571,
    "preview": "import express from \"express\";\nimport bodyParser from \"body-parser\";\nimport multer from \"multer\";\nimport cookieParser fr"
  },
  {
    "path": "src/backend/database/db/index.ts",
    "chars": 47656,
    "preview": "import { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport Database from \"better-sqlite3\";\nimport * as schema from \"./"
  },
  {
    "path": "src/backend/database/db/schema.ts",
    "chars": 17522,
    "preview": "import { sqliteTable, text, integer } from \"drizzle-orm/sqlite-core\";\nimport { sql } from \"drizzle-orm\";\n\nexport const u"
  },
  {
    "path": "src/backend/database/routes/alerts.ts",
    "chars": 8267,
    "preview": "import type {\n  AuthenticatedRequest,\n  CacheEntry,\n  TermixAlert,\n} from \"../../../types/index.js\";\nimport express from"
  },
  {
    "path": "src/backend/database/routes/credentials.ts",
    "chars": 57576,
    "preview": "import type {\n  AuthenticatedRequest,\n  CredentialBackend,\n} from \"../../../types/index.js\";\nimport express from \"expres"
  },
  {
    "path": "src/backend/database/routes/host.ts",
    "chars": 140784,
    "preview": "import type { AuthenticatedRequest } from \"../../../types/index.js\";\nimport express from \"express\";\nimport { db } from \""
  },
  {
    "path": "src/backend/database/routes/network-topology.ts",
    "chars": 6198,
    "preview": "import express from \"express\";\nimport { eq } from \"drizzle-orm\";\nimport { getDb } from \"../db/index.js\";\nimport { networ"
  },
  {
    "path": "src/backend/database/routes/rbac.ts",
    "chars": 32446,
    "preview": "import type { AuthenticatedRequest } from \"../../../types/index.js\";\nimport express from \"express\";\nimport { db } from \""
  },
  {
    "path": "src/backend/database/routes/snippets.ts",
    "chars": 34045,
    "preview": "import type { AuthenticatedRequest } from \"../../../types/index.js\";\nimport express from \"express\";\nimport { db } from \""
  },
  {
    "path": "src/backend/database/routes/terminal.ts",
    "chars": 11239,
    "preview": "import type { AuthenticatedRequest } from \"../../../types/index.js\";\nimport express from \"express\";\nimport { db } from \""
  },
  {
    "path": "src/backend/database/routes/users.ts",
    "chars": 124778,
    "preview": "import type { AuthenticatedRequest } from \"../../../types/index.js\";\nimport express from \"express\";\nimport { restartGuac"
  },
  {
    "path": "src/backend/guacamole/guacamole-server.ts",
    "chars": 3945,
    "preview": "import GuacamoleLite from \"guacamole-lite\";\nimport { parse as parseUrl } from \"url\";\nimport { guacLogger } from \"../util"
  },
  {
    "path": "src/backend/guacamole/routes.ts",
    "chars": 8899,
    "preview": "import express from \"express\";\nimport { GuacamoleTokenService } from \"./token-service.js\";\nimport { guacLogger } from \"."
  },
  {
    "path": "src/backend/guacamole/token-service.ts",
    "chars": 4423,
    "preview": "import crypto from \"crypto\";\nimport { guacLogger } from \"../utils/logger.js\";\n\nexport interface GuacamoleConnectionSetti"
  },
  {
    "path": "src/backend/scripts/enable-ssl.sh",
    "chars": 1991,
    "preview": "#!/bin/bash\n\nset -e\n\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nCYAN='\\033[0;36m'\nNC='\\03"
  },
  {
    "path": "src/backend/scripts/setup-ssl.sh",
    "chars": 2754,
    "preview": "#!/bin/bash\n\nset -e\n\nSSL_DIR=\"$(dirname \"$0\")/../ssl\"\nCERT_FILE=\"$SSL_DIR/termix.crt\"\nKEY_FILE=\"$SSL_DIR/termix.key\"\nDAY"
  },
  {
    "path": "src/backend/ssh/auth-manager.ts",
    "chars": 10800,
    "preview": "import type { WebSocket } from \"ws\";\nimport { sshLogger, authLogger } from \"../utils/logger.js\";\nimport { getDb } from \""
  },
  {
    "path": "src/backend/ssh/docker-console.ts",
    "chars": 19198,
    "preview": "import { Client as SSHClient } from \"ssh2\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { parse as parseUrl "
  },
  {
    "path": "src/backend/ssh/docker.ts",
    "chars": 85760,
    "preview": "import express from \"express\";\nimport cors from \"cors\";\nimport cookieParser from \"cookie-parser\";\nimport axios from \"axi"
  },
  {
    "path": "src/backend/ssh/file-manager.ts",
    "chars": 162899,
    "preview": "import express from \"express\";\nimport cors from \"cors\";\nimport cookieParser from \"cookie-parser\";\nimport axios from \"axi"
  },
  {
    "path": "src/backend/ssh/host-key-verifier.ts",
    "chars": 12193,
    "preview": "import type { WebSocket } from \"ws\";\nimport { db } from \"../database/db/index.js\";\nimport { hosts } from \"../database/db"
  },
  {
    "path": "src/backend/ssh/opkssh-auth.ts",
    "chars": 21282,
    "preview": "import { spawn, ChildProcess } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport { WebSocket } from \"ws\""
  },
  {
    "path": "src/backend/ssh/server-stats.ts",
    "chars": 91542,
    "preview": "import express from \"express\";\nimport net from \"net\";\nimport cors from \"cors\";\nimport cookieParser from \"cookie-parser\";"
  },
  {
    "path": "src/backend/ssh/ssh-connection-pool.ts",
    "chars": 5881,
    "preview": "import { Client } from \"ssh2\";\nimport { sshLogger } from \"../utils/logger.js\";\n\ninterface PooledConnection {\n  client: C"
  },
  {
    "path": "src/backend/ssh/terminal-session-manager.ts",
    "chars": 12527,
    "preview": "import { type Client, type ClientChannel } from \"ssh2\";\nimport { WebSocket } from \"ws\";\nimport { sshLogger } from \"../ut"
  },
  {
    "path": "src/backend/ssh/terminal.ts",
    "chars": 67256,
    "preview": "import { WebSocketServer, WebSocket, type RawData } from \"ws\";\nimport { Client, type ClientChannel, type PseudoTtyOption"
  },
  {
    "path": "src/backend/ssh/tunnel.ts",
    "chars": 68777,
    "preview": "import express, { type Response } from \"express\";\nimport cors from \"cors\";\nimport cookieParser from \"cookie-parser\";\nimp"
  },
  {
    "path": "src/backend/ssh/widgets/common-utils.ts",
    "chars": 2402,
    "preview": "import type { Client, ClientChannel } from \"ssh2\";\n\nexport function execCommand(\n  client: Client,\n  command: string,\n  "
  },
  {
    "path": "src/backend/ssh/widgets/cpu-collector.ts",
    "chars": 2620,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand, toFixedNum } from \"./common-utils.js\";\n\nfunction parseCpuLine("
  },
  {
    "path": "src/backend/ssh/widgets/disk-collector.ts",
    "chars": 1760,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand, toFixedNum } from \"./common-utils.js\";\n\nexport async function "
  },
  {
    "path": "src/backend/ssh/widgets/firewall-collector.ts",
    "chars": 5996,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand } from \"./common-utils.js\";\nimport type {\n  FirewallMetrics,\n  "
  },
  {
    "path": "src/backend/ssh/widgets/login-stats-collector.ts",
    "chars": 3647,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand } from \"./common-utils.js\";\n\nexport interface LoginRecord {\n  u"
  },
  {
    "path": "src/backend/ssh/widgets/memory-collector.ts",
    "chars": 1275,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand, toFixedNum, kibToGiB } from \"./common-utils.js\";\n\nexport async"
  },
  {
    "path": "src/backend/ssh/widgets/network-collector.ts",
    "chars": 1812,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand } from \"./common-utils.js\";\n\nexport async function collectNetwo"
  },
  {
    "path": "src/backend/ssh/widgets/ports-collector.ts",
    "chars": 3930,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand } from \"./common-utils.js\";\nimport type {\n  PortsMetrics,\n  Lis"
  },
  {
    "path": "src/backend/ssh/widgets/processes-collector.ts",
    "chars": 1874,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand } from \"./common-utils.js\";\n\nexport async function collectProce"
  },
  {
    "path": "src/backend/ssh/widgets/system-collector.ts",
    "chars": 820,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand } from \"./common-utils.js\";\n\nexport async function collectSyste"
  },
  {
    "path": "src/backend/ssh/widgets/uptime-collector.ts",
    "chars": 940,
    "preview": "import type { Client } from \"ssh2\";\nimport { execCommand } from \"./common-utils.js\";\n\nexport async function collectUptim"
  },
  {
    "path": "src/backend/starter.ts",
    "chars": 6873,
    "preview": "import dotenv from \"dotenv\";\nimport { promises as fs } from \"fs\";\nimport { readFileSync } from \"fs\";\nimport path from \"p"
  },
  {
    "path": "src/backend/swagger.ts",
    "chars": 3890,
    "preview": "import swaggerJSDoc from \"swagger-jsdoc\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { promise"
  },
  {
    "path": "src/backend/utils/auth-manager.ts",
    "chars": 25855,
    "preview": "import jwt from \"jsonwebtoken\";\nimport { UserCrypto } from \"./user-crypto.js\";\nimport { SystemCrypto } from \"./system-cr"
  },
  {
    "path": "src/backend/utils/auto-ssl-setup.ts",
    "chars": 7898,
    "preview": "import { execSync } from \"child_process\";\nimport { promises as fs } from \"fs\";\nimport path from \"path\";\nimport { systemL"
  },
  {
    "path": "src/backend/utils/credential-system-encryption-migration.ts",
    "chars": 3644,
    "preview": "import { db } from \"../database/db/index.js\";\nimport { sshCredentials } from \"../database/db/schema.js\";\nimport { eq, an"
  },
  {
    "path": "src/backend/utils/data-crypto.ts",
    "chars": 15770,
    "preview": "import { FieldCrypto } from \"./field-crypto.js\";\nimport { LazyFieldEncryption } from \"./lazy-field-encryption.js\";\nimpor"
  },
  {
    "path": "src/backend/utils/database-file-encryption.ts",
    "chars": 21645,
    "preview": "import crypto from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { databaseLogger } from \"./logger.js\";"
  },
  {
    "path": "src/backend/utils/database-migration.ts",
    "chars": 11853,
    "preview": "import Database from \"better-sqlite3\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { databaseLogger } from \"./l"
  },
  {
    "path": "src/backend/utils/database-save-trigger.ts",
    "chars": 2902,
    "preview": "import { databaseLogger } from \"./logger.js\";\n\nexport class DatabaseSaveTrigger {\n  private static saveFunction: (() => "
  },
  {
    "path": "src/backend/utils/field-crypto.ts",
    "chars": 3100,
    "preview": "import crypto from \"crypto\";\n\ninterface EncryptedData {\n  data: string;\n  iv: string;\n  tag: string;\n  salt: string;\n  r"
  },
  {
    "path": "src/backend/utils/lazy-field-encryption.ts",
    "chars": 11839,
    "preview": "import { FieldCrypto } from \"./field-crypto.js\";\nimport { databaseLogger } from \"./logger.js\";\n\ninterface DatabaseInstan"
  },
  {
    "path": "src/backend/utils/logger.ts",
    "chars": 7778,
    "preview": "import chalk from \"chalk\";\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\" | \"success\";\n\nexport interface Lo"
  },
  {
    "path": "src/backend/utils/login-rate-limiter.ts",
    "chars": 8121,
    "preview": "interface LoginAttempt {\n  count: number;\n  firstAttempt: number;\n  lockedUntil?: number;\n}\n\nclass LoginRateLimiter {\n  "
  },
  {
    "path": "src/backend/utils/opkssh-binary-manager.ts",
    "chars": 5883,
    "preview": "import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport { createWriteStream } from \"fs\";\nimport { pipeline "
  },
  {
    "path": "src/backend/utils/permission-manager.ts",
    "chars": 10560,
    "preview": "import type { Request, Response, NextFunction } from \"express\";\nimport { db } from \"../database/db/index.js\";\nimport {\n "
  },
  {
    "path": "src/backend/utils/proxy-agent.ts",
    "chars": 857,
    "preview": "import { HttpsProxyAgent } from \"https-proxy-agent\";\nimport type { Agent } from \"http\";\n\nexport function getProxyAgent(t"
  },
  {
    "path": "src/backend/utils/proxy-helper.ts",
    "chars": 9184,
    "preview": "import { SocksClient } from \"socks\";\nimport type { SocksClientOptions } from \"socks\";\nimport net from \"net\";\nimport { ss"
  },
  {
    "path": "src/backend/utils/request-origin.ts",
    "chars": 1807,
    "preview": "import type { Request } from \"express\";\nimport type { IncomingMessage } from \"http\";\n\nexport function getRequestOrigin(r"
  },
  {
    "path": "src/backend/utils/shared-credential-manager.ts",
    "chars": 17992,
    "preview": "import { db } from \"../database/db/index.js\";\nimport {\n  sharedCredentials,\n  sshCredentials,\n  hostAccess,\n  userRoles,"
  },
  {
    "path": "src/backend/utils/simple-db-ops.ts",
    "chars": 4534,
    "preview": "import { getDb, DatabaseSaveTrigger } from \"../database/db/index.js\";\nimport { DataCrypto } from \"./data-crypto.js\";\nimp"
  },
  {
    "path": "src/backend/utils/socks5-helper.ts",
    "chars": 234,
    "preview": "export {\n  createSocks5Connection,\n  createProxyConnection,\n  createHttpConnectConnection,\n  createMixedProxyChainConnec"
  },
  {
    "path": "src/backend/utils/ssh-key-utils.ts",
    "chars": 11606,
    "preview": "import ssh2Pkg from \"ssh2\";\nconst ssh2Utils = ssh2Pkg.utils;\n\nfunction detectKeyTypeFromContent(keyContent: string): str"
  },
  {
    "path": "src/backend/utils/system-crypto.ts",
    "chars": 10674,
    "preview": "import crypto from \"crypto\";\nimport { promises as fs } from \"fs\";\nimport path from \"path\";\nimport { databaseLogger } fro"
  },
  {
    "path": "src/backend/utils/user-agent-parser.ts",
    "chars": 7805,
    "preview": "import type { Request } from \"express\";\nimport crypto from \"crypto\";\n\nexport type DeviceType = \"web\" | \"desktop\" | \"mobi"
  },
  {
    "path": "src/backend/utils/user-crypto.ts",
    "chars": 16725,
    "preview": "import crypto from \"crypto\";\nimport { getDb } from \"../database/db/index.js\";\nimport { settings } from \"../database/db/s"
  },
  {
    "path": "src/backend/utils/user-data-export.ts",
    "chars": 7913,
    "preview": "import { getDb } from \"../database/db/index.js\";\nimport {\n  users,\n  hosts,\n  sshCredentials,\n  fileManagerRecent,\n  fil"
  },
  {
    "path": "src/backend/utils/user-data-import.ts",
    "chars": 14157,
    "preview": "import { getDb } from \"../database/db/index.js\";\nimport {\n  users,\n  hosts,\n  sshCredentials,\n  fileManagerRecent,\n  fil"
  },
  {
    "path": "src/components/theme-provider.tsx",
    "chars": 1664,
    "preview": "/* eslint-disable react-refresh/only-export-components */\nimport { createContext, useContext, useEffect, useState } from"
  },
  {
    "path": "src/components/ui/accordion.tsx",
    "chars": 2049,
    "preview": "import * as React from \"react\";\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\";\nimport { ChevronDownIco"
  },
  {
    "path": "src/components/ui/alert-dialog.tsx",
    "chars": 3868,
    "preview": "import * as React from \"react\";\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\";\n\nimport { cn } fro"
  },
  {
    "path": "src/components/ui/alert.tsx",
    "chars": 1634,
    "preview": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \""
  },
  {
    "path": "src/components/ui/badge.tsx",
    "chars": 1703,
    "preview": "/* eslint-disable react-refresh/only-export-components */\nimport * as React from \"react\";\nimport { Slot } from \"@radix-u"
  },
  {
    "path": "src/components/ui/button-group.tsx",
    "chars": 1594,
    "preview": "import { Children, ReactElement, cloneElement, isValidElement } from \"react\";\n\nimport { type ButtonProps } from \"@/compo"
  },
  {
    "path": "src/components/ui/button.tsx",
    "chars": 2286,
    "preview": "/* eslint-disable react-refresh/only-export-components */\nimport * as React from \"react\";\nimport { Slot } from \"@radix-u"
  },
  {
    "path": "src/components/ui/card.tsx",
    "chars": 2015,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Card({ className, ...props }: React.Compone"
  },
  {
    "path": "src/components/ui/chart.tsx",
    "chars": 1155,
    "preview": "import * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\n\nimport { cn } from \"@/lib/utils\";\n\n// Ch"
  },
  {
    "path": "src/components/ui/checkbox.tsx",
    "chars": 1219,
    "preview": "import * as React from \"react\";\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { CheckIcon } from"
  },
  {
    "path": "src/components/ui/command.tsx",
    "chars": 4859,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon }"
  },
  {
    "path": "src/components/ui/dialog.tsx",
    "chars": 3986,
    "preview": "import * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide"
  },
  {
    "path": "src/components/ui/dropdown-menu.tsx",
    "chars": 7351,
    "preview": "import * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { CheckIc"
  },
  {
    "path": "src/components/ui/form.tsx",
    "chars": 3846,
    "preview": "/* eslint-disable react-refresh/only-export-components */\nimport * as React from \"react\";\nimport * as LabelPrimitive fro"
  },
  {
    "path": "src/components/ui/input.tsx",
    "chars": 982,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Input({ className, type, ...props }: React."
  },
  {
    "path": "src/components/ui/kbd.tsx",
    "chars": 867,
    "preview": "import { cn } from \"@/lib/utils\";\n\nfunction Kbd({ className, ...props }: React.ComponentProps<\"kbd\">) {\n  return (\n    <"
  },
  {
    "path": "src/components/ui/label.tsx",
    "chars": 603,
    "preview": "import * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\n\nimport { cn } from \"@/lib/util"
  },
  {
    "path": "src/components/ui/password-input.tsx",
    "chars": 1176,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Eye, EyeOff } from \"lucide-react\";\nimport { Input } from \"@/comp"
  },
  {
    "path": "src/components/ui/popover.tsx",
    "chars": 1630,
    "preview": "import * as React from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\n\nimport { cn } from \"@/lib/"
  },
  {
    "path": "src/components/ui/progress.tsx",
    "chars": 732,
    "preview": "import * as React from \"react\";\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\n\nimport { cn } from \"@/li"
  },
  {
    "path": "src/components/ui/resizable.tsx",
    "chars": 2141,
    "preview": "import * as React from \"react\";\nimport { GripVerticalIcon } from \"lucide-react\";\nimport * as ResizablePrimitive from \"re"
  },
  {
    "path": "src/components/ui/scroll-area.tsx",
    "chars": 1638,
    "preview": "import * as React from \"react\";\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\";\n\nimport { cn } from "
  },
  {
    "path": "src/components/ui/select.tsx",
    "chars": 6276,
    "preview": "import * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { CheckIcon, ChevronDo"
  },
  {
    "path": "src/components/ui/separator.tsx",
    "chars": 706,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\";\n\nimport "
  },
  {
    "path": "src/components/ui/shadcn-io/status/index.tsx",
    "chars": 2094,
    "preview": "import type { ComponentProps, HTMLAttributes } from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { cn "
  },
  {
    "path": "src/components/ui/sheet.tsx",
    "chars": 4149,
    "preview": "import * as React from \"react\";\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-"
  },
  {
    "path": "src/components/ui/sidebar.tsx",
    "chars": 21687,
    "preview": "/* eslint-disable react-refresh/only-export-components */\nimport * as React from \"react\";\nimport { Slot } from \"@radix-u"
  },
  {
    "path": "src/components/ui/skeleton.tsx",
    "chars": 279,
    "preview": "import { cn } from \"@/lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n"
  },
  {
    "path": "src/components/ui/slider.tsx",
    "chars": 1995,
    "preview": "import * as React from \"react\";\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\n\nimport { cn } from \"@/lib/ut"
  },
  {
    "path": "src/components/ui/sonner.tsx",
    "chars": 1737,
    "preview": "import { useTheme } from \"@/components/theme-provider\";\nimport { Toaster as Sonner, type ToasterProps, toast } from \"son"
  },
  {
    "path": "src/components/ui/switch.tsx",
    "chars": 1170,
    "preview": "import * as React from \"react\";\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\";\n\nimport { cn } from \"@/lib/ut"
  },
  {
    "path": "src/components/ui/table.tsx",
    "chars": 2464,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Table({ className, ...props }: React.Compon"
  },
  {
    "path": "src/components/ui/tabs.tsx",
    "chars": 2177,
    "preview": "import * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\n\nimport { cn } from \"@/lib/utils\""
  },
  {
    "path": "src/components/ui/textarea.tsx",
    "chars": 837,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nexport type TextareaProps = React.TextareaHTMLAt"
  },
  {
    "path": "src/components/ui/tooltip.tsx",
    "chars": 1790,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\n\nimport { cn"
  },
  {
    "path": "src/components/ui/version-alert.tsx",
    "chars": 3203,
    "preview": "import React from \"react\";\nimport { Alert, AlertTitle, AlertDescription } from \"@/components/ui/alert.tsx\";\nimport { But"
  },
  {
    "path": "src/constants/terminal-themes.ts",
    "chars": 18686,
    "preview": "export interface TerminalTheme {\n  name: string;\n  category: \"dark\" | \"light\" | \"colorful\";\n  colors: {\n    background: "
  },
  {
    "path": "src/hooks/use-confirmation.ts",
    "chars": 4576,
    "preview": "import { useState, useEffect, useCallback } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface ConfirmationOptions"
  },
  {
    "path": "src/hooks/use-mobile.ts",
    "chars": 585,
    "preview": "import * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setI"
  },
  {
    "path": "src/hooks/use-service-worker.ts",
    "chars": 1811,
    "preview": "import { useEffect, useState, useCallback } from \"react\";\nimport { isElectron } from \"@/ui/main-axios\";\nimport { getBase"
  },
  {
    "path": "src/i18n/i18n.ts",
    "chars": 5287,
    "preview": "import i18n from \"i18next\";\nimport { initReactI18next } from \"react-i18next\";\nimport LanguageDetector from \"i18next-brow"
  },
  {
    "path": "src/index.css",
    "chars": 7908,
    "preview": "@import \"tailwindcss\";\n\n@custom-variant dark (&:is(.dark *));\n\n:root {\n  font-family: system-ui, Avenir, Helvetica, Aria"
  },
  {
    "path": "src/lib/base-path.ts",
    "chars": 194,
    "preview": "export function getBasePath(): string {\n  const base = import.meta.env.BASE_URL || \"/\";\n  if (base === \"./\" || base === "
  },
  {
    "path": "src/lib/clipboard-provider.ts",
    "chars": 1185,
    "preview": "import type {\n  IClipboardProvider,\n  ClipboardSelectionType,\n} from \"@xterm/addon-clipboard\";\n\nexport class RobustClipb"
  },
  {
    "path": "src/lib/db-health-monitor.ts",
    "chars": 3528,
    "preview": "type EventListener = (...args: any[]) => void;\n\nclass DatabaseHealthMonitor {\n  private static instance: DatabaseHealthM"
  },
  {
    "path": "src/lib/frontend-logger.ts",
    "chars": 10543,
    "preview": "export type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\" | \"success\";\n\nexport interface LogContext {\n  operation?: str"
  },
  {
    "path": "src/lib/terminal-syntax-highlighter.ts",
    "chars": 5749,
    "preview": "const ANSI_CODES = {\n  reset: \"\\x1b[0m\",\n  colors: {\n    red: \"\\x1b[31m\",\n    green: \"\\x1b[32m\",\n    yellow: \"\\x1b[33m\","
  },
  {
    "path": "src/lib/utils.ts",
    "chars": 169,
    "preview": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: C"
  },
  {
    "path": "src/locales/README.md",
    "chars": 117,
    "preview": "# DO NOT MODIFY TRANSLATED FILES DIRECTLY\n\nInstead, use Crowdsin. See [docs](https://docs.termix.site/translations).\n"
  },
  {
    "path": "src/locales/en.json",
    "chars": 146547,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Credentials\",\n    \"credentialsViewer\": \"Credentials Viewer\",\n    \"manageYourSSH"
  },
  {
    "path": "src/locales/translated/af_ZA.json",
    "chars": 152111,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Geloofsbriewe\",\n    \"credentialsViewer\": \"Geloofsbriewe-kyker\",\n    \"manageYour"
  },
  {
    "path": "src/locales/translated/ar_SA.json",
    "chars": 140116,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"وثائق التفويض\",\n    \"credentialsViewer\": \"عارض وثائق التفويض\",\n    \"manageYourS"
  },
  {
    "path": "src/locales/translated/bg_BG.json",
    "chars": 163297,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Пълномощия\",\n    \"credentialsViewer\": \"Преглед на идентификационни данни\",\n    "
  },
  {
    "path": "src/locales/translated/bn_BD.json",
    "chars": 154894,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"পরিচয়পত্র\",\n    \"credentialsViewer\": \"শংসাপত্র দর্শক\",\n    \"manageYourSSHCrede"
  },
  {
    "path": "src/locales/translated/ca_ES.json",
    "chars": 163669,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Credencials\",\n    \"credentialsViewer\": \"Visualitzador de credencials\",\n    \"man"
  },
  {
    "path": "src/locales/translated/cs_CZ.json",
    "chars": 150908,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Přihlašovací údaje\",\n    \"credentialsViewer\": \"Prohlížeč přihlašovacích údajů\","
  },
  {
    "path": "src/locales/translated/da_DK.json",
    "chars": 148897,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Legitimation\",\n    \"credentialsViewer\": \"Legitimationsfremviser\",\n    \"manageYo"
  },
  {
    "path": "src/locales/translated/de_DE.json",
    "chars": 159288,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Anmeldedaten\",\n    \"credentialsViewer\": \"Zugangsdaten-Betrachter\",\n    \"manageY"
  },
  {
    "path": "src/locales/translated/el_GR.json",
    "chars": 165154,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Διαπιστευτήρια\",\n    \"credentialsViewer\": \"Προβολέας Πιστοποιητικών\",\n    \"mana"
  },
  {
    "path": "src/locales/translated/es_ES.json",
    "chars": 158882,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Credenciales\",\n    \"credentialsViewer\": \"Visor de credenciales\",\n    \"manageYou"
  },
  {
    "path": "src/locales/translated/fi_FI.json",
    "chars": 154617,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Käyttäjätunnukset\",\n    \"credentialsViewer\": \"Käyttöoikeustietojen Katselin\",\n "
  },
  {
    "path": "src/locales/translated/fr_FR.json",
    "chars": 166086,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Identifiants\",\n    \"credentialsViewer\": \"Visionneuse des identifiants\",\n    \"ma"
  },
  {
    "path": "src/locales/translated/he_IL.json",
    "chars": 133874,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"אישורים\",\n    \"credentialsViewer\": \"מציג אישורים\",\n    \"manageYourSSHCredential"
  },
  {
    "path": "src/locales/translated/hi_IN.json",
    "chars": 153047,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"साख\",\n    \"credentialsViewer\": \"क्रेडेंशियल व्यूअर\",\n    \"manageYourSSHCredenti"
  },
  {
    "path": "src/locales/translated/hu_HU.json",
    "chars": 160146,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Hitelesítő adatok\",\n    \"credentialsViewer\": \"Hitelesítő adatok megtekintője\",\n"
  },
  {
    "path": "src/locales/translated/id_ID.json",
    "chars": 152582,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Kredensial\",\n    \"credentialsViewer\": \"Penampil Kredensial\",\n    \"manageYourSSH"
  },
  {
    "path": "src/locales/translated/it_IT.json",
    "chars": 157514,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"Credenziali\",\n    \"credentialsViewer\": \"Visualizzatore Di Credenziali\",\n    \"ma"
  },
  {
    "path": "src/locales/translated/ja_JP.json",
    "chars": 115506,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"資格情報\",\n    \"credentialsViewer\": \"資格情報ビューアー\",\n    \"manageYourSSHCredentials\": \"S"
  },
  {
    "path": "src/locales/translated/ko_KR.json",
    "chars": 115997,
    "preview": "{\n  \"credentials\": {\n    \"credentials\": \"자격 증명\",\n    \"credentialsViewer\": \"자격 증명 뷰어\",\n    \"manageYourSSHCredentials\": \"S"
  }
]

// ... and 177 more files (download for full content)

About this extraction

This page contains the full source code of the Termix-SSH/Termix GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 377 files (8.7 MB), approximately 2.3M tokens, and a symbol index with 1599 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!