API and UI for toggling IRC networks

This commit is contained in:
Travis Ralston 2018-03-31 11:26:00 -06:00
parent 61ca805b19
commit aa8ec0ed58
8 changed files with 147 additions and 3 deletions

View File

@ -6,6 +6,7 @@ import { ApiError } from "../ApiError";
import IrcBridgeRecord from "../../db/models/IrcBridgeRecord";
import { AvailableNetworks, IrcBridge } from "../../bridges/IrcBridge";
import Upstream from "../../db/models/Upstream";
import IrcBridgeNetwork from "../../db/models/IrcBridgeNetwork";
interface CreateWithUpstream {
upstreamId: number;
@ -23,6 +24,10 @@ interface BridgeResponse {
availableNetworks: AvailableNetworks;
}
interface SetEnabledRequest {
isEnabled: boolean;
}
/**
* Administrative API for configuring IRC bridge instances.
*/
@ -65,6 +70,31 @@ export class AdminIrcService {
};
}
@POST
@Path(":bridgeId/network/:networkId/enabled")
public async setNetworkEnabled(@QueryParam("scalar_token") scalarToken: string, @PathParam("bridgeId") bridgeId: number, @PathParam("networkId") networkId: string, request: SetEnabledRequest): Promise<any> {
const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken);
const ircBridge = await IrcBridgeRecord.findByPrimary(bridgeId);
if (!ircBridge) throw new ApiError(404, "IRC Bridge not found");
const localNetworkId = IrcBridge.parseNetworkId(networkId).bridgeNetworkId;
const network = await IrcBridgeNetwork.findOne({
where: {
bridgeId: ircBridge.id,
bridgeNetworkId: localNetworkId,
},
});
if (!network) throw new ApiError(404, "Network not found");
network.isEnabled = request.isEnabled;
await network.save();
LogService.info("AdminIrcService", userId + " toggled the network '" + localNetworkId + "' on bridge " + ircBridge.id);
Cache.for(CACHE_IRC_BRIDGE).clear();
return {}; // 200 OK
}
@POST
@Path("new/upstream")
public async newConfigForUpstream(@QueryParam("scalar_token") scalarToken: string, request: CreateWithUpstream): Promise<BridgeResponse> {

View File

@ -34,7 +34,7 @@ export class IrcBridge {
public static parseNetworkId(networkId: string): { bridgeId: string, bridgeNetworkId: string } {
const parts = networkId.split("-");
const bridgeId = parts.splice(1, 1)[0];
const bridgeId = parts.splice(0, 1)[0];
const bridgeNetworkId = parts.join("-");
return {bridgeId, bridgeNetworkId};
}

View File

@ -4,6 +4,8 @@ import { AdminIrcApiService } from "../../../shared/services/admin/admin-irc-api
import { FE_Upstream } from "../../../shared/models/admin-responses";
import { AdminUpstreamApiService } from "../../../shared/services/admin/admin-upstream-api.service";
import { FE_IrcBridge } from "../../../shared/models/irc";
import { Modal, overlayConfigFactory } from "ngx-modialog";
import { AdminIrcBridgeNetworksComponent, IrcNetworksDialogContext } from "./networks/networks.component";
@Component({
templateUrl: "./irc.component.html",
@ -20,7 +22,8 @@ export class AdminIrcBridgeComponent implements OnInit {
constructor(private upstreamApi: AdminUpstreamApiService,
private ircApi: AdminIrcApiService,
private toaster: ToasterService) {
private toaster: ToasterService,
private modal: Modal) {
}
public ngOnInit() {
@ -88,6 +91,11 @@ export class AdminIrcBridgeComponent implements OnInit {
}
public editNetworks(bridge: FE_IrcBridge) {
console.log(bridge);
this.modal.open(AdminIrcBridgeNetworksComponent, overlayConfigFactory({
bridge: bridge,
isBlocking: true,
size: 'lg',
}, IrcNetworksDialogContext));
}
}

View File

@ -0,0 +1,29 @@
<div class="dialog">
<div class="dialog-header">
<h4>{{ bridge.upstreamId ? "matrix.org's" : "Self-hosted" }} IRC Bridge Networks</h4>
</div>
<div class="dialog-content">
<table class="table table-striped table-condensed table-bordered">
<thead>
<tr>
<th>Network</th>
<th>Enabled</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let network of networks trackById">
<td>{{ network.name }}</td>
<td>
<ui-switch [checked]="network.isEnabled" size="small" [disabled]="isUpdating"
(change)="toggleNetwork(network)"></ui-switch>
</td>
</tr>
</tbody>
</table>
</div>
<div class="dialog-footer">
<button type="button" (click)="dialog.close()" title="close" class="btn btn-primary btn-sm">
<i class="far fa-times-circle"></i> Close
</button>
</div>
</div>

View File

@ -0,0 +1,5 @@
table tr th:last-child,
table tr td:last-child {
text-align: center;
width: 100px;
}

View File

@ -0,0 +1,63 @@
import { Component } from "@angular/core";
import { ToasterService } from "angular2-toaster";
import { DialogRef, ModalComponent } from "ngx-modialog";
import { BSModalContext } from "ngx-modialog/plugins/bootstrap";
import { FE_IrcBridge } from "../../../../shared/models/irc";
import { AdminIrcApiService } from "../../../../shared/services/admin/admin-irc-api.service";
export class IrcNetworksDialogContext extends BSModalContext {
public bridge: FE_IrcBridge;
}
interface LocalNetwork {
id: string;
name: string;
domain: string;
bridgeUserId: string;
isEnabled: boolean;
}
@Component({
templateUrl: "./networks.component.html",
styleUrls: ["./networks.component.scss"],
})
export class AdminIrcBridgeNetworksComponent implements ModalComponent<IrcNetworksDialogContext> {
public isUpdating = false;
public bridge: FE_IrcBridge;
public networks: LocalNetwork[];
constructor(public dialog: DialogRef<IrcNetworksDialogContext>,
private ircApi: AdminIrcApiService,
private toaster: ToasterService) {
this.bridge = dialog.context.bridge;
const networkIds = Object.keys(this.bridge.availableNetworks);
this.networks = networkIds.map(i => {
return {
id: i,
name: this.bridge.availableNetworks[i].name,
domain: this.bridge.availableNetworks[i].domain,
bridgeUserId: this.bridge.availableNetworks[i].bridgeUserId,
isEnabled: this.bridge.availableNetworks[i].isEnabled,
};
});
}
public toggleNetwork(network: LocalNetwork) {
network.isEnabled = !network.isEnabled;
this.bridge.availableNetworks[network.id].isEnabled = network.isEnabled;
this.isUpdating = true;
this.ircApi.setNetworkEnabled(this.bridge.id, network.id, network.isEnabled).then(() => {
this.isUpdating = false;
this.toaster.pop("success", "Network " + (network.isEnabled ? "enabled" : "disabled"));
}).catch(err => {
console.error(err);
this.isUpdating = false;
network.isEnabled = !network.isEnabled;
this.bridge.availableNetworks[network.id].isEnabled = network.isEnabled;
this.toaster.pop("error", "Failed to update network");
});
}
}

View File

@ -66,6 +66,7 @@ import { ConfigScreenBridgeComponent } from "./configs/bridge/config-screen/conf
import { AdminBridgesComponent } from "./admin/bridges/bridges.component";
import { AdminIrcBridgeComponent } from "./admin/bridges/irc/irc.component";
import { AdminIrcApiService } from "./shared/services/admin/admin-irc-api.service";
import { AdminIrcBridgeNetworksComponent } from "./admin/bridges/irc/networks/networks.component";
@NgModule({
imports: [
@ -125,6 +126,7 @@ import { AdminIrcApiService } from "./shared/services/admin/admin-irc-api.servic
ConfigScreenBridgeComponent,
AdminBridgesComponent,
AdminIrcBridgeComponent,
AdminIrcBridgeNetworksComponent,
// Vendor
],
@ -154,6 +156,7 @@ import { AdminIrcApiService } from "./shared/services/admin/admin-irc-api.servic
AdminNebGoogleConfigComponent,
AdminNebImgurConfigComponent,
ConfigSimpleBotComponent,
AdminIrcBridgeNetworksComponent,
]
})
export class AppModule {

View File

@ -25,4 +25,10 @@ export class AdminIrcApiService extends AuthedApi {
public newSelfhosted(provisionUrl: string): Promise<FE_IrcBridge> {
return this.authedPost("/api/v1/dimension/admin/irc/new/selfhosted", {provisionUrl: provisionUrl}).map(r => r.json()).toPromise();
}
public setNetworkEnabled(bridgeId: number, networkId: string, isEnabled: boolean): Promise<any> {
return this.authedPost("/api/v1/dimension/admin/irc/" + bridgeId + "/network/" + networkId + "/enabled", {
isEnabled: isEnabled,
}).map(r => r.json()).toPromise();
}
}