feat(core): migrate setup to vue

Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
pull/51464/head
skjnldsv 7 months ago
parent 9dea6185ad
commit cc12719df5
  1. 18
      core/Controller/SetupController.php
  2. 156
      core/src/install.js
  3. 49
      core/src/install.ts
  4. 420
      core/src/views/Setup.vue
  5. 165
      core/templates/installation.php
  6. 2
      webpack.modules.js

@ -8,6 +8,8 @@
namespace OC\Core\Controller;
use OC\Setup;
use OCP\IInitialStateService;
use OCP\IURLGenerator;
use OCP\Template\ITemplateManager;
use OCP\Util;
use Psr\Log\LoggerInterface;
@ -19,6 +21,8 @@ class SetupController {
protected Setup $setupHelper,
protected LoggerInterface $logger,
protected ITemplateManager $templateManager,
protected IInitialStateService $initialStateService,
protected IURLGenerator $urlGenerator,
) {
$this->autoConfigFile = \OC::$configDir . 'autoconfig.php';
}
@ -72,6 +76,8 @@ class SetupController {
'dbtablespace' => '',
'dbhost' => 'localhost',
'dbtype' => '',
'hasAutoconfig' => false,
'serverRoot' => \OC::$SERVERROOT,
];
$parameters = array_merge($defaults, $post);
@ -80,9 +86,18 @@ class SetupController {
// include common nextcloud webpack bundle
Util::addScript('core', 'common');
Util::addScript('core', 'main');
Util::addScript('core', 'install');
Util::addTranslations('core');
$this->templateManager->printGuestPage('', 'installation', $parameters);
$this->initialStateService->provideInitialState('core', 'config', $parameters);
$this->initialStateService->provideInitialState('core', 'data', false);
$this->initialStateService->provideInitialState('core', 'links', [
'adminInstall' => $this->urlGenerator->linkToDocs('admin-install'),
'adminSourceInstall' => $this->urlGenerator->linkToDocs('admin-source_install'),
'adminDBConfiguration' => $this->urlGenerator->linkToDocs('admin-db-configuration'),
]);
$this->templateManager->printGuestPage('', 'installation');
}
private function finishSetup(): void {
@ -107,6 +122,7 @@ class SetupController {
$this->logger->info('Autoconfig file found, setting up Nextcloud…');
$AUTOCONFIG = [];
include $this->autoConfigFile;
$post['hasAutoconfig'] = count($AUTOCONFIG) > 0;
$post = array_merge($post, $AUTOCONFIG);
}

@ -1,156 +0,0 @@
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import $ from 'jquery'
import { translate as t } from '@nextcloud/l10n'
import { linkTo } from '@nextcloud/router'
import { getToken } from './OC/requesttoken.js'
import getURLParameter from './Util/get-url-parameter.js'
import './jquery/showpassword.js'
import 'jquery-ui/ui/widgets/button.js'
import 'jquery-ui/themes/base/theme.css'
import 'jquery-ui/themes/base/button.css'
import 'strengthify'
import 'strengthify/strengthify.css'
window.addEventListener('DOMContentLoaded', function() {
const dbtypes = {
sqlite: !!$('#hasSQLite').val(),
mysql: !!$('#hasMySQL').val(),
postgresql: !!$('#hasPostgreSQL').val(),
oracle: !!$('#hasOracle').val(),
}
$('#selectDbType').buttonset()
// change links inside an info box back to their default appearance
$('#selectDbType p.info a').button('destroy')
if ($('#hasSQLite').val()) {
$('#use_other_db').hide()
$('#use_oracle_db').hide()
} else {
$('#sqliteInformation').hide()
}
$('#adminlogin').change(function() {
$('#adminlogin').val($.trim($('#adminlogin').val()))
})
$('#sqlite').click(function() {
$('#use_other_db').slideUp(250)
$('#use_oracle_db').slideUp(250)
$('#sqliteInformation').show()
$('#dbname').attr('pattern', '[0-9a-zA-Z$_-]+')
})
$('#mysql,#pgsql').click(function() {
$('#use_other_db').slideDown(250)
$('#use_oracle_db').slideUp(250)
$('#sqliteInformation').hide()
$('#dbname').attr('pattern', '[0-9a-zA-Z$_-]+')
})
$('#oci').click(function() {
$('#use_other_db').slideDown(250)
$('#use_oracle_db').show(250)
$('#sqliteInformation').hide()
$('#dbname').attr('pattern', '[0-9a-zA-Z$_-.]+')
})
$('#showAdvanced').click(function(e) {
e.preventDefault()
$('#datadirContent').slideToggle(250)
$('#databaseBackend').slideToggle(250)
$('#databaseField').slideToggle(250)
})
$('form').submit(function() {
// Save form parameters
const post = $(this).serializeArray()
// Show spinner while finishing setup
$('.float-spinner').show(250)
// Disable inputs
$('input[type="submit"]').attr('disabled', 'disabled').val($('input[type="submit"]').data('finishing'))
$('input', this).addClass('ui-state-disabled').attr('disabled', 'disabled')
// only disable buttons if they are present
if ($('#selectDbType').find('.ui-button').length > 0) {
$('#selectDbType').buttonset('disable')
}
$('.strengthify-wrapper, .tipsy')
.css('filter', 'alpha(opacity=30)')
.css('opacity', 0.3)
// Create the form
const form = $('<form>')
form.attr('action', $(this).attr('action'))
form.attr('method', 'POST')
for (let i = 0; i < post.length; i++) {
const input = $('<input type="hidden">')
input.attr(post[i])
form.append(input)
}
// Add redirect_url
const redirectURL = getURLParameter('redirect_url')
if (redirectURL) {
const redirectURLInput = $('<input type="hidden">')
redirectURLInput.attr({
name: 'redirect_url',
value: redirectURL,
})
form.append(redirectURLInput)
}
// Submit the form
form.appendTo(document.body)
form.submit()
return false
})
// Expand latest db settings if page was reloaded on error
const currentDbType = $('input[type="radio"]:checked').val()
if (currentDbType === undefined) {
$('input[type="radio"]').first().click()
}
if (
currentDbType === 'sqlite'
|| (dbtypes.sqlite && currentDbType === undefined)
) {
$('#datadirContent').hide(250)
$('#databaseBackend').hide(250)
$('#databaseField').hide(250)
$('.float-spinner').hide(250)
}
$('#adminpass').strengthify({
zxcvbn: linkTo('core', 'vendor/zxcvbn/dist/zxcvbn.js'),
titles: [
t('core', 'Very weak password'),
t('core', 'Weak password'),
t('core', 'So-so password'),
t('core', 'Good password'),
t('core', 'Strong password'),
],
drawTitles: true,
nonce: btoa(getToken()),
})
$('#dbpass').showPassword().keyup()
$('.toggle-password').click(function(event) {
event.preventDefault()
const currentValue = $(this).parent().children('input').attr('type')
if (currentValue === 'password') {
$(this).parent().children('input').attr('type', 'text')
} else {
$(this).parent().children('input').attr('type', 'password')
}
})
})

@ -0,0 +1,49 @@
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import Vue from 'vue'
import Setup from './views/Setup.vue'
type Error = {
error: string
hint: string
}
export type DbType = 'sqlite' | 'mysql' | 'pgsql' | 'oci'
export type SetupConfig = {
adminlogin: string
adminpass: string
dbuser: string
dbpass: string
dbname: string
dbtablespace: string
dbhost: string
dbtype: DbType | ''
hasSQLite: boolean
hasMySQL: boolean
hasPostgreSQL: boolean
hasOracle: boolean
databases: Record<DbType, string>
dbIsSet: boolean
directory: string
directoryIsSet: boolean
hasAutoconfig: boolean
htaccessWorking: boolean
serverRoot: string
errors: string[]|Error[]
}
export type SetupLinks = {
adminInstall: string
adminSourceInstall: string
adminDBConfiguration: string
}
const SetupVue = Vue.extend(Setup)
new SetupVue().$mount('#content')

@ -0,0 +1,420 @@
<!--
- SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<form ref="form"
class="setup-form"
:class="{ 'setup-form--loading': loading }"
action=""
method="POST"
@submit="onSubmit">
<!-- Autoconfig info -->
<NcNoteCard v-if="config.hasAutoconfig"
:heading="t('core', 'Autoconfig file detected')"
type="success">
{{ t('core', 'The setup form below is pre-filled with the values from the config file.') }}
</NcNoteCard>
<!-- Htaccess warning -->
<NcNoteCard v-if="config.htaccessWorking === false"
:heading="t('core', 'Security warning')"
type="warning">
<p v-html="htaccessWarning" />
</NcNoteCard>
<!-- Various errors -->
<NcNoteCard v-for="(error, index) in errors"
:key="index"
:heading="error.heading"
type="error">
{{ error.message }}
</NcNoteCard>
<!-- Admin creation -->
<fieldset class="setup-form__administration">
<legend>{{ t('core', 'Create administration account') }}</legend>
<!-- Username -->
<NcTextField v-model="config.adminlogin"
:label="t('core', 'Administration account name')"
name="adminlogin"
required />
<!-- Password -->
<NcPasswordField v-model="config.adminpass"
:label="t('core', 'Administration account password')"
name="adminpass"
required />
<!-- Password entropy -->
<NcNoteCard v-show="config.adminpass !== ''" :type="passwordHelperType">
{{ passwordHelperText }}
</NcNoteCard>
</fieldset>
<!-- Autoconfig toggle -->
<details :open="!isValidAutoconfig">
<summary>{{ t('core', 'Advanced settings') }}</summary>
<!-- Data folder -->
<fieldset class="setup-form__data-folder">
<legend>{{ t('core', 'Data folder') }}</legend>
<NcTextField v-model="config.directory"
:label="t('core', 'Data folder')"
:placeholder="config.serverRoot + '/data'"
required
autocomplete="off"
autocapitalize="none"
name="directory"
spellcheck="false" />
</fieldset>
<!-- Database -->
<fieldset class="setup-form__database">
<legend>{{ t('core', 'Database configuration') }}</legend>
<!-- Database type select -->
<fieldset class="setup-form__database-type">
<legend>{{ t('core', 'Database type') }}</legend>
<p v-if="Object.keys(config.databases).length > 1" class="setup-form__database-type-select">
<NcCheckboxRadioSwitch v-for="(name, db) in config.databases"
:key="db"
v-model="config.dbtype"
:button-variant="true"
:value="db"
name="dbtype"
button-variant-grouped="horizontal"
type="radio">
{{ name }}
</NcCheckboxRadioSwitch>
</p>
<NcNoteCard v-else type="warning">
{{ t('core', 'Only {db} is available.', { db: Object.values(config.databases).at(0) }) }}<br>
{{ t('core', 'Install and activate additional PHP modules to choose other database types.') }}<br>
<a :href="links.adminSourceInstall" target="_blank" rel="noreferrer noopener">
{{ t('core', 'For more details check out the documentation.') }}
</a>
</NcNoteCard>
<NcNoteCard v-if="config.dbtype === 'sqlite'"
:heading="t('core', 'Performance warning')"
type="warning">
{{ t('core', 'You chose SQLite as database.') }}<br>
{{ t('core', 'SQLite should only be used for minimal and development instances. For production we recommend a different database backend.') }}<br>
{{ t('core', 'If you use clients for file syncing, the use of SQLite is highly discouraged.') }}
</NcNoteCard>
</fieldset>
<!-- Database configuration -->
<fieldset v-if="config.dbtype !== 'sqlite'">
<NcTextField v-model="config.dbuser"
:label="t('core', 'Database user')"
autocapitalize="none"
autocomplete="off"
name="dbuser"
spellcheck="false"
required />
<NcPasswordField v-model="config.dbpass"
:label="t('core', 'Database password')"
autocapitalize="none"
autocomplete="off"
name="dbpass"
spellcheck="false"
required />
<NcTextField v-model="config.dbname"
:label="t('core', 'Database name')"
autocapitalize="none"
autocomplete="off"
name="dbname"
pattern="[0-9a-zA-Z\$_\-]+"
spellcheck="false"
required />
<NcTextField v-if="config.dbtype === 'oci'"
v-model="config.dbtablespace"
:label="t('core', 'Database tablespace')"
autocapitalize="none"
autocomplete="off"
name="dbtablespace"
spellcheck="false" />
<NcTextField v-model="config.dbhost"
:helper-text="t('core', 'Please specify the port number along with the host name (e.g., localhost:5432).')"
:label="t('core', 'Database host')"
:placeholder="t('core', 'localhost')"
autocapitalize="none"
autocomplete="off"
name="dbhost"
spellcheck="false" />
</fieldset>
</fieldset>
</details>
<!-- Submit -->
<NcButton class="setup-form__button"
:class="{ 'setup-form__button--loading': loading }"
:disabled="loading"
:loading="loading"
:wide="true"
alignment="center-reverse"
native-type="submit"
type="primary">
<template #icon>
<NcLoadingIcon v-if="loading" />
<IconArrowRight v-else />
</template>
{{ loading ? t('core', 'Installing …') : t('core', 'Install') }}
</NcButton>
<!-- Help note -->
<NcNoteCard type="info">
{{ t('core', 'Need help?') }}
<a target="_blank" rel="noreferrer noopener" :href="links.adminInstall">{{ t('core', 'See the documentation') }} </a>
</NcNoteCard>
</form>
</template>
<script lang="ts">
import type { DbType, SetupConfig, SetupLinks } from '../install'
import { defineComponent } from 'vue'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import DomPurify from 'dompurify'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import IconArrowRight from 'vue-material-design-icons/ArrowRight.vue'
const config = loadState<SetupConfig>('core', 'config')
const links = loadState<SetupLinks>('core', 'links')
enum PasswordStrength {
VeryWeak,
Weak,
Moderate,
Strong,
VeryStrong,
ExtremelyStrong,
}
export default defineComponent({
name: 'Setup',
components: {
IconArrowRight,
NcButton,
NcCheckboxRadioSwitch,
NcLoadingIcon,
NcNoteCard,
NcPasswordField,
NcTextField,
},
setup() {
return {
links,
t,
}
},
data() {
return {
config,
isValidAutoconfig: false,
loading: false,
}
},
computed: {
passwordHelperText(): string {
if (this.config.adminpass === '') {
return ''
}
const passwordStrength = this.checkPasswordEntropy(this.config.adminpass)
switch (passwordStrength) {
case PasswordStrength.VeryWeak:
return t('core', 'Password is too weak')
case PasswordStrength.Weak:
return t('core', 'Password is weak')
case PasswordStrength.Moderate:
return t('core', 'Password is average')
case PasswordStrength.Strong:
return t('core', 'Password is strong')
case PasswordStrength.VeryStrong:
return t('core', 'Password is very strong')
case PasswordStrength.ExtremelyStrong:
return t('core', 'Password is extremely strong')
}
return t('core', 'Unknown password strength')
},
passwordHelperType() {
if (this.checkPasswordEntropy(this.config.adminpass) < PasswordStrength.Moderate) {
return 'error'
}
if (this.checkPasswordEntropy(this.config.adminpass) < PasswordStrength.Strong) {
return 'warning'
}
return 'success'
},
htaccessWarning(): string {
// We use v-html, let's make sure we're safe
const message = [
t('core', 'Your data directory and files are probably accessible from the internet because the <code>.htaccess</code> file does not work.'),
t('core', 'For information how to properly configure your server, please {linkStart}see the documentation{linkEnd}', {
linkStart: '<a href="' + links.adminInstall + '" target="_blank" rel="noreferrer noopener">',
linkEnd: '</a>',
}, { escape: false }),
].join('<br>')
return DomPurify.sanitize(message)
},
errors() {
return this.config.errors.map(error => {
if (typeof error === 'string') {
return {
heading: '',
message: error,
}
}
// f no hint is set, we don't want to show a heading
if (error.hint === '') {
return {
heading: '',
message: error.error,
}
}
return {
heading: error.error,
message: error.hint,
}
})
},
},
mounted() {
if (this.config.dbtype === '') {
this.config.dbtype = Object.keys(this.config.databases).at(0) as DbType
}
// Validate the legitimacy of the autoconfig
if (this.config.hasAutoconfig) {
const form = this.$refs.form as HTMLFormElement
// Check the form without the administration account fields
form.querySelectorAll('input[name="adminlogin"], input[name="adminpass"]').forEach(input => {
input.removeAttribute('required')
})
if (form.checkValidity() && this.config.errors.length === 0) {
this.isValidAutoconfig = true
} else {
this.isValidAutoconfig = false
}
// Restore the required attribute
// Check the form without the administration account fields
form.querySelectorAll('input[name="adminlogin"], input[name="adminpass"]').forEach(input => {
input.setAttribute('required', 'true')
})
}
},
methods: {
async onSubmit() {
this.loading = true
},
checkPasswordEntropy(password: string): PasswordStrength {
const uniqueCharacters = new Set(password)
const entropy = parseInt(Math.log2(Math.pow(parseInt(uniqueCharacters.size.toString()), password.length)).toFixed(2))
if (entropy < 16) {
return PasswordStrength.VeryWeak
} else if (entropy < 31) {
return PasswordStrength.Weak
} else if (entropy < 46) {
return PasswordStrength.Moderate
} else if (entropy < 61) {
return PasswordStrength.Strong
} else if (entropy < 76) {
return PasswordStrength.VeryStrong
}
return PasswordStrength.ExtremelyStrong
},
},
})
</script>
<style lang="scss">
form {
padding: calc(3 * var(--default-grid-baseline));
color: var(--color-main-text);
border-radius: var(--border-radius-container);
background-color: var(--color-main-background-blur);
box-shadow: 0 0 10px var(--color-box-shadow);
-webkit-backdrop-filter: var(--filter-background-blur);
backdrop-filter: var(--filter-background-blur);
max-width: 300px;
margin-bottom: 30px;
> fieldset:first-child,
> .notecard:first-child {
margin-top: 0;
}
> .notecard:last-child {
margin-bottom: 0;
}
> fieldset,
> details {
margin-block: 1rem;
}
.setup-form__button:not(.setup-form__button--loading) {
.material-design-icon {
transition: all linear var(--animation-quick);
}
&:hover .material-design-icon {
transform: translateX(0.2em);
}
}
// Db select required styling
.setup-form__database-type-select {
display: flex;
}
}
code {
background-color: var(--color-background-dark);
margin-top: 1rem;
padding: 0 0.3em;
border-radius: var(--border-radius);
}
// Various overrides
.input-field {
margin-block-start: 1rem !important;
}
.notecard__heading {
font-size: inherit !important;
}
</style>

@ -4,168 +4,5 @@
* SPDX-FileCopyrightText: 2011-2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
script('core', 'install');
?>
<input type='hidden' id='hasMySQL' value='<?php p($_['hasMySQL']) ?>'>
<input type='hidden' id='hasSQLite' value='<?php p($_['hasSQLite']) ?>'>
<input type='hidden' id='hasPostgreSQL' value='<?php p($_['hasPostgreSQL']) ?>'>
<input type='hidden' id='hasOracle' value='<?php p($_['hasOracle']) ?>'>
<form method="post" class="guest-box install-form">
<input type="hidden" name="install" value="true">
<?php if (count($_['errors']) > 0): ?>
<fieldset class="warning">
<legend><strong><?php p($l->t('Error'));?></strong></legend>
<?php foreach ($_['errors'] as $err): ?>
<p>
<?php if (is_array($err)):?>
<?php p($err['error']); ?>
<span class='hint'><?php p($err['hint']); ?></span>
<?php else: ?>
<?php p($err); ?>
<?php endif; ?>
</p>
<?php endforeach; ?>
</fieldset>
<?php endif; ?>
<?php if (!$_['htaccessWorking']): ?>
<fieldset class="warning">
<legend><strong><?php p($l->t('Security warning'));?></strong></legend>
<p><?php p($l->t('Your data directory and files are probably accessible from the internet because the .htaccess file does not work.'));?><br>
<?php print_unescaped($l->t(
'For information how to properly configure your server, please see the <a href="%s" target="_blank" rel="noreferrer noopener">documentation</a>.',
[link_to_docs('admin-install')]
)); ?></p>
</fieldset>
<?php endif; ?>
<fieldset id="adminaccount">
<legend><?php print_unescaped($l->t('<strong>Create an admin account</strong>')); ?></legend>
<p>
<label for="adminlogin"><?php p($l->t('New admin account name')); ?></label>
<input type="text" name="adminlogin" id="adminlogin"
value="<?php p($_['adminlogin']); ?>"
autocomplete="off" autocapitalize="none" spellcheck="false" autofocus required>
</p>
<p class="groupbottom">
<label for="adminpass"><?php p($l->t('New admin password')); ?></label>
<input type="password" name="adminpass" data-typetoggle="#show" id="adminpass"
value="<?php p($_['adminpass']); ?>"
autocomplete="off" autocapitalize="none" spellcheck="false" required>
<button id="show" class="toggle-password" aria-label="<?php p($l->t('Show password')); ?>">
<img src="<?php print_unescaped(image_path('', 'actions/toggle.svg')); ?>" alt="<?php p($l->t('Toggle password visibility')); ?>">
</button>
</p>
</fieldset>
<?php if (!$_['directoryIsSet'] or !$_['dbIsSet'] or count($_['errors']) > 0): ?>
<fieldset id="advancedHeader">
<legend><a id="showAdvanced" tabindex="0" href="#"><?php p($l->t('Storage & database')); ?><img src="<?php print_unescaped(image_path('core', 'actions/caret.svg')); ?>" /></a></legend>
</fieldset>
<?php endif; ?>
<?php if (!$_['directoryIsSet'] or count($_['errors']) > 0): ?>
<fieldset id="datadirField">
<div id="datadirContent">
<label for="directory"><?php p($l->t('Data folder')); ?></label>
<input type="text" name="directory" id="directory"
placeholder="<?php p(OC::$SERVERROOT . '/data'); ?>"
value="<?php p($_['directory']); ?>"
autocomplete="off" autocapitalize="none" spellcheck="false">
</div>
</fieldset>
<?php endif; ?>
<?php if (!$_['dbIsSet'] or count($_['errors']) > 0): ?>
<fieldset id='databaseBackend'>
<?php if ($_['hasMySQL'] or $_['hasPostgreSQL'] or $_['hasOracle']) {
$hasOtherDB = true;
} else {
$hasOtherDB = false;
} //other than SQLite?>
<legend><?php p($l->t('Configure the database')); ?></legend>
<div id="selectDbType">
<?php foreach ($_['databases'] as $type => $label): ?>
<?php if (count($_['databases']) === 1): ?>
<p class="info">
<?php p($l->t('Only %s is available.', [$label])); ?>
<?php p($l->t('Install and activate additional PHP modules to choose other database types.')); ?><br>
<a href="<?php print_unescaped(link_to_docs('admin-source_install')); ?>" target="_blank" rel="noreferrer noopener">
<?php p($l->t('For more details check out the documentation.')); ?></a>
</p>
<input type="hidden" id="dbtype" name="dbtype" value="<?php p($type) ?>">
<?php else: ?>
<input type="radio" name="dbtype" value="<?php p($type) ?>" id="<?php p($type) ?>"
<?php print_unescaped($_['dbtype'] === $type ? 'checked="checked" ' : '') ?>/>
<label class="<?php p($type) ?>" for="<?php p($type) ?>"><?php p($label) ?></label>
<?php endif; ?>
<?php endforeach; ?>
</div>
</fieldset>
<?php if ($hasOtherDB): ?>
<fieldset id='databaseField'>
<div id="use_other_db">
<p class="grouptop">
<label for="dbuser"><?php p($l->t('Database account')); ?></label>
<input type="text" name="dbuser" id="dbuser"
value="<?php p($_['dbuser']); ?>"
autocomplete="off" autocapitalize="none" spellcheck="false">
</p>
<p class="groupmiddle">
<label for="dbpass"><?php p($l->t('Database password')); ?></label>
<input type="password" name="dbpass" id="dbpass"
value="<?php p($_['dbpass']); ?>"
autocomplete="off" autocapitalize="none" spellcheck="false">
<button id="show" class="toggle-password" aria-label="<?php p($l->t('Show password')); ?>">
<img src="<?php print_unescaped(image_path('', 'actions/toggle.svg')); ?>" alt="<?php p($l->t('Toggle password visibility')); ?>">
</button>
</p>
<p class="groupmiddle">
<label for="dbname"><?php p($l->t('Database name')); ?></label>
<input type="text" name="dbname" id="dbname"
value="<?php p($_['dbname']); ?>"
autocomplete="off" autocapitalize="none" spellcheck="false"
pattern="[0-9a-zA-Z$_-]+">
</p>
<?php if ($_['hasOracle']): ?>
<div id="use_oracle_db">
<p class="groupmiddle">
<label for="dbtablespace" class="infield"><?php p($l->t('Database tablespace')); ?></label>
<input type="text" name="dbtablespace" id="dbtablespace"
value="<?php p($_['dbtablespace']); ?>"
autocomplete="off" autocapitalize="none" spellcheck="false">
</p>
</div>
<?php endif; ?>
<p class="groupbottom">
<label for="dbhost"><?php p($l->t('Database host')); ?></label>
<input type="text" name="dbhost" id="dbhost"
value="<?php p($_['dbhost']); ?>"
autocomplete="off" autocapitalize="none" spellcheck="false">
</p>
<p class="info">
<?php p($l->t('Please specify the port number along with the host name (e.g., localhost:5432).')); ?>
</p>
</div>
</fieldset>
<?php endif; ?>
<?php endif; ?>
<?php if (!$_['dbIsSet'] or count($_['errors']) > 0): ?>
<div id="sqliteInformation" class="notecard warning">
<legend><?php p($l->t('Performance warning'));?></legend>
<p><?php p($l->t('You chose SQLite as database.'));?></p>
<p><?php p($l->t('SQLite should only be used for minimal and development instances. For production we recommend a different database backend.'));?></p>
<p><?php p($l->t('If you use clients for file syncing, the use of SQLite is highly discouraged.')); ?></p>
</div>
<?php endif ?>
<div class="icon-loading-dark float-spinner">&nbsp;</div>
<div class="buttons"><input type="submit" class="primary" value="<?php p($l->t('Install')); ?>" data-finishing="<?php p($l->t('Installing …')); ?>"></div>
<p class="info">
<span class="icon-info-white"></span>
<?php p($l->t('Need help?'));?>
<a target="_blank" rel="noreferrer noopener" href="<?php p(link_to_docs('admin-install')); ?>"><?php p($l->t('See the documentation'));?></a>
</p>
</form>
<div id="content"></div>

@ -14,7 +14,7 @@ module.exports = {
'ajax-cron': path.join(__dirname, 'core/src', 'ajax-cron.ts'),
files_client: path.join(__dirname, 'core/src', 'files/client.js'),
files_fileinfo: path.join(__dirname, 'core/src', 'files/fileinfo.js'),
install: path.join(__dirname, 'core/src', 'install.js'),
install: path.join(__dirname, 'core/src', 'install.ts'),
login: path.join(__dirname, 'core/src', 'login.js'),
main: path.join(__dirname, 'core/src', 'main.js'),
maintenance: path.join(__dirname, 'core/src', 'maintenance.js'),

Loading…
Cancel
Save