diff --git a/backend/src/Modules/Admin/Presentation/Endpoints/AdminEndpoints.cs b/backend/src/Modules/Admin/Presentation/Endpoints/AdminEndpoints.cs index 718fe3f..bf4d351 100644 --- a/backend/src/Modules/Admin/Presentation/Endpoints/AdminEndpoints.cs +++ b/backend/src/Modules/Admin/Presentation/Endpoints/AdminEndpoints.cs @@ -1,3 +1,4 @@ +using System.Text.Json.Serialization; using Knot.Contracts.Settings.Application.Abstractions; using Knot.Contracts.Settings.Application.DTOs; using Knot.Modules.Admin.Application.Admin.Commands; @@ -11,7 +12,9 @@ using Microsoft.AspNetCore.Routing; namespace Knot.Host.Presentation.Endpoints; -public record KlipyTestDto(string ApiKey, string AppName); +public record KlipyTestDto( + [property: JsonPropertyName("apiKey")] string ApiKey, + [property: JsonPropertyName("appName")] string AppName); public record ResetPasswordRequest(string NewPassword); public static class AdminEndpoints @@ -34,7 +37,16 @@ public static class AdminEndpoints group.MapPost("settings/test-klipy", async ([FromBody] KlipyTestDto dto, ISender sender, CancellationToken ct) => { + Console.WriteLine($"[Admin] TestKlipy received: ApiKey={(string.IsNullOrEmpty(dto.ApiKey) ? "EMPTY" : "present")}, AppName={(string.IsNullOrEmpty(dto.AppName) ? "EMPTY" : dto.AppName)}"); + + if (string.IsNullOrEmpty(dto.ApiKey) || string.IsNullOrEmpty(dto.AppName)) + { + Console.WriteLine($"[Admin] TestKlipy: Missing required fields - ApiKey={dto?.ApiKey}, AppName={dto?.AppName}"); + return Results.BadRequest(new { error = "ApiKey and AppName are required" }); + } + var result = await sender.Send(new TestKlipyConnectionCommand(dto.ApiKey, dto.AppName), ct); + Console.WriteLine($"[Admin] TestKlipy result: IsSuccess={result.IsSuccess}, Error={result.Error?.Description}"); return result.IsSuccess ? Results.Ok(new { success = true }) : Results.BadRequest(new { error = result.Error.Description }); }); diff --git a/backend/src/Modules/Klipy/DependencyInjection.cs b/backend/src/Modules/Klipy/DependencyInjection.cs index c6f5e20..78ec79a 100644 --- a/backend/src/Modules/Klipy/DependencyInjection.cs +++ b/backend/src/Modules/Klipy/DependencyInjection.cs @@ -2,11 +2,36 @@ using Microsoft.Extensions.DependencyInjection; using Knot.Modules.Klipy.Application.Abstractions; using Knot.Modules.Klipy.Infrastructure.External; +// Псевдонимы для устранения неоднозначности +using ContractIKlipyClient = Knot.Contracts.Klipy.Application.Abstractions.IKlipyClient; +using ModuleIKlipyClient = Knot.Modules.Klipy.Application.Abstractions.IKlipyClient; + namespace Knot.Modules.Klipy; -public static class DependencyInjection { - public static IServiceCollection AddKlipyModule(this IServiceCollection services) { - services.AddHttpClient(); +public static class DependencyInjection +{ + public static IServiceCollection AddKlipyModule(this IServiceCollection services) + { + services.AddHttpClient(); + // Также регистрируем contract-интерфейс для Admin модуля + services.AddScoped(sp => + new ContractKlipyClientAdapter(sp.GetRequiredService())); return services; } -} \ No newline at end of file +} + +// Адаптер для преобразования между интерфейсами +internal sealed class ContractKlipyClientAdapter : ContractIKlipyClient +{ + private readonly ModuleIKlipyClient _inner; + + public ContractKlipyClientAdapter(ModuleIKlipyClient inner) + { + _inner = inner; + } + + public async Task TestConnectionAsync(string apiKey, string appName, CancellationToken cancellationToken = default) + { + return await _inner.TestConnectionAsync(apiKey, appName, cancellationToken); + } +} diff --git a/backend/src/Modules/Klipy/Infrastructure/External/KlipyClient.cs b/backend/src/Modules/Klipy/Infrastructure/External/KlipyClient.cs index fb7c711..b4cb3b8 100644 --- a/backend/src/Modules/Klipy/Infrastructure/External/KlipyClient.cs +++ b/backend/src/Modules/Klipy/Infrastructure/External/KlipyClient.cs @@ -20,14 +20,21 @@ public sealed class KlipyClient : Knot.Modules.Klipy.Application.Abstractions.IK { try { - var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.klipy.com/api/v1/{appName}/gifs/trending?page=1&per_page=1&customer_id=knot_admin_test"); - request.Headers.Add("X-KLIPY-API-KEY", apiKey); + var url = $"https://api.klipy.com/api/v1/{apiKey}/gifs/trending?page=1&per_page=1&customer_id={appName}&locale=en"; + Console.WriteLine($"[Klipy] TestConnection URL: {url}"); + + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("User-Agent", "KnotMessenger/1.0"); + request.Headers.Add("Accept", "application/json"); using var response = await _httpClient.SendAsync(request, ct); + var responseBody = await response.Content.ReadAsStringAsync(ct); + Console.WriteLine($"[Klipy] TestConnection response: {response.StatusCode}, body: {responseBody}"); return response.IsSuccessStatusCode; } - catch + catch (Exception ex) { + Console.WriteLine($"[Klipy] TestConnection exception: {ex.Message}"); return false; } } diff --git a/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj b/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj index a01475c..56fc1ba 100644 --- a/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj +++ b/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj @@ -8,5 +8,6 @@ + diff --git a/client-web/src/core/infrastructure/httpClient.ts b/client-web/src/core/infrastructure/httpClient.ts index fd35f40..5488759 100644 --- a/client-web/src/core/infrastructure/httpClient.ts +++ b/client-web/src/core/infrastructure/httpClient.ts @@ -15,8 +15,8 @@ export class HttpClient { const isFormData = fetchOptions.body instanceof FormData; const computedHeaders: Record = { - ...(this.token - ? { Authorization: this.token.startsWith('Basic ') ? this.token : `Bearer ${this.token}` } + ...(this.token + ? { Authorization: this.token.startsWith('Basic ') ? this.token : `Bearer ${this.token}` } : {}), ...(fetchOptions.headers as Record), }; @@ -47,7 +47,13 @@ export class HttpClient { throw new Error(errorMessage); } - const jsonData = await response.json(); + // Handle empty responses (e.g., 200 OK with no body) + const text = await response.text(); + if (!text) { + return undefined as T; + } + + const jsonData = JSON.parse(text); return deepNormalize(jsonData); } } diff --git a/client-web/src/modules/admin/presentation/pages/AdminPage.tsx b/client-web/src/modules/admin/presentation/pages/AdminPage.tsx index 06f86ab..5fab256 100644 --- a/client-web/src/modules/admin/presentation/pages/AdminPage.tsx +++ b/client-web/src/modules/admin/presentation/pages/AdminPage.tsx @@ -181,6 +181,9 @@ const translations = { banUser: 'Ban User', unbanUser: 'Unban User', isBanned: 'Banned', + blockUser: 'Block User', + unblockUser: 'Unblock User', + blockedTooltip: 'Blocked', authSecurity: 'Security', bio: 'Bio', messagesSent: 'Messages', @@ -348,6 +351,9 @@ const translations = { banUser: 'Забанить', unbanUser: 'Разбанить', isBanned: 'Забанен', + blockUser: 'Заблокировать', + unblockUser: 'Разблокировать', + blockedTooltip: 'Заблокирован', authSecurity: 'Безопасность', bio: 'О себе', messagesSent: 'Сообщения', @@ -491,7 +497,7 @@ export default function AdminPage() { const formatDateTime = (dateStr: string) => { const d = new Date(dateStr); - const options: Intl.DateTimeFormatOptions = { + const options: Intl.DateTimeFormatOptions = { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }; @@ -541,7 +547,7 @@ export default function AdminPage() { const [tzSearch, setTzSearch] = useState(''); const [showTzDropdown, setShowTzDropdown] = useState(false); - const [toast, setToast] = useState<{message: string, type: 'success' | 'error'} | null>(null); + const [toast, setToast] = useState<{ message: string, type: 'success' | 'error' } | null>(null); const showToast = (message: string, type: 'success' | 'error' = 'success') => { setToast({ message, type }); @@ -551,7 +557,7 @@ export default function AdminPage() { const generatePassword = () => { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()'; let pass = ''; - for(let i=0; i<12; i++) pass += chars[Math.floor(Math.random() * chars.length)]; + for (let i = 0; i < 12; i++) pass += chars[Math.floor(Math.random() * chars.length)]; return pass; }; @@ -625,31 +631,31 @@ export default function AdminPage() { try { const res = await httpClient.request('/admin/dashboard'); setStats(res); - } catch {} + } catch { } }; const fetchSettings = async () => { try { const res = await httpClient.request('/admin/settings'); setConfig(res); - } catch {} + } catch { } }; const fetchTimezones = async () => { try { const res = await httpClient.request('/admin/timezones'); setTimezones(res); - } catch {} + } catch { } }; const saveSettings = async () => { if (!config) return; try { - await httpClient.request('/admin/settings', { - method: 'PUT', - body: JSON.stringify(config) - }); - showToast(t.successSave, 'success'); + await httpClient.request('/admin/settings', { + method: 'PUT', + body: JSON.stringify(config) + }); + showToast(t.successSave, 'success'); } catch { showToast(t.errorSave, 'error'); } }; @@ -659,86 +665,86 @@ export default function AdminPage() { let resolved = false; try { - const turnUrl = `turn:${config.webRtc.turnHost}:${config.webRtc.turnPort}`; - const stunUrl = `stun:${config.webRtc.turnHost}:${config.webRtc.turnPort}`; - - const servers: RTCIceServer[] = [ - { urls: stunUrl } - ]; + const turnUrl = `turn:${config.webRtc.turnHost}:${config.webRtc.turnPort}`; + const stunUrl = `stun:${config.webRtc.turnHost}:${config.webRtc.turnPort}`; - if (config.webRtc.turnUser) { - servers.push({ - urls: [turnUrl, turnUrl + "?transport=tcp"], - username: config.webRtc.turnUser, - credential: config.webRtc.turnSecret || config.webRtc.turnUser - }); - } + const servers: RTCIceServer[] = [ + { urls: stunUrl } + ]; - const pc = new RTCPeerConnection({ iceServers: servers }); - pc.createDataChannel('test'); - const offer = await pc.createOffer(); - await pc.setLocalDescription(offer); - - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - if (!resolved) { resolved = true; reject(new Error("Timeout")); } - }, 10000); - - pc.onicecandidate = (e) => { - if (e.candidate && (e.candidate.type === 'relay' || e.candidate.type === 'srflx')) { - if (!resolved) { resolved = true; clearTimeout(timeout); resolve(); } - } - if (e.candidate === null) { - if (!resolved) { resolved = true; clearTimeout(timeout); reject(new Error("Failed to gather candidates")); } - } - }; + if (config.webRtc.turnUser) { + servers.push({ + urls: [turnUrl, turnUrl + "?transport=tcp"], + username: config.webRtc.turnUser, + credential: config.webRtc.turnSecret || config.webRtc.turnUser }); + } - showToast("Success", 'success'); - pc.close(); + const pc = new RTCPeerConnection({ iceServers: servers }); + pc.createDataChannel('test'); + const offer = await pc.createOffer(); + await pc.setLocalDescription(offer); + + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + if (!resolved) { resolved = true; reject(new Error("Timeout")); } + }, 10000); + + pc.onicecandidate = (e) => { + if (e.candidate && (e.candidate.type === 'relay' || e.candidate.type === 'srflx')) { + if (!resolved) { resolved = true; clearTimeout(timeout); resolve(); } + } + if (e.candidate === null) { + if (!resolved) { resolved = true; clearTimeout(timeout); reject(new Error("Failed to gather candidates")); } + } + }; + }); + + showToast("Success", 'success'); + pc.close(); } catch (err: any) { - showToast("Error: " + err.message, 'error'); + showToast("Error: " + err.message, 'error'); } finally { - setIsTestingTurn(false); + setIsTestingTurn(false); } }; const handleTestKlipy = async () => { if (!config || !config.klipy.apiKey) { - showToast("API Key is required", 'error'); - return; + showToast("API Key is required", 'error'); + return; } - + setIsTestingKlipy(true); try { - await httpClient.request('/admin/settings/test-klipy', { - method: 'POST', - body: JSON.stringify({ - apiKey: config.klipy.apiKey, - appName: config.klipy.appName - }) - }); - showToast("Success", 'success'); + await httpClient.request('/admin/settings/test-klipy', { + method: 'POST', + body: JSON.stringify({ + apiKey: config.klipy.apiKey, + appName: config.klipy.appName + }) + }); + showToast("Success", 'success'); } catch { - showToast("Failed", 'error'); + showToast("Failed", 'error'); } finally { - setIsTestingKlipy(false); + setIsTestingKlipy(false); } }; const handleCalcCleanup = async () => { try { - const res = await httpClient.request('/admin/clean/dry-run'); - setCleanStats(res); + const res = await httpClient.request('/admin/clean/dry-run'); + setCleanStats(res); } catch { showToast(t.errorSave, 'error'); } }; const handleRunCleanup = async () => { try { - await httpClient.request('/admin/clean/run', { method: 'POST' }); - showToast(t.successSave, 'success'); - setCleanStats(null); - fetchDashboard(); + await httpClient.request('/admin/clean/run', { method: 'POST' }); + showToast(t.successSave, 'success'); + setCleanStats(null); + fetchDashboard(); } catch { showToast(t.errorSave, 'error'); } }; @@ -774,12 +780,12 @@ export default function AdminPage() { if (!file || !config) return; const reader = new FileReader(); reader.onload = (ev) => { - try { - const data = JSON.parse(ev.target?.result as string); - if (Array.isArray(data)) { - setConfig({ ...config, federation: { ...config.federation, allowedDomains: data } }); - } - } catch { showToast("Invalid JSON", 'error'); } + try { + const data = JSON.parse(ev.target?.result as string); + if (Array.isArray(data)) { + setConfig({ ...config, federation: { ...config.federation, allowedDomains: data } }); + } + } catch { showToast("Invalid JSON", 'error'); } }; reader.readAsText(file); }; @@ -789,7 +795,7 @@ export default function AdminPage() { try { const res = await httpClient.request(`/admin/users?query=${encodeURIComponent(q)}`); setUsers(res); - } catch {} finally { + } catch { } finally { setIsSearching(false); } }; @@ -798,14 +804,14 @@ export default function AdminPage() { try { const res = await httpClient.request(`/admin/users/${id}`); setSelectedUser(res); - } catch {} + } catch { } }; const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); const header = `Basic ${btoa(`${creds.user}:${creds.pass}`)}`; httpClient.setToken(header); - + try { await httpClient.request('/admin/dashboard'); setAuthHeader(header); @@ -837,8 +843,8 @@ export default function AdminPage() {

