admin: System page with restart steps, Record calls on the Calling tab
System shows whether telephony runs and needs a restart, and how to restart it (docker compose restart asterisk); the dashboard's restart warning, the Network hint and the public IP email point there. The user drawer's Calling tab gets the Record calls select the spec listed (recordMode, already in the dialplan). e2e m11-system.mjs. Task: M11-T7
This commit is contained in:
1 parent
324f9d9fe2
commit
a1525088dc
13 files changed
+322
-30
No files matched your search
@@ -154,7 +154,7 @@ export function createJobs(d: JobDeps) {
|
||||
d,
|
||||
`Public IP changed to ${ip}`,
|
||||
`The phone system's public IP changed from ${old ?? 'none'} to ${ip}.\n` +
|
||||
'Restart telephony from the dashboard so calls use the new address (it drops calls in progress).\n' +
|
||||
'Restart telephony so calls use the new address: Admin → System says how (it drops calls in progress).\n' +
|
||||
'Phones and the provider may need the new IP if they were set up with the old one.',
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ import { BlockedPage } from './pages/admin/BlockedPage.tsx';
|
||||
import { CallsPage } from './pages/admin/CallsPage.tsx';
|
||||
import { RecordingsPage } from './pages/admin/RecordingsPage.tsx';
|
||||
import { ReportsPage } from './pages/admin/ReportsPage.tsx';
|
||||
import { SystemPage } from './pages/admin/SystemPage.tsx';
|
||||
import { MePage } from './pages/user/MePage.tsx';
|
||||
import { SetupPage } from './pages/SetupPage.tsx';
|
||||
import './index.css';
|
||||
@@ -78,6 +79,8 @@ const router = createBrowserRouter([
|
||||
<RecordingsPage />
|
||||
) : p.path === 'reports' ? (
|
||||
<ReportsPage />
|
||||
) : p.path === 'system' ? (
|
||||
<SystemPage />
|
||||
) : (
|
||||
<ComingSoon label={p.label} milestone={p.milestone ?? ''} />
|
||||
),
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ADMIN_PAGES = [
|
||||
{ path: 'sms', label: 'SMS', milestone: 'M9' },
|
||||
{ path: 'blocked', label: 'Blocked numbers', milestone: null },
|
||||
{ path: 'settings', label: 'Settings', milestone: null },
|
||||
{ path: 'system', label: 'System', milestone: 'M11' },
|
||||
{ path: 'system', label: 'System', milestone: null },
|
||||
] as const;
|
||||
|
||||
export function AdminLayout({ user }: { user: AuthUser }) {
|
||||
|
||||
@@ -426,10 +426,7 @@ function NetworkTab({ s }: { s: SettingsDto }) {
|
||||
save.mutate({ 'network.publicIp': ip.trim() || null, 'network.publicIpWatch': watch });
|
||||
}}
|
||||
>
|
||||
<Field
|
||||
label="Public IP"
|
||||
hint="Changing it needs a telephony restart (the dashboard offers one)"
|
||||
>
|
||||
<Field label="Public IP" hint="Changing it needs a telephony restart (see System)">
|
||||
{(p) => (
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// System (05 §2): telephony status and how to restart it.
|
||||
// ponytail: the spec's generator runs, generated files, CLI views and audit log aren't here yet;
|
||||
// the admin guide's troubleshooting page covers them from the server's shell.
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { systemApi } from '../../api/client.ts';
|
||||
import { Alert } from '../../components/ui.tsx';
|
||||
|
||||
const RESTART = 'docker compose restart asterisk';
|
||||
|
||||
export function SystemPage() {
|
||||
const status = useQuery({
|
||||
queryKey: ['system-status'],
|
||||
queryFn: systemApi.status,
|
||||
refetchInterval: 10_000,
|
||||
});
|
||||
const s = status.data;
|
||||
return (
|
||||
<section className="flex max-w-2xl flex-col gap-6">
|
||||
<h1 className="text-xl font-semibold">System</h1>
|
||||
{status.error && <Alert>{status.error.message}</Alert>}
|
||||
|
||||
{s && (
|
||||
<dl aria-label="Status" className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="rounded-lg border border-zinc-200 p-3 dark:border-zinc-800">
|
||||
<dt className="text-zinc-500">Telephony (Asterisk)</dt>
|
||||
<dd className="font-semibold">{s.ami.connected ? 'Running' : 'Not reachable'}</dd>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 p-3 dark:border-zinc-800">
|
||||
<dt className="text-zinc-500">Restart</dt>
|
||||
<dd className="font-semibold">{s.restartRequired ? 'Needed' : 'Not needed'}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
)}
|
||||
{s?.restartRequired && (
|
||||
<p
|
||||
role="status"
|
||||
className="rounded-md bg-amber-50 p-3 text-sm text-amber-900 dark:bg-amber-950 dark:text-amber-100"
|
||||
>
|
||||
The public IP, SIP ports or TLS changed. Telephony keeps using the old ones until it
|
||||
restarts.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2 text-sm">
|
||||
<h2 className="text-base font-medium">Restarting telephony</h2>
|
||||
<p>
|
||||
On the server, in the folder SelfPhoneNG is installed in (where <code>.env</code> is),
|
||||
run:
|
||||
</p>
|
||||
<pre
|
||||
aria-label="Restart command"
|
||||
className="overflow-x-auto rounded-md bg-zinc-100 p-3 font-mono dark:bg-zinc-900"
|
||||
>
|
||||
{RESTART}
|
||||
</pre>
|
||||
<ul className="list-disc pl-5 text-zinc-600 dark:text-zinc-400">
|
||||
<li>
|
||||
<strong>Calls in progress drop</strong>, so do it when nobody is on the phone.
|
||||
</li>
|
||||
<li>
|
||||
Phones and the trunk sign in again by themselves within a minute. This page shows
|
||||
“Running” and “Not needed” once it's back.
|
||||
</li>
|
||||
<li>The web app, voicemail and call history aren't affected.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -458,6 +458,7 @@ function CallingTab({ ext, onSaved }: { ext: ExtensionDto; onSaved: () => void }
|
||||
defaultValues: {
|
||||
outboundClass: ext.outboundClass,
|
||||
outboundCallerIdDidId: ext.outboundCallerIdDidId ?? '',
|
||||
recordMode: ext.recordMode,
|
||||
},
|
||||
});
|
||||
const onSubmit = form.handleSubmit(async (v) => {
|
||||
@@ -466,6 +467,7 @@ function CallingTab({ ext, onSaved }: { ext: ExtensionDto; onSaved: () => void }
|
||||
await extensionsApi.update(ext.id, {
|
||||
outboundClass: v.outboundClass,
|
||||
outboundCallerIdDidId: v.outboundCallerIdDidId || null,
|
||||
recordMode: v.recordMode,
|
||||
});
|
||||
await qc.invalidateQueries({ queryKey: ['extension', ext.id] });
|
||||
onSaved();
|
||||
@@ -506,6 +508,19 @@ function CallingTab({ ext, onSaved }: { ext: ExtensionDto; onSaved: () => void }
|
||||
</select>
|
||||
)}
|
||||
</Field>
|
||||
<Field
|
||||
label="Record calls"
|
||||
hint="Calls between this person and outside numbers; calls inside the company are never recorded. Check the consent rules where you and your callers are (Settings → Recording)."
|
||||
>
|
||||
{(a) => (
|
||||
<select {...a} {...form.register('recordMode')} className={inputClass}>
|
||||
<option value="never">Never</option>
|
||||
<option value="inbound">Calls from outside</option>
|
||||
<option value="outbound">Calls to outside</option>
|
||||
<option value="all">All outside calls</option>
|
||||
</select>
|
||||
)}
|
||||
</Field>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user