feat(mcp): WhatsApp connector, archivable catalog, MCP connector config endpoint
This commit is contained in:
@@ -2,6 +2,23 @@ import { html, nothing } from 'lit';
|
||||
import { LightElement } from '../lib/base.js';
|
||||
import { t } from '../lib/i18n.js';
|
||||
|
||||
function _maybeT(key, fallback) {
|
||||
const v = t(key);
|
||||
return v !== key ? v : fallback;
|
||||
}
|
||||
|
||||
function _configSetSlug(name) {
|
||||
const slugs = {
|
||||
'Interface': 'interface',
|
||||
'TIC Agent': 'tic_agent',
|
||||
};
|
||||
return slugs[name] ?? null;
|
||||
}
|
||||
|
||||
function _propKeyId(propKey) {
|
||||
return propKey.replace(/\./g, '__');
|
||||
}
|
||||
|
||||
export class ConfigPage extends LightElement {
|
||||
static properties = {
|
||||
_open: { state: true },
|
||||
@@ -130,7 +147,7 @@ export class ConfigPage extends LightElement {
|
||||
.checked=${checked}
|
||||
@change=${e => { this._setValue(prop.key, e.target.checked ? 'true' : 'false'); this._save(prop); }} />
|
||||
<label class="form-check-label" for="cfg-${prop.key}">
|
||||
${checked ? 'Enabled' : 'Disabled'}
|
||||
${checked ? t('config.enabled') : t('config.disabled')}
|
||||
</label>
|
||||
</div>`;
|
||||
}
|
||||
@@ -144,7 +161,13 @@ export class ConfigPage extends LightElement {
|
||||
@input=${e => this._setValue(prop.key, e.target.value)} />`;
|
||||
}
|
||||
|
||||
// Dropdown-style property types. The backend ships the allowed values in
|
||||
// `prop.options` (a list of {id, name}); we only decide how to frame them.
|
||||
// Adding a new custom type from a config section? Give it a `property_type`
|
||||
// on the backend, attach its `options`, and add a branch like these — a
|
||||
// free-text box becomes a proper picker for the price of a few lines.
|
||||
if (prop.property_type === 'security_group') {
|
||||
// Nullable: the empty choice means "fall back to the instance default".
|
||||
const groups = prop.options ?? [];
|
||||
return html`
|
||||
<select class="form-select form-select-sm config-input"
|
||||
@@ -156,6 +179,20 @@ export class ConfigPage extends LightElement {
|
||||
</select>`;
|
||||
}
|
||||
|
||||
if (prop.property_type === 'locale') {
|
||||
// Interface languages the instance supports; labels are native endonyms.
|
||||
// Always a concrete pick (no empty option) — falls back to default_value.
|
||||
const locales = prop.options ?? [];
|
||||
const current = val || prop.default_value || 'en';
|
||||
return html`
|
||||
<select class="form-select form-select-sm config-input"
|
||||
.value=${current}
|
||||
@change=${e => { this._setValue(prop.key, e.target.value); this._save(prop); }}>
|
||||
${locales.map(l => html`
|
||||
<option value=${l.id} ?selected=${current === l.id}>${l.name}</option>`)}
|
||||
</select>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<input type="text"
|
||||
class="form-control form-control-sm config-input"
|
||||
@@ -165,11 +202,14 @@ export class ConfigPage extends LightElement {
|
||||
}
|
||||
|
||||
_renderSet(set) {
|
||||
const slug = _configSetSlug(set.name);
|
||||
const sName = slug ? _maybeT(`config.set.${slug}.name`, set.name) : set.name;
|
||||
const sDesc = slug ? _maybeT(`config.set.${slug}.desc`, set.description) : set.description;
|
||||
return html`
|
||||
<div class="config-set">
|
||||
<div class="config-set-header">
|
||||
<div class="config-set-name">${set.name}</div>
|
||||
<div class="config-set-desc">${set.description}</div>
|
||||
<div class="config-set-name">${sName}</div>
|
||||
<div class="config-set-desc">${sDesc}</div>
|
||||
</div>
|
||||
<div class="config-rows">
|
||||
${set.properties.map(p => this._renderRow(p))}
|
||||
@@ -180,22 +220,25 @@ export class ConfigPage extends LightElement {
|
||||
_renderRow(prop) {
|
||||
const saving = this._saving.has(prop.key);
|
||||
const saved = this._saved.has(prop.key);
|
||||
const pk = _propKeyId(prop.key);
|
||||
const pName = _maybeT(`config.prop.${pk}.name`, prop.name);
|
||||
const pDesc = _maybeT(`config.prop.${pk}.desc`, prop.description);
|
||||
|
||||
return html`
|
||||
<div class="config-row">
|
||||
<div class="config-row-meta">
|
||||
<div class="config-row-name">${prop.name}</div>
|
||||
<div class="config-row-desc">${prop.description}</div>
|
||||
<div class="config-row-name">${pName}</div>
|
||||
<div class="config-row-desc">${pDesc}</div>
|
||||
</div>
|
||||
<div class="config-row-control">
|
||||
${this._renderInput(prop)}
|
||||
${prop.property_type !== 'bool' ? html`
|
||||
${!['bool', 'locale'].includes(prop.property_type) ? html`
|
||||
<button class="btn btn-sm ${saved ? 'btn-success' : 'btn-primary'} config-save-btn"
|
||||
?disabled=${saving}
|
||||
@click=${() => this._save(prop)}>
|
||||
${saving
|
||||
? html`<span class="spinner-border spinner-border-sm"></span>`
|
||||
: saved ? 'Saved' : 'Save'}
|
||||
: saved ? t('common.saved') : t('common.save')}
|
||||
</button>` : nothing}
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -237,7 +280,7 @@ export class ConfigPage extends LightElement {
|
||||
?disabled=${this._debugLoading}
|
||||
@change=${() => this._toggleDebugMode()} />
|
||||
<label class="form-check-label" for="cfg-debug-mode">
|
||||
${this._debugMode ? 'Enabled' : 'Disabled'}
|
||||
${this._debugMode ? t('config.enabled') : t('config.disabled')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user