This commit is contained in:
onysd 2026-09-01 12:50:18 +03:00
parent 21a0856587
commit e8dc967e6a
26 changed files with 1373 additions and 481 deletions

View file

@ -23,7 +23,7 @@
})();
</script>
<script type="module" crossorigin src="/assets/index-CwTwvGWj.js"></script>
<script type="module" crossorigin src="/assets/index-TfcI68oK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-0MvM-hpw.css">
</head>
<body>

View file

@ -42,7 +42,7 @@ export function App() {
return (
<PermissionsProvider permissions={session.permissions ?? []} hideThirdPartyVerification={session.hide_third_party_verification ?? true}>
<Shell actor={session.actor} build={session.build} route={route} navigate={navigate} onLogout={() => setSession(null)}>
<Shell actor={session.actor} apiLayer={session.api_layer} build={session.build} route={route} navigate={navigate} onLogout={() => setSession(null)}>
<Routes route={route} navigate={navigate} />
</Shell>
</PermissionsProvider>

View file

@ -8,9 +8,11 @@ import { MultiUserPicker } from "./EntityPicker";
type TargetMode = "all" | "selected";
// CreateBroadcastModal composes the message and target list, then hands off to
// ActionButton for the usual dry-run/confirm flow. "All users" is resolved to an
// explicit id list server-side (cmd/telesrv-admin/server.go), not here -- the
// picker only ever deals with an actual, visible list of accounts.
// ActionButton for the usual dry-run/confirm flow. "All users" is never
// resolved into an id list at all -- the admin service snapshots the
// current eligible user set itself and a background worker enumerates it
// incrementally, so this only ever sends user_ids for "selected" mode,
// where the picker deals with an actual, visible list of accounts.
export function CreateBroadcastModal({ onClose, onCreated }: { onClose: () => void; onCreated: () => void }) {
const [message, setMessage] = useState("");
const [targetMode, setTargetMode] = useState<TargetMode>("all");

View file

@ -41,6 +41,7 @@ export function BootScreen() {
export function Shell({
actor,
apiLayer,
build,
route,
navigate,
@ -48,6 +49,7 @@ export function Shell({
children
}: {
actor: string;
apiLayer?: number;
build?: { commit: string; short_commit: string; dirty: boolean; build_time: string };
route: RouteState;
navigate: Navigate;
@ -178,6 +180,9 @@ export function Shell({
</nav>
<div className="sidebar-status">
<span className="sidebar-label">{"Version: O7"}</span>
{typeof apiLayer === "number" && (
<span className="sidebar-label sidebar-api-layer">{`API layer: ${apiLayer}`}</span>
)}
{build?.short_commit && (
<span className="sidebar-label sidebar-build" title={build.commit + (build.dirty ? " (uncommitted changes)" : "")}>
{`Build: ${build.short_commit}${build.dirty ? "+" : ""}`}

View file

@ -68,7 +68,7 @@ export function BroadcastsPage() {
}, []);
const rows = data?.rows ?? [];
const inFlight = rows.filter((row) => row.SentCount + row.FailedCount < row.TotalCount).length;
const inFlight = rows.filter((row) => !row.EnumerationDone || row.SentCount + row.FailedCount < row.TargetCount).length;
const canGoPrev = history.length > 0 && !busy;
const canGoNext = Boolean(data?.has_more) && !busy;
@ -110,7 +110,7 @@ export function BroadcastsPage() {
<tbody>
{rows.map((row) => {
const delivered = row.SentCount + row.FailedCount;
const done = row.TotalCount > 0 && delivered >= row.TotalCount;
const done = row.EnumerationDone && row.TargetCount > 0 && delivered >= row.TargetCount;
return (
<tr key={row.ID}>
<td className="mono">{row.ID}</td>
@ -118,7 +118,7 @@ export function BroadcastsPage() {
<td>{row.TargetMode === "all" ? <Badge tone="warn">{"All users"}</Badge> : <Badge>{"Selected"}</Badge>}</td>
<td>{row.SentCount}</td>
<td>{row.FailedCount > 0 ? <Badge tone="danger">{row.FailedCount}</Badge> : row.FailedCount}</td>
<td>{row.TotalCount}</td>
<td>{row.TargetCount}</td>
<td>{row.CreatedBy || "-"}</td>
<td>
{formatDate(row.CreatedAt)}

View file

@ -584,6 +584,9 @@ export type AdminSession = {
// comment. Used by Server Settings' Restart/Update flow to detect a
// genuinely new admin process after asking it to bounce.
boot_id?: string;
// The MTProto TL schema layer this server binary speaks -- shown in the
// sidebar footer above the build/commit line.
api_layer?: number;
// This admin binary's own build -- shown under "Version" in the sidebar
// footer so an operator can tell which build is actually running.
build?: {
@ -794,9 +797,11 @@ export type BroadcastRow = {
ID: number;
Message: string;
TargetMode: string;
TotalCount: number;
TargetCount: number;
MaterializedCount: number;
SentCount: number;
FailedCount: number;
EnumerationDone: boolean;
CreatedBy: string;
CreatedAt: string;
};