Consistency + cleanup pass
- Route Twilio Lookup v2 through the on-prem remote agent so all
third-party calls (SIW, Google, Twilio) share the same network path
and future IP allow-lists / corp proxies don't break it silently
- Delete unused GOOGLE_APPLICATION_CREDENTIALS / serviceAccountKeyPath
wiring in config.js, .env.example, and README (never read by any code)
- Extract 150 lines of STORE_DEVICE_CUSTOMIZATIONS out of
src/webex/devices.js into src/webex/deviceCustomizations.js so device
config diffs are self-contained and devices.js stays focused on API
- runStep learns a {critical: true} option that re-throws on failure
instead of the legacy always-swallow behavior; mark
enableLocationForCalling critical in buildStore + stageStore so a
failure there aborts the flow cleanly rather than cascading into
dozens of downstream 404s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
8b32975eaf
commit
461caa8086
9 changed files with 212 additions and 187 deletions
|
|
@ -36,15 +36,10 @@ WS_TOKEN=change-me-to-a-long-random-string
|
||||||
|
|
||||||
# --- Google ---
|
# --- Google ---
|
||||||
# Simple REST API key used by Address Validation + Time Zone endpoints.
|
# Simple REST API key used by Address Validation + Time Zone endpoints.
|
||||||
|
# All Google calls are proxied through the on-prem remote agent (see WS_*
|
||||||
|
# above) so requests originate from the IP subnet the key allow-lists.
|
||||||
GOOGLE_API_KEY=your-google-api-key
|
GOOGLE_API_KEY=your-google-api-key
|
||||||
|
|
||||||
# Optional. Path to a Google service-account JSON key file (the one shaped
|
|
||||||
# like { type: "service_account", private_key: "-----BEGIN...", ... }).
|
|
||||||
# Not required for the REST calls above, but standard for any future code
|
|
||||||
# that uses google-auth-library / googleapis. Keep the file itself outside
|
|
||||||
# of git (config/google-service-account.json is already ignored).
|
|
||||||
GOOGLE_APPLICATION_CREDENTIALS=./config/google-service-account.json
|
|
||||||
|
|
||||||
# --- Runtime ---
|
# --- Runtime ---
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,9 @@ greetings, build auto-attendants, and clean up user licensing.
|
||||||
- A Webex bot token, a Webex integration (service account) with the scopes
|
- A Webex bot token, a Webex integration (service account) with the scopes
|
||||||
currently used by admin API calls, a Twilio lookup account, an SIW basic-auth
|
currently used by admin API calls, a Twilio lookup account, an SIW basic-auth
|
||||||
user, and a Google API key with Address Validation + Time Zone enabled.
|
user, and a Google API key with Address Validation + Time Zone enabled.
|
||||||
- An on-prem host that can reach Store Info Web and Google APIs from an
|
- An on-prem host that can reach Store Info Web, Google APIs, and Twilio
|
||||||
IP-whitelisted subnet, to run the remote agent (see
|
Lookup from an IP-whitelisted subnet, to run the remote agent (see
|
||||||
[Remote agent (SIW + Google)](#remote-agent-siw--google) below).
|
[Remote agent (SIW + Google)](#remote-agent-siw--google) below).
|
||||||
- Optional: a Google service-account JSON key. Only the REST API key is
|
|
||||||
required today, but if/when you add code that uses `google-auth-library`,
|
|
||||||
save the JSON at `config/google-service-account.json` and set
|
|
||||||
`GOOGLE_APPLICATION_CREDENTIALS` in `.env` to point at it. The file is
|
|
||||||
git-ignored.
|
|
||||||
|
|
||||||
## Local setup
|
## Local setup
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ export const config = {
|
||||||
|
|
||||||
google: {
|
google: {
|
||||||
apiKey: required('GOOGLE_API_KEY'),
|
apiKey: required('GOOGLE_API_KEY'),
|
||||||
// Path to a service-account JSON key file. Optional — only set when
|
|
||||||
// future code needs google-auth-library-style credentials. The file
|
|
||||||
// itself lives outside of git.
|
|
||||||
serviceAccountKeyPath: (() => {
|
|
||||||
const raw = optional('GOOGLE_APPLICATION_CREDENTIALS', '');
|
|
||||||
return raw ? resolvePath(raw) : null;
|
|
||||||
})(),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,11 @@ export async function buildStoreLocation(bot, locationInfo, userInfo) {
|
||||||
`<blockquote class='success'>Created location ${location.name}.</blockquote>`,
|
`<blockquote class='success'>Created location ${location.name}.</blockquote>`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await runStep(bot, 'Enabled location for Webex Calling', () =>
|
await runStep(
|
||||||
enableLocationForCalling(location),
|
bot,
|
||||||
|
'Enabled location for Webex Calling',
|
||||||
|
() => enableLocationForCalling(location),
|
||||||
|
{ critical: true },
|
||||||
);
|
);
|
||||||
await runStep(bot, 'Updated location Webex Calling connection', () =>
|
await runStep(bot, 'Updated location Webex Calling connection', () =>
|
||||||
updateLocationRouteGroup(location),
|
updateLocationRouteGroup(location),
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,11 @@ export async function stageStoreLocation(bot, locationInfo, userInfo) {
|
||||||
`<blockquote class='success'>Created location ${location.name}.</blockquote>`,
|
`<blockquote class='success'>Created location ${location.name}.</blockquote>`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await runStep(bot, 'Enabled location for Webex Calling', () =>
|
await runStep(
|
||||||
enableLocationForCalling(location),
|
bot,
|
||||||
|
'Enabled location for Webex Calling',
|
||||||
|
() => enableLocationForCalling(location),
|
||||||
|
{ critical: true },
|
||||||
);
|
);
|
||||||
await runStep(bot, 'Updated internal dialing', () => updateInternalDialing(location));
|
await runStep(bot, 'Updated internal dialing', () => updateInternalDialing(location));
|
||||||
await runStep(bot, 'Updated location outgoing permission', () =>
|
await runStep(bot, 'Updated location outgoing permission', () =>
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,24 @@ import { logger } from '../logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a provisioning step, log its outcome to console + bot chat, and return
|
* Run a provisioning step, log its outcome to console + bot chat, and return
|
||||||
* the step's resolved value (or undefined on failure). Never throws — matches
|
* the step's resolved value.
|
||||||
* the legacy behavior where each step's error was captured but did not halt
|
*
|
||||||
* the flow.
|
* Options:
|
||||||
|
* - successMessage: text to show on success (defaults to description)
|
||||||
|
* - failureMessage: text to show on failure (defaults to "Error: {description}")
|
||||||
|
* - critical: when true, a failure re-throws so the caller aborts the flow.
|
||||||
|
* Use for steps that everything downstream depends on (e.g. enabling a
|
||||||
|
* new location for Webex Calling — every subsequent Calling API call will
|
||||||
|
* 404 otherwise). Non-critical steps swallow the error and return undefined,
|
||||||
|
* matching legacy behavior so a single flaky API call doesn't abort the
|
||||||
|
* entire provision.
|
||||||
*/
|
*/
|
||||||
export async function runStep(bot, description, fn, { successMessage, failureMessage } = {}) {
|
export async function runStep(
|
||||||
|
bot,
|
||||||
|
description,
|
||||||
|
fn,
|
||||||
|
{ successMessage, failureMessage, critical = false } = {},
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const result = await fn();
|
const result = await fn();
|
||||||
const msg = successMessage ?? description;
|
const msg = successMessage ?? description;
|
||||||
|
|
@ -14,8 +27,10 @@ export async function runStep(bot, description, fn, { successMessage, failureMes
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${description} failed:`, error);
|
logger.error(`${description} failed:`, error);
|
||||||
const msg = failureMessage ?? `Error: ${description}`;
|
const baseMsg = failureMessage ?? `Error: ${description}`;
|
||||||
|
const msg = critical ? `${baseMsg} — aborting (critical step).` : baseMsg;
|
||||||
bot?.say('markdown', `<blockquote class='failure'>${msg}</blockquote>`);
|
bot?.say('markdown', `<blockquote class='failure'>${msg}</blockquote>`);
|
||||||
|
if (critical) throw error;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
import { config } from '../config.js';
|
import { config } from '../config.js';
|
||||||
import { requestJson } from '../http.js';
|
import { proxyRequest } from '../services/websocket.js';
|
||||||
|
|
||||||
function twilioHeaders() {
|
// Twilio's Lookup v2 has no IP allow-list requirement today, but every other
|
||||||
|
// third-party call in this bot (SIW, Google) is already proxied through the
|
||||||
|
// on-prem agent. Doing the same for Twilio keeps the network surface
|
||||||
|
// consistent — if Twilio ever adds IP restrictions, or the bot moves behind
|
||||||
|
// a corporate egress proxy, this call keeps working without a code change.
|
||||||
|
// `insecure` is not set here: Twilio uses public CAs that Node trusts, and
|
||||||
|
// the agent's host reaches lookups.twilio.com without an SSL-inspecting hop.
|
||||||
|
|
||||||
|
function twilioAuthHeaders() {
|
||||||
const auth = Buffer.from(`${config.twilio.accountSid}:${config.twilio.authToken}`).toString(
|
const auth = Buffer.from(`${config.twilio.accountSid}:${config.twilio.authToken}`).toString(
|
||||||
'base64',
|
'base64',
|
||||||
);
|
);
|
||||||
|
|
@ -14,5 +22,10 @@ function twilioHeaders() {
|
||||||
*/
|
*/
|
||||||
export async function validatePhoneNumber(phoneNumber) {
|
export async function validatePhoneNumber(phoneNumber) {
|
||||||
const url = `https://lookups.twilio.com/v2/PhoneNumbers/${encodeURIComponent(phoneNumber)}`;
|
const url = `https://lookups.twilio.com/v2/PhoneNumbers/${encodeURIComponent(phoneNumber)}`;
|
||||||
return requestJson('GET', url, undefined, { headers: twilioHeaders() });
|
const response = await proxyRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url,
|
||||||
|
headers: twilioAuthHeaders(),
|
||||||
|
});
|
||||||
|
return response?.data;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
160
src/webex/deviceCustomizations.js
Normal file
160
src/webex/deviceCustomizations.js
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
// Per-device-family calling customizations applied to every store location
|
||||||
|
// as part of buildStore / stageStore. Sent verbatim to
|
||||||
|
// POST /telephony/config/jobs/devices/callDeviceSettings under `customizations`.
|
||||||
|
//
|
||||||
|
// This is intentionally a data-only module so device config changes are a
|
||||||
|
// clean, self-contained diff. Anything that changes here changes what gets
|
||||||
|
// pushed to phones store-wide, so review carefully.
|
||||||
|
|
||||||
|
export const STORE_DEVICE_CUSTOMIZATIONS = {
|
||||||
|
ata: {
|
||||||
|
audioCodecPriority: {
|
||||||
|
selection: 'REGIONAL',
|
||||||
|
primary: 'G711a',
|
||||||
|
secondary: 'G711u',
|
||||||
|
tertiary: 'G729a',
|
||||||
|
},
|
||||||
|
ataDtmfMode: 'STRICT',
|
||||||
|
ataDtmfMethod: 'AVT',
|
||||||
|
cdpEnabled: true,
|
||||||
|
lldpEnabled: true,
|
||||||
|
qosEnabled: true,
|
||||||
|
vlan: { enabled: false, value: 1 },
|
||||||
|
webAccessEnabled: false,
|
||||||
|
nightlyResyncEnabled: true,
|
||||||
|
snmp: {
|
||||||
|
enabled: false,
|
||||||
|
trustedIP: '0.0.0.0/0.0.0.0',
|
||||||
|
getCommunity: 'public',
|
||||||
|
setCommunity: 'private',
|
||||||
|
snmpV3Enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dect: {
|
||||||
|
audioCodecPriority: {
|
||||||
|
selection: 'REGIONAL',
|
||||||
|
primary: 'G729',
|
||||||
|
secondary: 'G711a',
|
||||||
|
tertiary: 'G711u',
|
||||||
|
},
|
||||||
|
cdpEnabled: true,
|
||||||
|
lldpEnabled: true,
|
||||||
|
qosEnabled: true,
|
||||||
|
vlan: { enabled: false, value: 0 },
|
||||||
|
webAccessEnabled: true,
|
||||||
|
nightlyResyncEnabled: true,
|
||||||
|
},
|
||||||
|
mpp: {
|
||||||
|
pnacEnabled: true,
|
||||||
|
audioCodecPriority: {
|
||||||
|
selection: 'REGIONAL',
|
||||||
|
primary: 'OPUS',
|
||||||
|
secondary: 'G722',
|
||||||
|
tertiary: 'G711u',
|
||||||
|
},
|
||||||
|
backlightTimer: 'FIVE_MIN',
|
||||||
|
background: { image: 'NONE' },
|
||||||
|
displayNameFormat: 'PERSON_NUMBER',
|
||||||
|
cdpEnabled: true,
|
||||||
|
defaultLoggingLevel: 'STANDARD',
|
||||||
|
dndServicesEnabled: false,
|
||||||
|
acd: { enabled: false, displayCallqueueAgentSoftkeys: 'LAST_PAGE' },
|
||||||
|
shortInterdigitTimer: 3,
|
||||||
|
longInterdigitTimer: 5,
|
||||||
|
lineKeyLabelFormat: 'PERSON_EXTENSION',
|
||||||
|
lineKeyLEDPattern: 'DEFAULT',
|
||||||
|
lldpEnabled: true,
|
||||||
|
mppUserWebAccessEnabled: false,
|
||||||
|
offHookTimer: 30,
|
||||||
|
phoneLanguage: 'PERSON_LANGUAGE',
|
||||||
|
poeMode: 'NORMAL',
|
||||||
|
qosEnabled: true,
|
||||||
|
screenTimeout: { enabled: false, value: 300 },
|
||||||
|
vlan: { enabled: false, value: 1, pcPort: 1 },
|
||||||
|
wifiNetwork: { enabled: false, authenticationMethod: 'NONE' },
|
||||||
|
callHistory: 'WEBEX_UNIFIED_CALL_HISTORY',
|
||||||
|
contacts: 'XSI_DIRECTORY',
|
||||||
|
webexMeetingsEnabled: false,
|
||||||
|
usbPorts: { enabled: false, sideUsbEnabled: false, rearUsbEnabled: false },
|
||||||
|
volumeSettings: {
|
||||||
|
ringerVolume: 9,
|
||||||
|
speakerVolume: 11,
|
||||||
|
handsetVolume: 10,
|
||||||
|
headsetVolume: 10,
|
||||||
|
eHookEnabled: true,
|
||||||
|
allowEndUserOverrideEnabled: false,
|
||||||
|
},
|
||||||
|
cfExpandedSoftKey: 'ALL_CALL_FORWARDS',
|
||||||
|
httpProxy: {
|
||||||
|
mode: 'OFF',
|
||||||
|
autoDiscoveryEnabled: true,
|
||||||
|
port: '3128',
|
||||||
|
authSettingsEnabled: false,
|
||||||
|
},
|
||||||
|
bluetooth: { enabled: false, mode: 'PHONE' },
|
||||||
|
passThroughPortEnabled: false,
|
||||||
|
userPasswordOverrideEnabled: false,
|
||||||
|
activeCallFocusEnabled: false,
|
||||||
|
peerFirmwareEnabled: true,
|
||||||
|
noiseCancellation: { enabled: true, allowEndUserOverrideEnabled: false },
|
||||||
|
dialAssistEnabled: true,
|
||||||
|
callsPerLine: 4,
|
||||||
|
nightlyResyncEnabled: true,
|
||||||
|
missedCallNotificationEnabled: true,
|
||||||
|
softKeyLayout: {
|
||||||
|
softKeyMenu: {
|
||||||
|
idleKeyList:
|
||||||
|
'guestin|;guestout|;acd_login|;acd_logout|;astate|;redial|;newcall|;cfwd|;recents|;dnd|;unpark|;psk1|;gpickup|;pickup|;dir|4;miss|5;selfview|;messages|;meetings',
|
||||||
|
offHookKeyList: 'endcall|1;redial|2;dir|3;lcr|4;unpark|5;pickup|6;gpickup|7',
|
||||||
|
dialingInputKeyList: 'dial|1;cancel|2;delchar|3;left|5;right|6',
|
||||||
|
progressingKeyList: 'endcall|2',
|
||||||
|
connectedKeyList:
|
||||||
|
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume;adhocparticipants',
|
||||||
|
connectedVideoKeyList:
|
||||||
|
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume;adhocparticipants',
|
||||||
|
startTransferKeyList: 'endcall|2;xfer|3',
|
||||||
|
startConferenceKeyList: 'endcall|2;conf|3',
|
||||||
|
conferencingKeyList: 'endcall;join;crdstart;crdstop;crdpause;crdresume',
|
||||||
|
releasingKeyList: 'endcall|2',
|
||||||
|
holdKeyList: 'resume|1;endcall|2;newcall|3;redial|4;dir|5;adhocparticipants',
|
||||||
|
ringingKeyList: 'answer|1;ignore|2',
|
||||||
|
sharedActiveKeyList: 'newcall|1;psk1|2;dir|3;back|4',
|
||||||
|
sharedHeldKeyList: 'resume|1;dir|4',
|
||||||
|
},
|
||||||
|
psk: { psk1: 'fnc=sd;ext=*11;nme=Call Pull' },
|
||||||
|
softKeyMenuDefaults: {
|
||||||
|
idleKeyList:
|
||||||
|
'guestin|;guestout|;acd_login|;acd_logout|;astate|;redial|;newcall|;cfwd|;recents|;dnd|;unpark|;psk1|;gpickup|;pickup|;dir|4;miss|5;selfview|;messages',
|
||||||
|
offHookKeyList: 'endcall|1;redial|2;dir|3;lcr|4;unpark|5;pickup|6;gpickup|7',
|
||||||
|
dialingInputKeyList: 'dial|1;cancel|2;delchar|3;left|5;right|6',
|
||||||
|
progressingKeyList: 'endcall|2',
|
||||||
|
connectedKeyList:
|
||||||
|
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume',
|
||||||
|
connectedVideoKeyList:
|
||||||
|
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume',
|
||||||
|
startTransferKeyList: 'endcall|2;xfer|3',
|
||||||
|
startConferenceKeyList: 'endcall|2;conf|3',
|
||||||
|
conferencingKeyList: 'endcall;join;crdstart;crdstop;crdpause;crdresume',
|
||||||
|
releasingKeyList: 'endcall|2',
|
||||||
|
holdKeyList: 'resume|1;endcall|2;newcall|3;redial|4;dir|5',
|
||||||
|
ringingKeyList: 'answer|1;ignore|2',
|
||||||
|
sharedActiveKeyList: 'newcall|1;psk1|2;dir|3;back|4',
|
||||||
|
sharedHeldKeyList: 'resume|1;dir|4',
|
||||||
|
},
|
||||||
|
pskDefaults: { psk1: 'fnc=sd;ext=*11;nme=Call Pull' },
|
||||||
|
},
|
||||||
|
backgroundImage8875: 'VIOLET_DARK',
|
||||||
|
backlightTimer68XX78XX: 'ALWAYS_ON',
|
||||||
|
voiceFeedbackAccessibilityEnabled: true,
|
||||||
|
},
|
||||||
|
wifi: {
|
||||||
|
audioCodecPriority: {
|
||||||
|
selection: 'REGIONAL',
|
||||||
|
primary: 'OPUS',
|
||||||
|
secondary: 'G722',
|
||||||
|
tertiary: 'G711u',
|
||||||
|
},
|
||||||
|
ldap: {},
|
||||||
|
webAccess: { enabled: true },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,157 +1,5 @@
|
||||||
import { webexJson } from './client.js';
|
import { webexJson } from './client.js';
|
||||||
|
import { STORE_DEVICE_CUSTOMIZATIONS } from './deviceCustomizations.js';
|
||||||
const STORE_DEVICE_CUSTOMIZATIONS = {
|
|
||||||
ata: {
|
|
||||||
audioCodecPriority: {
|
|
||||||
selection: 'REGIONAL',
|
|
||||||
primary: 'G711a',
|
|
||||||
secondary: 'G711u',
|
|
||||||
tertiary: 'G729a',
|
|
||||||
},
|
|
||||||
ataDtmfMode: 'STRICT',
|
|
||||||
ataDtmfMethod: 'AVT',
|
|
||||||
cdpEnabled: true,
|
|
||||||
lldpEnabled: true,
|
|
||||||
qosEnabled: true,
|
|
||||||
vlan: { enabled: false, value: 1 },
|
|
||||||
webAccessEnabled: false,
|
|
||||||
nightlyResyncEnabled: true,
|
|
||||||
snmp: {
|
|
||||||
enabled: false,
|
|
||||||
trustedIP: '0.0.0.0/0.0.0.0',
|
|
||||||
getCommunity: 'public',
|
|
||||||
setCommunity: 'private',
|
|
||||||
snmpV3Enabled: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dect: {
|
|
||||||
audioCodecPriority: {
|
|
||||||
selection: 'REGIONAL',
|
|
||||||
primary: 'G729',
|
|
||||||
secondary: 'G711a',
|
|
||||||
tertiary: 'G711u',
|
|
||||||
},
|
|
||||||
cdpEnabled: true,
|
|
||||||
lldpEnabled: true,
|
|
||||||
qosEnabled: true,
|
|
||||||
vlan: { enabled: false, value: 0 },
|
|
||||||
webAccessEnabled: true,
|
|
||||||
nightlyResyncEnabled: true,
|
|
||||||
},
|
|
||||||
mpp: {
|
|
||||||
pnacEnabled: true,
|
|
||||||
audioCodecPriority: {
|
|
||||||
selection: 'REGIONAL',
|
|
||||||
primary: 'OPUS',
|
|
||||||
secondary: 'G722',
|
|
||||||
tertiary: 'G711u',
|
|
||||||
},
|
|
||||||
backlightTimer: 'FIVE_MIN',
|
|
||||||
background: { image: 'NONE' },
|
|
||||||
displayNameFormat: 'PERSON_NUMBER',
|
|
||||||
cdpEnabled: true,
|
|
||||||
defaultLoggingLevel: 'STANDARD',
|
|
||||||
dndServicesEnabled: false,
|
|
||||||
acd: { enabled: false, displayCallqueueAgentSoftkeys: 'LAST_PAGE' },
|
|
||||||
shortInterdigitTimer: 3,
|
|
||||||
longInterdigitTimer: 5,
|
|
||||||
lineKeyLabelFormat: 'PERSON_EXTENSION',
|
|
||||||
lineKeyLEDPattern: 'DEFAULT',
|
|
||||||
lldpEnabled: true,
|
|
||||||
mppUserWebAccessEnabled: false,
|
|
||||||
offHookTimer: 30,
|
|
||||||
phoneLanguage: 'PERSON_LANGUAGE',
|
|
||||||
poeMode: 'NORMAL',
|
|
||||||
qosEnabled: true,
|
|
||||||
screenTimeout: { enabled: false, value: 300 },
|
|
||||||
vlan: { enabled: false, value: 1, pcPort: 1 },
|
|
||||||
wifiNetwork: { enabled: false, authenticationMethod: 'NONE' },
|
|
||||||
callHistory: 'WEBEX_UNIFIED_CALL_HISTORY',
|
|
||||||
contacts: 'XSI_DIRECTORY',
|
|
||||||
webexMeetingsEnabled: false,
|
|
||||||
usbPorts: { enabled: false, sideUsbEnabled: false, rearUsbEnabled: false },
|
|
||||||
volumeSettings: {
|
|
||||||
ringerVolume: 9,
|
|
||||||
speakerVolume: 11,
|
|
||||||
handsetVolume: 10,
|
|
||||||
headsetVolume: 10,
|
|
||||||
eHookEnabled: true,
|
|
||||||
allowEndUserOverrideEnabled: false,
|
|
||||||
},
|
|
||||||
cfExpandedSoftKey: 'ALL_CALL_FORWARDS',
|
|
||||||
httpProxy: {
|
|
||||||
mode: 'OFF',
|
|
||||||
autoDiscoveryEnabled: true,
|
|
||||||
port: '3128',
|
|
||||||
authSettingsEnabled: false,
|
|
||||||
},
|
|
||||||
bluetooth: { enabled: false, mode: 'PHONE' },
|
|
||||||
passThroughPortEnabled: false,
|
|
||||||
userPasswordOverrideEnabled: false,
|
|
||||||
activeCallFocusEnabled: false,
|
|
||||||
peerFirmwareEnabled: true,
|
|
||||||
noiseCancellation: { enabled: true, allowEndUserOverrideEnabled: false },
|
|
||||||
dialAssistEnabled: true,
|
|
||||||
callsPerLine: 4,
|
|
||||||
nightlyResyncEnabled: true,
|
|
||||||
missedCallNotificationEnabled: true,
|
|
||||||
softKeyLayout: {
|
|
||||||
softKeyMenu: {
|
|
||||||
idleKeyList:
|
|
||||||
'guestin|;guestout|;acd_login|;acd_logout|;astate|;redial|;newcall|;cfwd|;recents|;dnd|;unpark|;psk1|;gpickup|;pickup|;dir|4;miss|5;selfview|;messages|;meetings',
|
|
||||||
offHookKeyList: 'endcall|1;redial|2;dir|3;lcr|4;unpark|5;pickup|6;gpickup|7',
|
|
||||||
dialingInputKeyList: 'dial|1;cancel|2;delchar|3;left|5;right|6',
|
|
||||||
progressingKeyList: 'endcall|2',
|
|
||||||
connectedKeyList:
|
|
||||||
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume;adhocparticipants',
|
|
||||||
connectedVideoKeyList:
|
|
||||||
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume;adhocparticipants',
|
|
||||||
startTransferKeyList: 'endcall|2;xfer|3',
|
|
||||||
startConferenceKeyList: 'endcall|2;conf|3',
|
|
||||||
conferencingKeyList: 'endcall;join;crdstart;crdstop;crdpause;crdresume',
|
|
||||||
releasingKeyList: 'endcall|2',
|
|
||||||
holdKeyList: 'resume|1;endcall|2;newcall|3;redial|4;dir|5;adhocparticipants',
|
|
||||||
ringingKeyList: 'answer|1;ignore|2',
|
|
||||||
sharedActiveKeyList: 'newcall|1;psk1|2;dir|3;back|4',
|
|
||||||
sharedHeldKeyList: 'resume|1;dir|4',
|
|
||||||
},
|
|
||||||
psk: { psk1: 'fnc=sd;ext=*11;nme=Call Pull' },
|
|
||||||
softKeyMenuDefaults: {
|
|
||||||
idleKeyList:
|
|
||||||
'guestin|;guestout|;acd_login|;acd_logout|;astate|;redial|;newcall|;cfwd|;recents|;dnd|;unpark|;psk1|;gpickup|;pickup|;dir|4;miss|5;selfview|;messages',
|
|
||||||
offHookKeyList: 'endcall|1;redial|2;dir|3;lcr|4;unpark|5;pickup|6;gpickup|7',
|
|
||||||
dialingInputKeyList: 'dial|1;cancel|2;delchar|3;left|5;right|6',
|
|
||||||
progressingKeyList: 'endcall|2',
|
|
||||||
connectedKeyList:
|
|
||||||
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume',
|
|
||||||
connectedVideoKeyList:
|
|
||||||
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume',
|
|
||||||
startTransferKeyList: 'endcall|2;xfer|3',
|
|
||||||
startConferenceKeyList: 'endcall|2;conf|3',
|
|
||||||
conferencingKeyList: 'endcall;join;crdstart;crdstop;crdpause;crdresume',
|
|
||||||
releasingKeyList: 'endcall|2',
|
|
||||||
holdKeyList: 'resume|1;endcall|2;newcall|3;redial|4;dir|5',
|
|
||||||
ringingKeyList: 'answer|1;ignore|2',
|
|
||||||
sharedActiveKeyList: 'newcall|1;psk1|2;dir|3;back|4',
|
|
||||||
sharedHeldKeyList: 'resume|1;dir|4',
|
|
||||||
},
|
|
||||||
pskDefaults: { psk1: 'fnc=sd;ext=*11;nme=Call Pull' },
|
|
||||||
},
|
|
||||||
backgroundImage8875: 'VIOLET_DARK',
|
|
||||||
backlightTimer68XX78XX: 'ALWAYS_ON',
|
|
||||||
voiceFeedbackAccessibilityEnabled: true,
|
|
||||||
},
|
|
||||||
wifi: {
|
|
||||||
audioCodecPriority: {
|
|
||||||
selection: 'REGIONAL',
|
|
||||||
primary: 'OPUS',
|
|
||||||
secondary: 'G722',
|
|
||||||
tertiary: 'G711u',
|
|
||||||
},
|
|
||||||
ldap: {},
|
|
||||||
webAccess: { enabled: true },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function scheduleStoreDeviceSettings(location) {
|
export async function scheduleStoreDeviceSettings(location) {
|
||||||
const body = {
|
const body = {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue