Skip to content

Commit

Permalink
feat: Add setup check to verify whiteboard backend server version and…
Browse files Browse the repository at this point in the history
… connectivity

Signed-off-by: Julius Knorr <[email protected]>
  • Loading branch information
juliusknorr committed Dec 6, 2024
1 parent 7a7137f commit 581775a
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 6 deletions.
2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use OCA\Whiteboard\Listener\BeforeTemplateRenderedListener;
use OCA\Whiteboard\Listener\LoadViewerListener;
use OCA\Whiteboard\Listener\RegisterTemplateCreatorListener;
use OCA\Whiteboard\Settings\SetupCheck;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
Expand Down Expand Up @@ -44,6 +45,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(LoadViewer::class, LoadViewerListener::class);
$context->registerEventListener(RegisterTemplateCreatorEvent::class, RegisterTemplateCreatorListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
$context->registerSetupCheck(SetupCheck::class);
}

public function boot(IBootContext $context): void {
Expand Down
7 changes: 6 additions & 1 deletion lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use OCA\Whiteboard\Service\ConfigService;
use OCA\Whiteboard\Service\ExceptionService;
use OCA\Whiteboard\Service\JWTService;
use OCA\Whiteboard\Settings\SetupCheck;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
Expand All @@ -27,6 +28,7 @@ public function __construct(
private ExceptionService $exceptionService,
private JWTService $jwtService,
private ConfigService $configService,
private SetupCheck $setupCheck,
) {
parent::__construct('whiteboard', $request);
}
Expand All @@ -44,8 +46,11 @@ public function update(): DataResponse {
$this->configService->setWhiteboardSharedSecret($secret);
}

$result = $this->setupCheck->run();

return new DataResponse([
'jwt' => $this->jwtService->generateJWTFromPayload([ 'serverUrl' => $serverUrl ])
'jwt' => $this->jwtService->generateJWTFromPayload([ 'serverUrl' => $serverUrl ?: $this->configService->getCollabBackendUrl() ]),
'check' => $result->jsonSerialize(),
]);
} catch (Exception $e) {
return $this->exceptionService->handleException($e);
Expand Down
78 changes: 78 additions & 0 deletions lib/Settings/SetupCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Whiteboard\Settings;

use OCA\Whiteboard\AppInfo\Application;
use OCA\Whiteboard\Service\ConfigService;
use OCP\App\IAppManager;
use OCP\Http\Client\IClientService;
use OCP\IL10N;
use OCP\SetupCheck\SetupResult;
use Psr\Log\LoggerInterface;

class SetupCheck implements \OCP\SetupCheck\ISetupCheck {
public function __construct(
private IClientService $clientService,
private ConfigService $configService,
private LoggerInterface $logger,
private IL10N $l10n,
private IAppManager $appManager,
) {
}
public function getCategory(): string {
return 'system';
}

/**
* @inheritDoc
*/
public function getName(): string {
return 'Whiteboard server';
}

/**
* @inheritDoc
*/
public function run(): \OCP\SetupCheck\SetupResult {
$client = $this->clientService->newClient();
try {
$result = $client->get($this->configService->getCollabBackendUrl());
} catch (\Exception $e) {
$this->logger->error('Nextcloud server could not connect to whiteboard server', ['exception' => $e]);
return SetupResult::error($this->l10n->t('Nextcloud server could not connect to whiteboard server') . ': ' . $e->getMessage());
}

try {
$result = $client->get($this->configService->getCollabBackendUrl() . '/status');
$resultObject = json_decode((string)$result->getBody(), false, 512, JSON_THROW_ON_ERROR);
$backendVersion = $resultObject?->version ?? null;
if ($backendVersion === null) {
return SetupResult::error($this->l10n->t('No version provided by /status enpdoint'));
}

$appVersion = $this->appManager->getAppVersion(Application::APP_ID);
if (!version_compare($backendVersion, $appVersion, '==')) {
return SetupResult::warning(
$this->l10n->t('Backend server is running a different version, make sure to upgrade both to the same version. App: %s Backend version: %s', [$appVersion, $backendVersion])
);
}

if ($resultObject->connectBack !== true) {
return SetupResult::error(
$this->l10n->t('Whiteboard backend server could not reach Nextcloud: ' . $resultObject->connectBack)
);
}
} catch (\Exception $e) {
$this->logger->error('Failed to connect to whiteboard server status endpoint', ['exception' => $e]);
return SetupResult::error($this->l10n->t('Failed to connect to whiteboard server server status endpoint') . ': ' . $e->getMessage());
}

return SetupResult::success($this->l10n->t('Whiteboard server configured properly'));
}
}
28 changes: 23 additions & 5 deletions src/settings/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@
<div class="section">
<h3>{{ t('whiteboard', 'Whiteboard settings') }}</h3>

<NcNoteCard v-if="validConnection === true" type="success">
<NcNoteCard v-if="!loading && setupCheck !== null" :type="setupCheck.severity">
{{ setupCheck.description }}
</NcNoteCard>
<NcNoteCard v-else-if="!loading && validConnection === true" type="success">
{{ t('whiteboard', 'Whiteboard backend server is configured and connected.') }}
</NcNoteCard>
<NcNoteCard v-else-if="validConnection === false" type="error">
<NcNoteCard v-else-if="!loading && validConnection === false" type="error">
{{ t('whiteboard', 'Failed to verify the connection:') }} {{ connectionError }}
</NcNoteCard>
<NcNoteCard v-else type="info" :text="t('whiteboard', 'Verifying connection…')">
<template #icon>
<NcLoadingIcon />
</template>
</NcNoteCard>

<p>
{{ t('whiteboard', 'Whiteboard requires a separate collaboration server that is connected to Nextcloud.') }}
<a href="https://github.com/nextcloud/whiteboard?tab=readme-ov-file#backend"
Expand All @@ -35,7 +39,7 @@
</p>
<p>
<NcButton type="submit"
:disabled="!serverUrl"
:disabled="!serverUrl || loading"
@click.prevent="submit">
{{ t('whiteboard', 'Save settings') }}
</NcButton>
Expand Down Expand Up @@ -67,20 +71,32 @@ export default {
secret: loadState('whiteboard', 'secret', ''),
validConnection: undefined,
connectionError: undefined,
loading: false,
setupCheck: null,
}
},
mounted() {
this.callSettings()
this.verifyConnection({ jwt: loadState('whiteboard', 'jwt', '') })
},
methods: {
async submit() {
const { data } = await axios.post(generateUrl('/apps/whiteboard/settings'), {
const data = await this.callSettings({
serverUrl: this.serverUrl,
secret: this.secret,
})
await this.verifyConnection(data)
},
async callSettings(updateValues = {}) {
this.loading = true
const { data } = await axios.post(generateUrl('/apps/whiteboard/settings'), updateValues)
this.setupCheck = data.check.severity !== 'success' ? data.check : null
this.loading = false
return data
},
async verifyConnection(data) {
this.loading = true

const url = new URL(this.serverUrl)
const path = url.pathname.replace(/\/$/, '') + '/socket.io'

Expand All @@ -91,16 +107,18 @@ export default {
secret: data.jwt,
},
transports: ['websocket'],
timeout: 10000,
timeout: 5000,
})
socket.on('connect', () => {
this.validConnection = true
this.connectionError = undefined
this.loading = false
})
socket.on('connect_error', (error) => {
this.validConnection = error.message === 'Connection verified'
this.connectionError = this.validConnection === false ? error.message : undefined
socket.close()
this.loading = false
})
socket.connect()
},
Expand Down
26 changes: 26 additions & 0 deletions websocket_server/AppManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import dotenv from 'dotenv'
import express from 'express'
import PrometheusDataManager from './PrometheusDataManager.js'
import axios from 'axios'

dotenv.config()

Expand All @@ -21,13 +22,38 @@ export default class AppManager {

setupRoutes() {
this.app.get('/', this.homeHandler.bind(this))
this.app.get('/status', this.statusHandler.bind(this))
this.app.get('/metrics', this.metricsHandler.bind(this))
}

homeHandler(req, res) {
res.send('Excalidraw collaboration server is up :)')
}

async statusHandler(req, res) {
const NEXTCLOUD_URL = process.env.NEXTCLOUD_URL

// Rather simple connectivity check, but should do the trick for most cases
const statusUrl = NEXTCLOUD_URL + '/status.php'
let connectBack
try {
const response = await axios.get(statusUrl, {
timeout: 5000,
})
connectBack = response.data?.version ? true : ('No version found when requesting ' + statusUrl)
} catch (e) {
console.error(e)
connectBack = e?.message
}

res.set('Content-Type', 'application/json')
res.send(JSON.stringify({
version: process.env.npm_package_version,
connectBack: connectBack === true,
connectBackMessage: connectBack === true ? 'Connection successful' : connectBack,
}))
}

async metricsHandler(req, res) {
const token = req.headers.authorization?.split(' ')[1] || req.query.token
if (!this.METRICS_TOKEN || token !== this.METRICS_TOKEN) {
Expand Down

0 comments on commit 581775a

Please sign in to comment.