updated admin panel style

This commit is contained in:
onysd 2026-07-22 01:16:29 +03:00
parent 9844d78c5b
commit deae6c4a4a
26 changed files with 493 additions and 1356 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -3,9 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>telesrv admin</title>
<script type="module" crossorigin src="/assets/index-ybbM_ULl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BVA68zgn.css">
<link rel="icon" type="image/png" href="/logo.png" />
<title>OwpenGram Admin</title>
<script type="module" crossorigin src="/assets/index-CpvYOqis.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Da0KbwLb.css">
</head>
<body>
<div id="root"></div>

BIN
cmd/telesrv-admin/web/dist/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>telesrv admin</title>
<link rel="icon" type="image/png" href="/logo.png" />
<title>OwpenGram Admin</title>
</head>
<body>
<div id="root"></div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -12,7 +12,7 @@ import {
} from "lucide-react";
import { useEffect, useState, type ReactNode } from "react";
import { api } from "../api";
import { LanguageSwitch, useI18n } from "../i18n";
import { useI18n } from "../i18n";
import { type Navigate, type RouteState, routeSubtitle, routeTitle } from "../routing";
import { AppLink } from "./AppLink";
@ -21,9 +21,9 @@ export function BootScreen() {
return (
<div className="boot-screen">
<div className="brand compact brand-elevated">
<span className="brand-mark">T</span>
<span className="brand-mark"><img src="/logo.png" alt="OwpenGram" /></span>
<span>
<strong>telesrv</strong>
<strong>OwpenGram</strong>
<small>{t("app.adminConsole")}</small>
</span>
</div>
@ -64,9 +64,9 @@ export function Shell({
<div className="shell">
<aside className="sidebar">
<AppLink className="brand" href="/" navigate={navigate}>
<span className="brand-mark">T</span>
<span className="brand-mark"><img src="/logo.png" alt="OwpenGram" /></span>
<span>
<strong>telesrv</strong>
<strong>OwpenGram</strong>
<small>{t("app.adminConsole")}</small>
</span>
</AppLink>
@ -123,7 +123,6 @@ export function Shell({
<h1>{routeTitle(route.path, t)}</h1>
</div>
<div className="topbar-actions">
<LanguageSwitch />
<span className="actor-pill">{t("layout.actor", { actor })}</span>
<button className="btn ghost icon-text" type="button" onClick={logout} title={t("layout.logout")}>
<LogOut size={16} /> {t("layout.logout")}

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ import type { FormEvent } from "react";
import { useState } from "react";
import { api, errorMessage } from "../api";
import { Alert } from "../components/ui";
import { LanguageSwitch, useI18n } from "../i18n";
import { useI18n } from "../i18n";
export function LoginPage({ onLogin }: { onLogin: (actor: string) => void }) {
const { t } = useI18n();
@ -29,14 +29,13 @@ export function LoginPage({ onLogin }: { onLogin: (actor: string) => void }) {
<section className="login-panel">
<div className="login-head">
<div className="brand brand-elevated">
<span className="brand-mark">T</span>
<span className="brand-mark"><img src="/logo.png" alt="OwpenGram" /></span>
<span>
<strong>telesrv</strong>
<strong>OwpenGram</strong>
<small>{t("app.adminConsole")}</small>
</span>
</div>
<div className="login-head-actions">
<LanguageSwitch />
<span className="login-chip">{t("app.localAccess")}</span>
</div>
</div>

View file

@ -1,24 +1,31 @@
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/plus-jakarta-sans-400.woff2") format("woff2"); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/plus-jakarta-sans-500.woff2") format("woff2"); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 600; font-display: swap; src: url("/fonts/plus-jakarta-sans-600.woff2") format("woff2"); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 700; font-display: swap; src: url("/fonts/plus-jakarta-sans-700.woff2") format("woff2"); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 800; font-display: swap; src: url("/fonts/plus-jakarta-sans-800.woff2") format("woff2"); }
:root {
color-scheme: light;
--bg: #f3f5f7;
--bg: #f7f9fc;
--panel: #ffffff;
--panel-subtle: #f8fafb;
--panel-strong: #eef2f5;
--line: #d9e1e8;
--line-strong: #c2ccd6;
--text: #101828;
--muted: #667085;
--muted-2: #98a2b3;
--brand: #176d61;
--brand-2: #245b9d;
--panel-subtle: #f7f9fc;
--panel-strong: #f1f5f9;
--line: #e2e8f0;
--line-strong: #cbd5e1;
--text: #0f1720;
--muted: #64748b;
--muted-2: #94a3b8;
--brand: #2563eb;
--brand-2: #38bdf8;
--grad: linear-gradient(135deg, #38bdf8 0%, #2563eb 55%, #1e40af 100%);
--good: #167447;
--warn: #a15c07;
--danger: #b42318;
--sidebar: #11161d;
--sidebar-soft: #1b222b;
--sidebar-line: #2c3541;
--focus: rgba(23, 109, 97, 0.16);
--shadow: 0 18px 52px rgba(16, 24, 40, 0.14);
--sidebar: #08080e;
--sidebar-soft: #12121a;
--sidebar-line: #222228;
--focus: rgba(37, 99, 235, 0.16);
--shadow: 0 28px 70px -36px rgba(5, 5, 8, 0.35);
}
* {
@ -35,7 +42,7 @@ body {
margin: 0;
color: var(--text);
background: var(--bg);
font: 13px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font: 13px/1.45 "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
button,
@ -81,20 +88,18 @@ a {
justify-content: center;
}
.brand-elevated .brand-mark {
box-shadow: 0 8px 24px rgba(23, 109, 97, 0.26);
}
.brand-mark {
display: grid;
width: 34px;
height: 34px;
place-items: center;
color: #ffffff;
background: var(--brand);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
font-weight: 800;
}
.brand-mark img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
.brand strong {
@ -261,37 +266,6 @@ a {
gap: 8px;
}
.language-switch {
display: inline-flex;
min-height: 30px;
align-items: center;
padding: 2px;
background: var(--panel-subtle);
border: 1px solid var(--line);
border-radius: 999px;
}
.language-switch button {
min-width: 42px;
min-height: 24px;
padding: 0 9px;
color: var(--muted);
background: transparent;
border: 0;
border-radius: 999px;
cursor: pointer;
font-weight: 800;
}
.language-switch button.active {
color: #ffffff;
background: var(--brand);
}
.language-switch button:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
}
.actor-pill {
display: inline-flex;

View file

@ -110,8 +110,8 @@
height: 38px;
place-items: center;
color: var(--brand);
background: #edf7f4;
border: 1px solid #c9e2dc;
background: #eaf2fd;
border: 1px solid #c7dcf9;
border-radius: 8px;
}
@ -217,9 +217,9 @@
align-items: center;
gap: 8px;
padding: 7px 9px;
color: #0f3f38;
background: #eef8f5;
border: 1px solid #b9dcd3;
color: #1e3a8a;
background: #eaf2fe;
border: 1px solid #c3d9f7;
border-radius: 7px;
}
@ -291,7 +291,7 @@
.picker-row:hover,
.picker-row.selected {
background: #f3f8f6;
background: #eef4ff;
}
.picker-row strong,
@ -410,7 +410,7 @@ textarea:focus {
}
.btn.primary:hover:not(:disabled) {
background: #12594f;
background: #1d4ed8;
}
.btn.ghost {

View file

@ -265,12 +265,12 @@
flex: 0 0 auto;
place-items: center;
color: var(--brand);
background: #eaf6f3;
border: 1px solid #c7e3dc;
background: #eaf2fd;
border: 1px solid #c7dcf9;
}
.gift-format-chips { display: flex; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; gap: 6px; }
.gift-format-chips span { padding: 4px 8px; color: #33645d; background: #eef8f5; border: 1px solid #cfe5df; border-radius: 999px; font-size: 10px; font-weight: 800; letter-spacing: .02em; }
.gift-format-chips span { padding: 4px 8px; color: #1e40af; background: #eaf2fe; border: 1px solid #cbdcf7; border-radius: 999px; font-size: 10px; font-weight: 800; letter-spacing: .02em; }
.gift-list-summary { margin-left: auto; color: var(--muted); font-size: 11px; font-weight: 700; }
@ -284,12 +284,12 @@
.official-gift-categories { display: flex; flex-wrap: wrap; gap: 7px; }
.official-gift-categories button {
display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 5px 10px;
color: #49605c; background: #f7faf9; border: 1px solid #d7e2df; border-radius: 999px;
color: #3f5a78; background: #f5f8fd; border: 1px solid #d7e2f4; border-radius: 999px;
font: inherit; font-size: 11px; font-weight: 800; cursor: pointer;
transition: color .15s ease, background .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.official-gift-categories button:hover { color: var(--brand); border-color: #9fc9c0; }
.official-gift-categories button.active { color: #ffffff; background: var(--brand); border-color: var(--brand); box-shadow: 0 4px 12px rgba(23, 109, 97, .17); }
.official-gift-categories button:hover { color: var(--brand); border-color: #9dc3f5; }
.official-gift-categories button.active { color: #ffffff; background: var(--brand); border-color: var(--brand); box-shadow: 0 4px 12px rgba(37, 99, 235, .17); }
.official-gift-categories button span {
display: grid; min-width: 20px; height: 20px; padding: 0 5px; place-items: center;
color: inherit; background: rgba(255,255,255,.65); border-radius: 999px; font-size: 10px;
@ -298,20 +298,20 @@
.official-gift-list {
display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; max-height: 314px;
min-height: 126px; padding: 8px; overflow: auto; border: 1px solid var(--line); border-radius: 14px;
background: #f6f9f8; scrollbar-gutter: stable;
background: #f5f8fd; scrollbar-gutter: stable;
}
.official-gift-option {
display: grid; min-width: 0; gap: 8px; padding: 11px 12px; text-align: left; color: var(--text);
background: #ffffff; border: 1px solid #dce6e3; border-radius: 11px; cursor: pointer;
box-shadow: 0 1px 2px rgba(32, 54, 50, .03);
background: #ffffff; border: 1px solid #dbe6f7; border-radius: 11px; cursor: pointer;
box-shadow: 0 1px 2px rgba(30, 41, 82, .03);
transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.official-gift-option:hover { border-color: #9fc9c0; box-shadow: 0 5px 14px rgba(32, 76, 68, .08); transform: translateY(-1px); }
.official-gift-option.selected { border-color: var(--brand); box-shadow: 0 0 0 2px rgba(23, 109, 97, .12), 0 5px 14px rgba(32, 76, 68, .08); }
.official-gift-option:hover { border-color: #9dc3f5; box-shadow: 0 5px 14px rgba(30, 64, 175, .08); transform: translateY(-1px); }
.official-gift-option.selected { border-color: var(--brand); box-shadow: 0 0 0 2px rgba(37, 99, 235, .12), 0 5px 14px rgba(30, 64, 175, .08); }
.official-gift-option-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: baseline; }
.official-gift-option-head strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
.official-gift-option-head .mono { color: var(--muted); font-size: 9px; }
.official-gift-option-meta { display: flex; flex-wrap: wrap; gap: 10px; color: #667773; font-size: 10px; font-weight: 700; }
.official-gift-option-meta { display: flex; flex-wrap: wrap; gap: 10px; color: #5b6b85; font-size: 10px; font-weight: 700; }
.official-gift-capabilities { display: flex; flex-wrap: wrap; gap: 5px; }
.official-gift-capabilities > span {
padding: 3px 7px; border: 1px solid transparent; border-radius: 999px; font-size: 9px; font-weight: 850; letter-spacing: .01em;
@ -342,21 +342,21 @@
padding: 12px 14px;
color: var(--text);
background: #ffffff;
border: 1px dashed #b7ccc8;
border: 1px dashed #b7c9e8;
border-radius: 10px;
cursor: pointer;
transition: border-color .16s ease, background .16s ease, box-shadow .16s ease;
}
.gift-file-picker:hover,
.gift-file-picker.has-file { background: #f8fcfb; border-color: var(--brand); box-shadow: 0 0 0 2px rgba(23, 109, 97, .05); }
.gift-file-picker.has-file { background: #f5f9ff; border-color: var(--brand); box-shadow: 0 0 0 2px rgba(37, 99, 235, .05); }
.gift-file-picker input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.gift-file-icon { width: 40px; height: 40px; border-radius: 9px; }
.gift-file-copy { display: grid; min-width: 0; gap: 2px; }
.gift-field-label { color: var(--muted); font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
.gift-file-copy strong { overflow: hidden; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
.gift-file-copy small { color: var(--muted); font-size: 11px; font-weight: 500; }
.gift-file-action { padding: 7px 10px; color: var(--brand); background: #f0f8f6; border: 1px solid #c7e3dc; border-radius: 7px; font-size: 11px; font-weight: 800; }
.gift-file-action { padding: 7px 10px; color: var(--brand); background: #eef4ff; border: 1px solid #c7dcf9; border-radius: 7px; font-size: 11px; font-weight: 800; }
.gift-fields-grid {
display: grid;
@ -386,7 +386,7 @@
}
.gift-fields-grid input:focus,
.gift-reason-field input:focus { border-color: #77b6aa; box-shadow: 0 0 0 3px rgba(23, 109, 97, .08); outline: none; }
.gift-reason-field input:focus { border-color: #7bb4f0; box-shadow: 0 0 0 3px rgba(37, 99, 235, .08); outline: none; }
.gift-switch { display: inline-flex; align-items: center; gap: 9px; color: #344054; font-size: 12px; font-weight: 700; cursor: pointer; }
.gift-switch input { position: absolute; width: 1px; height: 1px; opacity: 0; }
@ -394,14 +394,14 @@
.gift-switch-track span { width: 15px; height: 15px; background: #ffffff; border-radius: 50%; box-shadow: 0 1px 3px rgba(16, 24, 40, .22); transition: transform .16s ease; }
.gift-switch input:checked + .gift-switch-track { background: var(--brand); }
.gift-switch input:checked + .gift-switch-track span { transform: translateX(15px); }
.gift-switch input:focus-visible + .gift-switch-track { outline: 3px solid rgba(23, 109, 97, .16); outline-offset: 2px; }
.gift-switch input:focus-visible + .gift-switch-track { outline: 3px solid rgba(37, 99, 235, .16); outline-offset: 2px; }
.gift-validation { overflow: hidden; color: #d5fff5; background: #173631; border: 1px solid #24564e; border-radius: 9px; }
.gift-validation-head { display: flex; align-items: center; gap: 9px; padding: 10px 12px; color: #e3fff9; background: rgba(255, 255, 255, .035); border-bottom: 1px solid rgba(255, 255, 255, .09); }
.gift-validation-head div { display: grid; gap: 2px; }
.gift-validation-head span { color: #99cfc4; font-size: 10px; }
.gift-validation pre { max-height: 180px; overflow: auto; margin: 0; padding: 11px 12px; color: #d5fff5; font-size: 11px; }
.gift-animation-shell { position: relative; display: grid; min-height: 210px; place-items: center; background: radial-gradient(circle, #f9f3ff, #eef8f5); }
.gift-animation-shell { position: relative; display: grid; min-height: 210px; place-items: center; background: radial-gradient(circle, #f9f3ff, #eaf2fe); }
.gift-animation { width: 200px; height: 200px; }
.gift-animation canvas { width: 100% !important; height: 100% !important; }
.gift-play { position: absolute; right: 8px; bottom: 8px; display: grid; width: 30px; height: 30px; place-items: center; color: var(--text); background: rgba(255,255,255,.9); border: 1px solid var(--line); border-radius: 50%; }

View file

@ -90,7 +90,7 @@
.command-step.active {
color: var(--brand);
border-color: #a9d8ce;
border-color: #a9c8f7;
}
.command-step.done {
@ -201,8 +201,8 @@
align-items: center;
padding: 0 8px;
color: var(--brand);
background: #edf7f4;
border: 1px solid #c9e2dc;
background: #eaf2fd;
border: 1px solid #c7dcf9;
border-radius: 999px;
font-size: 12px;
}

View file

@ -815,41 +815,41 @@ func run(logger *zap.Logger) error {
help.WithEmailSignupPhonePrefixes(cfg.EmailSignupPhonePrefixes),
help.WithAccountFreezeProvider(adminService),
),
AccountFreeze: adminService,
AICompose: aiComposeService,
Ephemeral: ephemeralService,
EphemeralPush: ephemeralStore,
EphemeralReports: ephemeralReportStore,
Users: usersService,
Updates: updatesService,
BootstrapUpdates: bootstrapUpdateStore,
BotAPIUpdates: botAPIUpdateStore,
BotCallbacks: botCallbackStore,
Contacts: contactsService,
Dialogs: dialogsService,
Chatlists: chatlistsService,
Messages: messagesService,
Translation: translationService,
Channels: channelsService,
Communities: communitiesService,
Files: filesService,
Bots: botsService,
Polls: pollsapp.NewService(pollStore),
Stories: storiesapp.NewService(storyStore, storiesapp.WithChannelStoryAccess(channelsService)),
Phone: phoneService,
SecretChats: secretChatService,
Stars: starsService,
Gifts: giftsService,
Passkey: passkeyService,
Themes: themeService,
GroupCalls: groupCallsService,
LiveStreams: liveStreamDep(liveStreamService),
SFU: sfuService,
TURN: turnService,
LangPack: langPackService,
Sessions: activeSessions,
Inline: inlineRegistryStore,
Limiter: rateLimiter,
AccountFreeze: adminService,
AICompose: aiComposeService,
Ephemeral: ephemeralService,
EphemeralPush: ephemeralStore,
EphemeralReports: ephemeralReportStore,
Users: usersService,
Updates: updatesService,
BootstrapUpdates: bootstrapUpdateStore,
BotAPIUpdates: botAPIUpdateStore,
BotCallbacks: botCallbackStore,
Contacts: contactsService,
Dialogs: dialogsService,
Chatlists: chatlistsService,
Messages: messagesService,
Translation: translationService,
Channels: channelsService,
Communities: communitiesService,
Files: filesService,
Bots: botsService,
Polls: pollsapp.NewService(pollStore),
Stories: storiesapp.NewService(storyStore, storiesapp.WithChannelStoryAccess(channelsService)),
Phone: phoneService,
SecretChats: secretChatService,
Stars: starsService,
Gifts: giftsService,
Passkey: passkeyService,
Themes: themeService,
GroupCalls: groupCallsService,
LiveStreams: liveStreamDep(liveStreamService),
SFU: sfuService,
TURN: turnService,
LangPack: langPackService,
Sessions: activeSessions,
Inline: inlineRegistryStore,
Limiter: rateLimiter,
}, logger.Named("rpc"), clock.System)
readModelListener := postgres.NewReadModelChangeListener(cfg.PostgresDSN, postgres.ReadModelCacheSet{
ReadModelVersions: readModelVersionStore,