Skip to content

Commit b066758

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 3cbf3cb commit b066758

6 files changed

Lines changed: 649 additions & 0 deletions

File tree

lib/Settings/AdminSettings.php

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

1111
namespace OCA\ServerInfo\Settings;
1212

13+
use OCA\ServerInfo\ActiveConnections;
14+
use OCA\ServerInfo\ActivityRate;
1315
use OCA\ServerInfo\DatabaseStatistics;
1416
use OCA\ServerInfo\FpmStatistics;
17+
use OCA\ServerInfo\LogTailReader;
18+
use OCA\ServerInfo\LoginStats;
1519
use OCA\ServerInfo\Os;
1620
use OCA\ServerInfo\PhpStatistics;
1721
use OCA\ServerInfo\SessionStatistics;
@@ -36,6 +40,10 @@ public function __construct(
3640
private ShareStatistics $shareStatistics,
3741
private SessionStatistics $sessionStatistics,
3842
private SystemStatistics $systemStatistics,
43+
private LogTailReader $logTailReader,
44+
private LoginStats $loginStats,
45+
private ActivityRate $activityRate,
46+
private ActiveConnections $activeConnections,
3947
private IConfig $config,
4048
) {
4149
}
@@ -60,6 +68,10 @@ public function getForm(): TemplateResponse {
6068
'activeUsers' => $this->sessionStatistics->getSessionStatistics(),
6169
'system' => $this->systemStatistics->getSystemStatistics(true, true),
6270
'thermalzones' => $this->os->getThermalZones(),
71+
'logTail' => $this->logTailReader->recentErrors(),
72+
'loginStats' => $this->loginStats->getStats(),
73+
'activityRate' => $this->activityRate->getActivityRate(),
74+
'activeConnections' => $this->activeConnections->getActiveConnections(),
6375
'phpinfo' => $this->config->getAppValue('serverinfo', 'phpinfo', 'no') === 'yes',
6476
'phpinfoUrl' => $this->urlGenerator->linkToRoute('serverinfo.page.phpinfo')
6577
];
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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\IDBConnection;
14+
use OCP\Server;
15+
use Test\TestCase;
16+
17+
/**
18+
* @group DB
19+
*/
20+
class ActiveConnectionsTest extends TestCase {
21+
private IDBConnection $db;
22+
private ActiveConnections $instance;
23+
24+
private array $insertedTokenIds = [];
25+
26+
protected function setUp(): void {
27+
parent::setUp();
28+
$this->db = Server::get(IDBConnection::class);
29+
$this->instance = new ActiveConnections($this->db);
30+
}
31+
32+
protected function tearDown(): void {
33+
$this->cleanUp();
34+
parent::tearDown();
35+
}
36+
37+
private function cleanUp(): void {
38+
if (empty($this->insertedTokenIds)) {
39+
return;
40+
}
41+
$qb = $this->db->getQueryBuilder();
42+
$qb->delete('authtoken')
43+
->where($qb->expr()->in('id', $qb->createNamedParameter($this->insertedTokenIds, IDBConnection::PARAM_INT_ARRAY)));
44+
$qb->executeStatement();
45+
$this->insertedTokenIds = [];
46+
}
47+
48+
private function insertToken(int $lastActivity, int $type = 0): int {
49+
static $uid = 0;
50+
$uid++;
51+
$qb = $this->db->getQueryBuilder();
52+
$qb->insert('authtoken')
53+
->values([
54+
'uid' => $qb->createNamedParameter('testuser' . $uid),
55+
'login_name' => $qb->createNamedParameter('testuser' . $uid),
56+
'password' => $qb->createNamedParameter(''),
57+
'name' => $qb->createNamedParameter('Test token ' . $uid),
58+
'token' => $qb->createNamedParameter(bin2hex(random_bytes(32))),
59+
'type' => $qb->createNamedParameter($type),
60+
'last_activity' => $qb->createNamedParameter($lastActivity),
61+
'last_check' => $qb->createNamedParameter(time()),
62+
]);
63+
$qb->executeStatement();
64+
$id = $this->db->lastInsertId('authtoken');
65+
$this->insertedTokenIds[] = $id;
66+
return (int)$id;
67+
}
68+
69+
public function testReturnShape(): void {
70+
$result = $this->instance->getActiveConnections();
71+
72+
$this->assertArrayHasKey('last5min', $result);
73+
$this->assertArrayHasKey('last1h', $result);
74+
$this->assertArrayHasKey('totalTokens', $result);
75+
$this->assertArrayHasKey('byType', $result);
76+
$this->assertIsInt($result['last5min']);
77+
$this->assertIsInt($result['last1h']);
78+
$this->assertIsInt($result['totalTokens']);
79+
$this->assertIsArray($result['byType']);
80+
}
81+
82+
public function testLast5minCountIncreases(): void {
83+
$baseline = $this->instance->getActiveConnections()['last5min'];
84+
85+
$this->insertToken(time() - 60);
86+
87+
$result = $this->instance->getActiveConnections();
88+
89+
$this->assertSame($baseline + 1, $result['last5min']);
90+
}
91+
92+
public function testLast5minExcludesOldTokens(): void {
93+
$baseline = $this->instance->getActiveConnections()['last5min'];
94+
95+
$this->insertToken(time() - 600);
96+
97+
$result = $this->instance->getActiveConnections();
98+
99+
$this->assertSame($baseline, $result['last5min']);
100+
}
101+
102+
public function testLast1hCountIncreases(): void {
103+
$baseline = $this->instance->getActiveConnections()['last1h'];
104+
105+
$this->insertToken(time() - 1800);
106+
107+
$result = $this->instance->getActiveConnections();
108+
109+
$this->assertSame($baseline + 1, $result['last1h']);
110+
}
111+
112+
public function testLast1hExcludesOldTokens(): void {
113+
$baseline = $this->instance->getActiveConnections()['last1h'];
114+
115+
$this->insertToken(time() - 7200);
116+
117+
$result = $this->instance->getActiveConnections();
118+
119+
$this->assertSame($baseline, $result['last1h']);
120+
}
121+
122+
public function testTotalTokensCountIncreases(): void {
123+
$baseline = $this->instance->getActiveConnections()['totalTokens'];
124+
125+
$this->insertToken(time() - 99999);
126+
127+
$result = $this->instance->getActiveConnections();
128+
129+
$this->assertSame($baseline + 1, $result['totalTokens']);
130+
}
131+
132+
public function testByTypeContainsSessionAndPermanent(): void {
133+
$result = $this->instance->getActiveConnections();
134+
135+
$this->assertArrayHasKey('session', $result['byType']);
136+
$this->assertArrayHasKey('permanent', $result['byType']);
137+
}
138+
139+
public function testByTypeCountsSessionTokens(): void {
140+
$baseline = $this->instance->getActiveConnections()['byType']['session'] ?? 0;
141+
142+
$this->insertToken(time() - 60, type: 0);
143+
144+
$result = $this->instance->getActiveConnections();
145+
146+
$this->assertSame($baseline + 1, $result['byType']['session']);
147+
}
148+
149+
public function testByTypeCountsPermanentTokens(): void {
150+
$baseline = $this->instance->getActiveConnections()['byType']['permanent'] ?? 0;
151+
152+
$this->insertToken(time() - 60, type: 1);
153+
154+
$result = $this->instance->getActiveConnections();
155+
156+
$this->assertSame($baseline + 1, $result['byType']['permanent']);
157+
}
158+
159+
public function testLast5minIsSubsetOfLast1h(): void {
160+
$result = $this->instance->getActiveConnections();
161+
162+
$this->assertLessThanOrEqual($result['last1h'], $result['last5min']);
163+
}
164+
165+
public function testLast1hIsSubsetOfTotalTokens(): void {
166+
$result = $this->instance->getActiveConnections();
167+
168+
$this->assertLessThanOrEqual($result['totalTokens'], $result['last1h']);
169+
}
170+
}

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)