Skip to content

Commit 28626fa

Browse files
feat(settings): wire log and login/session services into admin settings
Injects LogTailReader, LoginStats, ActivityRate, and ActiveConnections — added in #982 — into AdminSettings and passes their data to the template params, making the services active instead of dead code. Extracted from #977. Co-Authored-By: Frank Karlitschek <karlitschek@users.noreply.github.com> AI-assisted: Claude Code (claude-sonnet-4-6) Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
1 parent 5fbd0c4 commit 28626fa

6 files changed

Lines changed: 650 additions & 0 deletions

File tree

lib/Settings/AdminSettings.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010

1111
namespace OCA\ServerInfo\Settings;
1212

13+
use OCA\ServerInfo\ActiveConnections;
14+
use OCA\ServerInfo\ActivityRate;
1315
use OCA\ServerInfo\CronInfo;
1416
use OCA\ServerInfo\DatabaseStatistics;
1517
use OCA\ServerInfo\FpmStatistics;
1618
use OCA\ServerInfo\JobQueueInfo;
19+
use OCA\ServerInfo\LoginStats;
20+
use OCA\ServerInfo\LogTailReader;
1721
use OCA\ServerInfo\Os;
1822
use OCA\ServerInfo\PhpStatistics;
1923
use OCA\ServerInfo\SessionStatistics;
@@ -42,6 +46,10 @@ public function __construct(
4246
private CronInfo $cronInfo,
4347
private JobQueueInfo $jobQueueInfo,
4448
private SlowestJobs $slowestJobs,
49+
private LogTailReader $logTailReader,
50+
private LoginStats $loginStats,
51+
private ActivityRate $activityRate,
52+
private ActiveConnections $activeConnections,
4553
private IConfig $config,
4654
) {
4755
}
@@ -69,6 +77,10 @@ public function getForm(): TemplateResponse {
6977
'cron' => $this->cronInfo->getCronInfo(),
7078
'jobQueue' => $this->jobQueueInfo->getJobQueueInfo(),
7179
'slowestJobs' => $this->slowestJobs->getSlowestJobs(),
80+
'logTail' => $this->logTailReader->recentErrors(),
81+
'loginStats' => $this->loginStats->getStats(),
82+
'activityRate' => $this->activityRate->getActivityRate(),
83+
'activeConnections' => $this->activeConnections->getActiveConnections(),
7284
'phpinfo' => $this->config->getAppValue('serverinfo', 'phpinfo', 'no') === 'yes',
7385
'phpinfoUrl' => $this->urlGenerator->linkToRoute('serverinfo.page.phpinfo')
7486
];
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\ServerInfo\Tests;
11+
12+
use OCA\ServerInfo\ActiveConnections;
13+
use OCP\DB\QueryBuilder\IQueryBuilder;
14+
use OCP\IDBConnection;
15+
use OCP\Server;
16+
use Test\TestCase;
17+
18+
/**
19+
* @group DB
20+
*/
21+
class ActiveConnectionsTest extends TestCase {
22+
private IDBConnection $db;
23+
private ActiveConnections $instance;
24+
25+
private array $insertedTokenIds = [];
26+
27+
protected function setUp(): void {
28+
parent::setUp();
29+
$this->db = Server::get(IDBConnection::class);
30+
$this->instance = new ActiveConnections($this->db);
31+
}
32+
33+
protected function tearDown(): void {
34+
$this->cleanUp();
35+
parent::tearDown();
36+
}
37+
38+
private function cleanUp(): void {
39+
if (empty($this->insertedTokenIds)) {
40+
return;
41+
}
42+
$qb = $this->db->getQueryBuilder();
43+
$qb->delete('authtoken')
44+
->where($qb->expr()->in('id', $qb->createNamedParameter($this->insertedTokenIds, IQueryBuilder::PARAM_INT_ARRAY)));
45+
$qb->executeStatement();
46+
$this->insertedTokenIds = [];
47+
}
48+
49+
private function insertToken(int $lastActivity, int $type = 0): int {
50+
static $uid = 0;
51+
$uid++;
52+
$qb = $this->db->getQueryBuilder();
53+
$qb->insert('authtoken')
54+
->values([
55+
'uid' => $qb->createNamedParameter('testuser' . $uid),
56+
'login_name' => $qb->createNamedParameter('testuser' . $uid),
57+
'password' => $qb->createNamedParameter(''),
58+
'name' => $qb->createNamedParameter('Test token ' . $uid),
59+
'token' => $qb->createNamedParameter(bin2hex(random_bytes(32))),
60+
'type' => $qb->createNamedParameter($type),
61+
'last_activity' => $qb->createNamedParameter($lastActivity),
62+
'last_check' => $qb->createNamedParameter(time()),
63+
]);
64+
$qb->executeStatement();
65+
$id = $this->db->lastInsertId('authtoken');
66+
$this->insertedTokenIds[] = $id;
67+
return (int)$id;
68+
}
69+
70+
public function testReturnShape(): void {
71+
$result = $this->instance->getActiveConnections();
72+
73+
$this->assertArrayHasKey('last5min', $result);
74+
$this->assertArrayHasKey('last1h', $result);
75+
$this->assertArrayHasKey('totalTokens', $result);
76+
$this->assertArrayHasKey('byType', $result);
77+
$this->assertIsInt($result['last5min']);
78+
$this->assertIsInt($result['last1h']);
79+
$this->assertIsInt($result['totalTokens']);
80+
$this->assertIsArray($result['byType']);
81+
}
82+
83+
public function testLast5minCountIncreases(): void {
84+
$baseline = $this->instance->getActiveConnections()['last5min'];
85+
86+
$this->insertToken(time() - 60);
87+
88+
$result = $this->instance->getActiveConnections();
89+
90+
$this->assertSame($baseline + 1, $result['last5min']);
91+
}
92+
93+
public function testLast5minExcludesOldTokens(): void {
94+
$baseline = $this->instance->getActiveConnections()['last5min'];
95+
96+
$this->insertToken(time() - 600);
97+
98+
$result = $this->instance->getActiveConnections();
99+
100+
$this->assertSame($baseline, $result['last5min']);
101+
}
102+
103+
public function testLast1hCountIncreases(): void {
104+
$baseline = $this->instance->getActiveConnections()['last1h'];
105+
106+
$this->insertToken(time() - 1800);
107+
108+
$result = $this->instance->getActiveConnections();
109+
110+
$this->assertSame($baseline + 1, $result['last1h']);
111+
}
112+
113+
public function testLast1hExcludesOldTokens(): void {
114+
$baseline = $this->instance->getActiveConnections()['last1h'];
115+
116+
$this->insertToken(time() - 7200);
117+
118+
$result = $this->instance->getActiveConnections();
119+
120+
$this->assertSame($baseline, $result['last1h']);
121+
}
122+
123+
public function testTotalTokensCountIncreases(): void {
124+
$baseline = $this->instance->getActiveConnections()['totalTokens'];
125+
126+
$this->insertToken(time() - 99999);
127+
128+
$result = $this->instance->getActiveConnections();
129+
130+
$this->assertSame($baseline + 1, $result['totalTokens']);
131+
}
132+
133+
public function testByTypeContainsSessionAndPermanent(): void {
134+
$result = $this->instance->getActiveConnections();
135+
136+
$this->assertArrayHasKey('session', $result['byType']);
137+
$this->assertArrayHasKey('permanent', $result['byType']);
138+
}
139+
140+
public function testByTypeCountsSessionTokens(): void {
141+
$baseline = $this->instance->getActiveConnections()['byType']['session'] ?? 0;
142+
143+
$this->insertToken(time() - 60, type: 0);
144+
145+
$result = $this->instance->getActiveConnections();
146+
147+
$this->assertSame($baseline + 1, $result['byType']['session']);
148+
}
149+
150+
public function testByTypeCountsPermanentTokens(): void {
151+
$baseline = $this->instance->getActiveConnections()['byType']['permanent'] ?? 0;
152+
153+
$this->insertToken(time() - 60, type: 1);
154+
155+
$result = $this->instance->getActiveConnections();
156+
157+
$this->assertSame($baseline + 1, $result['byType']['permanent']);
158+
}
159+
160+
public function testLast5minIsSubsetOfLast1h(): void {
161+
$result = $this->instance->getActiveConnections();
162+
163+
$this->assertLessThanOrEqual($result['last1h'], $result['last5min']);
164+
}
165+
166+
public function testLast1hIsSubsetOfTotalTokens(): void {
167+
$result = $this->instance->getActiveConnections();
168+
169+
$this->assertLessThanOrEqual($result['totalTokens'], $result['last1h']);
170+
}
171+
}

