This commit is contained in:
Hari 2026-03-31 23:50:51 -04:00 committed by GitHub
parent 4f174ec3a8
commit b68151035a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 6263 additions and 545 deletions

View file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace OCA\BetterNasControlplane\AppInfo;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
class Application extends App implements IBootstrap {
public const APP_ID = 'betternascontrolplane';
public function __construct() {
parent::__construct(self::APP_ID);
}
public function register(IRegistrationContext $context): void {
}
public function boot(IBootContext $context): void {
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace OCA\BetterNasControlplane\Controller;
use OCA\BetterNasControlplane\AppInfo\Application;
use OCA\BetterNasControlplane\Service\ControlPlaneClient;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
class ApiController extends OCSController {
public function __construct(
IRequest $request,
private readonly ControlPlaneClient $controlPlaneClient,
) {
parent::__construct(Application::APP_ID, $request);
}
#[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/api/status')]
public function status(): DataResponse {
return new DataResponse($this->controlPlaneClient->fetchSnapshot());
}
}

View file

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace OCA\BetterNasControlplane\Controller;
use OCA\BetterNasControlplane\AppInfo\Application;
use OCA\BetterNasControlplane\Service\ControlPlaneClient;
use OCA\BetterNasControlplane\Service\ControlPlaneConfig;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest;
class PageController extends Controller {
public function __construct(
IRequest $request,
private readonly ControlPlaneClient $controlPlaneClient,
private readonly ControlPlaneConfig $controlPlaneConfig,
) {
parent::__construct(Application::APP_ID, $request);
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[OpenAPI(OpenAPI::SCOPE_IGNORE)]
#[FrontpageRoute(verb: 'GET', url: '/')]
public function index(): TemplateResponse {
return new TemplateResponse(
Application::APP_ID,
'index',
[
'appName' => 'betterNAS Control Plane',
'controlPlaneUrl' => $this->controlPlaneConfig->getBaseUrl(),
'snapshot' => $this->controlPlaneClient->fetchSnapshot(),
],
);
}
}

View file

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace OCA\BetterNasControlplane\Service;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
use Psr\Log\LoggerInterface;
class ControlPlaneClient {
public function __construct(
private readonly IClientService $clientService,
private readonly ControlPlaneConfig $controlPlaneConfig,
private readonly LoggerInterface $logger,
) {
}
/**
* @return array<string, mixed>
*/
public function fetchSnapshot(): array {
$baseUrl = $this->controlPlaneConfig->getBaseUrl();
try {
$healthResponse = $this->request($baseUrl . '/health');
$versionResponse = $this->request($baseUrl . '/version');
return [
'available' => $healthResponse['statusCode'] === 200,
'url' => $baseUrl,
'health' => $healthResponse['body'],
'version' => $versionResponse['body'],
];
} catch (\Throwable $exception) {
$this->logger->warning('Failed to reach betterNAS control plane', [
'exception' => $exception,
'url' => $baseUrl,
]);
return [
'available' => false,
'url' => $baseUrl,
'error' => $exception->getMessage(),
];
}
}
/**
* @return array{statusCode: int, body: array<string, mixed>}
*/
private function request(string $url): array {
$client = $this->clientService->newClient();
$response = $client->get($url, [
'headers' => [
'Accept' => 'application/json',
],
'http_errors' => false,
'timeout' => 2,
'nextcloud' => [
'allow_local_address' => true,
],
]);
return [
'statusCode' => $response->getStatusCode(),
'body' => $this->decodeBody($response),
];
}
/**
* @return array<string, mixed>
*/
private function decodeBody(IResponse $response): array {
$body = $response->getBody();
if ($body === '') {
return [];
}
$decoded = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($decoded)) {
return [];
}
return $decoded;
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace OCA\BetterNasControlplane\Service;
use OCA\BetterNasControlplane\AppInfo\Application;
use OCP\IAppConfig;
class ControlPlaneConfig {
public function __construct(
private readonly IAppConfig $appConfig,
) {
}
public function getBaseUrl(): string {
$environmentUrl = getenv('BETTERNAS_CONTROL_PLANE_URL');
if (is_string($environmentUrl) && $environmentUrl !== '') {
return rtrim($environmentUrl, '/');
}
$configuredUrl = $this->appConfig->getValueString(
Application::APP_ID,
'control_plane_url',
'http://control-plane:3000',
);
return rtrim($configuredUrl, '/');
}
}

View file

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace OCA\BetterNasControlplane\Settings;
use OCA\BetterNasControlplane\AppInfo\Application;
use OCA\BetterNasControlplane\Service\ControlPlaneClient;
use OCA\BetterNasControlplane\Service\ControlPlaneConfig;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Settings\ISettings;
class Admin implements ISettings {
public function __construct(
private readonly ControlPlaneConfig $controlPlaneConfig,
private readonly ControlPlaneClient $controlPlaneClient,
) {
}
public function getForm(): TemplateResponse {
return new TemplateResponse(
Application::APP_ID,
'admin',
[
'controlPlaneUrl' => $this->controlPlaneConfig->getBaseUrl(),
'snapshot' => $this->controlPlaneClient->fetchSnapshot(),
],
);
}
public function getSection(): string {
return Application::APP_ID;
}
public function getPriority(): int {
return 50;
}
}

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace OCA\BetterNasControlplane\Settings;
use OCA\BetterNasControlplane\AppInfo\Application;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
class AdminSection implements IIconSection {
public function __construct(
private readonly IURLGenerator $urlGenerator,
private readonly IL10N $l,
) {
}
public function getID(): string {
return Application::APP_ID;
}
public function getName(): string {
return $this->l->t('betterNAS');
}
public function getPriority(): int {
return 50;
}
public function getIcon(): ?string {
return $this->urlGenerator->imagePath(Application::APP_ID, 'app-dark.svg');
}
}