Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

use Spatie\LaravelSettings\Migrations\SettingsMigration;

return new class extends SettingsMigration
{
public function up(): void
{
rescue(fn () => $this->migrator->add('app.show_component_group_status', true));
}
};
2 changes: 2 additions & 0 deletions resources/lang/de/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
'api_protected_helper' => 'Für alle API-Anfragen einen API-Token verlangen, auch für rein lesende Endpunkte.',
'dynamic_favicon' => 'Dynamisches Favicon',
'dynamic_favicon_helper' => 'Das Favicon aktualisieren, damit es den aktuellen Status deiner Systeme widerspiegelt.',
'show_component_group_status' => 'Status der Komponentengruppe anzeigen',
'show_component_group_status_helper' => 'Den schwerwiegendsten Komponentenstatus jeder Gruppe auf der öffentlichen Statusseite anzeigen.',
],
'display_settings_title' => 'Anzeigeeinstellungen',
'api_settings_title' => 'API-Einstellungen',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/de_AT/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
'api_protected_helper' => 'Für alle API-Anfragen einen API-Token verlangen, auch für rein lesende Endpunkte.',
'dynamic_favicon' => 'Dynamisches Favicon',
'dynamic_favicon_helper' => 'Das Favicon aktualisieren, damit es den aktuellen Status deiner Systeme widerspiegelt.',
'show_component_group_status' => 'Status der Komponentengruppe anzeigen',
'show_component_group_status_helper' => 'Den schwerwiegendsten Komponentenstatus jeder Gruppe auf der öffentlichen Statusseite anzeigen.',
],
'display_settings_title' => 'Anzeigeeinstellungen',
'api_settings_title' => 'API-Einstellungen',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/de_CH/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
'api_protected_helper' => 'Für alle API-Anfragen einen API-Token verlangen, auch für rein lesende Endpunkte.',
'dynamic_favicon' => 'Dynamisches Favicon',
'dynamic_favicon_helper' => 'Das Favicon aktualisieren, damit es den aktuellen Status deiner Systeme widerspiegelt.',
'show_component_group_status' => 'Status der Komponentengruppe anzeigen',
'show_component_group_status_helper' => 'Den schwerwiegendsten Komponentenstatus jeder Gruppe auf der öffentlichen Statusseite anzeigen.',
],
'display_settings_title' => 'Anzeigeeinstellungen',
'api_settings_title' => 'API-Einstellungen',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/en/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
'mcp_protected_helper' => 'Require an API token for all MCP connections. When off, read-only tools are public and write tools still require an API token with the matching ability.',
'dynamic_favicon' => 'Dynamic favicon',
'dynamic_favicon_helper' => 'Update the favicon to reflect the current status of your systems.',
'show_component_group_status' => 'Show component group status',
'show_component_group_status_helper' => 'Display each group’s most severe component status on the public status page.',
],
'display_settings_title' => 'Display settings',
'api_settings_title' => 'API settings',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/en_GB/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
'show_site_name_helper' => 'Display the site name on the public status page.',
'show_about' => 'Show About this site',
'show_about_helper' => 'Display the About this site content on the public status page.',
'show_component_group_status' => 'Show component group status',
'show_component_group_status_helper' => 'Display each group’s most severe component status on the public status page.',
],
],
'manage_theme' => [
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/es_ES/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
'api_protected_helper' => 'Exigir un token de API para todas las peticiones, incluidos los endpoints de solo lectura.',
'dynamic_favicon' => 'Favicon Dinámico',
'dynamic_favicon_helper' => 'Actualiza el favicon para reflejar el estado actual de tus sistemas.',
'show_component_group_status' => 'Mostrar el estado del grupo de componentes',
'show_component_group_status_helper' => 'Muestra el estado más grave de los componentes de cada grupo en la página de estado pública.',
],
'display_settings_title' => 'Configuración de visualización',
'api_settings_title' => 'Configuración de la API',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/fr/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
'api_protected_helper' => "Exiger un jeton d\u{2019}API pour toutes les requ\u{00EA}tes, y compris les points de terminaison en lecture seule.",
'dynamic_favicon' => 'Favicon dynamique',
'dynamic_favicon_helper' => "Met \u{00E0} jour le favicon pour refl\u{00E9}ter l\u{2019}\u{00E9}tat actuel de vos syst\u{00E8}mes.",
'show_component_group_status' => 'Afficher l’état du groupe de composants',
'show_component_group_status_helper' => 'Affiche l’état le plus grave des composants de chaque groupe sur la page d’état publique.',
],
'display_settings_title' => "Param\u{00E8}tres d\u{2019}affichage",
'api_settings_title' => "Param\u{00E8}tres de l\u{2019}API",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/ko/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
'api_protected_helper' => '읽기 전용 엔드포인트를 포함한 모든 API 요청에 API 토큰을 요구합니다.',
'dynamic_favicon' => '동적 파비콘',
'dynamic_favicon_helper' => '시스템의 현재 상태를 반영하도록 파비콘을 업데이트합니다.',
'show_component_group_status' => '구성 요소 그룹 상태 표시',
'show_component_group_status_helper' => '공개 상태 페이지에 각 그룹에서 가장 심각한 구성 요소 상태를 표시합니다.',
],
'display_settings_title' => '표시 설정',
'api_settings_title' => 'API 설정',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/nl/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
'api_protected_helper' => 'Vereis een API-token voor alle API-verzoeken, inclusief alleen-lezen endpoints.',
'dynamic_favicon' => 'Dynamische favicon',
'dynamic_favicon_helper' => 'Werk de favicon bij zodat deze de huidige status van je systemen weergeeft.',
'show_component_group_status' => 'Status van componentgroep weergeven',
'show_component_group_status_helper' => 'Toon de ernstigste componentstatus van elke groep op de openbare statuspagina.',
],
'display_settings_title' => 'Weergave-instellingen',
'api_settings_title' => 'API-instellingen',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/ph/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
'api_protected_helper' => 'Mangailangan ng API token para sa lahat ng API request, kabilang ang mga read-only na endpoint.',
'dynamic_favicon' => 'Dynamic na Favicon',
'dynamic_favicon_helper' => 'I-update ang favicon upang ipakita ang kasalukuyang status ng iyong mga sistema.',
'show_component_group_status' => 'Ipakita ang status ng pangkat ng component',
'show_component_group_status_helper' => 'Ipakita ang pinakamalubhang status ng component sa bawat pangkat sa pampublikong pahina ng status.',
],
'display_settings_title' => 'Mga Setting ng Display',
'api_settings_title' => 'Mga Setting ng API',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/pt_BR/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
'api_protected_helper' => 'Exigir um token de API para todas as requisições, incluindo endpoints somente leitura.',
'dynamic_favicon' => 'Favicon Dinâmico',
'dynamic_favicon_helper' => 'Atualiza o favicon para refletir o status atual dos seus sistemas.',
'show_component_group_status' => 'Exibir status do grupo de componentes',
'show_component_group_status_helper' => 'Exibe o status mais grave dos componentes de cada grupo na página de status pública.',
],
'display_settings_title' => 'Configurações de Exibição',
'api_settings_title' => 'Configurações da API',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/zh_CN/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
'api_protected_helper' => '所有 API 请求(包括只读接口)都需要 API 令牌。',
'dynamic_favicon' => '动态网站图标',
'dynamic_favicon_helper' => '根据系统当前状态更新网站图标。',
'show_component_group_status' => '显示组件组状态',
'show_component_group_status_helper' => '在公开状态页面上显示每个组中最严重的组件状态。',
],
'display_settings_title' => '显示设置',
'api_settings_title' => 'API 设置',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/zh_TW/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
'api_protected_helper' => '所有 API 請求(包括唯讀端點)都需要 API 權杖。',
'dynamic_favicon' => '動態網站圖示',
'dynamic_favicon_helper' => '根據系統目前狀態更新網站圖示。',
'show_component_group_status' => '顯示元件群組狀態',
'show_component_group_status_helper' => '在公開狀態頁面上顯示每個群組中最嚴重的元件狀態。',
],
'display_settings_title' => '顯示設定',
'api_settings_title' => 'API 設定',
Expand Down
15 changes: 11 additions & 4 deletions resources/views/components/component-group.blade.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@props(['componentGroup' => null])

