mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-07-18 08:51:51 +00:00
Add the shell for configuring IRC bridges
This commit is contained in:
parent
bd03db7674
commit
76931819af
|
@ -6,6 +6,7 @@ import { WidgetStore } from "../../db/WidgetStore";
|
|||
import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache";
|
||||
import { Integration } from "../../integrations/Integration";
|
||||
import { LogService } from "matrix-js-snippets";
|
||||
import { BridgeStore } from "../../db/BridgeStore";
|
||||
|
||||
interface SetEnabledRequest {
|
||||
enabled: boolean;
|
||||
|
@ -42,6 +43,7 @@ export class AdminIntegrationsService {
|
|||
const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||
|
||||
if (category === "widget") await WidgetStore.setEnabled(type, body.enabled);
|
||||
else if (category === "bridge") await BridgeStore.setEnabled(type, body.enabled);
|
||||
else throw new ApiError(400, "Unrecognized category");
|
||||
|
||||
LogService.info("AdminIntegrationsService", userId + " set " + category + "/" + type + " to " + (body.enabled ? "enabled" : "disabled"));
|
||||
|
@ -51,10 +53,11 @@ export class AdminIntegrationsService {
|
|||
|
||||
@GET
|
||||
@Path(":category/all")
|
||||
public async getAllIntegrations(@QueryParam("scalar_token") scalarToken: string, @QueryParam("category") category: string): Promise<Integration[]> {
|
||||
await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||
public async getAllIntegrations(@QueryParam("scalar_token") scalarToken: string, @PathParam("category") category: string): Promise<Integration[]> {
|
||||
const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||
|
||||
if (category === "widget") return await DimensionIntegrationsService.getWidgets(false);
|
||||
else if (category === "bridge") return await DimensionIntegrationsService.getBridges(false, userId);
|
||||
else throw new ApiError(400, "Unrecongized category");
|
||||
}
|
||||
}
|
|
@ -8,11 +8,14 @@ import { WidgetStore } from "../../db/WidgetStore";
|
|||
import { SimpleBot } from "../../integrations/SimpleBot";
|
||||
import { NebStore } from "../../db/NebStore";
|
||||
import { ComplexBot } from "../../integrations/ComplexBot";
|
||||
import { Bridge } from "../../integrations/Bridge";
|
||||
import { BridgeStore } from "../../db/BridgeStore";
|
||||
|
||||
export interface IntegrationsResponse {
|
||||
widgets: Widget[],
|
||||
bots: SimpleBot[],
|
||||
complexBots: ComplexBot[],
|
||||
bridges: Bridge[],
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,6 +38,24 @@ export class DimensionIntegrationsService {
|
|||
return widgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of bridges
|
||||
* @param {boolean} enabledOnly True to only return the enabled bridges
|
||||
* @param {string} forUserId The requesting user ID
|
||||
* @param {string} inRoomId If specified, the room ID to list the bridges in
|
||||
* @returns {Promise<Bridge[]>} Resolves to the bridge list
|
||||
*/
|
||||
public static async getBridges(enabledOnly: boolean, forUserId: string, inRoomId?: string): Promise<Bridge[]> {
|
||||
const cacheKey = inRoomId ? "bridges_" + inRoomId : "bridges";
|
||||
|
||||
const cached = Cache.for(CACHE_INTEGRATIONS).get(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
const bridges = await BridgeStore.listAll(forUserId, enabledOnly ? true : null, inRoomId);
|
||||
Cache.for(CACHE_INTEGRATIONS).put(cacheKey, bridges);
|
||||
return bridges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of simple bots
|
||||
* @param {string} userId The requesting user ID
|
||||
|
@ -72,6 +93,7 @@ export class DimensionIntegrationsService {
|
|||
widgets: await DimensionIntegrationsService.getWidgets(true),
|
||||
bots: await DimensionIntegrationsService.getSimpleBots(userId),
|
||||
complexBots: await DimensionIntegrationsService.getComplexBots(userId, roomId),
|
||||
bridges: await DimensionIntegrationsService.getBridges(true, userId, roomId),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -83,6 +105,7 @@ export class DimensionIntegrationsService {
|
|||
if (category === "widget") return roomConfig.widgets.find(i => i.type === integrationType);
|
||||
else if (category === "bot") return roomConfig.bots.find(i => i.type === integrationType);
|
||||
else if (category === "complex-bot") return roomConfig.complexBots.find(i => i.type === integrationType);
|
||||
else if (category === "bridge") return roomConfig.bridges.find(i => i.type === integrationType);
|
||||
else throw new ApiError(400, "Unrecognized category");
|
||||
}
|
||||
|
||||
|
@ -92,6 +115,7 @@ export class DimensionIntegrationsService {
|
|||
const userId = await ScalarService.getTokenOwner(scalarToken);
|
||||
|
||||
if (category === "complex-bot") await NebStore.setComplexBotConfig(userId, integrationType, roomId, newConfig);
|
||||
else if (category === "bridge") await BridgeStore.setBridgeRoomConfig(userId, integrationType, roomId, newConfig);
|
||||
else throw new ApiError(400, "Unrecognized category");
|
||||
|
||||
Cache.for(CACHE_INTEGRATIONS).clear(); // TODO: Improve which cache we invalidate
|
||||
|
@ -106,6 +130,7 @@ export class DimensionIntegrationsService {
|
|||
if (category === "widget") throw new ApiError(400, "Widgets should be removed client-side");
|
||||
else if (category === "bot") await NebStore.removeSimpleBot(integrationType, roomId, userId);
|
||||
else if (category === "complex-bot") throw new ApiError(400, "Complex bots should be removed automatically");
|
||||
else if (category === "bridge") throw new ApiError(400, "Bridges should be removed automatically");
|
||||
else throw new ApiError(400, "Unrecognized category");
|
||||
|
||||
Cache.for(CACHE_INTEGRATIONS).clear(); // TODO: Improve which cache we invalidate
|
||||
|
|
19
src/bridges/IrcBridge.ts
Normal file
19
src/bridges/IrcBridge.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import BridgeRecord from "../db/models/BridgeRecord";
|
||||
import { IrcBridgeConfiguration } from "../integrations/Bridge";
|
||||
|
||||
export class IrcBridge {
|
||||
constructor(private bridgeRecord: BridgeRecord) {
|
||||
}
|
||||
|
||||
public async hasNetworks(): Promise<boolean> {
|
||||
return !!this.bridgeRecord;
|
||||
}
|
||||
|
||||
public async getRoomConfiguration(requestingUserId: string, inRoomId: string): Promise<IrcBridgeConfiguration> {
|
||||
return <any>{requestingUserId, inRoomId};
|
||||
}
|
||||
|
||||
public async setRoomConfiguration(requestingUserId: string, inRoomId: string, newConfig: IrcBridgeConfiguration): Promise<any> {
|
||||
return <any>{requestingUserId, inRoomId, newConfig};
|
||||
}
|
||||
}
|
66
src/db/BridgeStore.ts
Normal file
66
src/db/BridgeStore.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { Bridge } from "../integrations/Bridge";
|
||||
import BridgeRecord from "./models/BridgeRecord";
|
||||
import { IrcBridge } from "../bridges/IrcBridge";
|
||||
|
||||
export class BridgeStore {
|
||||
|
||||
public static async listAll(requestingUserId: string, isEnabled?: boolean, inRoomId?: string): Promise<Bridge[]> {
|
||||
let conditions = {};
|
||||
if (isEnabled === true || isEnabled === false) conditions = {where: {isEnabled: isEnabled}};
|
||||
|
||||
const allRecords = await BridgeRecord.findAll(conditions);
|
||||
const enabledBridges: Bridge[] = [];
|
||||
|
||||
for (const bridgeRecord of allRecords) {
|
||||
if (isEnabled === true || isEnabled === false) {
|
||||
const isLogicallyEnabled = await BridgeStore.isLogicallyEnabled(bridgeRecord);
|
||||
if (isLogicallyEnabled !== isEnabled) continue;
|
||||
}
|
||||
|
||||
const bridgeConfig = await BridgeStore.getConfiguration(bridgeRecord, requestingUserId, inRoomId);
|
||||
enabledBridges.push(new Bridge(bridgeRecord, bridgeConfig));
|
||||
}
|
||||
|
||||
return enabledBridges;
|
||||
}
|
||||
|
||||
public static async setEnabled(type: string, isEnabled: boolean): Promise<any> {
|
||||
const bridge = await BridgeRecord.findOne({where: {type: type}});
|
||||
if (!bridge) throw new Error("Bridge not found");
|
||||
|
||||
bridge.isEnabled = isEnabled;
|
||||
return bridge.save();
|
||||
}
|
||||
|
||||
public static async setBridgeRoomConfig(requestingUserId: string, integrationType: string, inRoomId: string, newConfig: any): Promise<any> {
|
||||
console.log(requestingUserId);
|
||||
console.log(inRoomId);
|
||||
console.log(newConfig);
|
||||
const record = await BridgeRecord.findOne({where: {type: integrationType}});
|
||||
if (!record) throw new Error("Bridge not found");
|
||||
|
||||
if (integrationType === "irc") {
|
||||
const irc = new IrcBridge(record);
|
||||
return irc.setRoomConfiguration(requestingUserId, inRoomId, newConfig);
|
||||
} else throw new Error("Unsupported bridge");
|
||||
}
|
||||
|
||||
private static async isLogicallyEnabled(record: BridgeRecord): Promise<boolean> {
|
||||
if (record.type === "irc") {
|
||||
const irc = new IrcBridge(record);
|
||||
return irc.hasNetworks();
|
||||
} else return true;
|
||||
}
|
||||
|
||||
private static async getConfiguration(record: BridgeRecord, requestingUserId: string, inRoomId?: string): Promise<any> {
|
||||
if (record.type === "irc") {
|
||||
if (!inRoomId) return {}; // The bridge's admin config is handled by other APIs
|
||||
const irc = new IrcBridge(record);
|
||||
return irc.getRoomConfiguration(requestingUserId, inRoomId);
|
||||
} else return {};
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@ import NebBotUser from "./models/NebBotUser";
|
|||
import NebNotificationUser from "./models/NebNotificationUser";
|
||||
import NebIntegrationConfig from "./models/NebIntegrationConfig";
|
||||
import Webhook from "./models/Webhook";
|
||||
import BridgeRecord from "./models/BridgeRecord";
|
||||
|
||||
class _DimensionStore {
|
||||
private sequelize: Sequelize;
|
||||
|
@ -41,6 +42,7 @@ class _DimensionStore {
|
|||
NebNotificationUser,
|
||||
NebIntegrationConfig,
|
||||
Webhook,
|
||||
BridgeRecord,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
30
src/db/migrations/20180330172445-AddBridges.ts
Normal file
30
src/db/migrations/20180330172445-AddBridges.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { QueryInterface } from "sequelize";
|
||||
import { DataType } from "sequelize-typescript";
|
||||
|
||||
export default {
|
||||
up: (queryInterface: QueryInterface) => {
|
||||
return Promise.resolve()
|
||||
.then(() => queryInterface.createTable("dimension_bridges", {
|
||||
"id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false},
|
||||
"type": {type: DataType.STRING, allowNull: false},
|
||||
"name": {type: DataType.STRING, allowNull: false},
|
||||
"avatarUrl": {type: DataType.STRING, allowNull: false},
|
||||
"description": {type: DataType.STRING, allowNull: false},
|
||||
"isEnabled": {type: DataType.BOOLEAN, allowNull: false},
|
||||
"isPublic": {type: DataType.BOOLEAN, allowNull: false},
|
||||
}))
|
||||
.then(() => queryInterface.bulkInsert("dimension_bridges", [
|
||||
{
|
||||
type: "irc",
|
||||
name: "IRC Bridge",
|
||||
avatarUrl: "/img/avatars/irc.png",
|
||||
isEnabled: true,
|
||||
isPublic: true,
|
||||
description: "Bridges IRC channels to rooms, supporting multiple networks",
|
||||
},
|
||||
]));
|
||||
},
|
||||
down: (queryInterface: QueryInterface) => {
|
||||
return queryInterface.dropTable("dimension_bridges");
|
||||
}
|
||||
}
|
32
src/db/models/BridgeRecord.ts
Normal file
32
src/db/models/BridgeRecord.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { AutoIncrement, Column, Model, PrimaryKey, Table } from "sequelize-typescript";
|
||||
import { IntegrationRecord } from "./IntegrationRecord";
|
||||
|
||||
@Table({
|
||||
tableName: "dimension_bridges",
|
||||
underscoredAll: false,
|
||||
timestamps: false,
|
||||
})
|
||||
export default class BridgeRecord extends Model<BridgeRecord> implements IntegrationRecord {
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column
|
||||
id: number;
|
||||
|
||||
@Column
|
||||
type: string;
|
||||
|
||||
@Column
|
||||
name: string;
|
||||
|
||||
@Column
|
||||
avatarUrl: string;
|
||||
|
||||
@Column
|
||||
description: string;
|
||||
|
||||
@Column
|
||||
isEnabled: boolean;
|
||||
|
||||
@Column
|
||||
isPublic: boolean;
|
||||
}
|
32
src/integrations/Bridge.ts
Normal file
32
src/integrations/Bridge.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Integration } from "./Integration";
|
||||
import BridgeRecord from "../db/models/BridgeRecord";
|
||||
|
||||
export class Bridge extends Integration {
|
||||
constructor(bridge: BridgeRecord, public config: any) {
|
||||
super(bridge);
|
||||
this.category = "bridge";
|
||||
this.requirements = [{
|
||||
condition: "publicRoom",
|
||||
expectedValue: true,
|
||||
argument: null, // not used
|
||||
}];
|
||||
|
||||
// We'll just say we aren't
|
||||
this.isEncryptionSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IrcBridgeConfiguration {
|
||||
availableNetworks: {
|
||||
[networkId: string]: {
|
||||
name: string;
|
||||
bridgeUserId: string;
|
||||
};
|
||||
};
|
||||
links: {
|
||||
[networkId: string]: {
|
||||
channelName: string;
|
||||
addedByUserId: string;
|
||||
}[];
|
||||
};
|
||||
}
|
|
@ -2,7 +2,6 @@ Release checklist:
|
|||
* IRC Bridge
|
||||
* Update documentation
|
||||
* Configuration migration (if possible)
|
||||
* Lots of logging
|
||||
* Final testing (widgets, bots, etc)
|
||||
|
||||
After release:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<li (click)="goto('')" [ngClass]="[isActive('', true) ? 'active' : '']">Dashboard</li>
|
||||
<li (click)="goto('widgets')" [ngClass]="[isActive('widgets') ? 'active' : '']">Widgets</li>
|
||||
<li (click)="goto('neb')" [ngClass]="[isActive('neb') ? 'active' : '']">go-neb</li>
|
||||
<li (click)="goto('bridges')" [ngClass]="[isActive('bridges') ? 'active' : '']">Bridges</li>
|
||||
</ul>
|
||||
<span class="version">{{ version }}</span>
|
||||
|
||||
|
|
37
web/app/admin/bridges/bridges.component.html
Normal file
37
web/app/admin/bridges/bridges.component.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
<div *ngIf="isLoading">
|
||||
<my-spinner></my-spinner>
|
||||
</div>
|
||||
<div *ngIf="!isLoading">
|
||||
<my-ibox title="Bridges">
|
||||
<div class="my-ibox-content">
|
||||
<p>
|
||||
Bridges provide a way for rooms to interact with and/or bring in events from a third party network. For
|
||||
example, an IRC bridge can allow IRC and matrix users to communicate with each other.
|
||||
</p>
|
||||
|
||||
<table class="table table-striped table-condensed table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th class="text-center" style="width: 120px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngIf="!bridges || bridges.length === 0">
|
||||
<td colspan="2"><i>No bridges.</i></td>
|
||||
</tr>
|
||||
<tr *ngFor="let bridge of bridges trackById">
|
||||
<td>{{ bridge.displayName }}</td>
|
||||
<td>{{ bridge.description }}</td>
|
||||
<td class="text-center">
|
||||
<span class="editButton" [routerLink]="[bridge.type]" title="edit">
|
||||
<i class="fa fa-pencil-alt"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</my-ibox>
|
||||
</div>
|
8
web/app/admin/bridges/bridges.component.scss
Normal file
8
web/app/admin/bridges/bridges.component.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
tr td:last-child {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.appsvcConfigButton,
|
||||
.editButton {
|
||||
cursor: pointer;
|
||||
}
|
28
web/app/admin/bridges/bridges.component.ts
Normal file
28
web/app/admin/bridges/bridges.component.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { ToasterService } from "angular2-toaster";
|
||||
import { FE_Bridge } from "../../shared/models/integration";
|
||||
import { AdminIntegrationsApiService } from "../../shared/services/admin/admin-integrations-api.service";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./bridges.component.html",
|
||||
styleUrls: ["./bridges.component.scss"],
|
||||
})
|
||||
export class AdminBridgesComponent implements OnInit {
|
||||
|
||||
public isLoading = true;
|
||||
public bridges: FE_Bridge<any>[];
|
||||
|
||||
constructor(private adminIntegrations: AdminIntegrationsApiService,
|
||||
private toaster: ToasterService) {
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.adminIntegrations.getAllBridges().then(bridges => {
|
||||
this.bridges = bridges.filter(b => b.isEnabled);
|
||||
this.isLoading = false;
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
this.toaster.pop("error", "Failed to load bridges");
|
||||
});
|
||||
}
|
||||
}
|
49
web/app/admin/bridges/irc/irc.component.html
Normal file
49
web/app/admin/bridges/irc/irc.component.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
<div *ngIf="isLoading">
|
||||
<my-spinner></my-spinner>
|
||||
</div>
|
||||
<div *ngIf="!isLoading">
|
||||
<my-ibox title="IRC Bridge Configurations">
|
||||
<div class="my-ibox-content">
|
||||
<p>
|
||||
<a href="https://github.com/matrix-org/matrix-appservice-irc" target="_blank">matrix-appservice-irc</a>
|
||||
is an IRC bridge that supports multiple IRC networks. Dimension is capable of using multiple IRC
|
||||
bridges to better distribute the load across multiple networks in large deployments.
|
||||
</p>
|
||||
|
||||
<table class="table table-striped table-condensed table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Enabled Networks</th>
|
||||
<th class="text-center" style="width: 120px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngIf="!configurations || configurations.length === 0">
|
||||
<td colspan="2"><i>No bridge configurations.</i></td>
|
||||
</tr>
|
||||
<tr *ngFor="let bridge of configurations trackById">
|
||||
<td>
|
||||
{{ bridge.upstreamId ? "matrix.org's bridge" : "Self-hosted bridge" }}
|
||||
<span class="text-muted" style="display: inline-block;" *ngIf="!bridge.upstreamId">({{ bridge.adminUrl }})</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ getEnabledNetworksString(bridge) }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="editButton" (click)="editNetworks(bridge)">
|
||||
<i class="fa fa-pencil-alt"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-success btn-sm" (click)="addModularHostedBridge()" *ngIf="!hasModularBridge">
|
||||
<i class="fa fa-plus"></i> Add matrix.org's bridge
|
||||
</button>
|
||||
<button type="button" class="btn btn-success btn-sm" (click)="addSelfHostedBridge()">
|
||||
<i class="fa fa-plus"></i> Add self-hosted bridge
|
||||
</button>
|
||||
</div>
|
||||
</my-ibox>
|
||||
</div>
|
0
web/app/admin/bridges/irc/irc.component.scss
Normal file
0
web/app/admin/bridges/irc/irc.component.scss
Normal file
31
web/app/admin/bridges/irc/irc.component.ts
Normal file
31
web/app/admin/bridges/irc/irc.component.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./irc.component.html",
|
||||
styleUrls: ["./irc.component.scss"],
|
||||
})
|
||||
export class AdminIrcBridgeComponent {
|
||||
|
||||
public isLoading = true;
|
||||
public hasModularBridge = false;
|
||||
public configurations: any[] = [];
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public getEnabledNetworksString(bridge: any): string {
|
||||
return "TODO: " + bridge;
|
||||
}
|
||||
|
||||
public addModularHostedBridge() {
|
||||
|
||||
}
|
||||
|
||||
public addSelfHostedBridge() {
|
||||
|
||||
}
|
||||
|
||||
public editNetworks(bridge: any) {
|
||||
console.log(bridge);
|
||||
}
|
||||
}
|
|
@ -22,9 +22,12 @@ export class AdminWidgetsComponent {
|
|||
public widgets: FE_Widget[];
|
||||
|
||||
constructor(private adminIntegrationsApi: AdminIntegrationsApiService, private toaster: ToasterService, private modal: Modal) {
|
||||
this.adminIntegrationsApi.getAllWidgets().then(integrations => {
|
||||
this.adminIntegrationsApi.getAllWidgets().then(widgets => {
|
||||
this.isLoading = false;
|
||||
this.widgets = integrations.widgets;
|
||||
this.widgets = widgets;
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
this.toaster.pop("error", "Failed to load widgets");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,9 @@ import { ConfigSimpleBotComponent } from "./configs/simple-bot/simple-bot.compon
|
|||
import { ConfigScreenComplexBotComponent } from "./configs/complex-bot/config-screen/config-screen.complex-bot.component";
|
||||
import { RssComplexBotConfigComponent } from "./configs/complex-bot/rss/rss.complex-bot.component";
|
||||
import { TravisCiComplexBotConfigComponent } from "./configs/complex-bot/travisci/travisci.complex-bot.component";
|
||||
import { ConfigScreenBridgeComponent } from "./configs/bridge/config-screen/config-screen.bridge.component";
|
||||
import { AdminBridgesComponent } from "./admin/bridges/bridges.component";
|
||||
import { AdminIrcBridgeComponent } from "./admin/bridges/irc/irc.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -118,6 +121,9 @@ import { TravisCiComplexBotConfigComponent } from "./configs/complex-bot/travisc
|
|||
ConfigScreenComplexBotComponent,
|
||||
RssComplexBotConfigComponent,
|
||||
TravisCiComplexBotConfigComponent,
|
||||
ConfigScreenBridgeComponent,
|
||||
AdminBridgesComponent,
|
||||
AdminIrcBridgeComponent,
|
||||
|
||||
// Vendor
|
||||
],
|
||||
|
|
|
@ -21,6 +21,8 @@ import { AdminEditNebComponent } from "./admin/neb/edit/edit.component";
|
|||
import { AdminAddSelfhostedNebComponent } from "./admin/neb/add-selfhosted/add-selfhosted.component";
|
||||
import { RssComplexBotConfigComponent } from "./configs/complex-bot/rss/rss.complex-bot.component";
|
||||
import { TravisCiComplexBotConfigComponent } from "./configs/complex-bot/travisci/travisci.complex-bot.component";
|
||||
import { AdminBridgesComponent } from "./admin/bridges/bridges.component";
|
||||
import { AdminIrcBridgeComponent } from "./admin/bridges/irc/irc.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: "", component: HomeComponent},
|
||||
|
@ -68,6 +70,21 @@ const routes: Routes = [
|
|||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "bridges",
|
||||
data: {breadcrumb: "Bridges", name: "Bridges"},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: AdminBridgesComponent,
|
||||
},
|
||||
{
|
||||
path: "irc",
|
||||
component: AdminIrcBridgeComponent,
|
||||
data: {breadcrumb: "IRC Bridge", name: "IRC Bridge"},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -125,6 +142,15 @@ const routes: Routes = [
|
|||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// path: "bridge",
|
||||
// children: [
|
||||
// {
|
||||
// path: "irc",
|
||||
//
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
69
web/app/configs/bridge/bridge.component.ts
Normal file
69
web/app/configs/bridge/bridge.component.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import { OnDestroy, OnInit } from "@angular/core";
|
||||
import { FE_Bridge } from "../../shared/models/integration";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { IntegrationsApiService } from "../../shared/services/integrations/integrations-api.service";
|
||||
import { ToasterService } from "angular2-toaster";
|
||||
import { ServiceLocator } from "../../shared/registry/locator.service";
|
||||
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
|
||||
|
||||
export class BridgeComponent<T> implements OnInit, OnDestroy {
|
||||
|
||||
public isLoading = true;
|
||||
public isUpdating = false;
|
||||
public bridge: FE_Bridge<T>;
|
||||
public newConfig: T;
|
||||
public roomId: string;
|
||||
|
||||
private routeQuerySubscription: Subscription;
|
||||
|
||||
protected toaster = ServiceLocator.injector.get(ToasterService);
|
||||
protected integrationsApi = ServiceLocator.injector.get(IntegrationsApiService);
|
||||
protected route = ServiceLocator.injector.get(ActivatedRoute);
|
||||
protected scalarClientApi = ServiceLocator.injector.get(ScalarClientApiService);
|
||||
|
||||
constructor(private integrationType: string) {
|
||||
this.isLoading = true;
|
||||
this.isUpdating = false;
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.routeQuerySubscription = this.route.queryParams.subscribe(params => {
|
||||
this.roomId = params['roomId'];
|
||||
this.loadBridge();
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
if (this.routeQuerySubscription) this.routeQuerySubscription.unsubscribe();
|
||||
}
|
||||
|
||||
private loadBridge() {
|
||||
this.isLoading = true;
|
||||
this.isUpdating = false;
|
||||
|
||||
this.newConfig = <T>{};
|
||||
|
||||
this.integrationsApi.getIntegrationInRoom("bridge", this.integrationType, this.roomId).then(i => {
|
||||
this.bridge = <FE_Bridge<T>>i;
|
||||
this.newConfig = JSON.parse(JSON.stringify(this.bridge.config));
|
||||
this.isLoading = false;
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
this.toaster.pop("error", "Failed to load configuration");
|
||||
});
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
this.isUpdating = true;
|
||||
this.integrationsApi.setIntegrationConfiguration("bridge", this.integrationType, this.roomId, this.newConfig).then(() => {
|
||||
this.toaster.pop("success", "Configuration updated");
|
||||
this.bridge.config = this.newConfig;
|
||||
this.isUpdating = false;
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
this.toaster.pop("error", "Error updating configuration");
|
||||
this.isUpdating = false;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<div *ngIf="bridgeComponent.isLoading">
|
||||
<my-spinner></my-spinner>
|
||||
</div>
|
||||
<div *ngIf="!bridgeComponent.isLoading">
|
||||
<ng-container *ngTemplateOutlet="bridgeParamsTemplate"></ng-container>
|
||||
</div>
|
|
@ -0,0 +1,16 @@
|
|||
import { Component, ContentChild, Input, TemplateRef } from "@angular/core";
|
||||
import { BridgeComponent } from "../bridge.component";
|
||||
|
||||
@Component({
|
||||
selector: "my-bridge-config",
|
||||
templateUrl: "config-screen.bridge.component.html",
|
||||
styleUrls: ["config-screen.bridge.component.scss"],
|
||||
})
|
||||
export class ConfigScreenBridgeComponent {
|
||||
|
||||
@Input() bridgeComponent: BridgeComponent<any>;
|
||||
@ContentChild(TemplateRef) bridgeParamsTemplate: TemplateRef<any>;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
|
@ -259,6 +259,8 @@ export class RiotHomeComponent {
|
|||
console.error(err);
|
||||
if (requirement.expectedValue) return Promise.reject("Expected to be able to send specific event types");
|
||||
});
|
||||
case "userInRoom":
|
||||
// TODO: Implement
|
||||
default:
|
||||
return Promise.reject("Requirement '" + requirement.condition + "' not found");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { FE_Widget } from "./integration";
|
||||
import { FE_Bridge, FE_Widget } from "./integration";
|
||||
|
||||
export interface FE_IntegrationsResponse {
|
||||
widgets: FE_Widget[];
|
||||
bridges: FE_Bridge<any>[];
|
||||
}
|
|
@ -26,6 +26,11 @@ export interface FE_ComplexBot<T> extends FE_Integration {
|
|||
config: T;
|
||||
}
|
||||
|
||||
export interface FE_Bridge<T> extends FE_Integration {
|
||||
bridgeUserId: string;
|
||||
config: T;
|
||||
}
|
||||
|
||||
export interface FE_Widget extends FE_Integration {
|
||||
options: any;
|
||||
}
|
||||
|
@ -44,7 +49,7 @@ export interface FE_JitsiWidget extends FE_Widget {
|
|||
}
|
||||
|
||||
export interface FE_IntegrationRequirement {
|
||||
condition: "publicRoom" | "canSendEventTypes";
|
||||
condition: "publicRoom" | "canSendEventTypes" | "userInRoom";
|
||||
argument: any;
|
||||
expectedValue: any;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from "@angular/core";
|
||||
import { Http } from "@angular/http";
|
||||
import { AuthedApi } from "../authed-api";
|
||||
import { FE_IntegrationsResponse } from "../../models/dimension-responses";
|
||||
import { FE_Bridge, FE_Widget } from "../../models/integration";
|
||||
|
||||
@Injectable()
|
||||
export class AdminIntegrationsApiService extends AuthedApi {
|
||||
|
@ -9,10 +9,14 @@ export class AdminIntegrationsApiService extends AuthedApi {
|
|||
super(http);
|
||||
}
|
||||
|
||||
public getAllWidgets(): Promise<FE_IntegrationsResponse> {
|
||||
public getAllWidgets(): Promise<FE_Widget[]> {
|
||||
return this.authedGet("/api/v1/dimension/admin/integrations/widget/all").map(r => r.json()).toPromise();
|
||||
}
|
||||
|
||||
public getAllBridges(): Promise<FE_Bridge<any>[]> {
|
||||
return this.authedGet("/api/v1/dimension/admin/integrations/bridge/all").map(r => r.json()).toPromise();
|
||||
}
|
||||
|
||||
public toggleIntegration(category: string, type: string, enabled: boolean): Promise<any> {
|
||||
return this.authedPost("/api/v1/dimension/admin/integrations/" + category + "/" + type + "/enabled", {enabled: enabled}).map(r => r.json()).toPromise();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user