diff --git a/src/api/admin/AdminIrcService.ts b/src/api/admin/AdminIrcService.ts index f5c2310..6461598 100644 --- a/src/api/admin/AdminIrcService.ts +++ b/src/api/admin/AdminIrcService.ts @@ -22,6 +22,7 @@ interface BridgeResponse { provisionUrl?: string; isEnabled: boolean; availableNetworks: AvailableNetworks; + isOnline: boolean; } interface SetEnabledRequest { @@ -42,12 +43,21 @@ export class AdminIrcService { const bridges = await IrcBridgeRecord.findAll(); const client = new IrcBridge(userId); return Promise.all(bridges.map(async b => { + let networks = null; + let isOnline = true; + try { + networks = await client.getNetworks(b); + } catch (e) { + LogService.error("AdminIrcService", e); + isOnline = false; + } return { id: b.id, upstreamId: b.upstreamId, provisionUrl: b.provisionUrl, isEnabled: b.isEnabled, - availableNetworks: await client.getNetworks(b), + availableNetworks: networks, + isOnline: isOnline, }; })); } @@ -61,12 +71,21 @@ export class AdminIrcService { if (!ircBridge) throw new ApiError(404, "IRC Bridge not found"); const client = new IrcBridge(userId); + let networks = null; + let isOnline = true; + try { + networks = await client.getNetworks(ircBridge); + } catch (e) { + LogService.error("AdminIrcService", e); + isOnline = false; + } return { id: ircBridge.id, upstreamId: ircBridge.upstreamId, provisionUrl: ircBridge.provisionUrl, isEnabled: ircBridge.isEnabled, - availableNetworks: await client.getNetworks(ircBridge), + availableNetworks: networks, + isOnline: isOnline, }; } diff --git a/src/api/dimension/DimensionIntegrationsService.ts b/src/api/dimension/DimensionIntegrationsService.ts index 1e2431d..7ef18b0 100644 --- a/src/api/dimension/DimensionIntegrationsService.ts +++ b/src/api/dimension/DimensionIntegrationsService.ts @@ -11,6 +11,7 @@ import { ComplexBot } from "../../integrations/ComplexBot"; import { Bridge } from "../../integrations/Bridge"; import { BridgeStore } from "../../db/BridgeStore"; import { BotStore } from "../../db/BotStore"; +import { AdminService } from "../admin/AdminService"; export interface IntegrationsResponse { widgets: Widget[], @@ -65,6 +66,11 @@ export class DimensionIntegrationsService { .map(b => SimpleBot.fromCached(b)); const bots = [...nebs, ...custom]; Cache.for(CACHE_INTEGRATIONS).put("simple_bots", bots); + + if (!AdminService.isAdmin(userId)) { + return bots.filter(b => b.isOnline); + } + return bots; } @@ -80,6 +86,11 @@ export class DimensionIntegrationsService { const bots = await NebStore.listComplexBots(userId, roomId); Cache.for(CACHE_INTEGRATIONS).put("complex_bots_" + roomId, bots); + + if (!AdminService.isAdmin(userId)) { + return bots.filter(b => b.isOnline); + } + return bots; } diff --git a/src/db/BridgeStore.ts b/src/db/BridgeStore.ts index 60ecb9b..e58b3ff 100644 --- a/src/db/BridgeStore.ts +++ b/src/db/BridgeStore.ts @@ -34,6 +34,10 @@ export class BridgeStore { } catch (e) { LogService.error("BridgeStore", "Failed to load configuration for bridge: " + bridgeRecord.name); LogService.error("BridgeStore", e); + + const bridge = new Bridge(bridgeRecord, {}); + bridge.isOnline = false; + enabledBridges.push(bridge); } } diff --git a/src/db/NebStore.ts b/src/db/NebStore.ts index f1832aa..83854f2 100644 --- a/src/db/NebStore.ts +++ b/src/db/NebStore.ts @@ -129,7 +129,15 @@ export class NebStore { const rawIntegrations = await NebStore.listEnabledNebSimpleBots(); return Promise.all(rawIntegrations.map(async i => { const proxy = new NebProxy(i.neb, requestingUserId); - return SimpleBot.fromNeb(i.integration, await proxy.getBotUserId(i.integration)); + let userId = null; + try { + userId = await proxy.getBotUserId(i.integration); + } catch (e) { + LogService.error("NebStore", e); + } + const bot = SimpleBot.fromNeb(i.integration, userId); + bot.isOnline = !!userId; + return bot; })); } @@ -137,10 +145,18 @@ export class NebStore { const rawIntegrations = await NebStore.listEnabledNebComplexBots(); return Promise.all(rawIntegrations.map(async i => { const proxy = new NebProxy(i.neb, requestingUserId); - const notifUserId = await proxy.getNotificationUserId(i.integration, roomId); - const botUserId = null; // TODO: For github - const botConfig = await proxy.getServiceConfiguration(i.integration, roomId); - return new ComplexBot(i.integration, notifUserId, botUserId, botConfig); + try { + const notifUserId = await proxy.getNotificationUserId(i.integration, roomId); + const botUserId = null; // TODO: For github + const botConfig = await proxy.getServiceConfiguration(i.integration, roomId); + return new ComplexBot(i.integration, notifUserId, botUserId, botConfig); + } catch (e) { + LogService.error("NebStore", e); + + const bot = new ComplexBot(i.integration, null, null, null); + bot.isOnline = false; + return bot; + } })); } diff --git a/src/integrations/Integration.ts b/src/integrations/Integration.ts index c30a071..c096995 100644 --- a/src/integrations/Integration.ts +++ b/src/integrations/Integration.ts @@ -13,6 +13,7 @@ export class Integration { public description: string; public isEnabled: boolean; public isPublic: boolean; + public isOnline: boolean; constructor(record: IntegrationRecord) { this.type = record.type; @@ -21,6 +22,7 @@ export class Integration { this.description = record.description; this.isEnabled = record.isEnabled; this.isPublic = record.isPublic; + this.isOnline = true; } } diff --git a/web/app/admin/bridges/irc/irc.component.html b/web/app/admin/bridges/irc/irc.component.html index bc03342..59b567f 100644 --- a/web/app/admin/bridges/irc/irc.component.html +++ b/web/app/admin/bridges/irc/irc.component.html @@ -27,10 +27,14 @@ {{ bridge.upstreamId ? "matrix.org's bridge" : "Self-hosted bridge" }} ({{ bridge.provisionUrl }}) - + {{ getEnabledNetworksString(bridge) }} - + + + This bridge is offline or unavailable. + + diff --git a/web/app/integration-bag/integration-bag.component.html b/web/app/integration-bag/integration-bag.component.html index df8789f..e0423c8 100644 --- a/web/app/integration-bag/integration-bag.component.html +++ b/web/app/integration-bag/integration-bag.component.html @@ -4,5 +4,6 @@
{{ integration.displayName }}
{{ integration.description }}
+
\ No newline at end of file diff --git a/web/app/integration-bag/integration-bag.component.scss b/web/app/integration-bag/integration-bag.component.scss index 50a70d5..57aa841 100644 --- a/web/app/integration-bag/integration-bag.component.scss +++ b/web/app/integration-bag/integration-bag.component.scss @@ -55,4 +55,10 @@ margin-right: 5px; color: themed(integrationDescriptionColor); } + + .integration .integration-offline { + position: absolute; + top: 5px; + right: 6px; + } } \ No newline at end of file diff --git a/web/app/riot/riot-home/home.component.ts b/web/app/riot/riot-home/home.component.ts index 77ffed7..064c1b2 100644 --- a/web/app/riot/riot-home/home.component.ts +++ b/web/app/riot/riot-home/home.component.ts @@ -233,6 +233,12 @@ export class RiotHomeComponent { } private async updateIntegrationState(integration: FE_Integration) { + if (!integration.isOnline) { + integration._isSupported = false; + integration._notSupportedReason = "This integration is offline or unavailable"; + return; + } + if (!integration.requirements) return; let promises = integration.requirements.map(r => this.checkRequirement(r)); diff --git a/web/app/shared/models/integration.ts b/web/app/shared/models/integration.ts index ec62f5f..a841e4d 100644 --- a/web/app/shared/models/integration.ts +++ b/web/app/shared/models/integration.ts @@ -8,6 +8,7 @@ export interface FE_Integration { description: string; isEnabled: boolean; isPublic: boolean; + isOnline: boolean; // Used by us _inRoom: boolean; diff --git a/web/app/shared/models/irc.ts b/web/app/shared/models/irc.ts index 40839ac..603cd8d 100644 --- a/web/app/shared/models/irc.ts +++ b/web/app/shared/models/irc.ts @@ -3,6 +3,7 @@ export interface FE_IrcBridge { upstreamId?: number; provisionUrl?: string; isEnabled: boolean; + isOnline: boolean; availableNetworks: FE_IrcBridgeAvailableNetworks; } diff --git a/web/style/app.scss b/web/style/app.scss index ad280b8..a86f827 100644 --- a/web/style/app.scss +++ b/web/style/app.scss @@ -52,3 +52,7 @@ button { display: block; font-size: 12px; } + +.error-text { + color: #bd362f; +} \ No newline at end of file