KI-AGENT: Asterisk-Trunk aus FEDEO anwenden
This commit is contained in:
@@ -62,6 +62,11 @@ TELEPHONY_ENABLED=false
|
|||||||
ASTERISK_IMAGE=andrius/asterisk:20
|
ASTERISK_IMAGE=andrius/asterisk:20
|
||||||
TELEPHONY_ASTERISK_HTTP_URL=http://asterisk-dev:8088/ws
|
TELEPHONY_ASTERISK_HTTP_URL=http://asterisk-dev:8088/ws
|
||||||
TELEPHONY_ASTERISK_WS_URL=ws://localhost:8088/ws
|
TELEPHONY_ASTERISK_WS_URL=ws://localhost:8088/ws
|
||||||
|
TELEPHONY_ASTERISK_GENERATED_DIR=/var/lib/fedeo/asterisk/generated
|
||||||
|
TELEPHONY_ASTERISK_AMI_HOST=asterisk-dev
|
||||||
|
TELEPHONY_ASTERISK_AMI_PORT=5038
|
||||||
|
TELEPHONY_ASTERISK_AMI_USER=fedeo
|
||||||
|
TELEPHONY_ASTERISK_AMI_PASSWORD=fedeo-ami-dev
|
||||||
TELEPHONY_SIP_DOMAIN=localhost
|
TELEPHONY_SIP_DOMAIN=localhost
|
||||||
TELEPHONY_TEST_EXTENSION=1001
|
TELEPHONY_TEST_EXTENSION=1001
|
||||||
TELEPHONY_TEST_PASSWORD=fedeo-test-1001
|
TELEPHONY_TEST_PASSWORD=fedeo-test-1001
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { FastifyInstance } from "fastify"
|
import { FastifyInstance } from "fastify"
|
||||||
|
import { promises as fs } from "node:fs"
|
||||||
|
import net from "node:net"
|
||||||
|
import path from "node:path"
|
||||||
import { and, desc, eq } from "drizzle-orm"
|
import { and, desc, eq } from "drizzle-orm"
|
||||||
import { telephonyCalls, telephonyTrunks } from "../../db/schema"
|
import { telephonyCalls, telephonyTrunks } from "../../db/schema"
|
||||||
|
|
||||||
@@ -27,6 +30,16 @@ const asteriskHttpStatusUrls = () => {
|
|||||||
const publicAsteriskWsUrl = () =>
|
const publicAsteriskWsUrl = () =>
|
||||||
process.env.TELEPHONY_ASTERISK_WS_URL || `ws://localhost:${process.env.TELEPHONY_DEV_WS_PORT || "8088"}/ws`
|
process.env.TELEPHONY_ASTERISK_WS_URL || `ws://localhost:${process.env.TELEPHONY_DEV_WS_PORT || "8088"}/ws`
|
||||||
|
|
||||||
|
const asteriskGeneratedDir = () =>
|
||||||
|
process.env.TELEPHONY_ASTERISK_GENERATED_DIR || "/var/lib/fedeo/asterisk/generated"
|
||||||
|
|
||||||
|
const asteriskAmiConfig = () => ({
|
||||||
|
host: process.env.TELEPHONY_ASTERISK_AMI_HOST || "asterisk-dev",
|
||||||
|
port: Number(process.env.TELEPHONY_ASTERISK_AMI_PORT || 5038),
|
||||||
|
username: process.env.TELEPHONY_ASTERISK_AMI_USER || "fedeo",
|
||||||
|
password: process.env.TELEPHONY_ASTERISK_AMI_PASSWORD || "fedeo-ami-dev",
|
||||||
|
})
|
||||||
|
|
||||||
const sipDomain = () =>
|
const sipDomain = () =>
|
||||||
process.env.TELEPHONY_SIP_DOMAIN || "localhost"
|
process.env.TELEPHONY_SIP_DOMAIN || "localhost"
|
||||||
|
|
||||||
@@ -119,6 +132,208 @@ const durationSeconds = (startedAt?: Date | null, endedAt?: Date | null) => {
|
|||||||
return Math.max(0, Math.round((endedAt.getTime() - startedAt.getTime()) / 1000))
|
return Math.max(0, Math.round((endedAt.getTime() - startedAt.getTime()) / 1000))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const asteriskValue = (value: string | null | undefined) =>
|
||||||
|
String(value || "").replace(/[\r\n]/g, "").trim()
|
||||||
|
|
||||||
|
const renderTelekomPjsipConfig = (trunk: any) => {
|
||||||
|
if (!trunk?.enabled) {
|
||||||
|
return [
|
||||||
|
"; Von FEDEO generiert.",
|
||||||
|
"; Telekom-Trunk ist deaktiviert.",
|
||||||
|
"",
|
||||||
|
].join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
const registrar = asteriskValue(trunk.registrar) || "tel.t-online.de"
|
||||||
|
const sipUser = asteriskValue(trunk.sipUser)
|
||||||
|
const authUser = asteriskValue(trunk.authUser) || sipUser
|
||||||
|
const password = asteriskValue(trunk.password)
|
||||||
|
const callerId = asteriskValue(trunk.callerId) || sipUser
|
||||||
|
|
||||||
|
return [
|
||||||
|
"; Von FEDEO generiert. Änderungen im Container können überschrieben werden.",
|
||||||
|
"[telekom-auth]",
|
||||||
|
"type=auth",
|
||||||
|
"auth_type=userpass",
|
||||||
|
`username=${authUser}`,
|
||||||
|
`password=${password}`,
|
||||||
|
"",
|
||||||
|
"[telekom-aor]",
|
||||||
|
"type=aor",
|
||||||
|
`contact=sip:${registrar}`,
|
||||||
|
"",
|
||||||
|
"[telekom]",
|
||||||
|
"type=endpoint",
|
||||||
|
"transport=transport-udp",
|
||||||
|
"context=from-telekom",
|
||||||
|
"disallow=all",
|
||||||
|
"allow=alaw,ulaw",
|
||||||
|
"aors=telekom-aor",
|
||||||
|
"outbound_auth=telekom-auth",
|
||||||
|
`from_user=${sipUser}`,
|
||||||
|
`from_domain=${registrar}`,
|
||||||
|
`callerid=Telekom <${callerId}>`,
|
||||||
|
"direct_media=no",
|
||||||
|
"force_rport=yes",
|
||||||
|
"rewrite_contact=yes",
|
||||||
|
"rtp_symmetric=yes",
|
||||||
|
"timers=no",
|
||||||
|
"",
|
||||||
|
"[telekom-identify]",
|
||||||
|
"type=identify",
|
||||||
|
"endpoint=telekom",
|
||||||
|
`match=${registrar}`,
|
||||||
|
"",
|
||||||
|
"[telekom-registration]",
|
||||||
|
"type=registration",
|
||||||
|
"transport=transport-udp",
|
||||||
|
"outbound_auth=telekom-auth",
|
||||||
|
`server_uri=sip:${registrar}`,
|
||||||
|
`client_uri=sip:${sipUser}@${registrar}`,
|
||||||
|
`contact_user=${sipUser}`,
|
||||||
|
"retry_interval=60",
|
||||||
|
"forbidden_retry_interval=300",
|
||||||
|
"expiration=480",
|
||||||
|
"line=yes",
|
||||||
|
"endpoint=telekom",
|
||||||
|
"",
|
||||||
|
].join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderTelekomExtensionsConfig = (trunk: any) => {
|
||||||
|
if (!trunk?.enabled) {
|
||||||
|
return [
|
||||||
|
"; Von FEDEO generiert.",
|
||||||
|
"; Telekom-Routing ist deaktiviert.",
|
||||||
|
"",
|
||||||
|
].join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
const inboundExtension = asteriskValue(trunk.inboundExtension) || "1001"
|
||||||
|
const outboundPrefix = asteriskValue(trunk.outboundPrefix) || "0"
|
||||||
|
const escapedPrefix = outboundPrefix.replace(/[^0-9*#+]/g, "")
|
||||||
|
const callerId = asteriskValue(trunk.callerId) || asteriskValue(trunk.sipUser)
|
||||||
|
|
||||||
|
return [
|
||||||
|
"; Von FEDEO generiert. Änderungen im Container können überschrieben werden.",
|
||||||
|
"[fedeo-local]",
|
||||||
|
escapedPrefix
|
||||||
|
? `exten => _${escapedPrefix}X.,1,NoOp(FEDEO ausgehend über Telekom: $` + "{EXTEN})"
|
||||||
|
: "exten => _X.,1,NoOp(FEDEO ausgehend über Telekom: ${EXTEN})",
|
||||||
|
` same => n,Set(CALLERID(num)=${callerId})`,
|
||||||
|
" same => n,Dial(PJSIP/${EXTEN}@telekom,60)",
|
||||||
|
" same => n,Hangup()",
|
||||||
|
"",
|
||||||
|
"exten => _+X.,1,NoOp(FEDEO ausgehend über Telekom: ${EXTEN})",
|
||||||
|
` same => n,Set(CALLERID(num)=${callerId})`,
|
||||||
|
" same => n,Dial(PJSIP/${EXTEN}@telekom,60)",
|
||||||
|
" same => n,Hangup()",
|
||||||
|
"",
|
||||||
|
"[from-telekom]",
|
||||||
|
"exten => s,1,NoOp(FEDEO eingehend über Telekom)",
|
||||||
|
` same => n,Dial(PJSIP/${inboundExtension},30)`,
|
||||||
|
" same => n,Hangup()",
|
||||||
|
"",
|
||||||
|
"exten => _X!,1,NoOp(FEDEO eingehend über Telekom: ${EXTEN})",
|
||||||
|
` same => n,Dial(PJSIP/${inboundExtension},30)`,
|
||||||
|
" same => n,Hangup()",
|
||||||
|
"",
|
||||||
|
].join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeAsteriskTrunkConfig = async (trunk: any) => {
|
||||||
|
const targetDir = asteriskGeneratedDir()
|
||||||
|
await fs.mkdir(targetDir, { recursive: true })
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
{
|
||||||
|
name: "pjsip.telekom.conf",
|
||||||
|
content: renderTelekomPjsipConfig(trunk),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extensions.telekom.conf",
|
||||||
|
content: renderTelekomExtensionsConfig(trunk),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
await Promise.all(files.map(async (file) => {
|
||||||
|
const target = path.join(targetDir, file.name)
|
||||||
|
await fs.writeFile(target, `${file.content}\n`, { mode: 0o600 })
|
||||||
|
}))
|
||||||
|
|
||||||
|
return files.map((file) => path.join(targetDir, file.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
const runAsteriskAmiCommand = async (command: string, timeoutMs = 5000) => {
|
||||||
|
const config = asteriskAmiConfig()
|
||||||
|
|
||||||
|
return await new Promise<{ command: string, ok: boolean, raw: string }>((resolve, reject) => {
|
||||||
|
const socket = net.createConnection(config.port, config.host)
|
||||||
|
let raw = ""
|
||||||
|
let settled = false
|
||||||
|
|
||||||
|
const finish = (ok: boolean) => {
|
||||||
|
if (settled) return
|
||||||
|
settled = true
|
||||||
|
socket.destroy()
|
||||||
|
resolve({ command, ok, raw })
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.setTimeout(timeoutMs)
|
||||||
|
socket.on("connect", () => {
|
||||||
|
socket.write([
|
||||||
|
"Action: Login",
|
||||||
|
`Username: ${config.username}`,
|
||||||
|
`Secret: ${config.password}`,
|
||||||
|
"Events: off",
|
||||||
|
"",
|
||||||
|
"Action: Command",
|
||||||
|
`Command: ${command}`,
|
||||||
|
"",
|
||||||
|
"Action: Logoff",
|
||||||
|
"",
|
||||||
|
].join("\r\n"))
|
||||||
|
})
|
||||||
|
socket.on("data", (chunk) => {
|
||||||
|
raw += chunk.toString("utf8")
|
||||||
|
if (raw.includes("Goodbye")) finish(!raw.includes("Authentication failed"))
|
||||||
|
})
|
||||||
|
socket.on("timeout", () => finish(raw.length > 0))
|
||||||
|
socket.on("end", () => finish(!raw.includes("Authentication failed")))
|
||||||
|
socket.on("error", reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const runAsteriskReload = async () => {
|
||||||
|
const commands = [
|
||||||
|
"pjsip reload",
|
||||||
|
"dialplan reload",
|
||||||
|
"pjsip send register telekom-registration",
|
||||||
|
]
|
||||||
|
const results = []
|
||||||
|
|
||||||
|
for (const command of commands) {
|
||||||
|
results.push(await runAsteriskAmiCommand(command))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: results.every((result) => result.ok),
|
||||||
|
commands: results,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const readAsteriskTrunkStatus = async () => {
|
||||||
|
const registrations = await runAsteriskAmiCommand("pjsip show registrations")
|
||||||
|
const raw = registrations.raw || ""
|
||||||
|
|
||||||
|
return {
|
||||||
|
reachable: registrations.ok,
|
||||||
|
registered: /telekom-registration[\s\S]*Registered/i.test(raw),
|
||||||
|
hasRegistration: raw.includes("telekom-registration"),
|
||||||
|
registrations: raw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function telephonyRoutes(server: FastifyInstance) {
|
export default async function telephonyRoutes(server: FastifyInstance) {
|
||||||
const loadTenantTrunk = async (tenantId: number | null, provider = "telekom") => {
|
const loadTenantTrunk = async (tenantId: number | null, provider = "telekom") => {
|
||||||
if (!tenantId) return null
|
if (!tenantId) return null
|
||||||
@@ -276,6 +491,58 @@ export default async function telephonyRoutes(server: FastifyInstance) {
|
|||||||
return sanitizeTrunk(created)
|
return sanitizeTrunk(created)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
server.post("/telephony/trunk-config/apply", async (req, reply) => {
|
||||||
|
const tenantId = requireTenant(req.user.tenant_id)
|
||||||
|
const trunk = await loadTenantTrunk(tenantId)
|
||||||
|
|
||||||
|
if (!trunk) {
|
||||||
|
return reply.code(404).send({ error: "Telefonie-Trunk ist noch nicht konfiguriert." })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trunk.enabled && (!trunk.sipUser || !trunk.password)) {
|
||||||
|
return reply.code(400).send({
|
||||||
|
error: "SIP-ID und Kennwort sind erforderlich, bevor der Trunk angewendet werden kann.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await writeAsteriskTrunkConfig(trunk)
|
||||||
|
let reload: any = null
|
||||||
|
let status: any = null
|
||||||
|
let warning: string | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
reload = await runAsteriskReload()
|
||||||
|
status = await readAsteriskTrunkStatus()
|
||||||
|
} catch (error: any) {
|
||||||
|
warning = error?.message || "Asterisk konnte nicht neu geladen werden."
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
generated: true,
|
||||||
|
files,
|
||||||
|
trunk: sanitizeTrunk(trunk),
|
||||||
|
reload,
|
||||||
|
status,
|
||||||
|
warning,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
server.get("/telephony/trunk-status", async (req, reply) => {
|
||||||
|
requireTenant(req.user.tenant_id)
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await readAsteriskTrunkStatus()
|
||||||
|
} catch (error: any) {
|
||||||
|
return reply.code(200).send({
|
||||||
|
reachable: false,
|
||||||
|
registered: false,
|
||||||
|
hasRegistration: false,
|
||||||
|
registrations: "",
|
||||||
|
message: error?.message || "Asterisk-AMI ist nicht erreichbar.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
server.get("/telephony/calls", async (req) => {
|
server.get("/telephony/calls", async (req) => {
|
||||||
const tenantId = requireTenant(req.user.tenant_id)
|
const tenantId = requireTenant(req.user.tenant_id)
|
||||||
const limit = Math.min(
|
const limit = Math.min(
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ services:
|
|||||||
- TELEPHONY_EXTERNAL_PROVIDER=${TELEPHONY_EXTERNAL_PROVIDER:-}
|
- TELEPHONY_EXTERNAL_PROVIDER=${TELEPHONY_EXTERNAL_PROVIDER:-}
|
||||||
- TELEPHONY_EXTERNAL_ENABLED=${TELEPHONY_EXTERNAL_ENABLED:-false}
|
- TELEPHONY_EXTERNAL_ENABLED=${TELEPHONY_EXTERNAL_ENABLED:-false}
|
||||||
- TELEPHONY_EXTERNAL_INBOUND_EXTENSION=${TELEPHONY_EXTERNAL_INBOUND_EXTENSION:-1001}
|
- TELEPHONY_EXTERNAL_INBOUND_EXTENSION=${TELEPHONY_EXTERNAL_INBOUND_EXTENSION:-1001}
|
||||||
|
- TELEPHONY_ASTERISK_GENERATED_DIR=${TELEPHONY_ASTERISK_GENERATED_DIR:-/var/lib/fedeo/asterisk/generated}
|
||||||
|
- TELEPHONY_ASTERISK_AMI_HOST=${TELEPHONY_ASTERISK_AMI_HOST:-asterisk-dev}
|
||||||
|
- TELEPHONY_ASTERISK_AMI_PORT=${TELEPHONY_ASTERISK_AMI_PORT:-5038}
|
||||||
|
- TELEPHONY_ASTERISK_AMI_USER=${TELEPHONY_ASTERISK_AMI_USER:-fedeo}
|
||||||
|
- TELEPHONY_ASTERISK_AMI_PASSWORD=${TELEPHONY_ASTERISK_AMI_PASSWORD:-fedeo-ami-dev}
|
||||||
|
volumes:
|
||||||
|
- asterisk-generated:/var/lib/fedeo/asterisk/generated
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
labels:
|
labels:
|
||||||
@@ -124,7 +131,7 @@ services:
|
|||||||
- -c
|
- -c
|
||||||
- /usr/local/bin/render-asterisk-config.sh && asterisk -f
|
- /usr/local/bin/render-asterisk-config.sh && asterisk -f
|
||||||
volumes:
|
volumes:
|
||||||
- ./telephony/asterisk:/etc/asterisk:ro
|
- ./telephony/asterisk:/etc/asterisk
|
||||||
- asterisk-generated:/etc/asterisk/generated
|
- asterisk-generated:/etc/asterisk/generated
|
||||||
- ./telephony/render-asterisk-config.sh:/usr/local/bin/render-asterisk-config.sh:ro
|
- ./telephony/render-asterisk-config.sh:/usr/local/bin/render-asterisk-config.sh:ro
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ TELEPHONY_TELEKOM_AUTH_USER=<anschlusskennung><zugangsnummer>#<mitbenutzernummer
|
|||||||
|
|
||||||
Das Kennwort wird nicht wieder an das Frontend zurückgegeben. In der Oberfläche siehst du nur, ob ein Kennwort gespeichert ist.
|
Das Kennwort wird nicht wieder an das Frontend zurückgegeben. In der Oberfläche siehst du nur, ob ein Kennwort gespeichert ist.
|
||||||
|
|
||||||
|
Nach dem Speichern muss die Konfiguration mit **In Asterisk anwenden** in die Asterisk-Include-Dateien geschrieben werden. FEDEO lädt danach PJSIP und den Dialplan über AMI neu und fordert die Telekom-Registration an. Den Laufzeitstatus findest du unter **Kommunikation -> Telefonie Setup** im Bereich **Externe Telefonie**.
|
||||||
|
|
||||||
## `.env` Fallback
|
## `.env` Fallback
|
||||||
|
|
||||||
Die `.env`-Werte bleiben nur als lokaler Fallback für den Asterisk-Teststack erhalten, falls keine mandantenbezogene Konfiguration gepflegt ist.
|
Die `.env`-Werte bleiben nur als lokaler Fallback für den Asterisk-Teststack erhalten, falls keine mandantenbezogene Konfiguration gepflegt ist.
|
||||||
@@ -42,6 +44,11 @@ TELEPHONY_TELEKOM_PASSWORD=<persönliches_kennwort_oder_sip_kennwort>
|
|||||||
TELEPHONY_TELEKOM_CALLER_ID=<rufnummer_mit_vorwahl_ohne_sonderzeichen>
|
TELEPHONY_TELEKOM_CALLER_ID=<rufnummer_mit_vorwahl_ohne_sonderzeichen>
|
||||||
TELEPHONY_TELEKOM_INBOUND_EXTENSION=1001
|
TELEPHONY_TELEKOM_INBOUND_EXTENSION=1001
|
||||||
TELEPHONY_TELEKOM_OUTBOUND_PREFIX=0
|
TELEPHONY_TELEKOM_OUTBOUND_PREFIX=0
|
||||||
|
TELEPHONY_ASTERISK_GENERATED_DIR=/var/lib/fedeo/asterisk/generated
|
||||||
|
TELEPHONY_ASTERISK_AMI_HOST=asterisk-dev
|
||||||
|
TELEPHONY_ASTERISK_AMI_PORT=5038
|
||||||
|
TELEPHONY_ASTERISK_AMI_USER=fedeo
|
||||||
|
TELEPHONY_ASTERISK_AMI_PASSWORD=fedeo-ami-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Wenn `TELEPHONY_TELEKOM_AUTH_USER` leer bleibt, verwendet Asterisk automatisch `TELEPHONY_TELEKOM_SIP_USER` als Authentifizierungsnutzer.
|
Wenn `TELEPHONY_TELEKOM_AUTH_USER` leer bleibt, verwendet Asterisk automatisch `TELEPHONY_TELEKOM_SIP_USER` als Authentifizierungsnutzer.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
const toast = useToast()
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
statusLoading,
|
statusLoading,
|
||||||
@@ -20,7 +21,55 @@ const {
|
|||||||
testWebSocket,
|
testWebSocket,
|
||||||
} = useTelephonySoftphone()
|
} = useTelephonySoftphone()
|
||||||
|
|
||||||
onMounted(loadTelephony)
|
const trunkStatus = ref(null)
|
||||||
|
const trunkStatusLoading = ref(false)
|
||||||
|
const trunkApplying = ref(false)
|
||||||
|
|
||||||
|
const loadTrunkStatus = async () => {
|
||||||
|
trunkStatusLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
trunkStatus.value = await useNuxtApp().$api("/api/telephony/trunk-status")
|
||||||
|
} catch (error) {
|
||||||
|
trunkStatus.value = {
|
||||||
|
reachable: false,
|
||||||
|
registered: false,
|
||||||
|
hasRegistration: false,
|
||||||
|
message: error?.data?.error || error?.message || "Trunk-Status konnte nicht geladen werden."
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
trunkStatusLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyTrunk = async () => {
|
||||||
|
trunkApplying.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await useNuxtApp().$api("/api/telephony/trunk-config/apply", {
|
||||||
|
method: "POST"
|
||||||
|
})
|
||||||
|
trunkStatus.value = res?.status || trunkStatus.value
|
||||||
|
toast.add({
|
||||||
|
title: res?.warning ? "Trunk-Konfiguration geschrieben" : "Trunk angewendet",
|
||||||
|
description: res?.warning || (res?.status?.registered ? "Telekom-Registration ist aktiv." : "Asterisk wurde neu geladen."),
|
||||||
|
color: res?.warning ? "orange" : "success"
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({
|
||||||
|
title: "Trunk konnte nicht angewendet werden",
|
||||||
|
description: error?.data?.error || error?.message,
|
||||||
|
color: "error"
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
trunkApplying.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadTelephony()
|
||||||
|
await loadTrunkStatus()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -143,6 +192,33 @@ onMounted(loadTelephony)
|
|||||||
:description="config?.external?.callerIdConfigured ? 'Konfiguriert' : 'Optional'"
|
:description="config?.external?.callerIdConfigured ? 'Konfiguriert' : 'Optional'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 grid gap-3 lg:grid-cols-[1fr_auto] lg:items-center">
|
||||||
|
<UAlert
|
||||||
|
:color="trunkStatus?.registered ? 'success' : (trunkStatus?.reachable ? 'warning' : 'neutral')"
|
||||||
|
:icon="trunkStatus?.registered ? 'i-heroicons-check-circle' : (trunkStatus?.reachable ? 'i-heroicons-exclamation-triangle' : 'i-heroicons-signal-slash')"
|
||||||
|
:title="trunkStatus?.registered ? 'Telekom-Trunk registriert' : (trunkStatus?.hasRegistration ? 'Telekom-Trunk nicht registriert' : 'Keine Telekom-Registration aktiv')"
|
||||||
|
:description="trunkStatus?.message || (trunkStatus?.reachable ? 'Asterisk-AMI ist erreichbar.' : 'Asterisk-AMI ist noch nicht erreichbar.')"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-wrap gap-2 lg:justify-end">
|
||||||
|
<UButton
|
||||||
|
icon="i-heroicons-signal"
|
||||||
|
variant="outline"
|
||||||
|
:loading="trunkStatusLoading"
|
||||||
|
@click="loadTrunkStatus"
|
||||||
|
>
|
||||||
|
Status prüfen
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
icon="i-heroicons-arrow-path-rounded-square"
|
||||||
|
variant="soft"
|
||||||
|
:loading="trunkApplying"
|
||||||
|
@click="applyTrunk"
|
||||||
|
>
|
||||||
|
Trunk anwenden
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</UCard>
|
</UCard>
|
||||||
|
|
||||||
<div class="grid gap-4 lg:grid-cols-[1.1fr_0.9fr]">
|
<div class="grid gap-4 lg:grid-cols-[1.1fr_0.9fr]">
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ const mcpTokenForm = reactive({
|
|||||||
})
|
})
|
||||||
const telephonyTrunkLoading = ref(false)
|
const telephonyTrunkLoading = ref(false)
|
||||||
const telephonyTrunkSaving = ref(false)
|
const telephonyTrunkSaving = ref(false)
|
||||||
|
const telephonyTrunkApplying = ref(false)
|
||||||
const telephonyTrunkForm = reactive({
|
const telephonyTrunkForm = reactive({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
registrar: "tel.t-online.de",
|
registrar: "tel.t-online.de",
|
||||||
@@ -272,6 +273,30 @@ const saveTelephonyTrunk = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const applyTelephonyTrunk = async () => {
|
||||||
|
telephonyTrunkApplying.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await useNuxtApp().$api("/api/telephony/trunk-config/apply", {
|
||||||
|
method: "POST"
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
title: res?.warning ? "Trunk-Konfiguration geschrieben" : "Telefonie-Trunk angewendet",
|
||||||
|
description: res?.warning || (res?.status?.registered ? "Telekom-Registration ist aktiv." : "Asterisk wurde neu geladen."),
|
||||||
|
color: res?.warning ? "orange" : "success"
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({
|
||||||
|
title: "Telefonie-Trunk konnte nicht angewendet werden",
|
||||||
|
description: error?.data?.error || error?.message,
|
||||||
|
color: "error"
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
telephonyTrunkApplying.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createMcpToken = async () => {
|
const createMcpToken = async () => {
|
||||||
if (!mcpTokenForm.name?.trim()) {
|
if (!mcpTokenForm.name?.trim()) {
|
||||||
toast.add({ title: "Name fehlt", description: "Bitte gib einen Namen für den Token an.", color: "orange" })
|
toast.add({ title: "Name fehlt", description: "Bitte gib einen Namen für den Token an.", color: "orange" })
|
||||||
@@ -516,6 +541,15 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
Telefonie-Trunk speichern
|
Telefonie-Trunk speichern
|
||||||
</UButton>
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
icon="i-heroicons-arrow-path-rounded-square"
|
||||||
|
color="primary"
|
||||||
|
variant="soft"
|
||||||
|
:loading="telephonyTrunkApplying"
|
||||||
|
@click="applyTelephonyTrunk"
|
||||||
|
>
|
||||||
|
In Asterisk anwenden
|
||||||
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,9 @@
|
|||||||
[general]
|
[general]
|
||||||
enabled=no
|
enabled=yes
|
||||||
|
port=5038
|
||||||
|
bindaddr=0.0.0.0
|
||||||
|
|
||||||
|
[fedeo]
|
||||||
|
secret=fedeo-ami-dev
|
||||||
|
read=system,call,log,verbose,command,agent,user,config,dtmf,reporting,cdr,dialplan
|
||||||
|
write=system,call,log,verbose,command,agent,user,config,originate,reporting
|
||||||
|
|||||||
Reference in New Issue
Block a user