{t.loginTitle}

- - + +
Knot Control - + } label={t.dashboard} active={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} /> } label={t.userAnalytics} active={activeTab === 'users'} onClick={() => { setActiveTab('users'); setSelectedUser(null); }} /> @@ -886,7 +892,7 @@ export default function AdminPage() { } label={t.federation} active={activeTab === 'settings' && activeSettingsTab === 'federation'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('federation'); }} />
-
- + {/* Content */}
@@ -911,66 +917,66 @@ export default function AdminPage() {

{t.dashboard}

-
-
-

- {t.storageUsed} -

-
- {formatBytes(stats.storageUsedBytes)} - / {formatBytes(stats.storageLimitBytes)} -
+
+
+

+ {t.storageUsed} +

+
+ {formatBytes(stats.storageUsedBytes)} + / {formatBytes(stats.storageLimitBytes)}
-
-
- {((stats.storageUsedBytes / (stats.storageLimitBytes || 1)) * 100).toFixed(1)}% -
-
{t.maxCapacity}
+
+
+
+ {((stats.storageUsedBytes / (stats.storageLimitBytes || 1)) * 100).toFixed(1)}%
-
-
- -
+
{t.maxCapacity}
+
+
+
+ +
-

- {t.totalRegistered} -

-
{stats.totalUsers}
-
- - - - - {stats.onlineUsers} {t.usersOnline} -
+

+ {t.totalRegistered} +

+
{stats.totalUsers}
+
+ + + + + {stats.onlineUsers} {t.usersOnline} +
-

{t.maintenance}

- {!cleanStats ? ( - - ) : ( -
-
-
{t.orphanedMessages}
-
{cleanStats.orphanedMessagesCount}
-
-
-
{t.orphanedMedia}
-
{formatBytes(cleanStats.orphanedMediaBytes)}
-
-
- -
-
- )} +

{t.maintenance}

+ {!cleanStats ? ( + + ) : ( +
+
+
{t.orphanedMessages}
+
{cleanStats.orphanedMessagesCount}
+
+
+
{t.orphanedMedia}
+
{formatBytes(cleanStats.orphanedMediaBytes)}
+
+
+ +
+
+ )}
@@ -979,399 +985,399 @@ export default function AdminPage() { {activeTab === 'settings' && config && (
-

- {String(t[activeSettingsTab as keyof typeof t] || activeSettingsTab)} -

-
- - -
+

+ {String(t[activeSettingsTab as keyof typeof t] || activeSettingsTab)} +

+
+ + +
{/* System Settings */} {activeSettingsTab === 'system' && (
-

{t.system}

-
- - -
-
- {t.enableReg} - setConfig({...config, system: {...config.system, enableRegistration: v}})} /> -
- {t.hints.enableRegistration} +

{t.system}

+
+ + +
+
+ {t.enableReg} + setConfig({ ...config, system: { ...config.system, enableRegistration: v } })} />
-
- {t.timezone} -
- setShowTzDropdown(true)} - onChange={e => { - setConfig({...config, system: {...config.system, serverTimezone: e.target.value}}); - setTzSearch(e.target.value); - }} - placeholder="UTC" - /> - - - - {showTzDropdown && ( - - {timezones - .filter(tz => tz.displayName.toLowerCase().includes(tzSearch.toLowerCase()) || tz.id.toLowerCase().includes(tzSearch.toLowerCase())) - .map(tz => ( - - )) - } - - )} - -
- {showTzDropdown &&
setShowTzDropdown(false)} />} - {t.hints.timezone} + {t.hints.enableRegistration} +
+
+ {t.timezone} +
+ setShowTzDropdown(true)} + onChange={e => { + setConfig({ ...config, system: { ...config.system, serverTimezone: e.target.value } }); + setTzSearch(e.target.value); + }} + placeholder="UTC" + /> + + + + {showTzDropdown && ( + + {timezones + .filter(tz => tz.displayName.toLowerCase().includes(tzSearch.toLowerCase()) || tz.id.toLowerCase().includes(tzSearch.toLowerCase())) + .map(tz => ( + + )) + } + + )} +
-
+ {showTzDropdown &&
setShowTzDropdown(false)} />} + {t.hints.timezone} +
+
)} {/* Stories Settings */} {activeSettingsTab === 'stories' && (
-
-

{t.stories}

- setConfig({...config, stories: {...config.stories, enabled: v}})} /> -
- {config.stories.enabled && ( -
- - -
-
- {t.textStories} - setConfig({...config, stories: {...config.stories, textStoriesEnabled: v}})} /> -
- {t.hints.textStoryDuration} +
+

{t.stories}

+ setConfig({ ...config, stories: { ...config.stories, enabled: v } })} /> +
+ {config.stories.enabled && ( +
+ + +
+
+ {t.textStories} + setConfig({ ...config, stories: { ...config.stories, textStoriesEnabled: v } })} />
- - -
- )} + {t.hints.textStoryDuration} +
+ + +
+ )}
)} {/* Chats Settings */} {activeSettingsTab === 'chats' && (
-

{t.chats}

-
-
-
-
- {t.supportGroups} - setConfig({...config, chats: {...config.chats, supportGroups: v}})} /> -
- {t.hints.supportGroups} +

{t.chats}

+
+
+
+
+ {t.supportGroups} + setConfig({ ...config, chats: { ...config.chats, supportGroups: v } })} />
- + {t.hints.supportGroups}
-
-
-
- {t.autoClean} - setConfig({...config, chats: {...config.chats, enableAutoClean: v}})} /> -
- {t.hints.autoClean} -
-
-
- {t.chatToGroup} - setConfig({...config, chats: {...config.chats, allowChatToGroupConversion: v}})} /> -
- {t.hints.chatToGroup} -
-
-
- {t.enableFolders} - setConfig({...config, chats: {...config.chats, enableFolders: v}})} /> -
- {t.hints.enableFolders} + +
+
+
+
+ {t.autoClean} + setConfig({ ...config, chats: { ...config.chats, enableAutoClean: v } })} />
+ {t.hints.autoClean}
-
+
+
+ {t.chatToGroup} + setConfig({ ...config, chats: { ...config.chats, allowChatToGroupConversion: v } })} /> +
+ {t.hints.chatToGroup} +
+
+
+ {t.enableFolders} + setConfig({ ...config, chats: { ...config.chats, enableFolders: v } })} /> +
+ {t.hints.enableFolders} +
+
+
)} {/* Messages Settings */} {activeSettingsTab === 'messages' && (
-

{t.messages}

-
-
- - - -
+

{t.messages}

+
+
+ + + +
-
- {[ - { k: 'allowMedia', l: t.allowMedia, h: t.hints.allowMedia }, - { k: 'allowVoiceMessages', l: t.voiceMessages, h: t.hints.voiceMessages }, - { k: 'allowForwarding', l: t.forwarding, h: t.hints.forwarding }, - { k: 'allowReactions', l: t.reactions, h: t.hints.reactions }, - { k: 'allowReplies', l: t.replies, h: t.hints.replies }, - { k: 'allowQuoting', l: t.quoting, h: t.hints.quoting }, - { k: 'allowMessageDeletion', l: t.allowDelete, h: t.hints.allowDelete }, - { k: 'forbidCopying', l: t.noCopy, h: t.hints.noCopy }, - { k: 'allowLinks', l: t.links, h: t.hints.links }, - { k: 'allowPolls', l: t.polls, h: t.hints.polls }, - { k: 'allowPinning', l: t.pinning, h: t.hints.pinning } - ].map(item => ( -
-
- {item.l} - setConfig({...config, messages: {...config.messages, [item.k]: v}})} /> -
- {item.h && {item.h}} -
- ))} -
-
+
+ {[ + { k: 'allowMedia', l: t.allowMedia, h: t.hints.allowMedia }, + { k: 'allowVoiceMessages', l: t.voiceMessages, h: t.hints.voiceMessages }, + { k: 'allowForwarding', l: t.forwarding, h: t.hints.forwarding }, + { k: 'allowReactions', l: t.reactions, h: t.hints.reactions }, + { k: 'allowReplies', l: t.replies, h: t.hints.replies }, + { k: 'allowQuoting', l: t.quoting, h: t.hints.quoting }, + { k: 'allowMessageDeletion', l: t.allowDelete, h: t.hints.allowDelete }, + { k: 'forbidCopying', l: t.noCopy, h: t.hints.noCopy }, + { k: 'allowLinks', l: t.links, h: t.hints.links }, + { k: 'allowPolls', l: t.polls, h: t.hints.polls }, + { k: 'allowPinning', l: t.pinning, h: t.hints.pinning } + ].map(item => ( +
+
+ {item.l} + setConfig({ ...config, messages: { ...config.messages, [item.k]: v } })} /> +
+ {item.h && {item.h}} +
+ ))} +
+
)} {/* WebRTC Settings */} {activeSettingsTab === 'webRtc' && (
-
-

- {t.webRtc} -

- setConfig({...config, webRtc: {...config.webRtc, enabled: v}})} /> -
- {t.hints.webRtc} - - {config.webRtc.enabled && (<> -
-
-
- {t.videoCalls} - setConfig({...config, webRtc: {...config.webRtc, enableVideoCalls: v}})} /> -
- {t.hints.videoCalls} -
-
-
- {t.screenSharing} - setConfig({...config, webRtc: {...config.webRtc, enableScreenSharing: v}})} /> -
- {t.hints.screenSharing} -
-
+
+

+ {t.webRtc} +

+ setConfig({ ...config, webRtc: { ...config.webRtc, enabled: v } })} /> +
+ {t.hints.webRtc} -
-
- - {t.turnServerConfig} -
-
- {t.hints.turn} -
- - - - -
- + {config.webRtc.enabled && (<> +
+
+
+ {t.videoCalls} + setConfig({ ...config, webRtc: { ...config.webRtc, enableVideoCalls: v } })} />
- )} + {t.hints.videoCalls} +
+
+
+ {t.screenSharing} + setConfig({ ...config, webRtc: { ...config.webRtc, enableScreenSharing: v } })} /> +
+ {t.hints.screenSharing} +
+
+ +
+
+ + {t.turnServerConfig} +
+
+ {t.hints.turn} +
+ + + + +
+ +
+ )}
)} {/* Klipy Settings */} {activeSettingsTab === 'klipy' && (
-
-

-
{t.klipy} -

- setConfig({...config, klipy: {...config.klipy, enabled: v}})} /> -
- {t.hints.klipy} - {config.klipy.enabled && ( -
-
- - -
- -
- )} +
+

+
{t.klipy} +

+ setConfig({ ...config, klipy: { ...config.klipy, enabled: v } })} /> +
+ {t.hints.klipy} + {config.klipy.enabled && ( +
+
+ + +
+ +
+ )}
)} {/* Import Settings */} {activeSettingsTab === 'import' && (
-
-

- {t.importTitle} -

-
- {t.hints.import} -
-
-
- {t.telegramImport} - setConfig({...config, import: {...config.import, enableTelegramImport: v}})} /> -
- {t.hints.import} -

- {t.importDescription} -

+
+

+ {t.importTitle} +

+
+ {t.hints.import} +
+
+
+ {t.telegramImport} + setConfig({ ...config, import: { ...config.import, enableTelegramImport: v } })} />
-
+ {t.hints.import} +