{{ \Cachet\Facades\CachetView::renderHook(\Cachet\View\RenderHook::STATUS_PAGE_COMPONENT_GROUPS_BEFORE) }}
@php($groupStatus = $componentGroup->worstComponentStatus())
@php($showComponentGroupStatus = app(\Cachet\Settings\AppSettings::class)->show_component_group_status)
<li x-data x-disclosure @if ($componentGroup->isExpanded(auth()->user())) default-open @endif>
<button x-disclosure:button class="relative flex w-full flex-col items-start gap-2 py-4 pl-6 pr-4 text-left transition hover:bg-zinc-50/60 dark:hover:bg-white/[0.02] sm:flex-row sm:items-center sm:justify-between sm:gap-3 sm:py-5 sm:pl-6 sm:pr-6">
<span class="absolute left-2 top-7 -translate-y-1/2 text-zinc-400 dark:text-zinc-500 sm:top-1/2">
Expand All @@ -12,9 +12,16 @@
{{ $componentGroup->name }}
</h2>

<span class="shrink-0 text-sm font-semibold tracking-tight {{ $groupStatus->getTextColorClasses() }}">
{{ $groupStatus->getLabel() }}
</span>
@if ($showComponentGroupStatus)
@php($groupStatus = $componentGroup->worstComponentStatus())
<span class="shrink-0 text-sm font-semibold tracking-tight {{ $groupStatus->getTextColorClasses() }}">
{{ $groupStatus->getLabel() }}
</span>
@else
<span class="shrink-0 text-sm font-semibold tracking-tight text-zinc-600 dark:text-zinc-300">
{{ trans_choice('cachet::component_group.incident_count', $componentGroup->openIncidentCount()) }}
</span>
@endif
</button>

