fix: resolve CI linting issues for Astroglobe

Fix all ESLint, Stylelint, PHP CS Fixer, and Psalm workflow errors.

Changes:
- ESLint fixes:
  - Remove unused APP_NAME constant
  - Remove unused TextBoxOutline and TextBoxRemoveOutline components
  - Remove unused container variable in adminSettings.js
  - Auto-fix trailing commas, line breaks, attribute ordering
- PHP CS Fixer:
  - Add trailing commas after last function parameters
  - Convert double quotes to single quotes in log messages
  - Remove unused NoCSRFRequired import
  - Fix arrow function formatting
- Stylelint:
  - Update config to use @nextcloud/stylelint-config
  - Fix extends directive (was using non-existent package)
- Psalm workflow:
  - Fix jq object indexing (.include[0] instead of .[0])
  - Correctly extract OCP version from matrix output

All checks now pass locally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2025-12-15 22:05:14 +01:00
parent 5a6205476a
commit dfc81923ba
15 changed files with 109 additions and 88 deletions
+1 -1
View File
@@ -247,7 +247,7 @@ jobs:
- name: Install OCP for static analysis - name: Install OCP for static analysis
run: | run: |
# Get first OCP version from matrix # Get first OCP version from matrix
OCP_VERSION=$(echo '${{ steps.ocp-versions.outputs.ocp-matrix }}' | jq -r '.[0]."ocp-version"') OCP_VERSION=$(echo '${{ steps.ocp-versions.outputs.ocp-matrix }}' | jq -r '.include[0]."ocp-version"')
composer require --dev "nextcloud/ocp:$OCP_VERSION" --ignore-platform-reqs --with-dependencies composer require --dev "nextcloud/ocp:$OCP_VERSION" --ignore-platform-reqs --with-dependencies
- name: Run Psalm - name: Run Psalm
+4 -5
View File
@@ -12,7 +12,6 @@ use OCA\Astroglobe\Settings\Admin as AdminSettings;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\RedirectResponse;
use OCP\IConfig; use OCP\IConfig;
@@ -44,7 +43,7 @@ class ApiController extends Controller {
LoggerInterface $logger, LoggerInterface $logger,
McpTokenStorage $tokenStorage, McpTokenStorage $tokenStorage,
IConfig $config, IConfig $config,
IdpTokenRefresher $tokenRefresher IdpTokenRefresher $tokenRefresher,
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
$this->client = $client; $this->client = $client;
@@ -126,7 +125,7 @@ class ApiController extends Controller {
string $algorithm = 'hybrid', string $algorithm = 'hybrid',
int $limit = 10, int $limit = 10,
string $doc_types = '', string $doc_types = '',
string $include_pca = 'true' string $include_pca = 'true',
): JSONResponse { ): JSONResponse {
if (empty($query)) { if (empty($query)) {
return new JSONResponse([ return new JSONResponse([
@@ -185,7 +184,7 @@ class ApiController extends Controller {
$validDocTypes = ['note', 'file', 'deck_card', 'calendar', 'contact', 'news_item']; $validDocTypes = ['note', 'file', 'deck_card', 'calendar', 'contact', 'news_item'];
$docTypesArray = array_filter( $docTypesArray = array_filter(
explode(',', $doc_types), explode(',', $doc_types),
fn($t) => in_array(trim($t), $validDocTypes) fn ($t) => in_array(trim($t), $validDocTypes)
); );
$docTypesArray = array_map('trim', $docTypesArray); $docTypesArray = array_map('trim', $docTypesArray);
if (empty($docTypesArray)) { if (empty($docTypesArray)) {
@@ -678,7 +677,7 @@ class ApiController extends Controller {
string $doc_type, string $doc_type,
string $doc_id, string $doc_id,
int $start, int $start,
int $end int $end,
): JSONResponse { ): JSONResponse {
$user = $this->userSession->getUser(); $user = $this->userSession->getUser();
if (!$user) { if (!$user) {
+12 -12
View File
@@ -47,7 +47,7 @@ class OAuthController extends Controller {
McpTokenStorage $tokenStorage, McpTokenStorage $tokenStorage,
LoggerInterface $logger, LoggerInterface $logger,
IL10N $l, IL10N $l,
IClientService $clientService IClientService $clientService,
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
$this->config = $config; $this->config = $config;
@@ -73,11 +73,11 @@ class OAuthController extends Controller {
#[NoAdminRequired] #[NoAdminRequired]
#[NoCSRFRequired] #[NoCSRFRequired]
public function initiateOAuth() { public function initiateOAuth() {
$this->logger->info("initiateOAuth called"); $this->logger->info('initiateOAuth called');
$user = $this->userSession->getUser(); $user = $this->userSession->getUser();
if (!$user) { if (!$user) {
$this->logger->error("initiateOAuth: User not authenticated"); $this->logger->error('initiateOAuth: User not authenticated');
return new TemplateResponse( return new TemplateResponse(
'astroglobe', 'astroglobe',
'settings/error', 'settings/error',
@@ -85,7 +85,7 @@ class OAuthController extends Controller {
); );
} }
$this->logger->info("initiateOAuth: User authenticated: " . $user->getUID()); $this->logger->info('initiateOAuth: User authenticated: ' . $user->getUID());
try { try {
// Get MCP server configuration // Get MCP server configuration
@@ -107,9 +107,9 @@ class OAuthController extends Controller {
$codeVerifier = bin2hex(random_bytes(32)); $codeVerifier = bin2hex(random_bytes(32));
$codeChallenge = $this->base64UrlEncode(hash('sha256', $codeVerifier, true)); $codeChallenge = $this->base64UrlEncode(hash('sha256', $codeVerifier, true));
$this->logger->info("Using public client mode with PKCE"); $this->logger->info('Using public client mode with PKCE');
} else { } else {
$this->logger->info("Using confidential client mode with client secret"); $this->logger->info('Using confidential client mode with client secret');
} }
// Generate state for CSRF protection // Generate state for CSRF protection
@@ -129,7 +129,7 @@ class OAuthController extends Controller {
$codeChallenge $codeChallenge
); );
$this->logger->info("Initiating OAuth flow for user: " . $user->getUID()); $this->logger->info('Initiating OAuth flow for user: ' . $user->getUID());
return new RedirectResponse($authUrl); return new RedirectResponse($authUrl);
} catch (\Exception $e) { } catch (\Exception $e) {
@@ -163,7 +163,7 @@ class OAuthController extends Controller {
string $code = '', string $code = '',
string $state = '', string $state = '',
?string $error = null, ?string $error = null,
?string $error_description = null ?string $error_description = null,
): RedirectResponse { ): RedirectResponse {
try { try {
// Check for errors from IdP // Check for errors from IdP
@@ -292,7 +292,7 @@ class OAuthController extends Controller {
private function buildAuthorizationUrl( private function buildAuthorizationUrl(
string $mcpServerUrl, string $mcpServerUrl,
string $state, string $state,
?string $codeChallenge ?string $codeChallenge,
): string { ): string {
// First, query MCP server to discover which IdP it's configured to use // First, query MCP server to discover which IdP it's configured to use
$this->logger->info('buildAuthorizationUrl: Starting', [ $this->logger->info('buildAuthorizationUrl: Starting', [
@@ -430,7 +430,7 @@ class OAuthController extends Controller {
private function exchangeCodeForToken( private function exchangeCodeForToken(
string $mcpServerUrl, string $mcpServerUrl,
string $code, string $code,
?string $codeVerifier ?string $codeVerifier,
): array { ): array {
// Query MCP server to discover which IdP it's configured to use // Query MCP server to discover which IdP it's configured to use
try { try {
@@ -496,11 +496,11 @@ class OAuthController extends Controller {
if (!empty($clientSecret)) { if (!empty($clientSecret)) {
// Confidential client: use client secret for authentication // Confidential client: use client secret for authentication
$postData['client_secret'] = $clientSecret; $postData['client_secret'] = $clientSecret;
$this->logger->info("Using client secret for token exchange"); $this->logger->info('Using client secret for token exchange');
} elseif ($codeVerifier !== null) { } elseif ($codeVerifier !== null) {
// Public client: use PKCE proof for authentication // Public client: use PKCE proof for authentication
$postData['code_verifier'] = $codeVerifier; $postData['code_verifier'] = $codeVerifier;
$this->logger->info("Using PKCE code verifier for token exchange"); $this->logger->info('Using PKCE code verifier for token exchange');
} else { } else {
throw new \Exception('Neither client_secret nor code_verifier available for token exchange'); throw new \Exception('Neither client_secret nor code_verifier available for token exchange');
} }
@@ -247,8 +247,8 @@ class SemanticSearchProvider implements IProvider {
: $this->urlGenerator->linkToRouteAbsolute('files.view.index'), : $this->urlGenerator->linkToRouteAbsolute('files.view.index'),
'deck_card' => isset($result['board_id']) && $id 'deck_card' => isset($result['board_id']) && $id
? $this->urlGenerator->linkToRoute('deck.page.index') . ? $this->urlGenerator->linkToRoute('deck.page.index')
"board/{$result['board_id']}/card/{$id}" . "board/{$result['board_id']}/card/{$id}"
: $this->urlGenerator->linkToRoute('deck.page.index'), : $this->urlGenerator->linkToRoute('deck.page.index'),
'calendar', 'calendar_event' => $this->urlGenerator->linkToRoute('calendar.view.index'), 'calendar', 'calendar_event' => $this->urlGenerator->linkToRoute('calendar.view.index'),
+1 -1
View File
@@ -25,7 +25,7 @@ class IdpTokenRefresher {
public function __construct( public function __construct(
IConfig $config, IConfig $config,
IClientService $clientService, IClientService $clientService,
LoggerInterface $logger LoggerInterface $logger,
) { ) {
$this->config = $config; $this->config = $config;
$this->httpClient = $clientService->newClient(); $this->httpClient = $clientService->newClient();
+7 -7
View File
@@ -24,7 +24,7 @@ class McpServerClient {
public function __construct( public function __construct(
IClientService $clientService, IClientService $clientService,
IConfig $config, IConfig $config,
LoggerInterface $logger LoggerInterface $logger,
) { ) {
$this->httpClient = $clientService->newClient(); $this->httpClient = $clientService->newClient();
$this->config = $config; $this->config = $config;
@@ -85,7 +85,7 @@ class McpServerClient {
public function getUserSession(string $userId, string $token): array { public function getUserSession(string $userId, string $token): array {
try { try {
$response = $this->httpClient->get( $response = $this->httpClient->get(
$this->baseUrl . "/api/v1/users/" . urlencode($userId) . "/session", $this->baseUrl . '/api/v1/users/' . urlencode($userId) . '/session',
[ [
'headers' => [ 'headers' => [
'Authorization' => 'Bearer ' . $token 'Authorization' => 'Bearer ' . $token
@@ -120,7 +120,7 @@ class McpServerClient {
public function revokeUserAccess(string $userId, string $token): array { public function revokeUserAccess(string $userId, string $token): array {
try { try {
$response = $this->httpClient->post( $response = $this->httpClient->post(
$this->baseUrl . "/api/v1/users/" . urlencode($userId) . "/revoke", $this->baseUrl . '/api/v1/users/' . urlencode($userId) . '/revoke',
[ [
'headers' => [ 'headers' => [
'Authorization' => 'Bearer ' . $token 'Authorization' => 'Bearer ' . $token
@@ -203,7 +203,7 @@ class McpServerClient {
int $limit = 10, int $limit = 10,
bool $includePca = true, bool $includePca = true,
?array $docTypes = null, ?array $docTypes = null,
?string $token = null ?string $token = null,
): array { ): array {
try { try {
$requestBody = [ $requestBody = [
@@ -284,7 +284,7 @@ class McpServerClient {
int $offset = 0, int $offset = 0,
string $algorithm = 'hybrid', string $algorithm = 'hybrid',
string $fusion = 'rrf', string $fusion = 'rrf',
float $scoreThreshold = 0.0 float $scoreThreshold = 0.0,
): array { ): array {
try { try {
$response = $this->httpClient->post( $response = $this->httpClient->post(
@@ -416,7 +416,7 @@ class McpServerClient {
string $event, string $event,
string $uri, string $uri,
?array $eventFilter, ?array $eventFilter,
string $token string $token,
): array { ): array {
try { try {
$requestBody = [ $requestBody = [
@@ -549,7 +549,7 @@ class McpServerClient {
string $docId, string $docId,
int $start, int $start,
int $end, int $end,
string $token string $token,
): array { ): array {
try { try {
$response = $this->httpClient->get( $response = $this->httpClient->get(
+3 -3
View File
@@ -22,7 +22,7 @@ class McpTokenStorage {
public function __construct( public function __construct(
IConfig $config, IConfig $config,
ICrypto $crypto, ICrypto $crypto,
LoggerInterface $logger LoggerInterface $logger,
) { ) {
$this->config = $config; $this->config = $config;
$this->crypto = $crypto; $this->crypto = $crypto;
@@ -43,7 +43,7 @@ class McpTokenStorage {
string $userId, string $userId,
string $accessToken, string $accessToken,
string $refreshToken, string $refreshToken,
int $expiresAt int $expiresAt,
): void { ): void {
try { try {
$tokenData = [ $tokenData = [
@@ -158,7 +158,7 @@ class McpTokenStorage {
* *
* @param string $userId User ID * @param string $userId User ID
* @param callable|null $refreshCallback Callback to refresh token if expired * @param callable|null $refreshCallback Callback to refresh token if expired
* Should accept (refreshToken) and return new token data * Should accept (refreshToken) and return new token data
* @return string|null Access token, or null if not available * @return string|null Access token, or null if not available
*/ */
public function getAccessToken(string $userId, ?callable $refreshCallback = null): ?string { public function getAccessToken(string $userId, ?callable $refreshCallback = null): ?string {
+11 -11
View File
@@ -12,24 +12,24 @@ namespace OCA\Astroglobe\Service;
*/ */
class WebhookPresets { class WebhookPresets {
// File/Notes webhook events // File/Notes webhook events
public const FILE_EVENT_CREATED = "OCP\\Files\\Events\\Node\\NodeCreatedEvent"; public const FILE_EVENT_CREATED = 'OCP\\Files\\Events\\Node\\NodeCreatedEvent';
public const FILE_EVENT_WRITTEN = "OCP\\Files\\Events\\Node\\NodeWrittenEvent"; public const FILE_EVENT_WRITTEN = 'OCP\\Files\\Events\\Node\\NodeWrittenEvent';
// Use BeforeNodeDeletedEvent instead of NodeDeletedEvent to get node.id // Use BeforeNodeDeletedEvent instead of NodeDeletedEvent to get node.id
// See: https://github.com/nextcloud/server/issues/56371 // See: https://github.com/nextcloud/server/issues/56371
public const FILE_EVENT_DELETED = "OCP\\Files\\Events\\Node\\BeforeNodeDeletedEvent"; public const FILE_EVENT_DELETED = 'OCP\\Files\\Events\\Node\\BeforeNodeDeletedEvent';
// Calendar webhook events // Calendar webhook events
public const CALENDAR_EVENT_CREATED = "OCP\\Calendar\\Events\\CalendarObjectCreatedEvent"; public const CALENDAR_EVENT_CREATED = 'OCP\\Calendar\\Events\\CalendarObjectCreatedEvent';
public const CALENDAR_EVENT_UPDATED = "OCP\\Calendar\\Events\\CalendarObjectUpdatedEvent"; public const CALENDAR_EVENT_UPDATED = 'OCP\\Calendar\\Events\\CalendarObjectUpdatedEvent';
public const CALENDAR_EVENT_DELETED = "OCP\\Calendar\\Events\\CalendarObjectDeletedEvent"; public const CALENDAR_EVENT_DELETED = 'OCP\\Calendar\\Events\\CalendarObjectDeletedEvent';
// Tables webhook events (Nextcloud 30+) // Tables webhook events (Nextcloud 30+)
public const TABLES_EVENT_ROW_ADDED = "OCA\\Tables\\Event\\RowAddedEvent"; public const TABLES_EVENT_ROW_ADDED = 'OCA\\Tables\\Event\\RowAddedEvent';
public const TABLES_EVENT_ROW_UPDATED = "OCA\\Tables\\Event\\RowUpdatedEvent"; public const TABLES_EVENT_ROW_UPDATED = 'OCA\\Tables\\Event\\RowUpdatedEvent';
public const TABLES_EVENT_ROW_DELETED = "OCA\\Tables\\Event\\RowDeletedEvent"; public const TABLES_EVENT_ROW_DELETED = 'OCA\\Tables\\Event\\RowDeletedEvent';
// Forms webhook events (Nextcloud 30+) // Forms webhook events (Nextcloud 30+)
public const FORMS_EVENT_FORM_SUBMITTED = "OCA\\Forms\\Events\\FormSubmittedEvent"; public const FORMS_EVENT_FORM_SUBMITTED = 'OCA\\Forms\\Events\\FormSubmittedEvent';
// NOTE: Deck and Contacts do NOT support webhooks // NOTE: Deck and Contacts do NOT support webhooks
// Their event classes do not implement IWebhookCompatibleEvent interface. // Their event classes do not implement IWebhookCompatibleEvent interface.
@@ -163,7 +163,7 @@ class WebhookPresets {
} }
return array_map( return array_map(
fn($eventConfig) => $eventConfig['event'], fn ($eventConfig) => $eventConfig['event'],
$preset['events'] $preset['events']
); );
} }
+1 -1
View File
@@ -36,7 +36,7 @@ class Admin implements ISettings {
public function __construct( public function __construct(
McpServerClient $client, McpServerClient $client,
IConfig $config, IConfig $config,
IInitialState $initialState IInitialState $initialState,
) { ) {
$this->client = $client; $this->client = $client;
$this->config = $config; $this->config = $config;
+1 -1
View File
@@ -33,7 +33,7 @@ class Personal implements ISettings {
IUserSession $userSession, IUserSession $userSession,
IInitialState $initialState, IInitialState $initialState,
McpTokenStorage $tokenStorage, McpTokenStorage $tokenStorage,
IURLGenerator $urlGenerator IURLGenerator $urlGenerator,
) { ) {
$this->client = $client; $this->client = $client;
$this->userSession = $userSession; $this->userSession = $userSession;
+35 -22
View File
@@ -118,7 +118,7 @@
min="0" min="0"
max="100" max="100"
step="5" step="5"
class="mcp-score-slider" /> class="mcp-score-slider">
</div> </div>
</div> </div>
</div> </div>
@@ -248,29 +248,43 @@
<div v-else-if="vectorStatus" class="mcp-status-cards"> <div v-else-if="vectorStatus" class="mcp-status-cards">
<div class="mcp-status-card"> <div class="mcp-status-card">
<div class="mcp-status-label">{{ t('astroglobe', 'Sync Status') }}</div> <div class="mcp-status-label">
{{ t('astroglobe', 'Sync Status') }}
</div>
<div class="mcp-status-value" :class="'status-' + vectorStatus.status"> <div class="mcp-status-value" :class="'status-' + vectorStatus.status">
{{ vectorStatus.status }} {{ vectorStatus.status }}
</div> </div>
</div> </div>
<div class="mcp-status-card"> <div class="mcp-status-card">
<div class="mcp-status-label">{{ t('astroglobe', 'Indexed Documents') }}</div> <div class="mcp-status-label">
<div class="mcp-status-value">{{ vectorStatus.indexed_documents || 0 }}</div> {{ t('astroglobe', 'Indexed Documents') }}
</div>
<div class="mcp-status-value">
{{ vectorStatus.indexed_documents || 0 }}
</div>
</div> </div>
<div class="mcp-status-card"> <div class="mcp-status-card">
<div class="mcp-status-label">{{ t('astroglobe', 'Pending Documents') }}</div> <div class="mcp-status-label">
<div class="mcp-status-value">{{ vectorStatus.pending_documents || 0 }}</div> {{ t('astroglobe', 'Pending Documents') }}
</div>
<div class="mcp-status-value">
{{ vectorStatus.pending_documents || 0 }}
</div>
</div> </div>
<div v-if="vectorStatus.last_sync_time" class="mcp-status-card"> <div v-if="vectorStatus.last_sync_time" class="mcp-status-card">
<div class="mcp-status-label">{{ t('astroglobe', 'Last Sync') }}</div> <div class="mcp-status-label">
<div class="mcp-status-value">{{ vectorStatus.last_sync_time }}</div> {{ t('astroglobe', 'Last Sync') }}
</div>
<div class="mcp-status-value">
{{ vectorStatus.last_sync_time }}
</div>
</div> </div>
</div> </div>
<NcButton type="secondary" @click="loadVectorStatus" :disabled="statusLoading"> <NcButton type="secondary" :disabled="statusLoading" @click="loadVectorStatus">
<template #icon> <template #icon>
<Refresh :size="20" /> <Refresh :size="20" />
</template> </template>
@@ -308,9 +322,15 @@
<!-- Text Viewer (for non-PDFs) --> <!-- Text Viewer (for non-PDFs) -->
<div v-else class="mcp-text-viewer"> <div v-else class="mcp-text-viewer">
<div v-if="viewerContext.before" class="mcp-context-text">{{ viewerContext.before }}</div> <div v-if="viewerContext.before" class="mcp-context-text">
<div class="mcp-highlighted-chunk">{{ viewerContext.chunk }}</div> {{ viewerContext.before }}
<div v-if="viewerContext.after" class="mcp-context-text">{{ viewerContext.after }}</div> </div>
<div class="mcp-highlighted-chunk">
{{ viewerContext.chunk }}
</div>
<div v-if="viewerContext.after" class="mcp-context-text">
{{ viewerContext.after }}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -337,8 +357,6 @@ import Cog from 'vue-material-design-icons/Cog.vue'
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue' import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
import ChevronUp from 'vue-material-design-icons/ChevronUp.vue' import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'
import Refresh from 'vue-material-design-icons/Refresh.vue' import Refresh from 'vue-material-design-icons/Refresh.vue'
import TextBoxOutline from 'vue-material-design-icons/TextBoxOutline.vue'
import TextBoxRemoveOutline from 'vue-material-design-icons/TextBoxRemoveOutline.vue'
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue' import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
import Eye from 'vue-material-design-icons/Eye.vue' import Eye from 'vue-material-design-icons/Eye.vue'
import Close from 'vue-material-design-icons/Close.vue' import Close from 'vue-material-design-icons/Close.vue'
@@ -354,16 +372,13 @@ import * as pdfjsLib from 'pdfjs-dist'
try { try {
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.mjs', 'pdfjs-dist/build/pdf.worker.mjs',
import.meta.url import.meta.url,
).toString() ).toString()
} catch (e) { } catch (e) {
console.warn('Failed to set PDF.js worker, will use fallback', e) console.warn('Failed to set PDF.js worker, will use fallback', e)
// PDF.js will use fake worker automatically // PDF.js will use fake worker automatically
} }
// App name for translations
const APP_NAME = 'astroglobe'
export default { export default {
name: 'App', name: 'App',
components: { components: {
@@ -385,8 +400,6 @@ export default {
ChevronDown, ChevronDown,
ChevronUp, ChevronUp,
Refresh, Refresh,
TextBoxOutline,
TextBoxRemoveOutline,
OpenInNew, OpenInNew,
Eye, Eye,
Close, Close,
@@ -754,7 +767,7 @@ export default {
closeViewer() { closeViewer() {
this.showViewer = false this.showViewer = false
} },
}, },
} }
</script> </script>
@@ -1177,7 +1190,7 @@ a.mcp-result-title {
.mcp-checkbox-grid { .mcp-checkbox-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.mcp-modal { .mcp-modal {
width: 100%; width: 100%;
height: 100%; height: 100%;
+5 -6
View File
@@ -59,7 +59,7 @@ function initSearchSettingsForm() {
const response = await axios.post( const response = await axios.post(
generateUrl('/apps/astroglobe/api/admin/search-settings'), generateUrl('/apps/astroglobe/api/admin/search-settings'),
data, data,
{ headers: { 'Content-Type': 'application/json' } } { headers: { 'Content-Type': 'application/json' } },
) )
if (response.data.success) { if (response.data.success) {
@@ -91,7 +91,7 @@ async function initWebhookManagement() {
try { try {
// Load webhook presets from API // Load webhook presets from API
const response = await axios.get( const response = await axios.get(
generateUrl('/apps/astroglobe/api/admin/webhooks/presets') generateUrl('/apps/astroglobe/api/admin/webhooks/presets'),
) )
if (!response.data.success) { if (!response.data.success) {
@@ -115,7 +115,7 @@ async function initWebhookManagement() {
* Render webhook preset cards. * Render webhook preset cards.
* *
* @param {HTMLElement} container Container element * @param {HTMLElement} container Container element
* @param {Object} presets Preset configurations * @param {object} presets Preset configurations
*/ */
function renderWebhookPresets(container, presets) { function renderWebhookPresets(container, presets) {
const presetIds = Object.keys(presets) const presetIds = Object.keys(presets)
@@ -147,7 +147,7 @@ function renderWebhookPresets(container, presets) {
* Create a webhook preset card. * Create a webhook preset card.
* *
* @param {string} presetId Preset ID * @param {string} presetId Preset ID
* @param {Object} preset Preset configuration * @param {object} preset Preset configuration
* @return {HTMLElement} Card element * @return {HTMLElement} Card element
*/ */
function createPresetCard(presetId, preset) { function createPresetCard(presetId, preset) {
@@ -212,7 +212,6 @@ async function togglePreset(presetId, currentlyEnabled) {
} }
// Reload presets to update UI // Reload presets to update UI
const container = document.getElementById('webhook-presets-container')
await initWebhookManagement() await initWebhookManagement()
// Show success notification // Show success notification
@@ -227,7 +226,7 @@ async function togglePreset(presetId, currentlyEnabled) {
// Show error notification // Show error notification
OC.Notification.showTemporary( OC.Notification.showTemporary(
error.message || 'Failed to toggle webhook preset', error.message || 'Failed to toggle webhook preset',
{ type: 'error' } { type: 'error' },
) )
} }
} }
+3 -3
View File
@@ -8,8 +8,8 @@
<AlertCircle :size="48" /> <AlertCircle :size="48" />
<p>{{ error }}</p> <p>{{ error }}</p>
</div> </div>
<div v-else class="pdf-canvas-container" ref="container"> <div v-else ref="container" class="pdf-canvas-container">
<canvas ref="canvas"></canvas> <canvas ref="canvas" />
</div> </div>
<div v-if="!loading && !error && totalPages > 0" class="pdf-controls"> <div v-if="!loading && !error && totalPages > 0" class="pdf-controls">
<NcButton <NcButton
@@ -178,7 +178,7 @@ export default {
// Render page to canvas // Render page to canvas
const renderContext = { const renderContext = {
canvasContext: context, canvasContext: context,
viewport: viewport, viewport,
} }
await page.render(renderContext).promise await page.render(renderContext).promise
+1 -1
View File
@@ -1,3 +1,3 @@
module.exports = { module.exports = {
extends: 'stylelint-config-recommended-vue', extends: '@nextcloud/stylelint-config',
} }
+22 -12
View File
@@ -113,9 +113,9 @@ style('astroglobe', 'astroglobe-settings');
<?php if (isset($_['serverStatus']['uptime_seconds'])): ?> <?php if (isset($_['serverStatus']['uptime_seconds'])): ?>
<?php <?php
$uptime = $_['serverStatus']['uptime_seconds']; $uptime = $_['serverStatus']['uptime_seconds'];
$hours = floor($uptime / 3600); $hours = floor($uptime / 3600);
$minutes = floor(($uptime % 3600) / 60); $minutes = floor(($uptime % 3600) / 60);
p(sprintf('%d hours, %d minutes', $hours, $minutes)); p(sprintf('%d hours, %d minutes', $hours, $minutes));
?> ?>
<?php else: ?> <?php else: ?>
<?php p($l->t('Unknown')); ?> <?php p($l->t('Unknown')); ?>
@@ -150,8 +150,8 @@ style('astroglobe', 'astroglobe-settings');
<td> <td>
<?php <?php
$status = $_['vectorSyncStatus']['status'] ?? 'unknown'; $status = $_['vectorSyncStatus']['status'] ?? 'unknown';
$statusClass = $status === 'idle' ? 'success' : ($status === 'syncing' ? 'info' : 'neutral'); $statusClass = $status === 'idle' ? 'success' : ($status === 'syncing' ? 'info' : 'neutral');
?> ?>
<span class="badge badge-<?php p($statusClass); ?>"> <span class="badge badge-<?php p($statusClass); ?>">
<?php p(ucfirst($status)); ?> <?php p(ucfirst($status)); ?>
</span> </span>
@@ -177,8 +177,8 @@ style('astroglobe', 'astroglobe-settings');
<td><strong><?php p($l->t('Errors (24h)')); ?></strong></td> <td><strong><?php p($l->t('Errors (24h)')); ?></strong></td>
<td> <td>
<?php <?php
$errors = $_['vectorSyncStatus']['errors_24h'] ?? 0; $errors = $_['vectorSyncStatus']['errors_24h'] ?? 0;
if ($errors > 0): ?> if ($errors > 0): ?>
<span class="error"><?php p($errors); ?></span> <span class="error"><?php p($errors); ?></span>
<?php else: ?> <?php else: ?>
<?php p('0'); ?> <?php p('0'); ?>
@@ -213,13 +213,19 @@ style('astroglobe', 'astroglobe-settings');
<div class="mcp-form-group"> <div class="mcp-form-group">
<label for="search-algorithm"><?php p($l->t('Search Algorithm')); ?></label> <label for="search-algorithm"><?php p($l->t('Search Algorithm')); ?></label>
<select id="search-algorithm" name="algorithm" class="mcp-select"> <select id="search-algorithm" name="algorithm" class="mcp-select">
<option value="hybrid" <?php if ($_['searchSettings']['algorithm'] === 'hybrid') echo 'selected'; ?>> <option value="hybrid" <?php if ($_['searchSettings']['algorithm'] === 'hybrid') {
echo 'selected';
} ?>>
<?php p($l->t('Hybrid (Recommended)')); ?> <?php p($l->t('Hybrid (Recommended)')); ?>
</option> </option>
<option value="semantic" <?php if ($_['searchSettings']['algorithm'] === 'semantic') echo 'selected'; ?>> <option value="semantic" <?php if ($_['searchSettings']['algorithm'] === 'semantic') {
echo 'selected';
} ?>>
<?php p($l->t('Semantic Only')); ?> <?php p($l->t('Semantic Only')); ?>
</option> </option>
<option value="bm25" <?php if ($_['searchSettings']['algorithm'] === 'bm25') echo 'selected'; ?>> <option value="bm25" <?php if ($_['searchSettings']['algorithm'] === 'bm25') {
echo 'selected';
} ?>>
<?php p($l->t('Keyword (BM25) Only')); ?> <?php p($l->t('Keyword (BM25) Only')); ?>
</option> </option>
</select> </select>
@@ -231,10 +237,14 @@ style('astroglobe', 'astroglobe-settings');
<div class="mcp-form-group"> <div class="mcp-form-group">
<label for="search-fusion"><?php p($l->t('Fusion Method')); ?></label> <label for="search-fusion"><?php p($l->t('Fusion Method')); ?></label>
<select id="search-fusion" name="fusion" class="mcp-select"> <select id="search-fusion" name="fusion" class="mcp-select">
<option value="rrf" <?php if ($_['searchSettings']['fusion'] === 'rrf') echo 'selected'; ?>> <option value="rrf" <?php if ($_['searchSettings']['fusion'] === 'rrf') {
echo 'selected';
} ?>>
<?php p($l->t('RRF - Reciprocal Rank Fusion (Recommended)')); ?> <?php p($l->t('RRF - Reciprocal Rank Fusion (Recommended)')); ?>
</option> </option>
<option value="dbsf" <?php if ($_['searchSettings']['fusion'] === 'dbsf') echo 'selected'; ?>> <option value="dbsf" <?php if ($_['searchSettings']['fusion'] === 'dbsf') {
echo 'selected';
} ?>>
<?php p($l->t('DBSF - Distribution-Based Score Fusion')); ?> <?php p($l->t('DBSF - Distribution-Based Score Fusion')); ?>
</option> </option>
</select> </select>