mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 01:05:53 -04:00
Support vector's RSS bot. Adds #13
This has a side effect of adding #23 as well. A more performant caching method is probably needed (as this doesn't cache at all).
This commit is contained in:
parent
3aa60b66a6
commit
58feb07119
@ -6,4 +6,3 @@ about: "Tracks any Atom/RSS feed and sends new items into this room"
|
|||||||
avatar: "/img/avatars/rssbot.png"
|
avatar: "/img/avatars/rssbot.png"
|
||||||
upstream:
|
upstream:
|
||||||
type: "vector"
|
type: "vector"
|
||||||
id: "rssbot"
|
|
@ -44,6 +44,7 @@
|
|||||||
"@angularclass/hmr-loader": "^3.0.2",
|
"@angularclass/hmr-loader": "^3.0.2",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.22",
|
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.22",
|
||||||
"@types/node": "^7.0.18",
|
"@types/node": "^7.0.18",
|
||||||
|
"angular2-modal": "^2.0.3",
|
||||||
"angular2-template-loader": "^0.6.2",
|
"angular2-template-loader": "^0.6.2",
|
||||||
"angular2-toaster": "^4.0.0",
|
"angular2-toaster": "^4.0.0",
|
||||||
"angular2-ui-switch": "^1.2.0",
|
"angular2-ui-switch": "^1.2.0",
|
||||||
|
@ -24,6 +24,7 @@ class DimensionApi {
|
|||||||
|
|
||||||
app.get("/api/v1/dimension/integrations/:roomId", this._getIntegrations.bind(this));
|
app.get("/api/v1/dimension/integrations/:roomId", this._getIntegrations.bind(this));
|
||||||
app.delete("/api/v1/dimension/integrations/:roomId/:type/:integrationType", this._removeIntegration.bind(this));
|
app.delete("/api/v1/dimension/integrations/:roomId/:type/:integrationType", this._removeIntegration.bind(this));
|
||||||
|
app.put("/api/v1/dimension/integrations/:roomId/:type/:integrationType/state", this._updateIntegrationState.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
_getIntegration(integrationConfig, roomId, scalarToken) {
|
_getIntegration(integrationConfig, roomId, scalarToken) {
|
||||||
@ -103,6 +104,38 @@ class DimensionApi {
|
|||||||
res.status(500).send({error: err.message});
|
res.status(500).send({error: err.message});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateIntegrationState(req, res) {
|
||||||
|
var roomId = req.params.roomId;
|
||||||
|
var scalarToken = req.body.scalar_token;
|
||||||
|
var type = req.params.type;
|
||||||
|
var integrationType = req.params.integrationType;
|
||||||
|
|
||||||
|
if (!roomId || !scalarToken || !type || !integrationType) {
|
||||||
|
res.status(400).send({error: "Missing room, integration type, type, or token"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var integrationConfig = Integrations.byType[type][integrationType];
|
||||||
|
if (!integrationConfig) {
|
||||||
|
res.status(400).send({error: "Unknown integration"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("DimensionApi", "Update state requested for " + type + " (" + integrationType + ") in room " + roomId);
|
||||||
|
|
||||||
|
this._db.checkToken(scalarToken).then(() => {
|
||||||
|
return this._getIntegration(integrationConfig, roomId, scalarToken);
|
||||||
|
}).then(integration => {
|
||||||
|
return integration.updateState(req.body.state);
|
||||||
|
}).then(newState => {
|
||||||
|
res.status(200).send(newState);
|
||||||
|
}).catch(err => {
|
||||||
|
log.error("DimensionApi", err);
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).send({error: err.message});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new DimensionApi();
|
module.exports = new DimensionApi();
|
||||||
|
@ -30,6 +30,15 @@ class IntegrationStub {
|
|||||||
removeFromRoom(roomId) {
|
removeFromRoom(roomId) {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the state information for this integration. The data passed is an implementation detail.
|
||||||
|
* @param {*} newState the new state
|
||||||
|
* @returns {Promise<*>} resolves when completed, with the new state of the integration
|
||||||
|
*/
|
||||||
|
updateState(newState) {
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = IntegrationStub;
|
module.exports = IntegrationStub;
|
||||||
|
@ -22,8 +22,16 @@ class RSSBot extends ComplexBot {
|
|||||||
|
|
||||||
/*override*/
|
/*override*/
|
||||||
getState() {
|
getState() {
|
||||||
|
var response = {
|
||||||
|
feeds: [],
|
||||||
|
immutableFeeds: []
|
||||||
|
};
|
||||||
return this._backbone.getFeeds().then(feeds => {
|
return this._backbone.getFeeds().then(feeds => {
|
||||||
return {feeds: feeds};
|
response.feeds = feeds;
|
||||||
|
return this._backbone.getImmutableFeeds();
|
||||||
|
}).then(feeds => {
|
||||||
|
response.immutableFeeds = feeds;
|
||||||
|
return response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,6 +39,11 @@ class RSSBot extends ComplexBot {
|
|||||||
removeFromRoom(roomId) {
|
removeFromRoom(roomId) {
|
||||||
return this._backbone.removeFromRoom(roomId);
|
return this._backbone.removeFromRoom(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*override*/
|
||||||
|
updateState(newState) {
|
||||||
|
return this._backbone.setFeeds(newState.feeds).then(() => this.getState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RSSBot;
|
module.exports = RSSBot;
|
@ -25,6 +25,23 @@ class StubbedRssBackbone {
|
|||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the new feeds for this backbone
|
||||||
|
* @param {string[]} newFeeds the new feed URLs
|
||||||
|
* @returns {Promise<>} resolves when complete
|
||||||
|
*/
|
||||||
|
setFeeds(newFeeds) {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the immutable feeds for this backbone
|
||||||
|
* @returns {Promise<{url:string,ownerId:string}>} resolves to the collection of immutable feeds
|
||||||
|
*/
|
||||||
|
getImmutableFeeds() {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the bot from the given room
|
* Removes the bot from the given room
|
||||||
* @param {string} roomId the room ID to remove the bot from
|
* @param {string} roomId the room ID to remove the bot from
|
||||||
|
@ -18,6 +18,7 @@ class VectorRssBackbone extends StubbedRssBackbone {
|
|||||||
this._roomId = roomId;
|
this._roomId = roomId;
|
||||||
this._scalarToken = upstreamScalarToken;
|
this._scalarToken = upstreamScalarToken;
|
||||||
this._info = null;
|
this._info = null;
|
||||||
|
this._otherFeeds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*override*/
|
/*override*/
|
||||||
@ -35,15 +36,46 @@ class VectorRssBackbone extends StubbedRssBackbone {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*override*/
|
||||||
|
setFeeds(newFeeds) {
|
||||||
|
var feedConfig = {};
|
||||||
|
for (var feed of newFeeds) feedConfig[feed] = {};
|
||||||
|
|
||||||
|
return VectorScalarClient.configureIntegration("rssbot", this._scalarToken, {
|
||||||
|
feeds: feedConfig,
|
||||||
|
room_id: this._roomId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*override*/
|
||||||
|
getImmutableFeeds() {
|
||||||
|
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||||
|
return this._otherFeeds;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_getInfo() {
|
_getInfo() {
|
||||||
return VectorScalarClient.getIntegration("rssbot", this._roomId, this._scalarToken).then(info => {
|
return VectorScalarClient.getIntegrationsForRoom(this._roomId, this._scalarToken).then(integrations => {
|
||||||
|
this._otherFeeds = [];
|
||||||
|
for (var integration of integrations) {
|
||||||
|
if (integration.self) continue; // skip - we're not looking for ones we know about
|
||||||
|
if (integration.type == "rssbot") {
|
||||||
|
var urls = _.keys(integration.config.feeds);
|
||||||
|
for (var url of urls) {
|
||||||
|
this._otherFeeds.push({url: url, ownerId: integration.user_id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return VectorScalarClient.getIntegration("rssbot", this._roomId, this._scalarToken);
|
||||||
|
}).then(info => {
|
||||||
this._info = info;
|
this._info = info;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*override*/
|
/*override*/
|
||||||
removeFromRoom(roomId) {
|
removeFromRoom(roomId) {
|
||||||
return VectorScalarClient.removeIntegration(this._config.upstream.id, roomId, this._upstreamToken);
|
return VectorScalarClient.removeIntegration("rssbot", roomId, this._scalarToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ module.exports = (db, integrationConfig, roomId, scalarToken) => {
|
|||||||
if (integrationConfig.upstream) {
|
if (integrationConfig.upstream) {
|
||||||
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
|
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
|
||||||
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
|
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
|
||||||
var backbone = new VectorSimpleBackbone(roomId, upstreamToken);
|
var backbone = new VectorSimpleBackbone(integrationConfig, upstreamToken);
|
||||||
return new SimpleBot(integrationConfig, backbone);
|
return new SimpleBot(integrationConfig, backbone);
|
||||||
});
|
});
|
||||||
} else if (integrationConfig.hosted) {
|
} else if (integrationConfig.hosted) {
|
||||||
|
@ -54,7 +54,7 @@ class VectorScalarClient {
|
|||||||
* @return {Promise<>} resolves when completed
|
* @return {Promise<>} resolves when completed
|
||||||
*/
|
*/
|
||||||
configureIntegration(type, scalarToken, config) {
|
configureIntegration(type, scalarToken, config) {
|
||||||
return this._do("POST", "/integrations/"+type+"/configureService", {scalar_token:scalarToken}, config).then((response, body) => {
|
return this._do("POST", "/integrations/" + type + "/configureService", {scalar_token: scalarToken}, config).then((response, body) => {
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
log.error("VectorScalarClient", response.body);
|
log.error("VectorScalarClient", response.body);
|
||||||
return Promise.reject(response.body);
|
return Promise.reject(response.body);
|
||||||
@ -64,6 +64,23 @@ class VectorScalarClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all of the integrations currently in a room
|
||||||
|
* @param {string} roomId the room ID
|
||||||
|
* @param {string} scalarToken the scalar token to use
|
||||||
|
* @returns {Promise<*[]>} resolves a collection of integrations
|
||||||
|
*/
|
||||||
|
getIntegrationsForRoom(roomId, scalarToken) {
|
||||||
|
return this._do("POST", "/integrations", {scalar_token: scalarToken}, {RoomId: roomId}).then((response, body) => {
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
log.error("VectorScalarClient", response.body);
|
||||||
|
return Promise.reject(response.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body.integrations;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets information on
|
* Gets information on
|
||||||
* @param {string} type the type to lookup
|
* @param {string} type the type to lookup
|
||||||
@ -72,7 +89,7 @@ class VectorScalarClient {
|
|||||||
* @return {Promise<{bot_user_id:string,integrations:[]}>} resolves to the integration information
|
* @return {Promise<{bot_user_id:string,integrations:[]}>} resolves to the integration information
|
||||||
*/
|
*/
|
||||||
getIntegration(type, roomId, scalarToken) {
|
getIntegration(type, roomId, scalarToken) {
|
||||||
return this._do("POST", "/integrations/"+type,{scalar_token:scalarToken}, {room_id:roomId}).then((response, body) => {
|
return this._do("POST", "/integrations/" + type, {scalar_token: scalarToken}, {room_id: roomId}).then((response, body) => {
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
log.error("VectorScalarClient", response.body);
|
log.error("VectorScalarClient", response.body);
|
||||||
return Promise.reject(response.body);
|
return Promise.reject(response.body);
|
||||||
|
@ -15,6 +15,10 @@ import { ToasterModule } from "angular2-toaster";
|
|||||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
import { IntegrationComponent } from "./integration/integration.component";
|
import { IntegrationComponent } from "./integration/integration.component";
|
||||||
import { ScalarCloseComponent } from "./riot/scalar-close/scalar-close.component";
|
import { ScalarCloseComponent } from "./riot/scalar-close/scalar-close.component";
|
||||||
|
import { IntegrationService } from "./shared/integration.service";
|
||||||
|
import { BootstrapModalModule } from "angular2-modal/plugins/bootstrap";
|
||||||
|
import { ModalModule } from "angular2-modal";
|
||||||
|
import { RssConfigComponent } from "./configs/rss/rss-config.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -26,6 +30,8 @@ import { ScalarCloseComponent } from "./riot/scalar-close/scalar-close.component
|
|||||||
UiSwitchModule,
|
UiSwitchModule,
|
||||||
ToasterModule,
|
ToasterModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
|
ModalModule.forRoot(),
|
||||||
|
BootstrapModalModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
@ -33,17 +39,21 @@ import { ScalarCloseComponent } from "./riot/scalar-close/scalar-close.component
|
|||||||
RiotComponent,
|
RiotComponent,
|
||||||
IntegrationComponent,
|
IntegrationComponent,
|
||||||
ScalarCloseComponent,
|
ScalarCloseComponent,
|
||||||
|
RssConfigComponent,
|
||||||
|
|
||||||
// Vendor
|
// Vendor
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ApiService,
|
ApiService,
|
||||||
ScalarService,
|
ScalarService,
|
||||||
|
IntegrationService,
|
||||||
|
|
||||||
// Vendor
|
// Vendor
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
entryComponents: []
|
entryComponents: [
|
||||||
|
RssConfigComponent,
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
constructor(public appRef: ApplicationRef) {
|
constructor(public appRef: ApplicationRef) {
|
||||||
|
32
web/app/configs/config.component.scss
Normal file
32
web/app/configs/config.component.scss
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// shared styling for all config screens
|
||||||
|
.config-wrapper {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
border-bottom: 1px solid #dadada;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header h4 {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header img {
|
||||||
|
margin-right: 7px;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
float: right;
|
||||||
|
margin: -17px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-content {
|
||||||
|
display: block;
|
||||||
|
}
|
38
web/app/configs/rss/rss-config.component.html
Normal file
38
web/app/configs/rss/rss-config.component.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<div class="config-wrapper">
|
||||||
|
<img src="/img/close.svg" (click)="dialog.close()" class="close-icon">
|
||||||
|
<div class="config-header">
|
||||||
|
<img src="/img/avatars/rssbot.png">
|
||||||
|
<h4>Configure RSS/Atom Feeds</h4>
|
||||||
|
</div>
|
||||||
|
<div class="config-content">
|
||||||
|
<form (submit)="addFeed()" novalidate name="addFeedForm">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8" style="margin-bottom: 12px;">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
[(ngModel)]="feedUrl" name="feedUrl"
|
||||||
|
[disabled]="isUpdating">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="submit" class="btn btn-success" [disabled]="isUpdating">
|
||||||
|
<i class="fa fa-plus-circle"></i> Add Feed
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12" *ngFor="let feed of integration.feeds trackById">
|
||||||
|
<a [href]="feed" rel="noopener" target="_blank">{{ feed }}</a>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger" (click)="removeFeed(feed)"
|
||||||
|
style="margin-top: -5px;" [disabled]="isUpdating">
|
||||||
|
<i class="fa fa-times"></i> Remove Feed
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12" *ngIf="integration.immutableFeeds.length > 0">
|
||||||
|
<h6 class="other-feeds-title">Feeds from other users in the room</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12 feed-list" *ngFor="let feed of integration.immutableFeeds trackById">
|
||||||
|
<a [href]="feed.url" rel="noopener" target="_blank">{{ feed.url }}</a> <span class="text-muted">(added by {{ feed.ownerId }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
9
web/app/configs/rss/rss-config.component.scss
Normal file
9
web/app/configs/rss/rss-config.component.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// component styles are encapsulated and only applied to their components
|
||||||
|
.feed-list {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-feeds-title {
|
||||||
|
margin-top: 25px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
67
web/app/configs/rss/rss-config.component.ts
Normal file
67
web/app/configs/rss/rss-config.component.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { RSSIntegration } from "../../shared/models/integration";
|
||||||
|
import { ModalComponent, DialogRef } from "angular2-modal";
|
||||||
|
import { ConfigModalContext } from "../../integration/integration.component";
|
||||||
|
import { ToasterService } from "angular2-toaster";
|
||||||
|
import { ApiService } from "../../shared/api.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-rss-config',
|
||||||
|
templateUrl: './rss-config.component.html',
|
||||||
|
styleUrls: ['./rss-config.component.scss', './../config.component.scss'],
|
||||||
|
})
|
||||||
|
export class RssConfigComponent implements ModalComponent<ConfigModalContext> {
|
||||||
|
|
||||||
|
public integration: RSSIntegration;
|
||||||
|
|
||||||
|
public isUpdating = false;
|
||||||
|
public feedUrl = "";
|
||||||
|
|
||||||
|
private roomId: string;
|
||||||
|
private scalarToken: string;
|
||||||
|
|
||||||
|
constructor(public dialog: DialogRef<ConfigModalContext>,
|
||||||
|
private toaster: ToasterService,
|
||||||
|
private api: ApiService) {
|
||||||
|
this.integration = <RSSIntegration>dialog.context.integration;
|
||||||
|
this.roomId = dialog.context.roomId;
|
||||||
|
this.scalarToken = dialog.context.scalarToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addFeed() {
|
||||||
|
if (!this.feedUrl || this.feedUrl.trim().length === 0) {
|
||||||
|
this.toaster.pop("warning", "Please enter a feed URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.integration.feeds.indexOf(this.feedUrl) !== -1) {
|
||||||
|
this.toaster.pop("error", "This feed has already been added");
|
||||||
|
}
|
||||||
|
|
||||||
|
let feedCopy = JSON.parse(JSON.stringify(this.integration.feeds));
|
||||||
|
feedCopy.push(this.feedUrl);
|
||||||
|
this.updateFeeds(feedCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFeed(feedUrl) {
|
||||||
|
let feedCopy = JSON.parse(JSON.stringify(this.integration.feeds));
|
||||||
|
const idx = feedCopy.indexOf(feedUrl);
|
||||||
|
feedCopy.splice(idx, 1);
|
||||||
|
this.updateFeeds(feedCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFeeds(newFeeds) {
|
||||||
|
this.isUpdating = true;
|
||||||
|
this.api.updateIntegrationState(this.roomId, this.integration.type, this.integration.integrationType, this.scalarToken, {
|
||||||
|
feeds: newFeeds
|
||||||
|
}).then(response => {
|
||||||
|
this.integration.feeds = response.feeds;
|
||||||
|
this.integration.immutableFeeds = response.immutableFeeds;
|
||||||
|
this.isUpdating = false;
|
||||||
|
this.toaster.pop("success", "Feeds updated");
|
||||||
|
}).catch(err => {
|
||||||
|
this.toaster.pop("error", err.json().error);
|
||||||
|
console.error(err);
|
||||||
|
this.isUpdating = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<i class="fa fa-question-circle text-info" ngbTooltip="{{integration.about}}" *ngIf="integration.about"></i>
|
<i class="fa fa-question-circle text-info" ngbTooltip="{{integration.about}}" *ngIf="integration.about"></i>
|
||||||
|
<i class="fa fa-cog text-info config-icon" (click)="configureIntegration()" *ngIf="integration.isEnabled && integration.hasConfig"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,3 +32,7 @@
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.config-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -1,5 +1,14 @@
|
|||||||
import { Component, Input, Output, EventEmitter } from "@angular/core";
|
import { Component, Input, Output, EventEmitter, ViewContainerRef } from "@angular/core";
|
||||||
import { Integration } from "../shared/models/integration";
|
import { Integration } from "../shared/models/integration";
|
||||||
|
import { Overlay, overlayConfigFactory } from "angular2-modal";
|
||||||
|
import { Modal, BSModalContext } from "angular2-modal/plugins/bootstrap";
|
||||||
|
import { IntegrationService } from "../shared/integration.service";
|
||||||
|
|
||||||
|
export class ConfigModalContext extends BSModalContext {
|
||||||
|
public integration: Integration;
|
||||||
|
public roomId: string;
|
||||||
|
public scalarToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-integration',
|
selector: 'my-integration',
|
||||||
@ -9,13 +18,26 @@ import { Integration } from "../shared/models/integration";
|
|||||||
export class IntegrationComponent {
|
export class IntegrationComponent {
|
||||||
|
|
||||||
@Input() integration: Integration;
|
@Input() integration: Integration;
|
||||||
|
@Input() roomId: string;
|
||||||
|
@Input() scalarToken: string;
|
||||||
@Output() updated: EventEmitter<any> = new EventEmitter();
|
@Output() updated: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
constructor() {
|
constructor(overlay: Overlay, vcRef: ViewContainerRef, public modal: Modal) {
|
||||||
|
overlay.defaultViewContainer = vcRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(): void {
|
public update(): void {
|
||||||
this.integration.isEnabled = !this.integration.isEnabled;
|
this.integration.isEnabled = !this.integration.isEnabled;
|
||||||
this.updated.emit();
|
this.updated.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public configureIntegration(): void {
|
||||||
|
this.modal.open(IntegrationService.getConfigComponent(this.integration), overlayConfigFactory({
|
||||||
|
integration: this.integration,
|
||||||
|
roomId: this.roomId,
|
||||||
|
scalarToken: this.scalarToken,
|
||||||
|
isBlocking: false,
|
||||||
|
size: 'lg'
|
||||||
|
}, BSModalContext));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div *ngIf="!error && !loading">
|
<div *ngIf="!error && !loading">
|
||||||
<h3>Manage Integrations</h3>
|
<h3>Manage Integrations</h3>
|
||||||
<p>Turn on anything you like. If an integration has some special configuration, it will have a configuration icon next to it.</p>
|
<p>Turn on anything you like. If an integration has some special configuration, it will have a configuration
|
||||||
|
icon next to it.</p>
|
||||||
<div class="integration-container">
|
<div class="integration-container">
|
||||||
<my-integration *ngFor="let integration of integrations" [integration]="integration" (updated)="updateIntegration(integration)"></my-integration>
|
<my-integration *ngFor="let integration of integrations"
|
||||||
|
[integration]="integration"
|
||||||
|
[roomId]="roomId"
|
||||||
|
[scalarToken]="scalarToken"
|
||||||
|
(updated)="updateIntegration(integration)"></my-integration>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -4,6 +4,8 @@ import { ApiService } from "../shared/api.service";
|
|||||||
import { ScalarService } from "../shared/scalar.service";
|
import { ScalarService } from "../shared/scalar.service";
|
||||||
import { ToasterService } from "angular2-toaster";
|
import { ToasterService } from "angular2-toaster";
|
||||||
import { Integration } from "../shared/models/integration";
|
import { Integration } from "../shared/models/integration";
|
||||||
|
import { IntegrationService } from "../shared/integration.service";
|
||||||
|
import * as _ from "lodash";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-riot',
|
selector: 'my-riot',
|
||||||
@ -16,8 +18,7 @@ export class RiotComponent {
|
|||||||
public integrations: Integration[] = [];
|
public integrations: Integration[] = [];
|
||||||
public loading = true;
|
public loading = true;
|
||||||
public roomId: string;
|
public roomId: string;
|
||||||
|
public scalarToken: string;
|
||||||
private scalarToken: string;
|
|
||||||
|
|
||||||
constructor(private activatedRoute: ActivatedRoute,
|
constructor(private activatedRoute: ActivatedRoute,
|
||||||
private api: ApiService,
|
private api: ApiService,
|
||||||
@ -41,7 +42,7 @@ export class RiotComponent {
|
|||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
this.api.getIntegrations(this.roomId, this.scalarToken).then(integrations => {
|
this.api.getIntegrations(this.roomId, this.scalarToken).then(integrations => {
|
||||||
this.integrations = integrations;
|
this.integrations = _.filter(integrations, i => IntegrationService.isSupported(i));
|
||||||
let promises = integrations.map(b => this.updateIntegrationState(b));
|
let promises = integrations.map(b => this.updateIntegrationState(b));
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}).then(() => this.loading = false).catch(err => {
|
}).then(() => this.loading = false).catch(err => {
|
||||||
@ -51,6 +52,8 @@ export class RiotComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateIntegrationState(integration: Integration) {
|
private updateIntegrationState(integration: Integration) {
|
||||||
|
integration.hasConfig = IntegrationService.hasConfig(integration);
|
||||||
|
|
||||||
return this.scalar.getMembershipState(this.roomId, integration.userId).then(payload => {
|
return this.scalar.getMembershipState(this.roomId, integration.userId).then(payload => {
|
||||||
integration.isBroken = false;
|
integration.isBroken = false;
|
||||||
|
|
||||||
@ -74,21 +77,19 @@ export class RiotComponent {
|
|||||||
promise = this.api.removeIntegration(this.roomId, integration.type, integration.integrationType, this.scalarToken);
|
promise = this.api.removeIntegration(this.roomId, integration.type, integration.integrationType, this.scalarToken);
|
||||||
} else promise = this.scalar.inviteUser(this.roomId, integration.userId);
|
} else promise = this.scalar.inviteUser(this.roomId, integration.userId);
|
||||||
|
|
||||||
promise
|
promise.then(() => {
|
||||||
.then(() => {
|
if (integration.isEnabled)
|
||||||
if (integration.isEnabled)
|
this.toaster.pop('success', integration.name + " was invited to the room");
|
||||||
this.toaster.pop('success', integration.name + " was invited to the room");
|
else this.toaster.pop('success', integration.name + " was removed from the room");
|
||||||
else this.toaster.pop('success', integration.name + " was removed from the room");
|
}).catch(err => {
|
||||||
})
|
let errorMessage = "Could not update integration status";
|
||||||
.catch(err => {
|
|
||||||
let errorMessage = "Could not update integration status";
|
|
||||||
|
|
||||||
if (err.json) {
|
if (err.json) {
|
||||||
errorMessage = err.json().error;
|
errorMessage = err.json().error;
|
||||||
} else errorMessage = err.response.error.message;
|
} else errorMessage = err.response.error.message;
|
||||||
|
|
||||||
integration.isEnabled = !integration.isEnabled;
|
integration.isEnabled = !integration.isEnabled;
|
||||||
this.toaster.pop("error", errorMessage);
|
this.toaster.pop("error", errorMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,14 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeIntegration(roomId: string, type: string, integrationType: string, scalarToken: string): Promise<any> {
|
removeIntegration(roomId: string, type: string, integrationType: string, scalarToken: string): Promise<any> {
|
||||||
return this.http.delete("/api/v1/dimension/integrations/" + roomId + "/" + type + "/" + integrationType, {params: {scalar_token: scalarToken}})
|
const url = "/api/v1/dimension/integrations/" + roomId + "/" + type + "/" + integrationType;
|
||||||
|
return this.http.delete(url, {params: {scalar_token: scalarToken}})
|
||||||
|
.map(res => res.json()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIntegrationState(roomId: string, type: string, integrationType: string, scalarToken: string, newState: any): Promise<any> {
|
||||||
|
const url = "/api/v1/dimension/integrations/" + roomId + "/" + type + "/" + integrationType + "/state";
|
||||||
|
return this.http.put(url, {scalar_token: scalarToken, state: newState})
|
||||||
.map(res => res.json()).toPromise();
|
.map(res => res.json()).toPromise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
web/app/shared/integration.service.ts
Normal file
38
web/app/shared/integration.service.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Integration } from "./models/integration";
|
||||||
|
import { RssConfigComponent } from "../configs/rss/rss-config.component";
|
||||||
|
import { ContainerContent } from "angular2-modal";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class IntegrationService {
|
||||||
|
|
||||||
|
private static supportedTypeMap = {
|
||||||
|
"bot": true,
|
||||||
|
"complex-bot": {
|
||||||
|
"rss": true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static components = {
|
||||||
|
"complex-bot": {
|
||||||
|
"rss": RssConfigComponent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static isSupported(integration: Integration): boolean {
|
||||||
|
if (IntegrationService.supportedTypeMap[integration.type] === true) return true;
|
||||||
|
if (!IntegrationService.supportedTypeMap[integration.type]) return false;
|
||||||
|
return IntegrationService.supportedTypeMap[integration.type][integration.integrationType] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hasConfig(integration: Integration): boolean {
|
||||||
|
return integration.type !== "bot";
|
||||||
|
}
|
||||||
|
|
||||||
|
static getConfigComponent(integration: Integration): ContainerContent {
|
||||||
|
return IntegrationService.components[integration.type][integration.integrationType];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
}
|
@ -7,4 +7,10 @@ export interface Integration {
|
|||||||
about: string; // nullable
|
about: string; // nullable
|
||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
isBroken: boolean;
|
isBroken: boolean;
|
||||||
|
hasConfig: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RSSIntegration extends Integration {
|
||||||
|
feeds: string[];
|
||||||
|
immutableFeeds: {url: string, ownerId: string}[];
|
||||||
}
|
}
|
@ -7,3 +7,23 @@ body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
color: #222;
|
color: #222;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HACK: Work around dialog not showing up
|
||||||
|
// ref: https://github.com/shlomiassaf/angular2-modal/issues/280
|
||||||
|
.fade.in {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.in .modal-dialog {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
-o-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop.in {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user