Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(settings): migrate setup checks to Vue to prevent visual issues
Signed-off-by: Ferdinand Thiessen <[email protected]>
  • Loading branch information
susnux committed Aug 26, 2025
commit 79184f3aed1f94ca85887d2a1b92b60546fb5ef1
100 changes: 0 additions & 100 deletions apps/settings/css/settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -625,39 +625,6 @@ table.grid td.date {
margin-top: 20px;
}

#security-warning-state-ok,
#security-warning-state-warning,
#security-warning-state-failure,
#security-warning-state-loading {
span {
vertical-align: middle;

&.message {
padding: 12px;
}

&.icon {
width: 32px;
height: 32px;
background-position: center center;
display: inline-block;
border-radius: 50%;
}

&.icon-checkmark-white {
background-color: var(--color-border-success);
}

&.icon-error-white {
background-color: var(--color-warning-text);
}

&.icon-close-white {
background-color: var(--color-border-error);
}
}
}

#shareAPI {
&.loading > div {
display: none;
Expand Down Expand Up @@ -799,73 +766,6 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
}

#postsetupchecks {
ul {
margin-inline-start: 44px;
list-style: disc;

li {
margin: 10px 0;
}

ul {
list-style: circle;
}
}

.loading {
height: 50px;
background-position: left center;
}

.errors, .errors a {
color: var(--color-text-error);
}

.warnings, .warnings a {
color: var(--color-warning-text);
}

.hint {
margin: 20px 0;
}
}

#security-warning {
a {
text-decoration: underline;
}

.extra-top-margin {
margin-top: 12px;
}
}

.security-warning__heading {
display: flex;
flex-wrap: wrap;
margin-bottom: calc(var(--default-grid-baseline) * 8);

> h2 {
margin: 0;
font-size: 20px;
font-weight: bold;
}

> a {
width: 44px;
}
}

#admin-tips li {
list-style: initial;

a {
display: inline-block;
padding: 3px 0;
}
}

#warning {
color: red;
}
Expand Down
6 changes: 1 addition & 5 deletions apps/settings/lib/Controller/CheckSetupController.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,6 @@ public function getFailedIntegrityCheckFiles(): DataDisplayResponse {
*/
#[AuthorizedAdminSetting(settings: Overview::class)]
public function check() {
return new DataResponse(
[
'generic' => $this->setupCheckManager->runAll(),
]
);
return new DataResponse($this->setupCheckManager->runAll());
}
}
12 changes: 12 additions & 0 deletions apps/settings/lib/Settings/Admin/Overview.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,35 @@
namespace OCA\Settings\Settings\Admin;

use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\ServerVersion;
use OCP\Settings\IDelegatedSettings;
use OCP\Util;

class Overview implements IDelegatedSettings {
public function __construct(
private ServerVersion $serverVersion,
private IConfig $config,
private IL10N $l,
private IInitialState $initialState,
private IURLGenerator $urlGenerator,
) {
}

/**
* @return TemplateResponse
*/
public function getForm() {
Util::addScript('settings', 'vue-settings-admin-overview');
$this->initialState->provideInitialState('setup-checks-section', [
'sectionDocsUrl' => $this->urlGenerator->linkToDocs('admin-warnings'),
'installationGuidesDocsUrl' => $this->urlGenerator->linkToDocs('admin-install'),
'loggingSectionUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'logging']),
]);

$parameters = [
'checkForWorkingWellKnownSetup' => $this->config->getSystemValue('check_for_working_wellknown_setup', true),
'version' => $this->serverVersion->getHumanVersion(),
Expand Down
58 changes: 0 additions & 58 deletions apps/settings/src/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,62 +92,4 @@ window.addEventListener('DOMContentLoaded', () => {
OC.msg.finishedError('#sendtestmail_msg', error)
})
})

const setupChecks = () => {
// run setup checks then gather error messages
$.when(
OC.SetupChecks.checkSetup(),
).then((messages) => {
const $el = $('#postsetupchecks')
$('#security-warning-state-loading').addClass('hidden')

const $errorsEl = $el.find('.errors')
const $warningsEl = $el.find('.warnings')
const $infoEl = $el.find('.info')

for (let i = 0; i < messages.length; i++) {
switch (messages[i].type) {
case OC.SetupChecks.MESSAGE_TYPE_INFO:
$infoEl.append('<li>' + messages[i].msg + '</li>')
break
case OC.SetupChecks.MESSAGE_TYPE_WARNING:
$warningsEl.append('<li>' + messages[i].msg + '</li>')
break
case OC.SetupChecks.MESSAGE_TYPE_ERROR:
default:
$errorsEl.append('<li>' + messages[i].msg + '</li>')
}
}

let hasErrors = false
let hasWarnings = false

if ($errorsEl.find('li').length > 0) {
$errorsEl.removeClass('hidden')
hasErrors = true
}
if ($warningsEl.find('li').length > 0) {
$warningsEl.removeClass('hidden')
hasWarnings = true
}
if ($infoEl.find('li').length > 0) {
$infoEl.removeClass('hidden')
}

if (hasErrors || hasWarnings) {
$('#postsetupchecks-hint').removeClass('hidden')
if (hasErrors) {
$('#security-warning-state-failure').removeClass('hidden')
} else {
$('#security-warning-state-warning').removeClass('hidden')
}
} else {
$('#security-warning-state-ok').removeClass('hidden')
}
})
}

if (document.getElementById('security-warning') !== null) {
setupChecks()
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import type { ISetupCheck } from '../../settings-types.ts'

import { t } from '@nextcloud/l10n'
import { computed } from 'vue'
import SettingsSetupChecksListItem from './SettingsSetupChecksListItem.vue'

const props = defineProps<{
severity: 'info' | 'warning' | 'error'
setupChecks: ISetupCheck[]
}>()

const ariaLabel = computed(() => {
if (props.severity === 'error') {
return t('settings', 'Setup errors')
} else if (props.severity === 'warning') {
return t('settings', 'Setup warnings')
}
return t('settings', 'Setup recommendations')
})

const shownChecks = computed(() => props.setupChecks.filter(({ severity }) => severity === props.severity))
</script>

<template>
<ul class="settings-setup-checks-list" :aria-label="ariaLabel">
<SettingsSetupChecksListItem v-for="(setupCheck, index) in shownChecks"
:key="index"
class="settings-setup-checks-list__item"
:setup-check="setupCheck" />
</ul>
</template>

<style scope lang="scss">
.settings-setup-checks-list {
&:not(:first-of-type) {
margin-top: calc(2 * var(--default-grid-baseline));
}

&__item:not(:last-of-type) {
margin-bottom: var(--default-grid-baseline);
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import type { IRichObjectParameters, ISetupCheck } from '../../settings-types.ts'

import { mdiAlert, mdiClose, mdiInformation } from '@mdi/js'
import { computed } from 'vue'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import escapeHTML from 'escape-html'

const props = defineProps<{
setupCheck: ISetupCheck
}>()

const leadingIcon = computed(() => {
if (props.setupCheck.severity === 'error') {
return mdiClose
} else if (props.setupCheck.severity === 'warning') {
return mdiAlert
}
return mdiInformation
})

const descriptionHtml = computed(() => parseRichObject(props.setupCheck.description, props.setupCheck.descriptionParameters))

/**
* Simplified RichObject parsing and replacing.
*
* @param message - The message that may contain rich objects
* @param parameters - The rich object parameters
*/
function parseRichObject(message: string, parameters?: IRichObjectParameters): string {
if (!parameters) {
return message
}

for (const [placeholder, parameter] of Object.entries(parameters)) {
let replacement: string
if (parameter.type === 'user') {
replacement = `@${escapeHTML(parameter.name)}`
} else if (parameter.type === 'file') {
replacement = escapeHTML(parameter.path || parameter.name)
} else if (parameter.type === 'highlight') {
if (parameter.link) {
replacement = '<a href="' + encodeURI(parameter.link) + '">' + escapeHTML(parameter.name) + '</a>'
} else {
replacement = '<em>' + escapeHTML(parameter.name) + '</em>'
}
} else {
replacement = escapeHTML(parameter.name)
}
message = message.replaceAll('{' + placeholder + '}', replacement)
}

return message
}
</script>

<template>
<li class="settings-setup-checks-item"
:class="{
[`settings-setup-checks-item--${setupCheck.severity}`]: true,
}">
<NcIconSvgWrapper class="settings-setup-checks-item__icon" :path="leadingIcon" />
<div class="settings-setup-checks-item__wrapper">
<div class="settings-setup-checks-item__name">
{{ setupCheck.name }}
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="settings-setup-checks-item__description" v-html="descriptionHtml" />
</div>
</li>
</template>

<style scope lang="scss">
.settings-setup-checks-item {
border-radius: var(--border-radius-element);
display: flex;
align-items: start;
flex-direction: row;

&:hover {
background-color: var(--color-background-hover);
}

&__wrapper {
display: flex;
flex-direction: column;
// align with icon
padding-top: calc((var(--default-clickable-area) - 1lh) / 2);
}

&__description {
color: var(--color-text-maxcontrast);
}

&__icon {
border-radius: calc(var(--default-clickable-area) / 2);
}

&--error &__icon {
color: var(--color-element-error);
}
&--warning &__icon {
color: var(--color-element-warning);
}
&--info &__icon {
color: var(--color-element-info);
}
}
</style>
Loading