KI-AGENT: Telefonie Nebenstellen in Einstellungen integrieren
This commit is contained in:
406
frontend/pages/settings/telephony.vue
Normal file
406
frontend/pages/settings/telephony.vue
Normal file
@@ -0,0 +1,406 @@
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
const telephonyTrunkLoading = ref(false)
|
||||
const telephonyTrunkSaving = ref(false)
|
||||
const telephonyTrunkApplying = ref(false)
|
||||
const telephonyExtensionsLoading = ref(false)
|
||||
const trunkStatusLoading = ref(false)
|
||||
const trunkStatus = ref(null)
|
||||
const telephonyExtensions = ref([])
|
||||
|
||||
const telephonyProviderOptions = [
|
||||
{ label: "Easybell", value: "easybell" },
|
||||
{ label: "Telekom", value: "telekom" }
|
||||
]
|
||||
|
||||
const telephonyProviderDefaults = {
|
||||
easybell: {
|
||||
registrar: "voip.easybell.de",
|
||||
title: "Easybell SIP-Trunk",
|
||||
description: "Trage die SIP-Daten aus dem Easybell-Kundenportal ein. Die Absendernummer ist die Stammnummer im internationalen Format ohne führende 00."
|
||||
},
|
||||
telekom: {
|
||||
registrar: "tel.t-online.de",
|
||||
title: "Telekom Zugangsdaten",
|
||||
description: "Die SIP-ID ist meistens die Rufnummer mit Vorwahl ohne Leerzeichen und Sonderzeichen. Klassische Zugangsdaten können als Auth-User hinterlegt werden."
|
||||
}
|
||||
}
|
||||
|
||||
const telephonyTrunkForm = reactive({
|
||||
provider: "easybell",
|
||||
enabled: false,
|
||||
registrar: "voip.easybell.de",
|
||||
sipUser: "",
|
||||
authUser: "",
|
||||
password: "",
|
||||
passwordConfigured: false,
|
||||
clearPassword: false,
|
||||
callerId: "",
|
||||
inboundExtension: "1001",
|
||||
defaultRouteExtensionId: null,
|
||||
outboundPrefix: "0",
|
||||
externalSignalingAddress: "",
|
||||
externalMediaAddress: "",
|
||||
localNetworks: "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8"
|
||||
})
|
||||
|
||||
const activeTelephonyProvider = computed(() =>
|
||||
telephonyProviderDefaults[telephonyTrunkForm.provider] || telephonyProviderDefaults.easybell
|
||||
)
|
||||
|
||||
const telephonyRouteOptions = computed(() => telephonyExtensions.value.map((extension) => ({
|
||||
label: `${extension.extension} - ${extension.targetLabel || extension.displayName || targetTypeLabel(extension.targetType)}`,
|
||||
value: extension.id
|
||||
})))
|
||||
|
||||
const targetTypeLabel = (targetType) => {
|
||||
if (targetType === "team") return "Team"
|
||||
if (targetType === "branch") return "Niederlassung"
|
||||
return "Benutzer"
|
||||
}
|
||||
|
||||
const loadTelephonyTrunk = async () => {
|
||||
telephonyTrunkLoading.value = true
|
||||
|
||||
try {
|
||||
const res = await useNuxtApp().$api("/api/telephony/trunk-config", {
|
||||
params: { provider: telephonyTrunkForm.provider }
|
||||
})
|
||||
telephonyTrunkForm.provider = res?.provider || telephonyTrunkForm.provider || "easybell"
|
||||
telephonyTrunkForm.enabled = Boolean(res?.enabled)
|
||||
telephonyTrunkForm.registrar = res?.registrar || activeTelephonyProvider.value.registrar
|
||||
telephonyTrunkForm.sipUser = res?.sipUser || ""
|
||||
telephonyTrunkForm.authUser = res?.authUser || ""
|
||||
telephonyTrunkForm.password = ""
|
||||
telephonyTrunkForm.passwordConfigured = Boolean(res?.passwordConfigured)
|
||||
telephonyTrunkForm.clearPassword = false
|
||||
telephonyTrunkForm.callerId = res?.callerId || ""
|
||||
telephonyTrunkForm.inboundExtension = res?.inboundExtension || "1001"
|
||||
telephonyTrunkForm.defaultRouteExtensionId = res?.defaultRouteExtensionId || null
|
||||
telephonyTrunkForm.outboundPrefix = res?.outboundPrefix || "0"
|
||||
telephonyTrunkForm.externalSignalingAddress = res?.externalSignalingAddress || ""
|
||||
telephonyTrunkForm.externalMediaAddress = res?.externalMediaAddress || ""
|
||||
telephonyTrunkForm.localNetworks = res?.localNetworks || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8"
|
||||
} catch (error) {
|
||||
toast.add({ title: "Telefonie konnte nicht geladen werden", color: "error" })
|
||||
} finally {
|
||||
telephonyTrunkLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const saveTelephonyTrunk = async () => {
|
||||
if (telephonyTrunkForm.enabled && (!telephonyTrunkForm.sipUser?.trim() || (!telephonyTrunkForm.password?.trim() && !telephonyTrunkForm.passwordConfigured))) {
|
||||
toast.add({
|
||||
title: "Trunk-Zugang unvollständig",
|
||||
description: "Bitte gib mindestens SIP-ID und Kennwort an.",
|
||||
color: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
telephonyTrunkSaving.value = true
|
||||
|
||||
try {
|
||||
const res = await useNuxtApp().$api("/api/telephony/trunk-config", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
provider: telephonyTrunkForm.provider,
|
||||
enabled: telephonyTrunkForm.enabled,
|
||||
registrar: telephonyTrunkForm.registrar,
|
||||
sipUser: telephonyTrunkForm.sipUser,
|
||||
authUser: telephonyTrunkForm.authUser,
|
||||
password: telephonyTrunkForm.password,
|
||||
clearPassword: telephonyTrunkForm.clearPassword,
|
||||
callerId: telephonyTrunkForm.callerId,
|
||||
inboundExtension: telephonyTrunkForm.inboundExtension,
|
||||
defaultRouteExtensionId: telephonyTrunkForm.defaultRouteExtensionId,
|
||||
outboundPrefix: telephonyTrunkForm.outboundPrefix,
|
||||
externalSignalingAddress: telephonyTrunkForm.externalSignalingAddress,
|
||||
externalMediaAddress: telephonyTrunkForm.externalMediaAddress,
|
||||
localNetworks: telephonyTrunkForm.localNetworks
|
||||
}
|
||||
})
|
||||
|
||||
telephonyTrunkForm.password = ""
|
||||
telephonyTrunkForm.passwordConfigured = Boolean(res?.passwordConfigured)
|
||||
telephonyTrunkForm.clearPassword = false
|
||||
toast.add({ title: "Telefonie gespeichert", color: "success" })
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Telefonie konnte nicht gespeichert werden",
|
||||
description: error?.data?.error || error?.message,
|
||||
color: "error"
|
||||
})
|
||||
} finally {
|
||||
telephonyTrunkSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadTelephonyExtensions = async () => {
|
||||
telephonyExtensionsLoading.value = true
|
||||
|
||||
try {
|
||||
const res = await useNuxtApp().$api("/api/telephony/extensions")
|
||||
telephonyExtensions.value = res?.rows || []
|
||||
} catch (error) {
|
||||
toast.add({ title: "Nebenstellen konnten nicht geladen werden", color: "error" })
|
||||
} finally {
|
||||
telephonyExtensionsLoading.value = 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 || "Status konnte nicht geladen werden."
|
||||
}
|
||||
} finally {
|
||||
trunkStatusLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const applyTelephonyTrunk = async () => {
|
||||
telephonyTrunkApplying.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 ? "Konfiguration geschrieben" : "Telefonie angewendet",
|
||||
description: res?.warning || (res?.status?.registered ? "Der SIP-Trunk ist registriert." : "Asterisk wurde neu geladen."),
|
||||
color: res?.warning ? "warning" : "success"
|
||||
})
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Telefonie konnte nicht angewendet werden",
|
||||
description: error?.data?.error || error?.message,
|
||||
color: "error"
|
||||
})
|
||||
} finally {
|
||||
telephonyTrunkApplying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => telephonyTrunkForm.provider, async (provider) => {
|
||||
const defaults = telephonyProviderDefaults[provider] || telephonyProviderDefaults.easybell
|
||||
if (!telephonyTrunkForm.registrar || ["tel.t-online.de", "voip.easybell.de"].includes(telephonyTrunkForm.registrar)) {
|
||||
telephonyTrunkForm.registrar = defaults.registrar
|
||||
}
|
||||
await loadTelephonyTrunk()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
loadTelephonyExtensions(),
|
||||
loadTelephonyTrunk(),
|
||||
loadTrunkStatus()
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar title="Telefonie Einstellungen">
|
||||
<template #right>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
variant="outline"
|
||||
:loading="telephonyTrunkLoading || telephonyExtensionsLoading || trunkStatusLoading"
|
||||
@click="Promise.all([loadTelephonyTrunk(), loadTelephonyExtensions(), loadTrunkStatus()])"
|
||||
>
|
||||
Aktualisieren
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardPanelContent>
|
||||
<div class="mx-auto flex max-w-6xl flex-col gap-5">
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-highlighted">Anschluss</h2>
|
||||
<p class="mt-1 text-sm text-muted">SIP-Trunk, Standardroute und öffentliche Erreichbarkeit.</p>
|
||||
</div>
|
||||
<UBadge :color="telephonyTrunkForm.enabled ? 'success' : 'neutral'" variant="soft">
|
||||
{{ telephonyTrunkForm.enabled ? "Aktiv" : "Inaktiv" }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UAlert
|
||||
:title="activeTelephonyProvider.title"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
:description="activeTelephonyProvider.description"
|
||||
/>
|
||||
|
||||
<div class="mt-5 grid gap-4 md:grid-cols-2">
|
||||
<UFormField label="Provider">
|
||||
<USelectMenu
|
||||
v-model="telephonyTrunkForm.provider"
|
||||
:items="telephonyProviderOptions"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Telefonie aktiv">
|
||||
<USwitch v-model="telephonyTrunkForm.enabled" />
|
||||
</UFormField>
|
||||
<UFormField label="Registrar">
|
||||
<UInput v-model="telephonyTrunkForm.registrar" :placeholder="activeTelephonyProvider.registrar" />
|
||||
</UFormField>
|
||||
<UFormField label="SIP-ID / Rufnummer">
|
||||
<UInput v-model="telephonyTrunkForm.sipUser" placeholder="SIP-Benutzername" />
|
||||
</UFormField>
|
||||
<UFormField label="Auth-User">
|
||||
<UInput v-model="telephonyTrunkForm.authUser" placeholder="Optional" />
|
||||
</UFormField>
|
||||
<UFormField :label="telephonyTrunkForm.passwordConfigured ? 'Kennwort ersetzen' : 'Kennwort'">
|
||||
<UInput
|
||||
v-model="telephonyTrunkForm.password"
|
||||
type="password"
|
||||
:placeholder="telephonyTrunkForm.passwordConfigured ? 'Bereits gespeichert' : 'SIP-Kennwort'"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Gespeichertes Kennwort löschen">
|
||||
<USwitch
|
||||
v-model="telephonyTrunkForm.clearPassword"
|
||||
:disabled="!telephonyTrunkForm.passwordConfigured"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Absendernummer">
|
||||
<UInput v-model="telephonyTrunkForm.callerId" placeholder="Optional, z. B. 49301234567" />
|
||||
</UFormField>
|
||||
<UFormField label="Eingehende Nebenstelle">
|
||||
<UInput v-model="telephonyTrunkForm.inboundExtension" placeholder="1001" />
|
||||
</UFormField>
|
||||
<UFormField label="Standardroute">
|
||||
<USelectMenu
|
||||
v-model="telephonyTrunkForm.defaultRouteExtensionId"
|
||||
:items="telephonyRouteOptions"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
placeholder="Nebenstelle auswählen"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Ausgehender Prefix">
|
||||
<UInput v-model="telephonyTrunkForm.outboundPrefix" placeholder="0" />
|
||||
</UFormField>
|
||||
<UFormField label="Öffentliche Signaling-Adresse">
|
||||
<UInput v-model="telephonyTrunkForm.externalSignalingAddress" placeholder="Öffentliche IP oder DNS-Name" />
|
||||
</UFormField>
|
||||
<UFormField label="Öffentliche Medien-Adresse">
|
||||
<UInput v-model="telephonyTrunkForm.externalMediaAddress" placeholder="Leer = Signaling-Adresse" />
|
||||
</UFormField>
|
||||
<UFormField label="Lokale Netze">
|
||||
<UInput v-model="telephonyTrunkForm.localNetworks" placeholder="172.16.0.0/12,192.168.0.0/16,10.0.0.0/8" />
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 flex flex-wrap gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-check"
|
||||
:loading="telephonyTrunkSaving"
|
||||
@click="saveTelephonyTrunk"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path-rounded-square"
|
||||
variant="soft"
|
||||
:loading="telephonyTrunkApplying"
|
||||
@click="applyTelephonyTrunk"
|
||||
>
|
||||
Anwenden
|
||||
</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-highlighted">Status</h2>
|
||||
<p class="mt-1 text-sm text-muted">Registrierung des externen Anschlusses.</p>
|
||||
</div>
|
||||
<UButton
|
||||
icon="i-heroicons-signal"
|
||||
variant="outline"
|
||||
:loading="trunkStatusLoading"
|
||||
@click="loadTrunkStatus"
|
||||
>
|
||||
Prüfen
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 ? 'SIP-Trunk registriert' : (trunkStatus?.hasRegistration ? 'SIP-Trunk nicht registriert' : 'Keine Trunk-Registration aktiv')"
|
||||
:description="trunkStatus?.message || 'Noch kein Status geladen.'"
|
||||
/>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-highlighted">Nebenstellen</h2>
|
||||
<p class="mt-1 text-sm text-muted">Die Nebenstellen werden direkt am Benutzer, Team oder an der Niederlassung gepflegt.</p>
|
||||
</div>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
variant="outline"
|
||||
:loading="telephonyExtensionsLoading"
|
||||
@click="loadTelephonyExtensions"
|
||||
>
|
||||
Aktualisieren
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="divide-y divide-default rounded-md border border-default">
|
||||
<div
|
||||
v-for="extension in telephonyExtensions"
|
||||
:key="extension.id"
|
||||
class="grid gap-3 p-3 md:grid-cols-[7rem_1fr_auto] md:items-center"
|
||||
>
|
||||
<div class="font-mono text-lg font-semibold text-highlighted">
|
||||
{{ extension.extension }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-highlighted">
|
||||
{{ extension.targetLabel || extension.displayName || targetTypeLabel(extension.targetType) }}
|
||||
</div>
|
||||
<div class="text-sm text-muted">
|
||||
{{ targetTypeLabel(extension.targetType) }}
|
||||
<span v-if="extension.targetType !== 'user'">
|
||||
· Ziele: {{ extension.dialTargets?.length ? extension.dialTargets.join(", ") : "noch keine Benutzer-Nebenstelle" }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<UBadge :color="extension.enabled ? 'success' : 'neutral'" variant="soft">
|
||||
{{ extension.enabled ? "Aktiv" : "Inaktiv" }}
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<div v-if="!telephonyExtensions.length" class="p-4 text-sm text-muted">
|
||||
Noch keine Nebenstellen angelegt.
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</UDashboardPanelContent>
|
||||
</template>
|
||||
@@ -562,17 +562,6 @@ const savePlanningBoardConfig = async () => {
|
||||
setupPage()
|
||||
onMounted(() => {
|
||||
loadMcpTokens()
|
||||
loadTelephonyExtensionOptions()
|
||||
loadTelephonyExtensions()
|
||||
loadTelephonyTrunk()
|
||||
})
|
||||
|
||||
watch(() => telephonyTrunkForm.provider, async (provider) => {
|
||||
const defaults = telephonyProviderDefaults[provider] || telephonyProviderDefaults.easybell
|
||||
if (!telephonyTrunkForm.registrar || ["tel.t-online.de", "voip.easybell.de"].includes(telephonyTrunkForm.registrar)) {
|
||||
telephonyTrunkForm.registrar = defaults.registrar
|
||||
}
|
||||
await loadTelephonyTrunk()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -727,248 +716,14 @@ watch(() => telephonyTrunkForm.provider, async (provider) => {
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Integrationen'">
|
||||
<UCard class="mt-5">
|
||||
<div class="mb-8 space-y-6">
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-highlighted">Telefonie-Trunk</h3>
|
||||
<p class="text-sm text-muted">Konfiguriere den SIP-Trunk für externe Anrufe über Asterisk.</p>
|
||||
</div>
|
||||
<UBadge :color="telephonyTrunkForm.enabled ? 'success' : 'neutral'" variant="soft">
|
||||
{{ telephonyTrunkForm.enabled ? "Aktiv" : "Inaktiv" }}
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
:title="activeTelephonyProvider.title"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
>
|
||||
<template #description>
|
||||
<p class="text-sm">
|
||||
{{ activeTelephonyProvider.description }}
|
||||
</p>
|
||||
</template>
|
||||
</UAlert>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<UFormField label="Provider">
|
||||
<USelectMenu
|
||||
v-model="telephonyTrunkForm.provider"
|
||||
:items="telephonyProviderOptions"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Trunk aktivieren">
|
||||
<USwitch v-model="telephonyTrunkForm.enabled" />
|
||||
</UFormField>
|
||||
<UFormField label="Registrar">
|
||||
<UInput v-model="telephonyTrunkForm.registrar" :placeholder="activeTelephonyProvider.registrar" />
|
||||
</UFormField>
|
||||
<UFormField label="SIP-ID / Rufnummer">
|
||||
<UInput v-model="telephonyTrunkForm.sipUser" placeholder="SIP-Benutzername" />
|
||||
</UFormField>
|
||||
<UFormField label="Auth-User">
|
||||
<UInput v-model="telephonyTrunkForm.authUser" placeholder="Optional" />
|
||||
</UFormField>
|
||||
<UFormField :label="telephonyTrunkForm.passwordConfigured ? 'Kennwort ersetzen' : 'Kennwort'">
|
||||
<UInput
|
||||
v-model="telephonyTrunkForm.password"
|
||||
type="password"
|
||||
:placeholder="telephonyTrunkForm.passwordConfigured ? 'Bereits gespeichert' : 'SIP-Kennwort'"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Gespeichertes Kennwort löschen">
|
||||
<USwitch
|
||||
v-model="telephonyTrunkForm.clearPassword"
|
||||
:disabled="!telephonyTrunkForm.passwordConfigured"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Absendernummer">
|
||||
<UInput v-model="telephonyTrunkForm.callerId" placeholder="Optional, z. B. 49301234567" />
|
||||
</UFormField>
|
||||
<UFormField label="Eingehende Nebenstelle">
|
||||
<UInput v-model="telephonyTrunkForm.inboundExtension" placeholder="1001" />
|
||||
</UFormField>
|
||||
<UFormField label="Standardroute">
|
||||
<USelectMenu
|
||||
v-model="telephonyTrunkForm.defaultRouteExtensionId"
|
||||
:items="telephonyRouteOptions"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
placeholder="Nebenstelle auswählen"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Ausgehender Prefix">
|
||||
<UInput v-model="telephonyTrunkForm.outboundPrefix" placeholder="0" />
|
||||
</UFormField>
|
||||
<UFormField label="Öffentliche Signaling-Adresse">
|
||||
<UInput v-model="telephonyTrunkForm.externalSignalingAddress" placeholder="Öffentliche IP oder DNS-Name" />
|
||||
</UFormField>
|
||||
<UFormField label="Öffentliche Medien-Adresse">
|
||||
<UInput v-model="telephonyTrunkForm.externalMediaAddress" placeholder="Leer = Signaling-Adresse" />
|
||||
</UFormField>
|
||||
<UFormField label="Lokale Netze">
|
||||
<UInput v-model="telephonyTrunkForm.localNetworks" placeholder="172.16.0.0/12,192.168.0.0/16,10.0.0.0/8" />
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
variant="outline"
|
||||
:loading="telephonyTrunkLoading"
|
||||
@click="loadTelephonyTrunk"
|
||||
>
|
||||
Laden
|
||||
</UButton>
|
||||
<UButton
|
||||
icon="i-heroicons-check"
|
||||
:loading="telephonyTrunkSaving"
|
||||
@click="saveTelephonyTrunk"
|
||||
>
|
||||
Telefonie-Trunk speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path-rounded-square"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
:loading="telephonyTrunkApplying"
|
||||
@click="applyTelephonyTrunk"
|
||||
>
|
||||
In Asterisk anwenden
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<USeparator />
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-highlighted">Nebenstellen</h4>
|
||||
<p class="text-sm text-muted">Ordne Benutzer, Teams und Niederlassungen routbaren Nebenstellen zu.</p>
|
||||
</div>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
variant="outline"
|
||||
:loading="telephonyExtensionsLoading"
|
||||
@click="loadTelephonyExtensions"
|
||||
>
|
||||
Aktualisieren
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<UFormField label="Zieltyp">
|
||||
<USelectMenu
|
||||
v-model="telephonyExtensionForm.targetType"
|
||||
:items="telephonyExtensionTargetTypes"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField v-if="telephonyExtensionForm.targetType === 'user'" label="Benutzer">
|
||||
<USelectMenu
|
||||
v-model="telephonyExtensionForm.targetUserId"
|
||||
:items="activeTelephonyTargetOptions"
|
||||
label-key="label"
|
||||
value-key="id"
|
||||
placeholder="Benutzer auswählen"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField v-else-if="telephonyExtensionForm.targetType === 'team'" label="Team">
|
||||
<USelectMenu
|
||||
v-model="telephonyExtensionForm.targetTeamId"
|
||||
:items="activeTelephonyTargetOptions"
|
||||
label-key="label"
|
||||
value-key="id"
|
||||
placeholder="Team auswählen"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField v-else label="Niederlassung">
|
||||
<USelectMenu
|
||||
v-model="telephonyExtensionForm.targetBranchId"
|
||||
:items="activeTelephonyTargetOptions"
|
||||
label-key="label"
|
||||
value-key="id"
|
||||
placeholder="Niederlassung auswählen"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField label="Nebenstelle">
|
||||
<UInput v-model="telephonyExtensionForm.extension" placeholder="1001" />
|
||||
</UFormField>
|
||||
<UFormField label="Anzeigename">
|
||||
<UInput v-model="telephonyExtensionForm.displayName" placeholder="Optional" />
|
||||
</UFormField>
|
||||
<UFormField label="SIP-Benutzername">
|
||||
<UInput v-model="telephonyExtensionForm.sipUsername" placeholder="Leer = Nebenstelle" />
|
||||
</UFormField>
|
||||
<UFormField label="SIP-Kennwort">
|
||||
<UInput v-model="telephonyExtensionForm.sipPassword" type="password" placeholder="Leer = automatisch" />
|
||||
</UFormField>
|
||||
<UFormField label="Aktiv">
|
||||
<USwitch v-model="telephonyExtensionForm.enabled" />
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-check"
|
||||
:loading="telephonyExtensionSaving"
|
||||
@click="saveTelephonyExtension"
|
||||
>
|
||||
{{ telephonyExtensionForm.id ? "Nebenstelle speichern" : "Nebenstelle anlegen" }}
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="telephonyExtensionForm.id"
|
||||
variant="outline"
|
||||
@click="resetTelephonyExtensionForm"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div class="divide-y divide-default rounded-md border border-default">
|
||||
<div
|
||||
v-for="extension in telephonyExtensions"
|
||||
:key="extension.id"
|
||||
class="flex flex-col gap-3 p-3 md:flex-row md:items-center md:justify-between"
|
||||
>
|
||||
<div>
|
||||
<div class="font-medium text-highlighted">
|
||||
{{ extension.extension }} - {{ extension.displayName || extension.targetType }}
|
||||
</div>
|
||||
<div class="text-sm text-muted">
|
||||
{{ extension.targetType }} · Ziele: {{ extension.dialTargets?.length ? extension.dialTargets.join(", ") : "noch keine Benutzer-Nebenstelle" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-pencil-square"
|
||||
variant="outline"
|
||||
@click="editTelephonyExtension(extension)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
<UButton
|
||||
icon="i-heroicons-trash"
|
||||
color="error"
|
||||
variant="soft"
|
||||
:loading="telephonyExtensionDeletingId === extension.id"
|
||||
@click="deleteTelephonyExtension(extension)"
|
||||
>
|
||||
Löschen
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!telephonyExtensions.length" class="p-3 text-sm text-muted">
|
||||
Noch keine Nebenstellen angelegt.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<USeparator class="mb-8" />
|
||||
<UAlert
|
||||
class="mb-8"
|
||||
color="neutral"
|
||||
icon="i-heroicons-phone"
|
||||
title="Telefonie ist jetzt eine eigene Einstellungsseite"
|
||||
description="SIP-Trunk, Standardroute und Nebenstellenübersicht findest du unter Einstellungen > Telefonie."
|
||||
:actions="[{ label: 'Telefonie öffnen', to: '/settings/telephony', icon: 'i-heroicons-arrow-top-right-on-square' }]"
|
||||
/>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
|
||||
Reference in New Issue
Block a user