Files
openfang/crates/openfang-api/static/js/pages/channels.js
iven 92e5def702
Some checks failed
CI / Check / macos-latest (push) Has been cancelled
CI / Check / ubuntu-latest (push) Has been cancelled
CI / Check / windows-latest (push) Has been cancelled
CI / Test / macos-latest (push) Has been cancelled
CI / Test / ubuntu-latest (push) Has been cancelled
CI / Test / windows-latest (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / Secrets Scan (push) Has been cancelled
CI / Install Script Smoke Test (push) Has been cancelled
初始化提交
2026-03-01 16:24:24 +08:00

301 lines
9.9 KiB
JavaScript

// OpenFang Channels Page — OpenClaw-style setup UX with QR code support
'use strict';
function channelsPage() {
return {
allChannels: [],
categoryFilter: 'all',
searchQuery: '',
setupModal: null,
configuring: false,
testing: {},
formValues: {},
showAdvanced: false,
showBusinessApi: false,
loading: true,
loadError: '',
pollTimer: null,
// Setup flow step tracking
setupStep: 1, // 1=Configure, 2=Verify, 3=Ready
testPassed: false,
// WhatsApp QR state
qr: {
loading: false,
available: false,
dataUrl: '',
sessionId: '',
message: '',
help: '',
connected: false,
expired: false,
error: ''
},
qrPollTimer: null,
categories: [
{ key: 'all', label: 'All' },
{ key: 'messaging', label: 'Messaging' },
{ key: 'social', label: 'Social' },
{ key: 'enterprise', label: 'Enterprise' },
{ key: 'developer', label: 'Developer' },
{ key: 'notifications', label: 'Notifications' }
],
get filteredChannels() {
var self = this;
return this.allChannels.filter(function(ch) {
if (self.categoryFilter !== 'all' && ch.category !== self.categoryFilter) return false;
if (self.searchQuery) {
var q = self.searchQuery.toLowerCase();
return ch.name.toLowerCase().indexOf(q) !== -1 ||
ch.display_name.toLowerCase().indexOf(q) !== -1 ||
ch.description.toLowerCase().indexOf(q) !== -1;
}
return true;
});
},
get configuredCount() {
return this.allChannels.filter(function(ch) { return ch.configured; }).length;
},
categoryCount(cat) {
var all = this.allChannels.filter(function(ch) { return cat === 'all' || ch.category === cat; });
var configured = all.filter(function(ch) { return ch.configured; });
return configured.length + '/' + all.length;
},
basicFields() {
if (!this.setupModal || !this.setupModal.fields) return [];
return this.setupModal.fields.filter(function(f) { return !f.advanced; });
},
advancedFields() {
if (!this.setupModal || !this.setupModal.fields) return [];
return this.setupModal.fields.filter(function(f) { return f.advanced; });
},
hasAdvanced() {
return this.advancedFields().length > 0;
},
isQrChannel() {
return this.setupModal && this.setupModal.setup_type === 'qr';
},
async loadChannels() {
this.loading = true;
this.loadError = '';
try {
var data = await OpenFangAPI.get('/api/channels');
this.allChannels = (data.channels || []).map(function(ch) {
ch.connected = ch.configured && ch.has_token;
return ch;
});
} catch(e) {
this.loadError = e.message || 'Could not load channels.';
}
this.loading = false;
this.startPolling();
},
async loadData() { return this.loadChannels(); },
startPolling() {
var self = this;
if (this.pollTimer) clearInterval(this.pollTimer);
this.pollTimer = setInterval(function() { self.refreshStatus(); }, 15000);
},
async refreshStatus() {
try {
var data = await OpenFangAPI.get('/api/channels');
var byName = {};
(data.channels || []).forEach(function(ch) { byName[ch.name] = ch; });
this.allChannels.forEach(function(c) {
var fresh = byName[c.name];
if (fresh) {
c.configured = fresh.configured;
c.has_token = fresh.has_token;
c.connected = fresh.configured && fresh.has_token;
c.fields = fresh.fields;
}
});
} catch(e) { console.warn('Channel refresh failed:', e.message); }
},
statusBadge(ch) {
if (!ch.configured) return { text: 'Not Configured', cls: 'badge-muted' };
if (!ch.has_token) return { text: 'Missing Token', cls: 'badge-warn' };
if (ch.connected) return { text: 'Ready', cls: 'badge-success' };
return { text: 'Configured', cls: 'badge-info' };
},
difficultyClass(d) {
if (d === 'Easy') return 'difficulty-easy';
if (d === 'Hard') return 'difficulty-hard';
return 'difficulty-medium';
},
openSetup(ch) {
this.setupModal = ch;
this.formValues = {};
this.showAdvanced = false;
this.showBusinessApi = false;
this.setupStep = ch.configured ? 3 : 1;
this.testPassed = !!ch.configured;
this.resetQR();
// Auto-start QR flow for QR-type channels
if (ch.setup_type === 'qr') {
this.startQR();
}
},
// ── QR Code Flow (WhatsApp Web style) ──────────────────────────
resetQR() {
this.qr = {
loading: false, available: false, dataUrl: '', sessionId: '',
message: '', help: '', connected: false, expired: false, error: ''
};
if (this.qrPollTimer) { clearInterval(this.qrPollTimer); this.qrPollTimer = null; }
},
async startQR() {
this.qr.loading = true;
this.qr.error = '';
this.qr.connected = false;
this.qr.expired = false;
try {
var result = await OpenFangAPI.post('/api/channels/whatsapp/qr/start', {});
this.qr.available = result.available || false;
this.qr.dataUrl = result.qr_data_url || '';
this.qr.sessionId = result.session_id || '';
this.qr.message = result.message || '';
this.qr.help = result.help || '';
this.qr.connected = result.connected || false;
if (this.qr.available && this.qr.dataUrl && !this.qr.connected) {
this.pollQR();
}
if (this.qr.connected) {
OpenFangToast.success('WhatsApp connected!');
await this.refreshStatus();
}
} catch(e) {
this.qr.error = e.message || 'Could not start QR login';
}
this.qr.loading = false;
},
pollQR() {
var self = this;
if (this.qrPollTimer) clearInterval(this.qrPollTimer);
this.qrPollTimer = setInterval(async function() {
try {
var result = await OpenFangAPI.get('/api/channels/whatsapp/qr/status?session_id=' + encodeURIComponent(self.qr.sessionId));
if (result.connected) {
clearInterval(self.qrPollTimer);
self.qrPollTimer = null;
self.qr.connected = true;
self.qr.message = result.message || 'Connected!';
OpenFangToast.success('WhatsApp linked successfully!');
await self.refreshStatus();
} else if (result.expired) {
clearInterval(self.qrPollTimer);
self.qrPollTimer = null;
self.qr.expired = true;
self.qr.message = 'QR code expired. Click to generate a new one.';
} else {
self.qr.message = result.message || 'Waiting for scan...';
}
} catch(e) { /* silent retry */ }
}, 3000);
},
// ── Standard Form Flow ─────────────────────────────────────────
async saveChannel() {
if (!this.setupModal) return;
var name = this.setupModal.name;
this.configuring = true;
try {
await OpenFangAPI.post('/api/channels/' + name + '/configure', {
fields: this.formValues
});
this.setupStep = 2;
// Auto-test after save
try {
var testResult = await OpenFangAPI.post('/api/channels/' + name + '/test', {});
if (testResult.status === 'ok') {
this.testPassed = true;
this.setupStep = 3;
OpenFangToast.success(this.setupModal.display_name + ' activated!');
} else {
OpenFangToast.success(this.setupModal.display_name + ' saved. ' + (testResult.message || ''));
}
} catch(te) {
OpenFangToast.success(this.setupModal.display_name + ' saved. Test to verify connection.');
}
await this.refreshStatus();
} catch(e) {
OpenFangToast.error('Failed: ' + (e.message || 'Unknown error'));
}
this.configuring = false;
},
async removeChannel() {
if (!this.setupModal) return;
var name = this.setupModal.name;
var displayName = this.setupModal.display_name;
var self = this;
OpenFangToast.confirm('Remove Channel', 'Remove ' + displayName + ' configuration? This will deactivate the channel.', async function() {
try {
await OpenFangAPI.delete('/api/channels/' + name + '/configure');
OpenFangToast.success(displayName + ' removed and deactivated.');
await self.refreshStatus();
self.setupModal = null;
} catch(e) {
OpenFangToast.error('Failed: ' + (e.message || 'Unknown error'));
}
});
},
async testChannel() {
if (!this.setupModal) return;
var name = this.setupModal.name;
this.testing[name] = true;
try {
var result = await OpenFangAPI.post('/api/channels/' + name + '/test', {});
if (result.status === 'ok') {
this.testPassed = true;
this.setupStep = 3;
OpenFangToast.success(result.message);
} else {
OpenFangToast.error(result.message);
}
} catch(e) {
OpenFangToast.error('Test failed: ' + (e.message || 'Unknown error'));
}
this.testing[name] = false;
},
async copyConfig(ch) {
var tpl = ch ? ch.config_template : (this.setupModal ? this.setupModal.config_template : '');
if (!tpl) return;
try {
await navigator.clipboard.writeText(tpl);
OpenFangToast.success('Copied to clipboard');
} catch(e) {
OpenFangToast.error('Copy failed');
}
},
destroy() {
if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; }
if (this.qrPollTimer) { clearInterval(this.qrPollTimer); this.qrPollTimer = null; }
}
};
}