471cfdd161
- 1 - Add: Gitea now creates timestamped database backup bundles under `[backup].PATH`, exposes the backup schedule in the installer, and surfaces the `database_backup` cron task in admin monitoring. - 2 - Add: installed instances now use `.gitea-installed` and `.gitea-recovery.ini` to enter email-gated recovery instead of falling back to public install mode when configuration or database access is broken. - 3 - Mod: the installer recovery flow now covers backup-bundle restore, bundled or manual `app.ini` reuse, uploaded SQL/GZ database restores, and repository-filesystem recovery with source-specific validation, confirmations, and preserved launcher state. - 4 - Fix: recovery now restores bundled `app.ini` snapshots when needed, discovers backup bundles from both the active backup path and persisted `.gitea-recovery.ini` path, and preserves SMTP and other rebuilt settings correctly when `app.ini` is missing or incomplete. - 5 - Fix: recovery validation and restore handling now accept either a selected backup bundle or an uploaded SQL/GZ dump, keep sensitive secrets and existing `LFS_JWT_SECRET` when appropriate, clear SQLite restore targets before import, and complete the post-install handoff without redirect loops. - 6 - Mod: fresh installs now default recovery email authorization to enabled with first-admin fallback, and the install/recovery UI, styling, and EN/RO wording were refined to match the final launcher behavior. Co-Authored-By: petru @ codex (GPT-5) <codex@openai.com> (cherry picked from commit 9879caf2292691b0cb521d12e6fee924b066bae2)
1283 lines
72 KiB
Handlebars
1283 lines
72 KiB
Handlebars
{{template "base/head" .}}
|
|
<div role="main" aria-label="{{.Title}}" class="page-content install">
|
|
<div class="ui grid install-config-container">
|
|
<div class="sixteen wide tw-text-center centered column">
|
|
<h3 class="ui top attached header">
|
|
{{ctx.Locale.Tr "install.title"}}
|
|
</h3>
|
|
<div class="ui attached segment">
|
|
{{if not (or .Err_DbInstalledBefore .Err_RepositoryFilesystemRecovery .Err_DatabaseBackupRecovery)}}{{template "base/alert" .}}{{end}} <!-- edit/add - by petru @ codex -->
|
|
|
|
<p>{{ctx.Locale.Tr "install.docker_helper" "https://docs.gitea.com/installation/install-with-docker"}}</p>
|
|
|
|
<form id="install-form" class="ui form js-install-form" action="{{.InstallFormAction}}" method="post" enctype="multipart/form-data"> <!-- edit/add - by petru @ codex -->
|
|
<!-- start edit/add - by petru @ codex -->
|
|
<div class="install-recovery-entry">
|
|
<div class="install-recovery-entry-copy">
|
|
<h4 class="ui header">{{.InstallRecoverySectionTitle}}</h4>
|
|
<p>{{.InstallRecoverySectionDesc}}</p>
|
|
</div>
|
|
<div class="install-recovery-entry-actions">
|
|
<button type="button" class="ui primary button js-install-recovery-launcher">{{ctx.Locale.Tr "install.recovery_reinstall_open"}}</button>
|
|
</div>
|
|
</div>
|
|
<!-- end edit/add - by petru @ codex -->
|
|
<input type="hidden" name="imported_app_ini" value="{{if .imported_app_ini}}true{{end}}"> <!-- edit/add - by petru @ codex -->
|
|
<input type="hidden" id="install_backup_restore_id" name="backup_restore_id" value="{{.backup_restore_id}}"> <!-- edit/add - by petru @ codex -->
|
|
<input type="hidden" id="install_backup_import_app_ini" name="backup_import_app_ini" value="{{if .backup_import_app_ini}}true{{end}}"> <!-- edit/add - by petru @ codex -->
|
|
<input type="hidden" id="install_backup_restore_db" name="backup_restore_db" value="{{if .backup_restore_db}}true{{end}}"> <!-- edit/add - by petru @ codex -->
|
|
<input type="hidden" id="install_recovery_mode" name="recovery_mode" value="{{.recovery_mode}}"> <!-- edit/add - by petru @ codex -->
|
|
<input type="hidden" id="install_reinstall_confirm_first" name="reinstall_confirm_first" value="{{if .reinstall_confirm_first}}true{{end}}"> <!-- edit/add - by petru @ codex -->
|
|
<input type="hidden" id="install_reinstall_confirm_second" name="reinstall_confirm_second" value="{{if .reinstall_confirm_second}}true{{end}}"> <!-- edit/add - by petru @ codex -->
|
|
<input type="hidden" id="install_reinstall_confirm_third" name="reinstall_confirm_third" value="{{if .reinstall_confirm_third}}true{{end}}"> <!-- edit/add - by petru @ codex -->
|
|
{{if .InstallIsRecoveryRequest}}<input type="hidden" name="recovery_request" value="true">{{end}} <!-- edit/add - by petru @ codex -->
|
|
<input type="hidden" name="imported_lfs_jwt_secret" value="{{.imported_lfs_jwt_secret}}">
|
|
<input type="hidden" name="imported_internal_token" value="{{.imported_internal_token}}">
|
|
<input type="hidden" name="imported_o_auth2_jwt_secret" value="{{.imported_o_auth2_jwt_secret}}">
|
|
|
|
<!-- Database Settings -->
|
|
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.db_title"}}</h4>
|
|
<p>{{ctx.Locale.Tr "install.require_db_desc"}}</p>
|
|
<div class="inline required field {{if .Err_DbType}}error{{end}}">
|
|
<label>{{ctx.Locale.Tr "install.db_type"}}</label>
|
|
<div class="ui selection database type dropdown">
|
|
<input type="hidden" id="db_type" name="db_type" value="{{.CurDbType}}">
|
|
<div class="text">{{.CurDbType}}</div>
|
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
<div class="menu">
|
|
{{range .DbTypeNames}}
|
|
<div class="item" data-value="{{.type}}">{{.name}}</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tw-mt-4 tw-hidden" data-db-setting-for="common-host">
|
|
<div class="inline required field {{if .Err_DbSetting}}error{{end}}">
|
|
<label for="db_host">{{ctx.Locale.Tr "install.host"}}</label>
|
|
<input id="db_host" name="db_host" value="{{.db_host}}">
|
|
</div>
|
|
<div class="inline required field {{if .Err_DbSetting}}error{{end}}">
|
|
<label for="db_user">{{ctx.Locale.Tr "install.user"}}</label>
|
|
<input id="db_user" name="db_user" value="{{.db_user}}">
|
|
</div>
|
|
<div class="inline required field {{if .Err_DbSetting}}error{{end}}">
|
|
<label for="db_passwd">{{ctx.Locale.Tr "install.password"}}</label>
|
|
<div class="ui input js-password-toggle-group" data-global-init="initPasswordVisibilityToggle" data-show-label="{{ctx.Locale.Tr "auth.show_password"}}" data-hide-label="{{ctx.Locale.Tr "auth.hide_password"}}">
|
|
<input id="db_passwd" name="db_passwd" type="password" value="{{.db_passwd}}" class="js-password-toggle-source">
|
|
</div>
|
|
</div>
|
|
<div class="inline required field {{if .Err_DbSetting}}error{{end}}">
|
|
<label for="db_name">{{ctx.Locale.Tr "install.db_name"}}</label>
|
|
<input id="db_name" name="db_name" value="{{.db_name}}">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tw-mt-4 tw-hidden" data-db-setting-for="postgres">
|
|
<div class="inline required field">
|
|
<label>{{ctx.Locale.Tr "install.ssl_mode"}}</label>
|
|
<div class="ui selection database type dropdown">
|
|
<input type="hidden" name="ssl_mode" value="{{if .ssl_mode}}{{.ssl_mode}}{{else}}disable{{end}}">
|
|
<div class="default text">disable</div>
|
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
<div class="menu">
|
|
<div class="item" data-value="disable">Disable</div>
|
|
<div class="item" data-value="require">Require</div>
|
|
<div class="item" data-value="verify-full">Verify Full</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="inline field {{if .Err_DbSetting}}error{{end}}">
|
|
<label for="db_schema">{{ctx.Locale.Tr "install.db_schema"}}</label>
|
|
<input id="db_schema" name="db_schema" value="{{.db_schema}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.db_schema_helper"}}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tw-mt-4 tw-hidden" data-db-setting-for="sqlite3">
|
|
<div class="inline required field {{if or .Err_DbPath .Err_DbSetting}}error{{end}}">
|
|
<label for="db_path">{{ctx.Locale.Tr "install.path"}}</label>
|
|
<input id="db_path" name="db_path" value="{{.db_path}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.sqlite_helper"}}</span>
|
|
</div>
|
|
</div>
|
|
<!-- start edit/add - by petru @ codex -->
|
|
<details class="optional field"{{if or .Err_BackupPath .Err_BackupRecovery}} open{{end}}>
|
|
<summary class="right-content tw-py-2{{if or .Err_BackupPath .Err_BackupRecovery}} tw-text-red{{end}}">
|
|
{{ctx.Locale.Tr "install.database_backup_title"}}
|
|
</summary>
|
|
<span class="desc">{{ctx.Locale.Tr "install.database_backup_desc"}}</span>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label>{{ctx.Locale.Tr "install.database_backup_enabled"}}</label>
|
|
<input name="backup_enabled" type="checkbox" {{if .backup_enabled}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.database_backup_enabled_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label>{{ctx.Locale.Tr "install.database_backup_run_at_start"}}</label>
|
|
<input name="backup_run_at_start" type="checkbox" {{if .backup_run_at_start}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.database_backup_run_at_start_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="backup_schedule">{{ctx.Locale.Tr "install.database_backup_schedule"}}</label>
|
|
<input id="backup_schedule" name="backup_schedule" value="{{.backup_schedule}}" placeholder="@daily">
|
|
<span class="help">{{ctx.Locale.Tr "install.database_backup_schedule_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="backup_path">{{ctx.Locale.Tr "install.database_backup_path"}}</label>
|
|
<input id="backup_path" name="backup_path" value="{{.backup_path}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.database_backup_path_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="backup_retention_count">{{ctx.Locale.Tr "install.database_backup_retention_count"}}</label>
|
|
<input id="backup_retention_count" name="backup_retention_count" type="number" min="0" value="{{.backup_retention_count}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.database_backup_retention_count_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label>{{ctx.Locale.Tr "install.database_backup_compress"}}</label>
|
|
<input name="backup_compress" type="checkbox" {{if .backup_compress}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.database_backup_compress_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label>{{ctx.Locale.Tr "install.database_backup_include_app_ini_snapshot"}}</label>
|
|
<input name="backup_include_app_ini_snapshot" type="checkbox" {{if .backup_include_app_ini_snapshot}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.database_backup_include_app_ini_snapshot_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label>{{ctx.Locale.Tr "install.recovery_email_enabled"}}</label>
|
|
<input name="recovery_email_enabled" type="checkbox" {{if .recovery_email_enabled}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.recovery_email_enabled_helper"}}</span>
|
|
</div>
|
|
<div class="inline field {{if .Err_BackupRecovery}}error{{end}}">
|
|
<label for="recovery_allowed_emails">{{ctx.Locale.Tr "install.recovery_allowed_emails"}}</label>
|
|
<textarea id="recovery_allowed_emails" name="recovery_allowed_emails" rows="3">{{.recovery_allowed_emails}}</textarea>
|
|
<span class="help">{{ctx.Locale.Tr "install.recovery_allowed_emails_helper"}}</span>
|
|
</div>
|
|
</details>
|
|
<!-- end edit/add - by petru @ codex -->
|
|
|
|
<!-- General Settings -->
|
|
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.general_title"}}</h4>
|
|
<div class="inline required field {{if .Err_AppName}}error{{end}}">
|
|
<label for="app_name">{{ctx.Locale.Tr "install.app_name"}}</label>
|
|
<input id="app_name" name="app_name" value="{{.app_name}}" required>
|
|
<span class="help">{{ctx.Locale.Tr "install.app_name_helper"}}</span>
|
|
</div>
|
|
<div class="inline field {{if .Err_DefaultLanguage}}error{{end}}">
|
|
<label>{{ctx.Locale.Tr "install.default_language"}}</label>
|
|
<div class="ui language selection dropdown">
|
|
<input name="default_language" type="hidden" value="{{.default_language}}">
|
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
<div class="text">{{range .AllLangs}}{{if eq $.default_language .Lang}}{{.Name}}{{end}}{{end}}</div>
|
|
<div class="menu">
|
|
{{range .AllLangs}}
|
|
<div class="item{{if eq $.default_language .Lang}} active selected{{end}}" data-value="{{.Lang}}">{{.Name}}</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.default_language_helper"}}</span>
|
|
</div>
|
|
<div class="inline required field {{if .Err_RepoRootPath}}error{{end}}">
|
|
<label for="repo_root_path">{{ctx.Locale.Tr "install.repo_path"}}</label>
|
|
<input id="repo_root_path" name="repo_root_path" value="{{.repo_root_path}}" required>
|
|
<span class="help">{{ctx.Locale.Tr "install.repo_path_helper"}}</span>
|
|
</div>
|
|
<div class="inline field {{if .Err_LFSRootPath}}error{{end}}">
|
|
<label for="lfs_root_path">{{ctx.Locale.Tr "install.lfs_path"}}</label>
|
|
<input id="lfs_root_path" name="lfs_root_path" value="{{.lfs_root_path}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.lfs_path_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="run_user">{{ctx.Locale.Tr "install.run_user"}}</label>
|
|
<input id="run_user" name="run_user" value="{{.run_user}}" readonly>
|
|
<span class="help">{{ctx.Locale.Tr "install.run_user_helper"}}</span>
|
|
</div>
|
|
<div class="inline required field">
|
|
<label for="domain">{{ctx.Locale.Tr "install.domain"}}</label>
|
|
<input id="domain" name="domain" value="{{.domain}}" placeholder="demo.gitea.com" required>
|
|
<span class="help">{{ctx.Locale.Tr "install.domain_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="ssh_port">{{ctx.Locale.Tr "install.ssh_port"}}</label>
|
|
<input id="ssh_port" name="ssh_port" value="{{.ssh_port}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.ssh_port_helper"}}</span>
|
|
</div>
|
|
<div class="inline required field">
|
|
<label for="http_port">{{ctx.Locale.Tr "install.http_port"}}</label>
|
|
<input id="http_port" name="http_port" value="{{.http_port}}" required>
|
|
<span class="help">{{ctx.Locale.Tr "install.http_port_helper"}}</span>
|
|
</div>
|
|
<div class="inline required field">
|
|
<label for="app_url">{{ctx.Locale.Tr "install.app_url"}}</label>
|
|
<input id="app_url" name="app_url" value="{{.app_url}}" placeholder="https://demo.gitea.com" required>
|
|
<span class="help">{{ctx.Locale.Tr "install.app_url_helper"}}</span>
|
|
</div>
|
|
<div class="inline required field">
|
|
<label for="log_root_path">{{ctx.Locale.Tr "install.log_root_path"}}</label>
|
|
<input id="log_root_path" name="log_root_path" value="{{.log_root_path}}" placeholder="log" required>
|
|
<span class="help">{{ctx.Locale.Tr "install.log_root_path_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label>{{ctx.Locale.Tr "install.enable_update_checker"}}</label>
|
|
<input name="enable_update_checker" type="checkbox">
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.enable_update_checker_helper"}}</span>
|
|
</div>
|
|
|
|
<!-- Optional Settings -->
|
|
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.optional_title"}}</h4>
|
|
<div>
|
|
<!-- Branding -->
|
|
<details class="optional field"{{if .Err_Branding}} open{{end}}>
|
|
<summary class="right-content tw-py-2{{if .Err_Branding}} tw-text-red{{end}}">
|
|
{{ctx.Locale.Tr "install.branding_title"}}
|
|
</summary>
|
|
<span class="desc">{{ctx.Locale.Tr "install.branding_desc"}}</span>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label for="branding_use_shared_assets">{{ctx.Locale.Tr "install.branding.shared_assets"}}</label>
|
|
<input id="branding_use_shared_assets" name="branding_use_shared_assets" type="checkbox" {{if .branding_use_shared_assets}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.branding.shared_assets_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="logo_svg" id="branding_logo_svg_label" data-default-label="{{ctx.Locale.Tr "install.branding.logo_svg"}}" data-shared-label="{{ctx.Locale.Tr "install.branding.logo_and_favicon_svg"}}">{{ctx.Locale.Tr "install.branding.logo_svg"}}</label>
|
|
<input id="logo_svg" name="logo_svg" type="file" accept=".svg,image/svg+xml">
|
|
<span class="help">{{ctx.Locale.Tr "install.branding.logo_svg_helper" .BrandingMaxFileSizeKB}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="logo_png" id="branding_logo_png_label" data-default-label="{{ctx.Locale.Tr "install.branding.logo_png"}}" data-shared-label="{{ctx.Locale.Tr "install.branding.logo_and_favicon_png"}}">{{ctx.Locale.Tr "install.branding.logo_png"}}</label>
|
|
<input id="logo_png" name="logo_png" type="file" accept=".png,image/png">
|
|
<span class="help">{{ctx.Locale.Tr "install.branding.logo_png_helper" .BrandingMaxFileSizeKB .BrandingMinPNGEdge .BrandingMinPNGEdge}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="loading_png">{{ctx.Locale.Tr "install.branding.loading_png"}}</label>
|
|
<input id="loading_png" name="loading_png" type="file" accept=".png,image/png">
|
|
<span class="help">{{ctx.Locale.Tr "install.branding.loading_png_helper" .BrandingMaxFileSizeKB .BrandingMinPNGEdge .BrandingMinPNGEdge}}</span>
|
|
</div>
|
|
<div class="inline field js-install-branding-favicon-field">
|
|
<label for="favicon_svg">{{ctx.Locale.Tr "install.branding.favicon_svg"}}</label>
|
|
<input id="favicon_svg" name="favicon_svg" type="file" accept=".svg,image/svg+xml">
|
|
<span class="help">{{ctx.Locale.Tr "install.branding.favicon_svg_helper" .BrandingMaxFileSizeKB}}</span>
|
|
</div>
|
|
<div class="inline field js-install-branding-favicon-field">
|
|
<label for="favicon_png">{{ctx.Locale.Tr "install.branding.favicon_png"}}</label>
|
|
<input id="favicon_png" name="favicon_png" type="file" accept=".png,image/png">
|
|
<span class="help">{{ctx.Locale.Tr "install.branding.favicon_png_helper" .BrandingMaxFileSizeKB .BrandingMinPNGEdge .BrandingMinPNGEdge}}</span>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Email -->
|
|
<details class="optional field">
|
|
<summary class="right-content tw-py-2{{if .Err_SMTP}} tw-text-red{{end}}">
|
|
{{ctx.Locale.Tr "install.email_title"}}
|
|
</summary>
|
|
<div class="inline field">
|
|
<label for="smtp_addr">{{ctx.Locale.Tr "install.smtp_addr"}}</label>
|
|
<input id="smtp_addr" name="smtp_addr" value="{{.smtp_addr}}">
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="smtp_port">{{ctx.Locale.Tr "install.smtp_port"}}</label>
|
|
<input id="smtp_port" name="smtp_port" value="{{.smtp_port}}">
|
|
</div>
|
|
<div class="inline field {{if .Err_SMTPFrom}}error{{end}}">
|
|
<label for="smtp_from_name">{{ctx.Locale.Tr "install.smtp_from"}}</label>
|
|
<div class="tw-inline-flex tw-items-center tw-gap-2 tw-flex-wrap" style="width: 60%;">
|
|
<div class="ui input" style="flex: 1 1 200px;">
|
|
<input id="smtp_from_name" name="smtp_from_name" value="{{.smtp_from_name}}" placeholder="{{ctx.Locale.Tr "install.smtp_from_name_placeholder"}}">
|
|
</div>
|
|
<div class="ui input" style="flex: 1 1 240px;">
|
|
<input id="smtp_from_address" name="smtp_from_address" type="email" value="{{.smtp_from_address}}" placeholder="{{ctx.Locale.Tr "install.smtp_from_address_placeholder"}}">
|
|
</div>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.TrString "install.smtp_from_helper"}}{{/* it contains lt/gt chars*/}}</span>
|
|
</div>
|
|
<div class="inline field {{if .Err_SMTPUser}}error{{end}}">
|
|
<label for="smtp_user">{{ctx.Locale.Tr "install.mailer_user"}}</label>
|
|
<input id="smtp_user" name="smtp_user" value="{{.smtp_user}}">
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="smtp_passwd">{{ctx.Locale.Tr "install.mailer_password"}}</label>
|
|
<div class="ui input js-password-toggle-group" data-global-init="initPasswordVisibilityToggle" data-show-label="{{ctx.Locale.Tr "auth.show_password"}}" data-hide-label="{{ctx.Locale.Tr "auth.hide_password"}}">
|
|
<input id="smtp_passwd" name="smtp_passwd" type="password" value="{{.smtp_passwd}}" class="js-password-toggle-source">
|
|
</div>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="test_mail_email">{{ctx.Locale.Tr "install.test_mail_to"}}</label>
|
|
<div class="tw-inline-flex tw-items-center tw-gap-2 tw-flex-wrap" style="width: 60%;">
|
|
<div class="ui test_mail small input">
|
|
<input id="test_mail_email" name="test_mail_email" type="email" placeholder="{{ctx.Locale.Tr "admin.config.test_email_placeholder"}}" size="40">
|
|
<button class="ui tiny js-install-test-mail-button button" data-mail-action-ui="inline-v2" data-action="{{.InstallTestMailAction}}" type="button" style="background: var(--color-primary); color: var(--color-primary-contrast); border-color: var(--color-primary);">{{ctx.Locale.Tr "admin.config.send_test_mail_submit"}}</button>
|
|
</div>
|
|
<span class="js-install-test-mail-message"></span>
|
|
</div>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label>{{ctx.Locale.Tr "install.mail_notify"}}</label>
|
|
<input name="mail_notify" type="checkbox" {{if .mail_notify}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.mail_notify_helper"}}</span>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Registration management -->
|
|
<details class="optional field">
|
|
<summary class="right-content tw-py-2{{if .Err_Registration}} tw-text-red{{end}}">
|
|
{{ctx.Locale.Tr "install.registration_title"}}
|
|
</summary>
|
|
<div class="inline field">
|
|
<label>{{ctx.Locale.Tr "install.registration_mode"}}</label>
|
|
<div class="grouped fields">
|
|
<div class="field">
|
|
<div class="ui radio checkbox">
|
|
<input name="registration_mode" type="radio" value="admin_only" {{if eq .registration_mode "admin_only"}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.registration_mode.admin_only"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.registration_mode.admin_only_helper"}}</p>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui radio checkbox">
|
|
<input name="registration_mode" type="radio" value="local_only" {{if eq .registration_mode "local_only"}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.registration_mode.local_only"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.registration_mode.local_only_helper"}}</p>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui radio checkbox">
|
|
<input name="registration_mode" type="radio" value="external_only" {{if eq .registration_mode "external_only"}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.registration_mode.external_only"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.registration_mode.external_only_helper"}}</p>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui radio checkbox">
|
|
<input name="registration_mode" type="radio" value="local_and_external" {{if or (not .registration_mode) (eq .registration_mode "local_and_external")}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.registration_mode.local_and_external"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.registration_mode.local_and_external_helper"}}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="inline field" id="registration-local-options">
|
|
<label>{{ctx.Locale.Tr "install.registration_local_options"}}</label>
|
|
<div class="grouped fields">
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input name="register_confirm" type="checkbox" {{if or .register_confirm .register_manual_confirm}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.register_confirm"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.register_confirm_helper"}}</p>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input name="register_manual_confirm" type="checkbox" {{if .register_manual_confirm}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.register_manual_confirm"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.register_manual_confirm_helper"}}</p>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input name="enable_captcha" type="checkbox" {{if .enable_captcha}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.enable_captcha"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.enable_captcha_popup"}}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="inline field" id="registration-external-options">
|
|
<label>{{ctx.Locale.Tr "install.registration_external_options"}}</label>
|
|
<div class="grouped fields">
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input name="enable_open_id_sign_in" type="checkbox" {{if .enable_open_id_sign_in}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.openid_signin"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.openid_signin_helper"}}</p>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input name="enable_open_id_sign_up" type="checkbox" {{if .enable_open_id_sign_up}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.openid_signup"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.openid_signup_helper"}}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Server and other services -->
|
|
<details class="optional field">
|
|
<summary class="right-content tw-py-2">
|
|
{{ctx.Locale.Tr "install.server_service_title"}}
|
|
</summary>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label data-tooltip-content="{{ctx.Locale.Tr "install.require_sign_in_view_popup"}}">{{ctx.Locale.Tr "install.require_sign_in_view"}}</label>
|
|
<input name="require_sign_in_view" type="checkbox" {{if .require_sign_in_view}}checked{{end}}>
|
|
</div>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_keep_email_private_popup"}}">{{ctx.Locale.Tr "install.default_keep_email_private"}}</label>
|
|
<input name="default_keep_email_private" type="checkbox" {{if .default_keep_email_private}}checked{{end}}>
|
|
</div>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_allow_create_organization_popup"}}">{{ctx.Locale.Tr "install.default_allow_create_organization"}}</label>
|
|
<input name="default_allow_create_organization" type="checkbox" {{if .default_allow_create_organization}}checked{{end}}>
|
|
</div>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_enable_timetracking_popup"}}">{{ctx.Locale.Tr "install.default_enable_timetracking"}}</label>
|
|
<input name="default_enable_timetracking" type="checkbox" {{if .default_enable_timetracking}}checked{{end}}>
|
|
</div>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="no_reply_address">{{ctx.Locale.Tr "install.no_reply_address"}}</label>
|
|
<input id="_no_reply_address" name="no_reply_address" value="{{.no_reply_address}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.no_reply_address_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="password_algorithm">{{ctx.Locale.Tr "install.password_algorithm"}}</label>
|
|
<div class="ui selection dropdown">
|
|
<input id="password_algorithm" type="hidden" name="password_algorithm" value="{{.password_algorithm}}">
|
|
<div class="text">{{.password_algorithm}}</div>
|
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
<div class="menu">
|
|
{{range .PasswordHashAlgorithms}}
|
|
<div class="item" data-value="{{.}}">{{.}}</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.password_algorithm_helper"}}</span>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Repository options -->
|
|
<details class="optional field">
|
|
<summary class="right-content tw-py-2">
|
|
{{ctx.Locale.Tr "install.repository_options_title"}}
|
|
</summary>
|
|
{{/* start edit/add - by petru @ codex */}}
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label for="allow_adoption_of_unadopted_repositories">{{ctx.Locale.Tr "install.allow_adoption_of_unadopted_repositories"}}</label>
|
|
<input id="allow_adoption_of_unadopted_repositories" name="allow_adoption_of_unadopted_repositories" type="checkbox" {{if .allow_adoption_of_unadopted_repositories}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.allow_adoption_of_unadopted_repositories_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<div class="ui checkbox">
|
|
<label for="allow_delete_of_unadopted_repositories">{{ctx.Locale.Tr "install.allow_delete_of_unadopted_repositories"}}</label>
|
|
<input id="allow_delete_of_unadopted_repositories" name="allow_delete_of_unadopted_repositories" type="checkbox" {{if .allow_delete_of_unadopted_repositories}}checked{{end}}>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.allow_delete_of_unadopted_repositories_helper"}}</span>
|
|
</div>
|
|
{{/* end edit/add - by petru @ codex */}}
|
|
<div class="inline field">
|
|
<label for="release_max_files">{{ctx.Locale.Tr "install.release_max_files"}}</label>
|
|
<input id="release_max_files" name="release_max_files" type="number" min="1" step="1" value="{{.release_max_files}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.release_max_files_helper"}}</span>
|
|
</div>
|
|
<div class="inline field">
|
|
<label for="release_file_max_size">{{ctx.Locale.Tr "install.release_file_max_size"}}</label>
|
|
<input id="release_file_max_size" name="release_file_max_size" type="number" min="1" step="1" value="{{.release_file_max_size}}">
|
|
<span class="help">{{ctx.Locale.Tr "install.release_file_max_size_helper"}}</span>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Admin -->
|
|
<details class="optional field">
|
|
<summary class="right-content tw-py-2{{if .Err_Admin}} tw-text-red{{end}}">
|
|
{{ctx.Locale.Tr "install.admin_title"}}
|
|
</summary>
|
|
<span class="desc">{{ctx.Locale.Tr "install.admin_setting_desc"}}</span>
|
|
<div class="inline field {{if .Err_AdminName}}error{{end}}">
|
|
<label for="admin_name">{{ctx.Locale.Tr "install.admin_name"}}</label>
|
|
<input id="admin_name" name="admin_name" value="{{.admin_name}}">
|
|
</div>
|
|
<div class="inline field {{if .Err_AdminEmail}}error{{end}}">
|
|
<label for="admin_email">{{ctx.Locale.Tr "install.admin_email"}}</label>
|
|
<input id="admin_email" name="admin_email" type="email" value="{{.admin_email}}">
|
|
</div>
|
|
<div class="inline field {{if .Err_AdminPasswd}}error{{end}}">
|
|
<label for="admin_passwd">{{ctx.Locale.Tr "install.admin_password"}}</label>
|
|
<div class="ui input js-password-toggle-group" data-global-init="initPasswordVisibilityToggle" data-show-label="{{ctx.Locale.Tr "auth.show_password"}}" data-hide-label="{{ctx.Locale.Tr "auth.hide_password"}}" data-confirm-field-selector=".js-install-admin-password-confirm-field" data-confirm-input-selector=".js-install-admin-password-confirm">
|
|
<input id="admin_passwd" name="admin_passwd" type="password" autocomplete="new-password" value="{{.admin_passwd}}" class="js-password-toggle-source">
|
|
</div>
|
|
</div>
|
|
<div class="inline field js-password-toggle-confirm-field js-install-admin-password-confirm-field {{if .Err_AdminPasswd}}error{{end}}">
|
|
<label for="admin_confirm_passwd">{{ctx.Locale.Tr "install.confirm_password"}}</label>
|
|
<input id="admin_confirm_passwd" name="admin_confirm_passwd" autocomplete="new-password" type="password" value="{{.admin_confirm_passwd}}" class="js-password-toggle-confirm js-install-admin-password-confirm">
|
|
</div>
|
|
<div class="inline field">
|
|
<label>{{ctx.Locale.Tr "install.admin_management_policy"}}</label>
|
|
<div class="grouped fields">
|
|
<div class="field">
|
|
<div class="ui radio checkbox">
|
|
<input name="admin_management_policy" type="radio" value="super_admin_only" {{if eq .admin_management_policy "super_admin_only"}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.admin_management_policy.super_admin_only"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.admin_management_policy.super_admin_only_helper"}}</p>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui radio checkbox">
|
|
<input name="admin_management_policy" type="radio" value="grantor_only" {{if or (not .admin_management_policy) (eq .admin_management_policy "grantor_only")}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.admin_management_policy.grantor_only"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.admin_management_policy.grantor_only_helper"}}</p>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui radio checkbox">
|
|
<input name="admin_management_policy" type="radio" value="grantor_inheritance" {{if eq .admin_management_policy "grantor_inheritance"}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "install.admin_management_policy.grantor_inheritance"}}</label>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.admin_management_policy.grantor_inheritance_helper"}}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
|
|
<div class="divider"></div>
|
|
|
|
{{if .EnvConfigKeys}}
|
|
<!-- Environment Config -->
|
|
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.env_config_keys"}}</h4>
|
|
<div class="inline field">
|
|
<div class="right-content">
|
|
{{ctx.Locale.Tr "install.env_config_keys_prompt"}}
|
|
</div>
|
|
<div class="right-content tw-mt-2">
|
|
{{range .EnvConfigKeys}}<span class="ui label">{{.}}</span>{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="inline field">
|
|
<div class="right-content">
|
|
{{$copyBtn := svg "octicon-copy" 14}}
|
|
{{$filePath := HTMLFormat `<span class="ui label">%s</span> <button class="btn interact-fg" data-clipboard-text="%s">%s</button>` .CustomConfFile .CustomConfFile $copyBtn}}
|
|
{{ctx.Locale.Tr "install.config_write_file_prompt" $filePath}}
|
|
</div>
|
|
{{if not (or .Err_DbInstalledBefore .Err_RepositoryFilesystemRecovery .Err_DatabaseBackupRecovery)}} <!-- edit/add - by petru @ codex -->
|
|
<div class="tw-mt-4 tw-mb-2 tw-text-center">
|
|
<button
|
|
class="ui primary button js-install-confirm-button"
|
|
data-install-label-template="{{ctx.Locale.Tr "install.install_btn_confirm" "__SITE_NAME__"}}"
|
|
>{{ctx.Locale.Tr "install.install_btn_confirm" .InstallerSiteName}}</button>
|
|
</div>
|
|
{{end}} <!-- edit/add - by petru @ codex -->
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- start edit/add - by petru @ codex -->
|
|
<div class="ui large modal install-recovery-launcher-modal js-install-recovery-launcher-modal">
|
|
<div class="header">{{.InstallRecoveryLauncherTitle}}</div>
|
|
<div class="content">
|
|
{{if .InstallRecoveryProblemText}}
|
|
<div class="ui negative message install-recovery-launcher-problem">{{.InstallRecoveryProblemText}}</div>
|
|
{{end}}
|
|
<div class="ui form">
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "install.recovery_source_title"}}</h5>
|
|
{{if .InstallRecoveryOptionDatabaseBackupAvailable}}
|
|
<div class="ui raised segment">
|
|
<h5 class="ui small header">{{ctx.Locale.Tr "install.recovery_launcher_backup_select"}}</h5>
|
|
<div class="inline field install-recovery-inline-field">
|
|
<label for="launcher_backup_restore_id">{{ctx.Locale.Tr "install.recovery_launcher_backup_bundle"}}</label>
|
|
<select id="launcher_backup_restore_id" class="install-recovery-select">
|
|
<option value="">{{ctx.Locale.Tr "install.recovery_launcher_backup_select_placeholder"}}</option>
|
|
{{range .InstallRecoveryBackups}}
|
|
<option value="{{.ID}}" data-has-app-ini="{{if .HasAppINI}}true{{else}}false{{end}}" data-has-database="{{if .HasDatabase}}true{{else}}false{{end}}" {{if eq $.backup_restore_id .ID}}selected{{end}}>{{.Label}}</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
<p class="help">{{ctx.Locale.Tr "install.recovery_database_backup_select_helper"}}</p>
|
|
<div class="grouped fields js-launcher-backup-actions tw-hidden">
|
|
<div class="field js-launcher-backup-import-app-ini-field tw-hidden">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_backup_import_app_ini" type="checkbox" data-import-action="{{.InstallBackupAppINIImportAction}}" {{if .backup_import_app_ini}}checked{{end}}>
|
|
<label for="launcher_backup_import_app_ini">{{ctx.Locale.Tr "install.recovery_launcher_backup_import_app_ini"}}</label>
|
|
</div>
|
|
</div>
|
|
<div class="field js-launcher-backup-restore-db-field tw-hidden">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_backup_restore_db" type="checkbox" {{if .backup_restore_db}}checked{{end}}>
|
|
<label for="launcher_backup_restore_db">{{ctx.Locale.Tr "install.recovery_launcher_backup_restore_db"}}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
<div class="ui raised segment js-launcher-import-app-ini-segment">
|
|
<h5 class="ui small header">{{ctx.Locale.Tr "install.import_app_ini_title"}}</h5>
|
|
<div class="inline field install-recovery-inline-field">
|
|
<div class="install-recovery-inline-control-row">
|
|
<label for="app_ini_file">{{ctx.Locale.Tr "install.import_app_ini_file"}}</label>
|
|
<input id="app_ini_file" name="app_ini_file" type="file" accept=".ini,text/plain" form="install-form" data-import-action="{{.InstallImportAction}}">
|
|
<button type="button" class="ui button basic small js-install-recovery-reset-app-ini tw-hidden">{{ctx.Locale.Tr "install.recovery_launcher_reset"}}</button>
|
|
</div>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.import_app_ini_helper" .AppINIImportMaxSizeKB}}</span>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="import_sensitive_secrets" name="import_sensitive_secrets" type="checkbox" form="install-form" {{if .import_sensitive_secrets}}checked{{end}}>
|
|
<label for="import_sensitive_secrets">{{ctx.Locale.Tr "install.import_app_ini_sensitive_secrets"}}</label>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.import_app_ini_sensitive_secrets_helper"}}</span>
|
|
</div>
|
|
</div>
|
|
<div class="ui raised segment js-launcher-database-backup-file-segment">
|
|
<h5 class="ui small header">{{ctx.Locale.Tr "install.recovery_launcher_database_backup_file"}}</h5>
|
|
<div class="inline field install-recovery-inline-field">
|
|
<div class="install-recovery-inline-control-row">
|
|
<label for="database_backup_file">{{ctx.Locale.Tr "install.recovery_launcher_sql_backup_file"}}</label>
|
|
<input id="database_backup_file" name="database_backup_file" type="file" accept=".sql,.gz,application/gzip,application/x-gzip,text/plain" form="install-form">
|
|
<button type="button" class="ui button basic small js-install-recovery-reset-db-backup tw-hidden">{{ctx.Locale.Tr "install.recovery_launcher_reset"}}</button>
|
|
</div>
|
|
</div>
|
|
<span class="help">{{ctx.Locale.Tr "install.recovery_launcher_sql_backup_file_helper"}}</span>
|
|
</div>
|
|
{{if and .InstallRecoveryOptionRepositoryFilesystemAvailable (not .InstallRecoveryOptionExistingDBAvailable) (not .InstallRecoveryOptionDatabaseBackupAvailable)}}
|
|
<div class="ui raised segment js-launcher-repository-filesystem-segment">
|
|
<h5 class="ui small header">{{ctx.Locale.Tr "install.recovery_source_repository_filesystem"}}</h5>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_repository_filesystem" type="checkbox" {{if eq .recovery_mode "repository_filesystem"}}checked{{end}}>
|
|
<label for="launcher_repository_filesystem">{{ctx.Locale.Tr "install.recovery_source_repository_filesystem_helper"}}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
<div class="ui raised segment js-install-recovery-launcher-confirm-panel tw-hidden" data-confirm-kind="backup_restore">
|
|
<div class="grouped fields">
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_backup_confirm_first" type="checkbox">
|
|
<label for="launcher_backup_confirm_first">{{ctx.Locale.Tr "install.recovery_database_backup_confirm_check_1"}}</label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_backup_confirm_second" type="checkbox">
|
|
<label for="launcher_backup_confirm_second">{{ctx.Locale.Tr "install.recovery_database_backup_confirm_check_2"}}</label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_backup_confirm_third" type="checkbox">
|
|
<label for="launcher_backup_confirm_third">{{ctx.Locale.Tr "install.recovery_database_backup_confirm_check_3"}}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="ui raised segment js-install-recovery-launcher-confirm-panel tw-hidden" data-confirm-kind="partial_restore">
|
|
<div class="grouped fields">
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_partial_confirm_first" type="checkbox">
|
|
<label for="launcher_partial_confirm_first">{{ctx.Locale.Tr "install.reinstall_confirm_check_1"}}</label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_partial_confirm_second" type="checkbox">
|
|
<label for="launcher_partial_confirm_second">{{ctx.Locale.Tr "install.reinstall_confirm_check_2"}}</label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_partial_confirm_third" type="checkbox">
|
|
<label for="launcher_partial_confirm_third">{{ctx.Locale.Tr "install.reinstall_confirm_check_3"}}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="ui raised segment js-install-recovery-launcher-confirm-panel tw-hidden" data-confirm-kind="repository_filesystem">
|
|
<div class="grouped fields">
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_repository_confirm_first" type="checkbox">
|
|
<label for="launcher_repository_confirm_first">{{ctx.Locale.Tr "install.recovery_repository_filesystem_confirm_check_1"}}</label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_repository_confirm_second" type="checkbox">
|
|
<label for="launcher_repository_confirm_second">{{ctx.Locale.Tr "install.recovery_repository_filesystem_confirm_check_2"}}</label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input id="launcher_repository_confirm_third" type="checkbox">
|
|
<label for="launcher_repository_confirm_third">{{ctx.Locale.Tr "install.recovery_repository_filesystem_confirm_check_3"}}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="actions">
|
|
<button type="button" class="ui button deny">{{ctx.Locale.Tr "cancel"}}</button>
|
|
<button type="submit" form="install-form" class="ui primary button approve js-install-recovery-launcher-save">{{ctx.Locale.Tr "save"}}</button>
|
|
</div>
|
|
</div>
|
|
<!-- end edit/add - by petru @ codex -->
|
|
<div class="install-language-balloon" id="install-language-balloon" role="status" aria-live="polite">
|
|
{{ctx.Locale.Tr "install.language_balloon"}}
|
|
</div>
|
|
<script>
|
|
const installAppINIInput = document.querySelector('#app_ini_file');
|
|
const dismissInstallSuccessFlash = () => {
|
|
window.setTimeout(() => {
|
|
for (const element of document.querySelectorAll('.flash-message.flash-success')) {
|
|
element.remove();
|
|
}
|
|
}, 5000);
|
|
};
|
|
|
|
// start edit/add - by petru @ codex
|
|
const syncInstallFormFromImportedResponse = (form, parsed) => {
|
|
const importedForm = parsed.querySelector('.js-install-form');
|
|
if (!importedForm) throw new Error('import form not found');
|
|
|
|
for (const importedInput of importedForm.querySelectorAll('input, textarea, select')) {
|
|
if (!(importedInput instanceof HTMLInputElement || importedInput instanceof HTMLTextAreaElement || importedInput instanceof HTMLSelectElement)) continue;
|
|
if (!importedInput.name) continue;
|
|
|
|
if (importedInput.type === 'file') continue;
|
|
if (importedInput.type === 'radio') {
|
|
const currentRadio = form.querySelector(`input[type="radio"][name="${CSS.escape(importedInput.name)}"][value="${CSS.escape(importedInput.value)}"]`);
|
|
if (currentRadio instanceof HTMLInputElement) currentRadio.checked = importedInput.checked;
|
|
continue;
|
|
}
|
|
if (importedInput.type === 'checkbox') {
|
|
const currentCheckbox = form.querySelector(`input[type="checkbox"][name="${CSS.escape(importedInput.name)}"]`) || document.querySelector(`input[type="checkbox"][name="${CSS.escape(importedInput.name)}"][form="install-form"]`); // edit/add - by petru @ codex
|
|
if (currentCheckbox instanceof HTMLInputElement) {
|
|
currentCheckbox.checked = importedInput.checked;
|
|
currentCheckbox.dispatchEvent(new Event('change', {bubbles: true}));
|
|
const checkboxContainer = currentCheckbox.closest('.ui.checkbox');
|
|
if (checkboxContainer) {
|
|
checkboxContainer.classList.toggle('checked', importedInput.checked);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const currentInput = form.querySelector(`[name="${CSS.escape(importedInput.name)}"]`) || document.querySelector(`[name="${CSS.escape(importedInput.name)}"][form="install-form"]`); // edit/add - by petru @ codex
|
|
if (currentInput instanceof HTMLInputElement || currentInput instanceof HTMLTextAreaElement || currentInput instanceof HTMLSelectElement) {
|
|
currentInput.value = importedInput.value;
|
|
currentInput.dispatchEvent(new Event('change', {bubbles: true}));
|
|
}
|
|
}
|
|
|
|
const importedDbTypeInput = importedForm.querySelector('#db_type');
|
|
const currentDbTypeInput = form.querySelector('#db_type');
|
|
if (importedDbTypeInput instanceof HTMLInputElement && currentDbTypeInput instanceof HTMLInputElement) {
|
|
currentDbTypeInput.value = importedDbTypeInput.value;
|
|
currentDbTypeInput.dispatchEvent(new Event('change', {bubbles: true}));
|
|
const currentDbText = form.querySelector('.database.type.dropdown .text');
|
|
const importedDbText = importedForm.querySelector('.database.type.dropdown .text');
|
|
if (currentDbText && importedDbText) currentDbText.textContent = importedDbText.textContent;
|
|
}
|
|
|
|
const importedLangInput = importedForm.querySelector('input[name="default_language"]');
|
|
const currentLangInput = form.querySelector('input[name="default_language"]');
|
|
if (importedLangInput instanceof HTMLInputElement && currentLangInput instanceof HTMLInputElement) {
|
|
currentLangInput.value = importedLangInput.value;
|
|
currentLangInput.dispatchEvent(new Event('change', {bubbles: true}));
|
|
const currentLangText = form.querySelector('.language.selection.dropdown .text');
|
|
const importedLangText = importedForm.querySelector('.language.selection.dropdown .text');
|
|
if (currentLangText && importedLangText) currentLangText.textContent = importedLangText.textContent;
|
|
}
|
|
|
|
for (const existingFlash of document.querySelectorAll('.flash-message')) {
|
|
existingFlash.remove();
|
|
}
|
|
const importedAlerts = parsed.querySelector('.ui.attached.segment > .ui.message.flash-message, .ui.attached.segment > .flash-message');
|
|
if (importedAlerts) {
|
|
const segment = document.querySelector('.ui.attached.segment');
|
|
if (segment) {
|
|
segment.insertBefore(importedAlerts.cloneNode(true), segment.firstChild);
|
|
dismissInstallSuccessFlash();
|
|
}
|
|
}
|
|
};
|
|
|
|
const importInstallFormState = async (form, action) => {
|
|
const formData = new FormData(form);
|
|
try {
|
|
const response = await fetch(action, {
|
|
method: 'POST',
|
|
body: formData,
|
|
credentials: 'same-origin',
|
|
});
|
|
const html = await response.text();
|
|
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
syncInstallFormFromImportedResponse(form, parsed);
|
|
return true;
|
|
} catch {
|
|
form.action = action;
|
|
form.submit();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// end edit/add - by petru @ codex
|
|
|
|
if (installAppINIInput) {
|
|
installAppINIInput.addEventListener('change', async () => {
|
|
if (!installAppINIInput.files || installAppINIInput.files.length === 0) return;
|
|
const form = document.querySelector('.js-install-form');
|
|
if (!form) return;
|
|
const imported = await importInstallFormState(form, installAppINIInput.dataset.importAction); // edit/add - by petru @ codex
|
|
if (imported && importSensitiveSecretsCheckbox instanceof HTMLInputElement) {
|
|
importSensitiveSecretsCheckbox.checked = true; // edit/add - by petru @ codex
|
|
const checkboxContainer = importSensitiveSecretsCheckbox.closest('.ui.checkbox');
|
|
if (checkboxContainer) checkboxContainer.classList.add('checked');
|
|
}
|
|
if (imported) syncLauncherBackupState(); // edit/add - by petru @ codex
|
|
syncRecoveryFileResetButtons(); // edit/add - by petru @ codex
|
|
});
|
|
}
|
|
|
|
const setInstallTestMailButtonState = (button, state) => {
|
|
if (state === 'success') {
|
|
button.style.background = 'var(--color-green)';
|
|
button.style.color = 'var(--color-white)';
|
|
button.style.borderColor = 'var(--color-green)';
|
|
} else if (state === 'failed') {
|
|
button.style.background = 'var(--color-red)';
|
|
button.style.color = 'var(--color-white)';
|
|
button.style.borderColor = 'var(--color-red)';
|
|
} else {
|
|
button.style.background = 'var(--color-primary)';
|
|
button.style.color = 'var(--color-primary-contrast)';
|
|
button.style.borderColor = 'var(--color-primary)';
|
|
}
|
|
};
|
|
|
|
const deriveInstallMailerName = (appName) => {
|
|
const trimmed = appName.trim();
|
|
if (!trimmed) return 'Gitea';
|
|
const colonIndex = trimmed.indexOf(':');
|
|
if (colonIndex >= 0) {
|
|
const siteName = trimmed.slice(0, colonIndex).trim();
|
|
return siteName || 'Gitea';
|
|
}
|
|
return trimmed.split(/\s+/, 1)[0] || 'Gitea';
|
|
};
|
|
|
|
const installForm = document.querySelector('.js-install-form');
|
|
const recoveryLauncherButton = document.querySelector('.js-install-recovery-launcher');
|
|
const recoveryLauncherModal = document.querySelector('.js-install-recovery-launcher-modal');
|
|
const hiddenBackupRestoreID = installForm?.querySelector('#install_backup_restore_id');
|
|
const hiddenBackupImportAppINI = installForm?.querySelector('#install_backup_import_app_ini');
|
|
const hiddenBackupRestoreDB = installForm?.querySelector('#install_backup_restore_db');
|
|
const hiddenRecoveryMode = installForm?.querySelector('#install_recovery_mode');
|
|
const hiddenReinstallConfirmFirst = installForm?.querySelector('#install_reinstall_confirm_first');
|
|
const hiddenReinstallConfirmSecond = installForm?.querySelector('#install_reinstall_confirm_second');
|
|
const hiddenReinstallConfirmThird = installForm?.querySelector('#install_reinstall_confirm_third');
|
|
const launcherBackupRestoreID = recoveryLauncherModal?.querySelector('#launcher_backup_restore_id');
|
|
const launcherBackupImportAppINI = recoveryLauncherModal?.querySelector('#launcher_backup_import_app_ini');
|
|
const launcherBackupRestoreDB = recoveryLauncherModal?.querySelector('#launcher_backup_restore_db');
|
|
const launcherRepositoryFilesystem = recoveryLauncherModal?.querySelector('#launcher_repository_filesystem');
|
|
const launcherDatabaseBackupFile = recoveryLauncherModal?.querySelector('#database_backup_file');
|
|
const importSensitiveSecretsCheckbox = document.querySelector('#import_sensitive_secrets'); // edit/add - by petru @ codex
|
|
const resetAppINIButton = recoveryLauncherModal?.querySelector('.js-install-recovery-reset-app-ini');
|
|
const resetDatabaseBackupButton = recoveryLauncherModal?.querySelector('.js-install-recovery-reset-db-backup');
|
|
const launcherBackupActions = recoveryLauncherModal?.querySelector('.js-launcher-backup-actions');
|
|
const launcherBackupImportAppINIField = recoveryLauncherModal?.querySelector('.js-launcher-backup-import-app-ini-field');
|
|
const launcherBackupRestoreDBField = recoveryLauncherModal?.querySelector('.js-launcher-backup-restore-db-field');
|
|
const launcherImportAppINISegment = recoveryLauncherModal?.querySelector('.js-launcher-import-app-ini-segment');
|
|
const launcherDatabaseBackupFileSegment = recoveryLauncherModal?.querySelector('.js-launcher-database-backup-file-segment');
|
|
const launcherRepositoryFilesystemSegment = recoveryLauncherModal?.querySelector('.js-launcher-repository-filesystem-segment');
|
|
const launcherConfirmPanels = recoveryLauncherModal ? recoveryLauncherModal.querySelectorAll('.js-install-recovery-launcher-confirm-panel') : [];
|
|
const launcherSaveButton = recoveryLauncherModal?.querySelector('.js-install-recovery-launcher-save');
|
|
const appNameInput = installForm?.querySelector('#app_name');
|
|
const smtpFromNameInput = installForm?.querySelector('#smtp_from_name');
|
|
const installConfirmButtons = installForm ? installForm.querySelectorAll('.js-install-confirm-button') : [];
|
|
|
|
const setLauncherPanelVisible = (panel, visible) => {
|
|
panel.classList.toggle('tw-hidden', !visible);
|
|
for (const input of panel.querySelectorAll('input[type="checkbox"]')) {
|
|
if (!(input instanceof HTMLInputElement)) continue;
|
|
if (!visible) input.checked = false;
|
|
const checkboxContainer = input.closest('.ui.checkbox');
|
|
if (checkboxContainer) checkboxContainer.classList.toggle('checked', visible && input.checked);
|
|
}
|
|
};
|
|
|
|
const resetLauncherConfirmations = () => { // edit/add - by petru @ codex
|
|
for (const panel of launcherConfirmPanels) {
|
|
for (const input of panel.querySelectorAll('input[type="checkbox"]')) {
|
|
if (!(input instanceof HTMLInputElement)) continue;
|
|
input.checked = false;
|
|
const checkboxContainer = input.closest('.ui.checkbox');
|
|
if (checkboxContainer) checkboxContainer.classList.remove('checked');
|
|
}
|
|
}
|
|
if (hiddenReinstallConfirmFirst instanceof HTMLInputElement) hiddenReinstallConfirmFirst.value = '';
|
|
if (hiddenReinstallConfirmSecond instanceof HTMLInputElement) hiddenReinstallConfirmSecond.value = '';
|
|
if (hiddenReinstallConfirmThird instanceof HTMLInputElement) hiddenReinstallConfirmThird.value = '';
|
|
};
|
|
|
|
const clearImportedAppINIPreviewState = () => { // edit/add - by petru @ codex
|
|
if (!installForm) return;
|
|
const importedAppINIState = installForm.querySelector('input[name="imported_app_ini"]');
|
|
if (importedAppINIState instanceof HTMLInputElement) importedAppINIState.value = '';
|
|
const importedLFSJWTSecret = installForm.querySelector('input[name="imported_lfs_jwt_secret"]');
|
|
if (importedLFSJWTSecret instanceof HTMLInputElement) importedLFSJWTSecret.value = '';
|
|
const importedInternalToken = installForm.querySelector('input[name="imported_internal_token"]');
|
|
if (importedInternalToken instanceof HTMLInputElement) importedInternalToken.value = '';
|
|
const importedOAuth2JWTSecret = installForm.querySelector('input[name="imported_o_auth2_jwt_secret"]');
|
|
if (importedOAuth2JWTSecret instanceof HTMLInputElement) importedOAuth2JWTSecret.value = '';
|
|
if (importSensitiveSecretsCheckbox instanceof HTMLInputElement) {
|
|
importSensitiveSecretsCheckbox.checked = false; // edit/add - by petru @ codex
|
|
const checkboxContainer = importSensitiveSecretsCheckbox.closest('.ui.checkbox');
|
|
if (checkboxContainer) checkboxContainer.classList.remove('checked');
|
|
}
|
|
};
|
|
|
|
const syncLauncherSaveButton = () => {
|
|
if (!(launcherSaveButton instanceof HTMLButtonElement)) return;
|
|
const hasBundleSelected = launcherBackupRestoreID instanceof HTMLSelectElement && launcherBackupRestoreID.value !== '';
|
|
const hasSQLBackupSelected = launcherDatabaseBackupFile instanceof HTMLInputElement && !!launcherDatabaseBackupFile.files && launcherDatabaseBackupFile.files.length > 0;
|
|
const repositoryFilesystemSelected = launcherRepositoryFilesystem instanceof HTMLInputElement && launcherRepositoryFilesystem.checked;
|
|
const importedAppINIState = installForm?.querySelector('input[name="imported_app_ini"]'); // edit/add - by petru @ codex
|
|
const hasImportedAppINI = importedAppINIState instanceof HTMLInputElement && importedAppINIState.value === 'true'; // edit/add - by petru @ codex
|
|
const importSelected = launcherBackupImportAppINI instanceof HTMLInputElement && !launcherBackupImportAppINI.closest('.tw-hidden') && launcherBackupImportAppINI.checked;
|
|
const restoreSelected = launcherBackupRestoreDB instanceof HTMLInputElement && !launcherBackupRestoreDB.closest('.tw-hidden') && launcherBackupRestoreDB.checked;
|
|
|
|
let requiredPanel = null;
|
|
if (repositoryFilesystemSelected) {
|
|
requiredPanel = recoveryLauncherModal?.querySelector('[data-confirm-kind="repository_filesystem"]');
|
|
} else if (hasSQLBackupSelected || (hasBundleSelected && restoreSelected)) {
|
|
requiredPanel = recoveryLauncherModal?.querySelector('[data-confirm-kind="backup_restore"]');
|
|
} else if ((hasBundleSelected && (importSelected || restoreSelected)) || hasImportedAppINI) {
|
|
requiredPanel = recoveryLauncherModal?.querySelector('[data-confirm-kind="partial_restore"]');
|
|
}
|
|
|
|
if (!requiredPanel) {
|
|
if (hiddenReinstallConfirmFirst instanceof HTMLInputElement) hiddenReinstallConfirmFirst.value = '';
|
|
if (hiddenReinstallConfirmSecond instanceof HTMLInputElement) hiddenReinstallConfirmSecond.value = '';
|
|
if (hiddenReinstallConfirmThird instanceof HTMLInputElement) hiddenReinstallConfirmThird.value = '';
|
|
launcherSaveButton.disabled = (hasBundleSelected && !(importSelected || restoreSelected)) || (!hasBundleSelected && !hasSQLBackupSelected && !hasImportedAppINI);
|
|
return;
|
|
}
|
|
|
|
const confirmationInputs = requiredPanel.querySelectorAll('input[type="checkbox"]');
|
|
if (hiddenReinstallConfirmFirst instanceof HTMLInputElement) hiddenReinstallConfirmFirst.value = confirmationInputs[0] instanceof HTMLInputElement && confirmationInputs[0].checked ? 'true' : '';
|
|
if (hiddenReinstallConfirmSecond instanceof HTMLInputElement) hiddenReinstallConfirmSecond.value = confirmationInputs[1] instanceof HTMLInputElement && confirmationInputs[1].checked ? 'true' : '';
|
|
if (hiddenReinstallConfirmThird instanceof HTMLInputElement) hiddenReinstallConfirmThird.value = confirmationInputs[2] instanceof HTMLInputElement && confirmationInputs[2].checked ? 'true' : '';
|
|
launcherSaveButton.disabled = [...confirmationInputs].some((input) => !(input instanceof HTMLInputElement) || !input.checked);
|
|
};
|
|
|
|
const syncLauncherBackupState = () => {
|
|
if (hiddenBackupRestoreID instanceof HTMLInputElement && launcherBackupRestoreID instanceof HTMLSelectElement) {
|
|
hiddenBackupRestoreID.value = launcherBackupRestoreID.value;
|
|
}
|
|
if (hiddenBackupImportAppINI instanceof HTMLInputElement && launcherBackupImportAppINI instanceof HTMLInputElement) {
|
|
hiddenBackupImportAppINI.value = launcherBackupImportAppINI.checked ? 'true' : '';
|
|
}
|
|
if (hiddenBackupRestoreDB instanceof HTMLInputElement && launcherBackupRestoreDB instanceof HTMLInputElement) {
|
|
hiddenBackupRestoreDB.value = launcherBackupRestoreDB.checked ? 'true' : '';
|
|
}
|
|
const selectedBackupOption = launcherBackupRestoreID instanceof HTMLSelectElement ? launcherBackupRestoreID.selectedOptions[0] : null;
|
|
const hasBundleSelected = launcherBackupRestoreID instanceof HTMLSelectElement && launcherBackupRestoreID.value !== '';
|
|
const hasAppINIResource = !!selectedBackupOption && selectedBackupOption.dataset.hasAppIni === 'true';
|
|
const hasDatabaseResource = !!selectedBackupOption && selectedBackupOption.dataset.hasDatabase === 'true';
|
|
const repositoryFilesystemSelected = launcherRepositoryFilesystem instanceof HTMLInputElement && launcherRepositoryFilesystem.checked;
|
|
if (launcherBackupActions) {
|
|
launcherBackupActions.classList.toggle('tw-hidden', !hasBundleSelected);
|
|
}
|
|
if (launcherBackupImportAppINIField) {
|
|
launcherBackupImportAppINIField.classList.toggle('tw-hidden', !(hasBundleSelected && hasAppINIResource));
|
|
}
|
|
if (launcherBackupRestoreDBField) {
|
|
launcherBackupRestoreDBField.classList.toggle('tw-hidden', !(hasBundleSelected && hasDatabaseResource));
|
|
}
|
|
if (launcherBackupImportAppINI instanceof HTMLInputElement && !(hasBundleSelected && hasAppINIResource)) {
|
|
launcherBackupImportAppINI.checked = false;
|
|
const checkboxContainer = launcherBackupImportAppINI.closest('.ui.checkbox');
|
|
if (checkboxContainer) checkboxContainer.classList.remove('checked');
|
|
}
|
|
if (launcherBackupRestoreDB instanceof HTMLInputElement && !(hasBundleSelected && hasDatabaseResource)) {
|
|
launcherBackupRestoreDB.checked = false;
|
|
const checkboxContainer = launcherBackupRestoreDB.closest('.ui.checkbox');
|
|
if (checkboxContainer) checkboxContainer.classList.remove('checked');
|
|
}
|
|
if (repositoryFilesystemSelected) {
|
|
if (launcherBackupRestoreDB instanceof HTMLInputElement) {
|
|
launcherBackupRestoreDB.checked = false;
|
|
const checkboxContainer = launcherBackupRestoreDB.closest('.ui.checkbox');
|
|
if (checkboxContainer) checkboxContainer.classList.remove('checked');
|
|
}
|
|
if (launcherDatabaseBackupFile instanceof HTMLInputElement) {
|
|
launcherDatabaseBackupFile.value = '';
|
|
}
|
|
}
|
|
if (launcherImportAppINISegment) {
|
|
const hideImportAppINISection = hasBundleSelected && launcherBackupImportAppINI instanceof HTMLInputElement && launcherBackupImportAppINI.checked;
|
|
launcherImportAppINISegment.classList.toggle('tw-hidden', hideImportAppINISection);
|
|
if (hideImportAppINISection && installAppINIInput instanceof HTMLInputElement) {
|
|
installAppINIInput.value = '';
|
|
}
|
|
}
|
|
const importedAppINIState = installForm?.querySelector('input[name="imported_app_ini"]');
|
|
const hasImportedAppINI = importedAppINIState instanceof HTMLInputElement && importedAppINIState.value === 'true';
|
|
const forceExactAppINIImport = hasBundleSelected && launcherBackupImportAppINI instanceof HTMLInputElement && launcherBackupImportAppINI.checked;
|
|
if (importSensitiveSecretsCheckbox instanceof HTMLInputElement) {
|
|
if (forceExactAppINIImport) {
|
|
importSensitiveSecretsCheckbox.checked = true; // edit/add - by petru @ codex
|
|
}
|
|
importSensitiveSecretsCheckbox.disabled = forceExactAppINIImport;
|
|
const checkboxContainer = importSensitiveSecretsCheckbox.closest('.ui.checkbox');
|
|
if (checkboxContainer) {
|
|
checkboxContainer.classList.toggle('checked', importSensitiveSecretsCheckbox.checked);
|
|
checkboxContainer.classList.toggle('disabled', forceExactAppINIImport);
|
|
}
|
|
}
|
|
if (launcherDatabaseBackupFileSegment) {
|
|
const hideDatabaseBackupFileSection = hasBundleSelected && launcherBackupRestoreDB instanceof HTMLInputElement && launcherBackupRestoreDB.checked;
|
|
launcherDatabaseBackupFileSegment.classList.toggle('tw-hidden', hideDatabaseBackupFileSection);
|
|
if (hideDatabaseBackupFileSection && launcherDatabaseBackupFile instanceof HTMLInputElement) {
|
|
launcherDatabaseBackupFile.value = '';
|
|
}
|
|
}
|
|
const hasSQLBackupSelected = launcherDatabaseBackupFile instanceof HTMLInputElement && !!launcherDatabaseBackupFile.files && launcherDatabaseBackupFile.files.length > 0;
|
|
let activeConfirmKind = '';
|
|
if (repositoryFilesystemSelected) {
|
|
activeConfirmKind = 'repository_filesystem';
|
|
} else if (hasSQLBackupSelected || (hasBundleSelected && launcherBackupRestoreDB instanceof HTMLInputElement && launcherBackupRestoreDB.checked)) {
|
|
activeConfirmKind = 'backup_restore';
|
|
} else if ((hasBundleSelected && ((launcherBackupImportAppINI instanceof HTMLInputElement && launcherBackupImportAppINI.checked) || (launcherBackupRestoreDB instanceof HTMLInputElement && launcherBackupRestoreDB.checked))) || hasImportedAppINI) {
|
|
activeConfirmKind = 'partial_restore';
|
|
}
|
|
if (hiddenRecoveryMode instanceof HTMLInputElement) {
|
|
hiddenRecoveryMode.value = activeConfirmKind === 'backup_restore' ? 'database_backup' : activeConfirmKind === 'partial_restore' ? 'existing_database' : activeConfirmKind === 'repository_filesystem' ? 'repository_filesystem' : '';
|
|
}
|
|
if (launcherRepositoryFilesystemSegment) {
|
|
launcherRepositoryFilesystemSegment.classList.toggle('tw-hidden', hasSQLBackupSelected);
|
|
}
|
|
if (hasSQLBackupSelected && launcherRepositoryFilesystem instanceof HTMLInputElement) {
|
|
launcherRepositoryFilesystem.checked = false;
|
|
const checkboxContainer = launcherRepositoryFilesystem.closest('.ui.checkbox');
|
|
if (checkboxContainer) checkboxContainer.classList.remove('checked');
|
|
}
|
|
for (const panel of launcherConfirmPanels) {
|
|
setLauncherPanelVisible(panel, panel instanceof HTMLElement && panel.dataset.confirmKind === activeConfirmKind);
|
|
}
|
|
syncLauncherSaveButton();
|
|
};
|
|
|
|
// start edit/add - by petru @ codex
|
|
const syncInstallPanelFromSelectedBackupAppINI = async () => {
|
|
if (!(installForm instanceof HTMLFormElement)) return;
|
|
if (!(launcherBackupImportAppINI instanceof HTMLInputElement) || !launcherBackupImportAppINI.checked) return;
|
|
if (!(launcherBackupRestoreID instanceof HTMLSelectElement) || launcherBackupRestoreID.value === '') return;
|
|
const importAction = launcherBackupImportAppINI.dataset.importAction;
|
|
if (!importAction) return;
|
|
await importInstallFormState(installForm, importAction);
|
|
syncLauncherBackupState();
|
|
};
|
|
|
|
const syncRecoveryFileResetButtons = () => {
|
|
if (resetAppINIButton) {
|
|
const importedAppINIState = installForm?.querySelector('input[name="imported_app_ini"]'); // edit/add - by petru @ codex
|
|
const hasImportedAppINI = importedAppINIState instanceof HTMLInputElement && importedAppINIState.value === 'true'; // edit/add - by petru @ codex
|
|
const hasBundleImportedAppINI = hiddenBackupImportAppINI instanceof HTMLInputElement && hiddenBackupImportAppINI.value === 'true'; // edit/add - by petru @ codex
|
|
resetAppINIButton.classList.toggle('tw-hidden', !(hasImportedAppINI && !hasBundleImportedAppINI)); // edit/add - by petru @ codex
|
|
}
|
|
if (resetDatabaseBackupButton) {
|
|
const hasDatabaseBackupFile = launcherDatabaseBackupFile instanceof HTMLInputElement && !!launcherDatabaseBackupFile.files && launcherDatabaseBackupFile.files.length > 0;
|
|
resetDatabaseBackupButton.classList.toggle('tw-hidden', !hasDatabaseBackupFile);
|
|
}
|
|
};
|
|
|
|
// end edit/add - by petru @ codex
|
|
|
|
if (recoveryLauncherButton && recoveryLauncherModal) {
|
|
recoveryLauncherButton.addEventListener('click', () => {
|
|
window.$(recoveryLauncherModal).modal({
|
|
autofocus: false,
|
|
closable: true,
|
|
}).modal('show');
|
|
});
|
|
}
|
|
|
|
if (launcherBackupRestoreID instanceof HTMLSelectElement) {
|
|
launcherBackupRestoreID.addEventListener('change', async () => {
|
|
if (launcherBackupRestoreID.value === '') resetLauncherConfirmations(); // edit/add - by petru @ codex
|
|
syncLauncherBackupState();
|
|
await syncInstallPanelFromSelectedBackupAppINI();
|
|
});
|
|
}
|
|
if (launcherBackupImportAppINI instanceof HTMLInputElement) {
|
|
launcherBackupImportAppINI.addEventListener('change', async () => {
|
|
if (!launcherBackupImportAppINI.checked) resetLauncherConfirmations(); // edit/add - by petru @ codex
|
|
syncLauncherBackupState();
|
|
await syncInstallPanelFromSelectedBackupAppINI();
|
|
});
|
|
}
|
|
if (launcherBackupRestoreDB instanceof HTMLInputElement) {
|
|
launcherBackupRestoreDB.addEventListener('change', () => {
|
|
if (!launcherBackupRestoreDB.checked) resetLauncherConfirmations(); // edit/add - by petru @ codex
|
|
syncLauncherBackupState();
|
|
});
|
|
}
|
|
if (launcherRepositoryFilesystem instanceof HTMLInputElement) {
|
|
launcherRepositoryFilesystem.addEventListener('change', () => {
|
|
if (!launcherRepositoryFilesystem.checked) resetLauncherConfirmations(); // edit/add - by petru @ codex
|
|
syncLauncherBackupState();
|
|
syncRecoveryFileResetButtons(); // edit/add - by petru @ codex
|
|
});
|
|
}
|
|
if (launcherDatabaseBackupFile instanceof HTMLInputElement) {
|
|
launcherDatabaseBackupFile.addEventListener('change', () => {
|
|
if (!launcherDatabaseBackupFile.files || launcherDatabaseBackupFile.files.length === 0) resetLauncherConfirmations(); // edit/add - by petru @ codex
|
|
syncLauncherBackupState();
|
|
syncRecoveryFileResetButtons(); // edit/add - by petru @ codex
|
|
});
|
|
}
|
|
if (resetAppINIButton instanceof HTMLButtonElement && installAppINIInput instanceof HTMLInputElement) {
|
|
resetAppINIButton.addEventListener('click', () => {
|
|
installAppINIInput.value = ''; // edit/add - by petru @ codex
|
|
clearImportedAppINIPreviewState(); // edit/add - by petru @ codex
|
|
resetLauncherConfirmations(); // edit/add - by petru @ codex
|
|
syncLauncherBackupState(); // edit/add - by petru @ codex
|
|
syncRecoveryFileResetButtons(); // edit/add - by petru @ codex
|
|
});
|
|
}
|
|
if (resetDatabaseBackupButton instanceof HTMLButtonElement && launcherDatabaseBackupFile instanceof HTMLInputElement) {
|
|
resetDatabaseBackupButton.addEventListener('click', () => {
|
|
launcherDatabaseBackupFile.value = ''; // edit/add - by petru @ codex
|
|
resetLauncherConfirmations(); // edit/add - by petru @ codex
|
|
syncLauncherBackupState(); // edit/add - by petru @ codex
|
|
syncRecoveryFileResetButtons(); // edit/add - by petru @ codex
|
|
});
|
|
}
|
|
for (const panel of launcherConfirmPanels) {
|
|
for (const input of panel.querySelectorAll('input[type="checkbox"]')) {
|
|
if (!(input instanceof HTMLInputElement)) continue;
|
|
input.addEventListener('change', syncLauncherSaveButton);
|
|
}
|
|
}
|
|
syncLauncherBackupState();
|
|
syncRecoveryFileResetButtons(); // edit/add - by petru @ codex
|
|
if (appNameInput instanceof HTMLInputElement && smtpFromNameInput instanceof HTMLInputElement) {
|
|
let lastAutoMailerName = smtpFromNameInput.value.trim() || deriveInstallMailerName(appNameInput.value);
|
|
const syncInstallBranding = () => {
|
|
const siteName = deriveInstallMailerName(appNameInput.value);
|
|
const currentValue = smtpFromNameInput.value.trim();
|
|
if (!currentValue || currentValue === lastAutoMailerName) {
|
|
smtpFromNameInput.value = siteName;
|
|
}
|
|
lastAutoMailerName = siteName;
|
|
for (const installConfirmButton of installConfirmButtons) {
|
|
if (!(installConfirmButton instanceof HTMLButtonElement)) continue;
|
|
installConfirmButton.textContent = installConfirmButton.dataset.installLabelTemplate.replace('__SITE_NAME__', siteName);
|
|
}
|
|
};
|
|
|
|
syncInstallBranding();
|
|
appNameInput.addEventListener('input', syncInstallBranding);
|
|
appNameInput.addEventListener('change', syncInstallBranding);
|
|
} else if (appNameInput instanceof HTMLInputElement && installConfirmButtons.length > 0) {
|
|
const syncInstallButton = () => {
|
|
const siteName = deriveInstallMailerName(appNameInput.value);
|
|
for (const installConfirmButton of installConfirmButtons) {
|
|
if (!(installConfirmButton instanceof HTMLButtonElement)) continue;
|
|
installConfirmButton.textContent = installConfirmButton.dataset.installLabelTemplate.replace('__SITE_NAME__', siteName);
|
|
}
|
|
};
|
|
syncInstallButton();
|
|
appNameInput.addEventListener('input', syncInstallButton);
|
|
appNameInput.addEventListener('change', syncInstallButton);
|
|
}
|
|
|
|
for (const button of document.querySelectorAll('.js-install-test-mail-button')) {
|
|
button.addEventListener('click', async () => {
|
|
const form = button.closest('.js-install-form');
|
|
if (!form) return;
|
|
const message = form.querySelector('.js-install-test-mail-message');
|
|
const testMailInput = form.querySelector('#test_mail_email');
|
|
const adminEmailInput = form.querySelector('#admin_email');
|
|
if (testMailInput && !testMailInput.value.trim() && adminEmailInput?.value.trim()) {
|
|
testMailInput.value = adminEmailInput.value.trim();
|
|
}
|
|
|
|
button.disabled = true;
|
|
try {
|
|
const formData = new FormData(form);
|
|
const response = await fetch(button.getAttribute('data-action'), {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
},
|
|
});
|
|
if (!response.ok) throw new Error(response.statusText);
|
|
const result = await response.json();
|
|
setInstallTestMailButtonState(button, result.state);
|
|
message.textContent = result.message;
|
|
message.style.color = result.state === 'success' ? 'var(--color-green)' : 'var(--color-red)';
|
|
if (result.state === 'success') {
|
|
window.setTimeout(() => {
|
|
setInstallTestMailButtonState(button, '');
|
|
}, 5000);
|
|
}
|
|
} catch {
|
|
setInstallTestMailButtonState(button, 'failed');
|
|
message.textContent = window.config.i18n.error_occurred;
|
|
message.style.color = 'var(--color-red)';
|
|
} finally {
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
dismissInstallSuccessFlash();
|
|
|
|
{{if or .Err_DbInstalledBefore .Err_RepositoryFilesystemRecovery .Err_DatabaseBackupRecovery}}
|
|
if (recoveryLauncherModal) {
|
|
window.$(recoveryLauncherModal).modal({
|
|
autofocus: false,
|
|
closable: false,
|
|
}).modal('show');
|
|
}
|
|
{{end}}
|
|
|
|
const installLanguageBalloon = document.querySelector('#install-language-balloon');
|
|
const footerLanguageSelector = document.querySelector('#footer-language-selector');
|
|
if (!installLanguageBalloon || !footerLanguageSelector) return;
|
|
|
|
const hideInstallLanguageBalloon = () => {
|
|
installLanguageBalloon.classList.add('is-hidden');
|
|
};
|
|
|
|
window.setTimeout(() => {
|
|
installLanguageBalloon.classList.add('is-visible');
|
|
}, 0);
|
|
document.addEventListener('click', hideInstallLanguageBalloon, {once: true});
|
|
});
|
|
</script>
|
|
{{template "base/footer" .}}
|