<div x-disclosure:panel x-collapse class="border-t border-zinc-900/10 dark:border-white/15">
Expand Down
3 changes: 3 additions & 0 deletions src/Filament/Pages/Settings/ManageCachet.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ public function form(Schema $schema): Schema
Toggle::make('dynamic_favicon')
->label(__('cachet::settings.manage_cachet.toggles.dynamic_favicon'))
->helperText(__('cachet::settings.manage_cachet.toggles.dynamic_favicon_helper')),
Toggle::make('show_component_group_status')
->label(__('cachet::settings.manage_cachet.toggles.show_component_group_status'))
->helperText(__('cachet::settings.manage_cachet.toggles.show_component_group_status_helper')),
]),

Section::make(__('cachet::settings.manage_cachet.api_settings_title'))
Expand Down
13 changes: 13 additions & 0 deletions src/Models/ComponentGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ public function worstComponentStatus(): ComponentStatusEnum
->first() ?? ComponentStatusEnum::operational;
}

/**
* Count the distinct unresolved incidents affecting this group.
*/
public function openIncidentCount(): int
{
return $this->components
->flatMap(fn (Component $component) => $component->relationLoaded('unresolvedIncidents')
? $component->unresolvedIncidents
: $component->unresolvedIncidents()->get())
->unique('id')
->count();
}

/**
* Determine whether an incident the given viewer can see is affecting the group.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Settings/AppSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class AppSettings extends Settings

public bool $dynamic_favicon = false;

public bool $show_component_group_status = true;

public static function group(): string
{
return 'app';
Expand Down
11 changes: 11 additions & 0 deletions tests/Feature/Filament/Settings/ManageCachetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@
expect(app(AppSettings::class)->refresh()->dynamic_favicon)->toBeTrue();
});

it('saves the component group status visibility setting', function () {
expect(app(AppSettings::class)->show_component_group_status)->toBeTrue();

livewire(ManageCachet::class)
->fillForm(['show_component_group_status' => false])
->call('save')
->assertHasNoFormErrors();

expect(app(AppSettings::class)->refresh()->show_component_group_status)->toBeFalse();
});

it('saves the status page title and about visibility settings', function () {
expect(app(AppSettings::class))
->show_site_name->toBeFalse()
Expand Down
25 changes: 25 additions & 0 deletions tests/Feature/StatusPage/StatusPageTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Cachet\Enums\ComponentStatusEnum;
use Cachet\Enums\IncidentStatusEnum;
use Cachet\Models\Component;
use Cachet\Models\ComponentGroup;
use Cachet\Models\Incident;
Expand Down Expand Up @@ -61,6 +62,30 @@
->toMatch('/<h2[^>]*>\s*Public API\s*<\\/h2>/');
});

it('can hide component group statuses', function () {
$settings = app(AppSettings::class);
$settings->show_component_group_status = false;
$settings->save();

$group = ComponentGroup::factory()->create(['name' => 'Core services']);
$firstComponent = Component::factory()->create([
'component_group_id' => $group->id,
'status' => ComponentStatusEnum::major_outage,
]);
$secondComponent = Component::factory()->create(['component_group_id' => $group->id]);
$incident = Incident::factory()->create(['status' => IncidentStatusEnum::investigating]);
$incident->components()->attach([$firstComponent->id, $secondComponent->id]);

$page = $this->get(route('cachet.status-page'))
->assertOk()
->getContent();

expect($page)
->toContain('Core services')
->toContain('1 Incident')
->not->toMatch('/Core services\s*<\\/h2>\s*<span[^>]*>\s*Major outage\s*<\\/span>/');
});

it('renders the status page in the configured locale', function () {
$settings = app(AppSettings::class);
$settings->locale = 'de';
Expand Down
12 changes: 12 additions & 0 deletions tests/Unit/Models/ComponentGroupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,18 @@
expect($group->worstComponentStatus())->toBe(ComponentStatusEnum::operational);
});

it('counts each unresolved incident once across a group', function () {
$group = ComponentGroup::factory()->hasComponents(2)->create();
$incident = Incident::factory()->create(['status' => IncidentStatusEnum::investigating]);
$incident->components()->attach($group->components->pluck('id'));
$resolvedIncident = Incident::factory()->create(['status' => IncidentStatusEnum::fixed]);
$resolvedIncident->components()->attach($group->components->first());

$group->load('components.unresolvedIncidents');

expect($group->openIncidentCount())->toBe(1);
});

it('has meta', function () {
$group = ComponentGroup::factory()->withMeta()->create();

Expand Down
Loading