tests/lib/ActivityRateTest.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\ServerInfo\Tests;
11+
12+
use OCA\ServerInfo\ActivityRate;
13+
use OCP\App\IAppManager;
14+
use OCP\IDBConnection;
15+
use OCP\Server;
16+
use PHPUnit\Framework\MockObject\MockObject;
17+
use Test\TestCase;
18+
19+
/**
20+
* @group DB
21+
*/
22+
class ActivityRateTest extends TestCase {
23+
private IDBConnection $db;
24+
private IAppManager&MockObject $appManager;
25+
private ActivityRate $instance;
26+
27+
protected function setUp(): void {
28+
parent::setUp();
29+
$this->db = Server::get(IDBConnection::class);
30+
$this->appManager = $this->createMock(IAppManager::class);
31+
$this->instance = new ActivityRate($this->appManager, $this->db);
32+
}
33+
34+
private function activityTableExists(): bool {
35+
try {
36+
$qb = $this->db->getQueryBuilder();
37+
$qb->select('activity_id')->from('activity')->setMaxResults(1);
38+
$qb->executeQuery()->closeCursor();
39+
return true;
40+
} catch (\Throwable) {
41+
return false;
42+
}
43+
}
44+
45+
public function testNotInstalledReturnsInstalledFalse(): void {
46+
$this->appManager->method('isInstalled')->with('activity')->willReturn(false);
47+
48+
$result = $this->instance->getActivityRate();
49+
50+
$this->assertFalse($result['installed']);
51+
$this->assertSame(0, $result['last1h']);
52+
$this->assertSame(0, $result['last24h']);
53+
$this->assertSame(0, $result['last7d']);
54+
$this->assertSame([], $result['topActions']);
55+
}
56+
57+
public function testReturnShape(): void {
58+
$this->appManager->method('isInstalled')->with('activity')->willReturn(true);
59+
60+
$result = $this->instance->getActivityRate();
61+
62+
$this->assertArrayHasKey('installed', $result);
63+
$this->assertArrayHasKey('last1h', $result);
64+
$this->assertArrayHasKey('last24h', $result);
65+
$this->assertArrayHasKey('last7d', $result);
66+
$this->assertArrayHasKey('topActions', $result);
67+
$this->assertTrue($result['installed']);
68+
$this->assertIsInt($result['last1h']);
69+
$this->assertIsInt($result['last24h']);
70+
$this->assertIsInt($result['last7d']);
71+
$this->assertIsArray($result['topActions']);
72+
}
73+
74+
public function testCountsAreNonNegative(): void {
75+
$this->appManager->method('isInstalled')->with('activity')->willReturn(true);
76+
77+
$result = $this->instance->getActivityRate();
78+
79+
$this->assertGreaterThanOrEqual(0, $result['last1h']);
80+
$this->assertGreaterThanOrEqual(0, $result['last24h']);
81+
$this->assertGreaterThanOrEqual(0, $result['last7d']);
82+
}
83+
84+
public function testHierarchyLast1hLeqlast24hLeqlast7d(): void {
85+
$this->appManager->method('isInstalled')->with('activity')->willReturn(true);
86+
87+
$result = $this->instance->getActivityRate();
88+
89+
$this->assertLessThanOrEqual($result['last24h'], $result['last1h']);
90+
$this->assertLessThanOrEqual($result['last7d'], $result['last24h']);
91+
}
92+
93+
public function testTopActionsShape(): void {
94+
$this->appManager->method('isInstalled')->with('activity')->willReturn(true);
95+
96+
$result = $this->instance->getActivityRate();
97+
98+
foreach ($result['topActions'] as $entry) {
99+
$this->assertArrayHasKey('action', $entry);
100+
$this->assertArrayHasKey('count', $entry);
101+
$this->assertIsString($entry['action']);
102+
$this->assertIsInt($entry['count']);
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)