+ {t.importDescription} +

+
+
)} {/* Federation Settings */} {activeSettingsTab === 'federation' && (
-
-

{t.federation}

- setConfig({...config, federation: {...config.federation, enabled: v}})} /> -
- - {config.federation.enabled && ( -
-
- {t.allowedNodes} -
- - - -
-
+
+

{t.federation}

+ setConfig({ ...config, federation: { ...config.federation, enabled: v } })} /> +
+ {config.federation.enabled && ( +
+
+ {t.allowedNodes}
- setDomainInput(e.target.value)} placeholder="domain.tld" /> - + + +
+
-
- {config.federation.allowedDomains.map(d => ( -
- {d.domain} - -
- ))} - {config.federation.allowedDomains.length === 0 && ( -
- {t.noNodes} -
- )} -
-
- )} +
+ setDomainInput(e.target.value)} placeholder="domain.tld" /> + +
+ +
+ {config.federation.allowedDomains.map(d => ( +
+ {d.domain} + +
+ ))} + {config.federation.allowedDomains.length === 0 && ( +
+ {t.noNodes} +
+ )} +
+
+ )}
)} @@ -1393,32 +1399,36 @@ export default function AdminPage() { {users.length === 0 && !isSearching &&
{t.noUsersFound}
} {users.map(u => ( @@ -1429,14 +1439,20 @@ export default function AdminPage() { {selectedUser ? (
-
- {selectedUser.avatar ? : selectedUser.username.charAt(0).toUpperCase()} +
+
+ {selectedUser.avatar ? : selectedUser.username.charAt(0).toUpperCase()} +
+ {selectedUser.isBanned && ( +
+ +
+ )}

{selectedUser.displayName}

@{selectedUser.username}
- {selectedUser.isBanned && {t.isBanned}}
@@ -1455,18 +1471,18 @@ export default function AdminPage() { {t.resetPassword} {generatedPass && ( -
- {generatedPass} - -
+
+ {generatedPass} + +
)} {selectedUser.isBanned ? ( ) : ( )} -

{t.addUser}

-
- - - - -
+ +

{t.addUser}

+
+ + + + +
)} @@ -1517,8 +1533,8 @@ export default function AdminPage() { {/* Global Toast */} {toast && (
- {toast.type === 'success' ? : } - {toast.message} + {toast.type === 'success' ? : } + {toast.message}
)}