Merge pull request #3646 from matrix-org/travis/integration-manager-opt
Add an option to disable the use of integration managers for provisioning
This commit is contained in:
commit
6597e0bd42
15 changed files with 215 additions and 228 deletions
57
src/components/views/dialogs/IntegrationsDisabledDialog.js
Normal file
57
src/components/views/dialogs/IntegrationsDisabledDialog.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import sdk from "../../../index";
|
||||
import dis from '../../../dispatcher';
|
||||
|
||||
export default class IntegrationsDisabledDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
_onAcknowledgeClick = () => {
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
_onOpenSettingsClick = () => {
|
||||
this.props.onFinished();
|
||||
dis.dispatch({action: "view_user_settings"});
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
return (
|
||||
<BaseDialog className='mx_IntegrationsDisabledDialog' hasCancel={true}
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Integrations are disabled")}>
|
||||
<div className='mx_IntegrationsDisabledDialog_content'>
|
||||
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Settings")}
|
||||
onPrimaryButtonClick={this._onOpenSettingsClick}
|
||||
cancelButton={_t("OK")}
|
||||
onCancel={this._onAcknowledgeClick}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
55
src/components/views/dialogs/IntegrationsImpossibleDialog.js
Normal file
55
src/components/views/dialogs/IntegrationsImpossibleDialog.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import sdk from "../../../index";
|
||||
|
||||
export default class IntegrationsImpossibleDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
_onAcknowledgeClick = () => {
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
return (
|
||||
<BaseDialog className='mx_IntegrationsImpossibleDialog' hasCancel={false}
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Integrations not allowed")}>
|
||||
<div className='mx_IntegrationsImpossibleDialog_content'>
|
||||
<p>
|
||||
{_t(
|
||||
"Your Riot doesn't allow you to use an Integration Manager to do this. " +
|
||||
"Please contact an admin.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("OK")}
|
||||
onPrimaryButtonClick={this._onAcknowledgeClick}
|
||||
hasCancel={false}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -82,10 +82,10 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
|||
|
||||
client.setTermsInteractionCallback((policyInfo, agreedUrls) => {
|
||||
// To avoid visual glitching of two modals stacking briefly, we customise the
|
||||
// terms dialog sizing when it will appear for the integrations manager so that
|
||||
// terms dialog sizing when it will appear for the integration manager so that
|
||||
// it gets the same basic size as the IM's own modal.
|
||||
return dialogTermsInteractionCallback(
|
||||
policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationsManager',
|
||||
policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationManager',
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -139,7 +139,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
|||
}
|
||||
|
||||
_renderTab() {
|
||||
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
||||
const IntegrationManager = sdk.getComponent("views.settings.IntegrationManager");
|
||||
let uiUrl = null;
|
||||
if (this.state.currentScalarClient) {
|
||||
uiUrl = this.state.currentScalarClient.getScalarInterfaceUrlForRoom(
|
||||
|
@ -148,7 +148,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
|||
this.props.integrationId,
|
||||
);
|
||||
}
|
||||
return <IntegrationsManager
|
||||
return <IntegrationManager
|
||||
configured={true}
|
||||
loading={this.state.currentLoading}
|
||||
connected={this.state.currentConnected}
|
||||
|
|
|
@ -86,7 +86,7 @@ export default class TermsDialog extends React.PureComponent {
|
|||
case Matrix.SERVICE_TYPES.IS:
|
||||
return <div>{_t("Identity Server")}<br />({host})</div>;
|
||||
case Matrix.SERVICE_TYPES.IM:
|
||||
return <div>{_t("Integrations Manager")}<br />({host})</div>;
|
||||
return <div>{_t("Integration Manager")}<br />({host})</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,10 +74,10 @@ export default class Stickerpicker extends React.Component {
|
|||
this.forceUpdate();
|
||||
return this.scalarClient;
|
||||
}).catch((e) => {
|
||||
this._imError(_td("Failed to connect to integrations server"), e);
|
||||
this._imError(_td("Failed to connect to integration manager"), e);
|
||||
});
|
||||
} else {
|
||||
this._imError(_td("No integrations server is configured to manage stickers with"));
|
||||
IntegrationManagers.sharedInstance().openNoManagerDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,12 +287,17 @@ export default class Stickerpicker extends React.Component {
|
|||
return stickersContent;
|
||||
}
|
||||
|
||||
/**
|
||||
// Dev note: this isn't jsdoc because it's angry.
|
||||
/*
|
||||
* Show the sticker picker overlay
|
||||
* If no stickerpacks have been added, show a link to the integration manager add sticker packs page.
|
||||
* @param {Event} e Event that triggered the function
|
||||
*/
|
||||
_onShowStickersClick(e) {
|
||||
if (!SettingsStore.getValue("integrationProvisioning")) {
|
||||
// Intercept this case and spawn a warning.
|
||||
return IntegrationManagers.sharedInstance().showDisabledDialog();
|
||||
}
|
||||
|
||||
// XXX: Simplify by using a context menu that is positioned relative to the sticker picker button
|
||||
|
||||
const buttonRect = e.target.getBoundingClientRect();
|
||||
|
@ -346,7 +351,7 @@ export default class Stickerpicker extends React.Component {
|
|||
}
|
||||
|
||||
/**
|
||||
* Launch the integrations manager on the stickers integration page
|
||||
* Launch the integration manager on the stickers integration page
|
||||
*/
|
||||
_launchManageIntegrations() {
|
||||
// TODO: Open the right integration manager for the widget
|
||||
|
|
|
@ -21,12 +21,9 @@ import sdk from '../../../index';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher';
|
||||
|
||||
export default class IntegrationsManager extends React.Component {
|
||||
export default class IntegrationManager extends React.Component {
|
||||
static propTypes = {
|
||||
// false to display an error saying that there is no integrations manager configured
|
||||
configured: PropTypes.bool.isRequired,
|
||||
|
||||
// false to display an error saying that we couldn't connect to the integrations manager
|
||||
// false to display an error saying that we couldn't connect to the integration manager
|
||||
connected: PropTypes.bool.isRequired,
|
||||
|
||||
// true to display a loading spinner
|
||||
|
@ -40,7 +37,6 @@ export default class IntegrationsManager extends React.Component {
|
|||
};
|
||||
|
||||
static defaultProps = {
|
||||
configured: true,
|
||||
connected: true,
|
||||
loading: false,
|
||||
};
|
||||
|
@ -70,20 +66,11 @@ export default class IntegrationsManager extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.configured) {
|
||||
return (
|
||||
<div className='mx_IntegrationsManager_error'>
|
||||
<h3>{_t("No integrations server configured")}</h3>
|
||||
<p>{_t("This Riot instance does not have an integrations server configured.")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.loading) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return (
|
||||
<div className='mx_IntegrationsManager_loading'>
|
||||
<h3>{_t("Connecting to integrations server...")}</h3>
|
||||
<div className='mx_IntegrationManager_loading'>
|
||||
<h3>{_t("Connecting to integration manager...")}</h3>
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
|
@ -91,9 +78,9 @@ export default class IntegrationsManager extends React.Component {
|
|||
|
||||
if (!this.props.connected) {
|
||||
return (
|
||||
<div className='mx_IntegrationsManager_error'>
|
||||
<h3>{_t("Cannot connect to integrations server")}</h3>
|
||||
<p>{_t("The integrations server is offline or it cannot reach your homeserver.")}</p>
|
||||
<div className='mx_IntegrationManager_error'>
|
||||
<h3>{_t("Cannot connect to integration manager")}</h3>
|
||||
<p>{_t("The integration manager is offline or it cannot reach your homeserver.")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -16,13 +16,9 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import sdk from '../../../index';
|
||||
import Field from "../elements/Field";
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import {SERVICE_TYPES} from "matrix-js-sdk";
|
||||
import {IntegrationManagerInstance} from "../../../integrations/IntegrationManagerInstance";
|
||||
import Modal from "../../../Modal";
|
||||
import sdk from '../../../index';
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
|
||||
export default class SetIntegrationManager extends React.Component {
|
||||
constructor() {
|
||||
|
@ -32,135 +28,23 @@ export default class SetIntegrationManager extends React.Component {
|
|||
|
||||
this.state = {
|
||||
currentManager,
|
||||
url: "", // user-entered text
|
||||
error: null,
|
||||
busy: false,
|
||||
checking: false,
|
||||
provisioningEnabled: SettingsStore.getValue("integrationProvisioning"),
|
||||
};
|
||||
}
|
||||
|
||||
_onUrlChanged = (ev) => {
|
||||
const u = ev.target.value;
|
||||
this.setState({url: u});
|
||||
};
|
||||
onProvisioningToggled = () => {
|
||||
const current = this.state.provisioningEnabled;
|
||||
SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => {
|
||||
console.error("Error changing integration manager provisioning");
|
||||
console.error(err);
|
||||
|
||||
_getTooltip = () => {
|
||||
if (this.state.checking) {
|
||||
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
|
||||
return <div>
|
||||
<InlineSpinner />
|
||||
{ _t("Checking server") }
|
||||
</div>;
|
||||
} else if (this.state.error) {
|
||||
return <span className="warning">{this.state.error}</span>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
_canChange = () => {
|
||||
return !!this.state.url && !this.state.busy;
|
||||
};
|
||||
|
||||
_continueTerms = async (manager) => {
|
||||
try {
|
||||
await IntegrationManagers.sharedInstance().overwriteManagerOnAccount(manager);
|
||||
this.setState({
|
||||
busy: false,
|
||||
error: null,
|
||||
currentManager: IntegrationManagers.sharedInstance().getPrimaryManager(),
|
||||
url: "", // clear input
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.setState({
|
||||
busy: false,
|
||||
error: _t("Failed to update integration manager"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_setManager = async (ev) => {
|
||||
// Don't reload the page when the user hits enter in the form.
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.setState({busy: true, checking: true, error: null});
|
||||
|
||||
let offline = false;
|
||||
let manager: IntegrationManagerInstance;
|
||||
try {
|
||||
manager = await IntegrationManagers.sharedInstance().tryDiscoverManager(this.state.url);
|
||||
offline = !manager; // no manager implies offline
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
offline = true; // probably a connection error
|
||||
}
|
||||
if (offline) {
|
||||
this.setState({
|
||||
busy: false,
|
||||
checking: false,
|
||||
error: _t("Integration manager offline or not accessible."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Test the manager (causes terms of service prompt if agreement is needed)
|
||||
// We also cancel the tooltip at this point so it doesn't collide with the dialog.
|
||||
this.setState({checking: false});
|
||||
try {
|
||||
const client = manager.getScalarClient();
|
||||
await client.connect();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.setState({
|
||||
busy: false,
|
||||
error: _t("Terms of service not accepted or the integration manager is invalid."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Specifically request the terms of service to see if there are any.
|
||||
// The above won't trigger a terms of service check if there are no terms to
|
||||
// sign, so when there's no terms at all we need to ensure we tell the user.
|
||||
let hasTerms = true;
|
||||
try {
|
||||
const terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IM, manager.trimmedApiUrl);
|
||||
hasTerms = terms && terms['policies'] && Object.keys(terms['policies']).length > 0;
|
||||
} catch (e) {
|
||||
// Assume errors mean there are no terms. This could be a 404, 500, etc
|
||||
console.error(e);
|
||||
hasTerms = false;
|
||||
}
|
||||
if (!hasTerms) {
|
||||
this.setState({busy: false});
|
||||
const QuestionDialog = sdk.getComponent("views.dialogs.QuestionDialog");
|
||||
Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, {
|
||||
title: _t("Integration manager has no terms of service"),
|
||||
description: (
|
||||
<div>
|
||||
<span className="warning">
|
||||
{_t("The integration manager you have chosen does not have any terms of service.")}
|
||||
</span>
|
||||
<span>
|
||||
{_t("Only continue if you trust the owner of the server.")}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
button: _t("Continue"),
|
||||
onFinished: async (confirmed) => {
|
||||
if (!confirmed) return;
|
||||
this._continueTerms(manager);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._continueTerms(manager);
|
||||
this.setState({provisioningEnabled: current});
|
||||
});
|
||||
this.setState({provisioningEnabled: !current});
|
||||
};
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
|
||||
const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch");
|
||||
|
||||
const currentManager = this.state.currentManager;
|
||||
let managerName;
|
||||
|
@ -168,45 +52,32 @@ export default class SetIntegrationManager extends React.Component {
|
|||
if (currentManager) {
|
||||
managerName = `(${currentManager.name})`;
|
||||
bodyText = _t(
|
||||
"You are currently using <b>%(serverName)s</b> to manage your bots, widgets, " +
|
||||
"Use an Integration Manager <b>(%(serverName)s)</b> to manage bots, widgets, " +
|
||||
"and sticker packs.",
|
||||
{serverName: currentManager.name},
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
);
|
||||
} else {
|
||||
bodyText = _t(
|
||||
"Add which integration manager you want to manage your bots, widgets, " +
|
||||
"and sticker packs.",
|
||||
);
|
||||
bodyText = _t("Use an Integration Manager to manage bots, widgets, and sticker packs.");
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="mx_SettingsTab_section mx_SetIntegrationManager" onSubmit={this._setManager}>
|
||||
<div className='mx_SetIntegrationManager'>
|
||||
<div className="mx_SettingsTab_heading">
|
||||
<span>{_t("Integration Manager")}</span>
|
||||
<span>{_t("Integrations")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{managerName}</span>
|
||||
<ToggleSwitch checked={this.state.provisioningEnabled} onChange={this.onProvisioningToggled} />
|
||||
</div>
|
||||
<span className="mx_SettingsTab_subsectionText">
|
||||
{bodyText}
|
||||
<br />
|
||||
<br />
|
||||
{_t(
|
||||
"Integration Managers receive configuration data, and can modify widgets, " +
|
||||
"send room invites, and set power levels on your behalf.",
|
||||
)}
|
||||
</span>
|
||||
<Field
|
||||
label={_t("Enter a new integration manager")}
|
||||
id="mx_SetIntegrationManager_newUrl"
|
||||
type="text" value={this.state.url}
|
||||
autoComplete="off"
|
||||
onChange={this._onUrlChanged}
|
||||
tooltipContent={this._getTooltip()}
|
||||
tooltipClassName="mx_SetIntegrationManager_tooltip"
|
||||
disabled={this.state.busy}
|
||||
flagInvalid={!!this.state.error}
|
||||
/>
|
||||
<AccessibleButton
|
||||
kind="primary_sm"
|
||||
type="submit"
|
||||
disabled={!this._canChange()}
|
||||
onClick={this._setManager}
|
||||
>{_t("Change")}</AccessibleButton>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue