Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
116 changes: 0 additions & 116 deletions apps/federation/js/settings-admin.js

This file was deleted.

4 changes: 3 additions & 1 deletion apps/federation/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

class Application extends App implements IBootstrap {

public const APP_ID = 'federation';

/**
* @param array $urlParams
*/
public function __construct($urlParams = []) {
parent::__construct('federation', $urlParams);
parent::__construct(self::APP_ID, $urlParams);
}

public function register(IRegistrationContext $context): void {
Expand Down
15 changes: 13 additions & 2 deletions apps/federation/lib/Settings/Admin.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
<?php

/**
/*!
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Federation\Settings;

use OCA\Federation\AppInfo\Application;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IDelegatedSettings;
use OCP\Util;

class Admin implements IDelegatedSettings {
public function __construct(
private TrustedServers $trustedServers,
private IInitialState $initialState,
private IURLGenerator $urlGenerator,
private IL10N $l,
) {
}
Expand All @@ -24,9 +30,14 @@ public function __construct(
public function getForm() {
$parameters = [
'trustedServers' => $this->trustedServers->getServers(),
'docUrl' => $this->urlGenerator->linkToDocs('admin-sharing-federated') . '#configuring-trusted-nextcloud-servers',
];

return new TemplateResponse('federation', 'settings-admin', $parameters, '');
$this->initialState->provideInitialState('adminSettings', $parameters);

Util::addStyle(Application::APP_ID, 'settings-admin');
Util::addScript(Application::APP_ID, 'settings-admin');
return new TemplateResponse(Application::APP_ID, 'settings-admin', renderAs: '');
}

/**
Expand Down
90 changes: 90 additions & 0 deletions apps/federation/src/components/AddTrustedServerForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!--
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import type { ITrustedServer } from '../services/api.ts'

import { mdiPlus } from '@mdi/js'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { nextTick, ref, useTemplateRef } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import { addServer, ApiError } from '../services/api.ts'
import { logger } from '../services/logger.ts'

const emit = defineEmits<{
add: [server: ITrustedServer]
}>()

const formElement = useTemplateRef<HTMLFormElement>('form')
const newServerUrl = ref('')

/**
* Handle add trusted server form submission
*/
async function onAdd() {
try {
const server = await addServer(newServerUrl.value)
newServerUrl.value = ''
emit('add', server)

nextTick(() => formElement.value?.reset()) // Reset native form validation state
showSuccess(t('federation', 'Added to the list of trusted servers'))
} catch (error) {
logger.error('Failed to add trusted server', { error })
if (error instanceof ApiError) {
showError(error.message)
} else {
showError(t('federation', 'Could not add trusted server. Please try again later.'))
}
}
}
</script>

<template>
<form ref="form" @submit.prevent="onAdd">
<h3 :class="$style.addTrustedServerForm__heading">
{{ t('federation', 'Add trusted server') }}
</h3>
<div :class="$style.addTrustedServerForm__wrapper">
<NcTextField
v-model="newServerUrl"
:label="t('federation', 'Server url')"
placeholder="https://…"
required
type="url" />
<NcButton
:class="$style.addTrustedServerForm__submitButton"
:aria-label="t('federation', 'Add')"
:title="t('federation', 'Add')"
type="submit"
variant="primary">
<template #icon>
<NcIconSvgWrapper :path="mdiPlus" />
</template>
</NcButton>
</div>
</form>
</template>

<style module>
.addTrustedServerForm__heading {
font-size: 1.2rem;
margin-block: 0.5lh 0.25lh;
}

.addTrustedServerForm__wrapper {
display: flex;
gap: var(--default-grid-baseline);
align-items: end;
max-width: 600px;
}

.addTrustedServerForm__submitButton {
max-height: var(--default-clickable-area);
}
</style>
121 changes: 121 additions & 0 deletions apps/federation/src/components/TrustedServer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<!--
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import type { ITrustedServer } from '../services/api.ts'

import { mdiCheckNetworkOutline, mdiCloseNetworkOutline, mdiHelpNetworkOutline, mdiTrashCanOutline } from '@mdi/js'
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { computed, ref } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import { TrustedServerStatus } from '../services/api.ts'
import { deleteServer } from '../services/api.ts'
import { logger } from '../services/logger.ts'

const props = defineProps<{
server: ITrustedServer
}>()

const emit = defineEmits<{
delete: [ITrustedServer]
}>()

const isLoading = ref(false)

const hasError = computed(() => props.server.status === TrustedServerStatus.STATUS_FAILURE)
const serverIcon = computed(() => {
switch (props.server.status) {
case TrustedServerStatus.STATUS_OK:
return mdiCheckNetworkOutline
case TrustedServerStatus.STATUS_PENDING:
case TrustedServerStatus.STATUS_ACCESS_REVOKED:
return mdiHelpNetworkOutline
case TrustedServerStatus.STATUS_FAILURE:
default:
return mdiCloseNetworkOutline
}
})

const serverStatus = computed(() => {
switch (props.server.status) {
case TrustedServerStatus.STATUS_OK:
return [t('federation', 'Server ok'), t('federation', 'User list was exchanged at least once successfully with the remote server.')]
case TrustedServerStatus.STATUS_PENDING:
return [t('federation', 'Server pending'), t('federation', 'Waiting for shared secret or initial user list exchange.')]
case TrustedServerStatus.STATUS_ACCESS_REVOKED:
return [t('federation', 'Server access revoked'), t('federation', 'Server access revoked')]
case TrustedServerStatus.STATUS_FAILURE:
default:
return [t('federation', 'Server failure'), t('federation', 'Connection to the remote server failed or the remote server is misconfigured.')]
}
})

/**
* Emit delete event
*/
async function onDelete() {
try {
isLoading.value = true
await deleteServer(props.server.id)
emit('delete', props.server)
} catch (error) {
isLoading.value = false
logger.error('Failed to delete trusted server', { error })
showError(t('federation', 'Failed to delete trusted server. Please try again later.'))
}
}
</script>

<template>
<li :class="$style.trustedServer">
<NcIconSvgWrapper
:class="{
[$style.trustedServer__icon_error]: hasError,
}"
:path="serverIcon"
:name="serverStatus[0]"
:title="serverStatus[1]" />

<code :class="$style.trustedServer__url" v-text="server.url" />

<NcButton
:aria-label="t('federation', 'Delete')"
:title="t('federation', 'Delete')"
:disabled="isLoading"
@click="onDelete">
<template #icon>
<NcLoadingIcon v-if="isLoading" />
<NcIconSvgWrapper v-else :path="mdiTrashCanOutline" />
</template>
</NcButton>
</li>
</template>

<style module>
.trustedServer {
display: flex;
flex-direction: row;
gap: var(--default-grid-baseline);
align-items: center;
border-radius: var(--border-radius-element);
padding-inline-start: var(--default-grid-baseline);
}

.trustedServer:hover {
background-color: var(--color-background-hover);
}

.trustedServer__icon_error {
color: var(--color-element-error);
}

.trustedServer__url {
padding-inline: 1ch;
flex: 1 0 auto;
}
</style>
Loading
Loading