mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-12-24 01:09:48 +01:00
Remove plugin center authentication
Squash commits of branch feature/remove_plugin_center_auth: - Remove plugin center authentication - Fix i18n file - Fix tests - Changelog entry
This commit is contained in:
@@ -50,8 +50,7 @@
|
||||
"title": {
|
||||
"install": "{{name}} Plugin installieren",
|
||||
"update": "{{name}} Plugin aktualisieren",
|
||||
"uninstall": "{{name}} Plugin deinstallieren",
|
||||
"cloudoguInstall": "{{name}} Plugin installieren"
|
||||
"uninstall": "{{name}} Plugin deinstallieren"
|
||||
},
|
||||
"restart": "Neustarten, um Plugin-Änderungen wirksam zu machen",
|
||||
"install": "Installieren",
|
||||
@@ -71,8 +70,6 @@
|
||||
"version": "Version",
|
||||
"currentVersion": "Installierte Version",
|
||||
"newVersion": "Neue Version",
|
||||
"cloudoguInstallInfo": "Verbinden Sie Ihre SCM-Manager-Instanz mit ihrem cloudogu platform-Account um Zugriff auf cloudogu platform-Plugins zu erhalten. Falls Sie noch über keinen Account verfügen, können Sie sich einfach kostenlos registrieren.",
|
||||
"cloudoguInstall": "Mit cloudogu platform verbinden und installieren",
|
||||
"dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installiert bzw. aktualisiert, wenn sie noch nicht in der aktuellen Version vorhanden sind!",
|
||||
"optionalDependencyNotification": "Mit diesem Plugin werden folgende optionale Abhängigkeiten mit aktualisiert, falls sie installiert sind!",
|
||||
"dependencies": "Abhängigkeiten",
|
||||
@@ -87,26 +84,6 @@
|
||||
"updateAllInfo": "Die folgenden Plugins werden aktualisiert. Die Änderungen werden nach dem nächsten Neustart wirksam.",
|
||||
"manualRestartRequired": "Nachdem die Plugin-Änderung durchgeführt wurde, muss SCM-Manager neu gestartet werden.",
|
||||
"showPending": "Um die folgenden Plugin-Änderungen auszuführen, muss SCM-Manager neu gestartet werden."
|
||||
},
|
||||
"cloudoguPlatform": {
|
||||
"connectionInfo": "Instanz ist mit der cloudogu platform verbunden.\nAccount: {{pluginCenterSubject}}",
|
||||
"error": {
|
||||
"info": "cloudogu platform Authentifizierungsinformationen konnten nicht abgerufen werden. Klicken Sie, um Details zu sehen.",
|
||||
"title": "Fehler"
|
||||
},
|
||||
"failed": {
|
||||
"info": "Verbindung zur cloudogu platform mit Account {{pluginCenterSubject}} is fehlgeschlagen",
|
||||
"message": "Die Verbindung der SCM-Manager Instanz mit der <0>cloudogu platform</0> is fehlgeschlagen. Der Benutzer <1>{{subject}}</1> konnte nicht authentifiziert werden.",
|
||||
"button": {
|
||||
"label": "Erneut mit der <0>cloudogu platform</0> verbinden"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"button": {
|
||||
"label": "Mit der <0>cloudogu platform</0> verbinden"
|
||||
},
|
||||
"description": "Verbinden Sie Ihren SCM-Manager mit der <0>cloudogu platform</0>, um besondere Plugins zu installieren. Die cloudogu platform ist die Heimat der SCM-Manager Community, getragen von Maintainern des SCM-Managers. Sie haben noch kein Konto? Erstellen Sie während der Verbindung der SCM-Manager-Instanz kostenfrei ein cloudogu platform-Konto. <1>Mehr Details zur Datenverarbeitung.</1>"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositoryRole": {
|
||||
|
||||
@@ -11,20 +11,6 @@
|
||||
"no-write-permission-notification": "Hinweis: Es fehlen Berechtigungen zum Bearbeiten der Einstellungen!"
|
||||
}
|
||||
},
|
||||
"pluginSettings": {
|
||||
"subtitle": "Plugin Einstellungen",
|
||||
"pluginUrl": "Plugin Center URL",
|
||||
"pluginAuthUrl": "Plugin Center Authentifizierungs URL",
|
||||
"auth": {
|
||||
"loading": "Lade Authentifizierungs Informationen ...",
|
||||
"notAuthenticated": "Das Plugin Center ist nicht authentifiziert",
|
||||
"authenticate": "Authentifizieren",
|
||||
"authenticated": "Das Plugin Center ist als <0 /> authentifiziert",
|
||||
"subjectTooltip": "Authentifiziert als {{ principal }} {{ ago }}",
|
||||
"logout": "Abmelden",
|
||||
"notConfiguredHint": "Authentifizierungs URL ist nicht gesetzt"
|
||||
}
|
||||
},
|
||||
"jwtSettings": {
|
||||
"subtitle": "JWT Einstellungen",
|
||||
"label": "Ablaufzeit",
|
||||
@@ -87,6 +73,7 @@
|
||||
"enabled-file-search": "Dateisuche aktivieren",
|
||||
"namespace-strategy": "Namespace Strategie",
|
||||
"login-info-url": "Login Info URL",
|
||||
"pluginUrl": "Plugin Center URL",
|
||||
"emergencyContacts": {
|
||||
"label": "Notfallkontakte",
|
||||
"helpText": "Liste der Benutzer, die über administrative Vorfälle informiert werden.",
|
||||
@@ -116,7 +103,6 @@
|
||||
"realmDescriptionHelpText": "Beschreibung des Authentication Realm.",
|
||||
"dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.",
|
||||
"pluginUrlHelpText": "Die URL der Plugin Center API. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur",
|
||||
"pluginAuthUrlHelpText": "Die URL der Plugin Center Authentifizierungs API.",
|
||||
"alertsUrlHelpText": "Die URL der Alerts API. Darüber wird über Alerts die Ihr System betreffen informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.",
|
||||
"releaseFeedUrlHelpText": "Die URL des RSS Release Feed des SCM-Manager. Darüber wird über die neue SCM-Manager Version informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.",
|
||||
"mailDomainNameHelpText": "Dieser Domain Name wird genutzt, wenn für einen User eine E-Mail-Adresse benötigt wird, für den keine hinterlegt ist. Diese Domain wird nicht zum Versenden von E-Mails genutzt und auch keine anderweitige Verbindung aufgebaut.",
|
||||
|
||||
@@ -50,8 +50,7 @@
|
||||
"title": {
|
||||
"install": "Install {{name}} Plugin",
|
||||
"update": "Update {{name}} Plugin",
|
||||
"uninstall": "Uninstall {{name}} Plugin",
|
||||
"cloudoguInstall": "Install {{name}} Plugin"
|
||||
"uninstall": "Uninstall {{name}} Plugin"
|
||||
},
|
||||
"restart": "Restart to make plugin changes effective",
|
||||
"install": "Install",
|
||||
@@ -71,8 +70,6 @@
|
||||
"version": "Version",
|
||||
"currentVersion": "Installed version",
|
||||
"newVersion": "New version",
|
||||
"cloudoguInstallInfo": "Connect your SCM-Manager instance with your cloudogu platform account to access cloudogu platform plugins. If you do not already have an account you can easily register for free.",
|
||||
"cloudoguInstall": "Connect cloudogu platform and install",
|
||||
"dependencyNotification": "With this plugin, the following dependencies will be installed/updated if their latest versions are not installed yet!",
|
||||
"optionalDependencyNotification": "With this plugin, the following optional dependencies will be updated if they are installed!",
|
||||
"dependencies": "Dependencies",
|
||||
@@ -87,26 +84,6 @@
|
||||
"updateAllInfo": "The following plugin changes will be executed. You need to restart the SCM-Manager to make these changes effective.",
|
||||
"manualRestartRequired": "After the plugin change has been made, SCM-Manager must be restarted.",
|
||||
"showPending": "To execute the following plugin changes, SCM-Manager must be restarted."
|
||||
},
|
||||
"cloudoguPlatform": {
|
||||
"connectionInfo": "Instance is connected to the cloudogu platform.\nAccount: {{pluginCenterSubject}}",
|
||||
"error": {
|
||||
"info": "Failed to retrieve cloudogu platform authentication information. Click for more details.",
|
||||
"title": "Error"
|
||||
},
|
||||
"failed": {
|
||||
"info": "Connection to the cloudogu platform failed for account {{pluginCenterSubject}}",
|
||||
"message": "The connection of the SCM-Manager instance with the <0>cloudogu platform</0> failed. The user <1>{{subject}}</1> could not be authenticated. Click Reconnect to restore the connection.",
|
||||
"button": {
|
||||
"label": "Reconnect to the <0>cloudogu platform</0>"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"button": {
|
||||
"label": "Connect to the <0>cloudogu platform</0>"
|
||||
},
|
||||
"description": "Connect your SCM-Manager with the <0>cloudogu platform</0> to install special plugins. The cloudogu platform is the home of the SCM-Manager Community, sustained by the maintainers of the SCM-Manager. You don't have an account yet? Create a free cloudogu platform account while connecting your SCM-Manager instance. <1>More details on data processing.</1>"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositoryRole": {
|
||||
|
||||
@@ -11,20 +11,6 @@
|
||||
"no-write-permission-notification": "Please note: You do not have the permission to edit the config!"
|
||||
}
|
||||
},
|
||||
"pluginSettings": {
|
||||
"subtitle": "Plugin Settings",
|
||||
"pluginUrl": "Plugin Center URL",
|
||||
"pluginAuthUrl": "Plugin Center Authentication URL",
|
||||
"auth": {
|
||||
"loading": "Loading authentication info ...",
|
||||
"notAuthenticated": "Plugin Center is not authenticated",
|
||||
"authenticate": "Authenticate",
|
||||
"authenticated": "Plugin Center is authenticated as <0 />",
|
||||
"subjectTooltip": "Authenticated by {{ principal }} {{ ago }}",
|
||||
"logout": "Logout",
|
||||
"notConfiguredHint": "Authentication URL is not configured"
|
||||
}
|
||||
},
|
||||
"jwtSettings": {
|
||||
"subtitle": "JWT Settings",
|
||||
"label": "Expiration time",
|
||||
@@ -87,6 +73,7 @@
|
||||
"enabled-file-search": "Enabled File Search",
|
||||
"namespace-strategy": "Namespace Strategy",
|
||||
"login-info-url": "Login Info URL",
|
||||
"pluginUrl": "Plugin Center URL",
|
||||
"emergencyContacts": {
|
||||
"label": "Emergency Contacts",
|
||||
"helpText": "List of users notified of administrative incidents.",
|
||||
@@ -116,7 +103,6 @@
|
||||
"realmDescriptionHelpText": "Enter authentication realm description.",
|
||||
"dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation.",
|
||||
"pluginUrlHelpText": "The url of the Plugin Center API. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture",
|
||||
"pluginAuthUrlHelpText": "The url of the Plugin Center authentication API.",
|
||||
"alertsUrlHelpText": "The url of the alerts api. This provides up-to-date alerts regarding your system. To disable this feature just leave the url blank.",
|
||||
"releaseFeedUrlHelpText": "The url of the RSS Release Feed for SCM-Manager. This provides up-to-date version information. To disable this feature just leave the url blank.",
|
||||
"mailDomainNameHelpText": "This domain name will be used to create email addresses for users without one when needed. It will not be used to send mails nor will be accessed otherwise.",
|
||||
|
||||
@@ -68,7 +68,6 @@ const ConfigForm: FC<Props> = ({
|
||||
proxyExcludes: [],
|
||||
skipFailedAuthenticators: false,
|
||||
pluginUrl: "",
|
||||
pluginAuthUrl: "",
|
||||
loginAttemptLimitTimeout: 0,
|
||||
enabledXsrfProtection: true,
|
||||
enabledUserConverter: false,
|
||||
@@ -149,6 +148,7 @@ const ConfigForm: FC<Props> = ({
|
||||
dateFormat={innerConfig.dateFormat}
|
||||
anonymousMode={innerConfig.anonymousMode}
|
||||
skipFailedAuthenticators={innerConfig.skipFailedAuthenticators}
|
||||
pluginUrl={innerConfig.pluginUrl}
|
||||
alertsUrl={innerConfig.alertsUrl}
|
||||
releaseFeedUrl={innerConfig.releaseFeedUrl}
|
||||
mailDomainName={innerConfig.mailDomainName}
|
||||
@@ -181,13 +181,6 @@ const ConfigForm: FC<Props> = ({
|
||||
hasUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
<hr />
|
||||
<PluginSettings
|
||||
pluginUrl={innerConfig.pluginUrl}
|
||||
pluginAuthUrl={innerConfig.pluginAuthUrl}
|
||||
onChange={(isValid, changedValue, name) => onChange(isValid, changedValue, name)}
|
||||
hasUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
<hr />
|
||||
<JwtSettings
|
||||
enabledJwtEndless={innerConfig.enabledJwtEndless || false}
|
||||
jwtExpirationInH={innerConfig.jwtExpirationInH || 1}
|
||||
|
||||
@@ -26,6 +26,7 @@ import classNames from "classnames";
|
||||
type Props = {
|
||||
realmDescription: string;
|
||||
loginInfoUrl: string;
|
||||
pluginUrl: string;
|
||||
disableGroupingGrid: boolean;
|
||||
dateFormat: string;
|
||||
anonymousMode: AnonymousMode;
|
||||
@@ -44,6 +45,7 @@ type Props = {
|
||||
const GeneralSettings: FC<Props> = ({
|
||||
realmDescription,
|
||||
loginInfoUrl,
|
||||
pluginUrl,
|
||||
anonymousMode,
|
||||
alertsUrl,
|
||||
releaseFeedUrl,
|
||||
@@ -62,6 +64,9 @@ const GeneralSettings: FC<Props> = ({
|
||||
const handleLoginInfoUrlChange = (value: string) => {
|
||||
onChange(true, value, "loginInfoUrl");
|
||||
};
|
||||
const handlePluginCenterUrlChange = (value: string) => {
|
||||
onChange(true, value, "pluginUrl");
|
||||
};
|
||||
const handleRealmDescriptionChange = (value: string) => {
|
||||
onChange(true, value, "realmDescription");
|
||||
};
|
||||
@@ -127,6 +132,17 @@ const GeneralSettings: FC<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<InputField
|
||||
label={t("general-settings.pluginUrl")}
|
||||
onChange={handlePluginCenterUrlChange}
|
||||
value={pluginUrl}
|
||||
disabled={!hasUpdatePermission}
|
||||
helpText={t("help.pluginUrlHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<Checkbox
|
||||
@@ -148,9 +164,9 @@ const GeneralSettings: FC<Props> = ({
|
||||
disabled={!hasUpdatePermission}
|
||||
className="is-fullwidth"
|
||||
options={[
|
||||
{ label: t("general-settings.anonymousMode.full"), value: "FULL" },
|
||||
{ label: t("general-settings.anonymousMode.protocolOnly"), value: "PROTOCOL_ONLY" },
|
||||
{ label: t("general-settings.anonymousMode.off"), value: "OFF" },
|
||||
{label: t("general-settings.anonymousMode.full"), value: "FULL"},
|
||||
{label: t("general-settings.anonymousMode.protocolOnly"), value: "PROTOCOL_ONLY"},
|
||||
{label: t("general-settings.anonymousMode.off"), value: "OFF"},
|
||||
]}
|
||||
helpText={t("help.allowAnonymousAccessHelpText")}
|
||||
testId={"anonymous-mode-select"}
|
||||
@@ -197,12 +213,12 @@ const GeneralSettings: FC<Props> = ({
|
||||
helpText={t("general-settings.emergencyContacts.helpText")}
|
||||
placeholder={t("general-settings.emergencyContacts.autocompletePlaceholder")}
|
||||
aria-label="general-settings.emergencyContacts.ariaLabel"
|
||||
value={emergencyContacts.map((m) => ({ label: m, value: { id: m, displayName: m } }))}
|
||||
value={emergencyContacts.map((m) => ({label: m, value: {id: m, displayName: m}}))}
|
||||
onChange={handleEmergencyContactsChange}
|
||||
>
|
||||
<Combobox<AutocompleteObject>
|
||||
options={userOptions || []}
|
||||
className={classNames({ "is-loading": userOptionsLoading })}
|
||||
className={classNames({"is-loading": userOptionsLoading})}
|
||||
onQueryChange={setQuery}
|
||||
/>
|
||||
</ChipInputField>
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { usePluginCenterAuthInfo, usePluginCenterLogout } from "@scm-manager/ui-api";
|
||||
import { Button, ErrorNotification, Notification, Tooltip, useDateFormatter } from "@scm-manager/ui-components";
|
||||
import { Link, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types";
|
||||
import styled from "styled-components";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
const Message = styled.p`
|
||||
line-height: 2.5rem;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
authenticationInfo: PluginCenterAuthenticationInfo;
|
||||
};
|
||||
|
||||
const PluginCenterSubject: FC<Props> = ({ authenticationInfo }) => {
|
||||
const formatter = useDateFormatter({ date: authenticationInfo.date });
|
||||
const [t] = useTranslation("config");
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
location="top"
|
||||
message={t("pluginSettings.auth.subjectTooltip", {
|
||||
principal: authenticationInfo.principal,
|
||||
ago: formatter?.formatDistance()
|
||||
})}
|
||||
>
|
||||
<strong>{authenticationInfo.pluginCenterSubject}</strong>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AuthenticatedInfo: FC<Props> = ({ authenticationInfo }) => {
|
||||
const { logout, isLoading, error } = usePluginCenterLogout(authenticationInfo);
|
||||
const [t] = useTranslation("config");
|
||||
|
||||
const subject = <PluginCenterSubject authenticationInfo={authenticationInfo} />;
|
||||
|
||||
return (
|
||||
<Notification type="inherit">
|
||||
<div className="is-full-width is-flex is-justify-content-space-between is-align-content-center">
|
||||
<Message>
|
||||
<Trans t={t} i18nKey="pluginSettings.auth.authenticated" components={[subject]} />
|
||||
</Message>
|
||||
{authenticationInfo._links.logout ? (
|
||||
<Button color="warning" loading={isLoading} action={logout}>
|
||||
{t("pluginSettings.auth.logout")}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
{error ? (
|
||||
<div className="pt-4">
|
||||
<ErrorNotification error={error} />
|
||||
</div>
|
||||
) : null}
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
const LoginButton: FC<{ link: Link }> = ({ link }) => {
|
||||
const [t] = useTranslation("config");
|
||||
return (
|
||||
<Button color="primary" link={link.href}>
|
||||
{t("pluginSettings.auth.authenticate")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const PluginCenterAuthentication: FC = () => {
|
||||
const { data, isLoading, error } = usePluginCenterAuthInfo();
|
||||
const [t] = useTranslation("config");
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="is-flex is-align-content-center">
|
||||
<span className="small-loading-spinner pt-1 pr-3" />
|
||||
<p>{t("pluginSettings.auth.loading")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorNotification error={error} />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.principal) {
|
||||
return <AuthenticatedInfo authenticationInfo={data} />;
|
||||
}
|
||||
|
||||
if (data._links.login) {
|
||||
return (
|
||||
<Notification type="inherit" className="is-flex is-justify-content-space-between is-align-content-center">
|
||||
<Message>{t("pluginSettings.auth.notAuthenticated")}</Message>
|
||||
<LoginButton link={data._links.login as Link} />
|
||||
</Notification>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default PluginCenterAuthentication;
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { InputField, Subtitle } from "@scm-manager/ui-components";
|
||||
import PluginCenterAuthentication from "./PluginCenterAuthentication";
|
||||
|
||||
type Props = {
|
||||
pluginUrl: string;
|
||||
pluginAuthUrl: string;
|
||||
onChange: (isValid: boolean, changedValue: string, name: string) => void;
|
||||
hasUpdatePermission: boolean;
|
||||
};
|
||||
|
||||
const PluginSettings: FC<Props> = ({ pluginUrl, pluginAuthUrl, onChange, hasUpdatePermission }) => {
|
||||
const { t } = useTranslation("config");
|
||||
|
||||
const handlePluginCenterUrlChange = (value: string) => {
|
||||
onChange(true, value, "pluginUrl");
|
||||
};
|
||||
|
||||
const handlePluginCenterAuthUrlChange = (value: string) => {
|
||||
onChange(true, value, "pluginAuthUrl");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Subtitle subtitle={t("pluginSettings.subtitle")} />
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<InputField
|
||||
label={t("pluginSettings.pluginUrl")}
|
||||
onChange={handlePluginCenterUrlChange}
|
||||
value={pluginUrl}
|
||||
disabled={!hasUpdatePermission}
|
||||
helpText={t("help.pluginUrlHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<InputField
|
||||
label={t("pluginSettings.pluginAuthUrl")}
|
||||
onChange={handlePluginCenterAuthUrlChange}
|
||||
value={pluginAuthUrl}
|
||||
disabled={!hasUpdatePermission}
|
||||
helpText={t("help.pluginAuthUrlHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<PluginCenterAuthentication />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PluginSettings;
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Link, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { ExternalLinkButton, Icon } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
info: PluginCenterAuthenticationInfo;
|
||||
};
|
||||
|
||||
const CloudoguPlatformBanner: FC<Props> = ({ info }) => {
|
||||
const loginLink = (info._links.login as Link)?.href;
|
||||
if (loginLink) {
|
||||
return <Unauthenticated info={info} link={loginLink} />;
|
||||
}
|
||||
|
||||
if (info.failed) {
|
||||
const reconnectLink = (info._links.reconnect as Link)?.href;
|
||||
if (reconnectLink) {
|
||||
return <FailedAuthentication info={info} link={reconnectLink} />;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
type PropsWithLink = Props & {
|
||||
link: string;
|
||||
};
|
||||
|
||||
const FailedAuthentication: FC<PropsWithLink> = ({ info, link }) => {
|
||||
const [t] = useTranslation("admin");
|
||||
return (
|
||||
<Container className="has-border-danger">
|
||||
<p className="is-align-self-flex-start">
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="plugins.cloudoguPlatform.failed.message"
|
||||
values={{ subject: info.pluginCenterSubject }}
|
||||
components={[<a href="https://platform.cloudogu.com/">cloudogu platform</a>, <strong />]}
|
||||
/>
|
||||
</p>
|
||||
<ExternalLinkButton className="mt-5 has-text-weight-normal has-border-info" href={link}>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="plugins.cloudoguPlatform.failed.button.label"
|
||||
components={[<span className="mx-1 has-text-info">cloudogu platform</span>]}
|
||||
/>
|
||||
</ExternalLinkButton>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
type ContainerProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Container: FC<ContainerProps> = ({ className, children }) => (
|
||||
<DivWithSolidBorder
|
||||
className={classNames(
|
||||
"has-rounded-border is-flex is-flex-direction-column is-align-items-center p-5 mb-4",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</DivWithSolidBorder>
|
||||
);
|
||||
|
||||
const DivWithSolidBorder = styled.div`
|
||||
border: 2px solid;
|
||||
`;
|
||||
|
||||
const Unauthenticated: FC<PropsWithLink> = ({ link, info }) => {
|
||||
const [t] = useTranslation("admin");
|
||||
return (
|
||||
<Container className="has-border-success">
|
||||
<ExternalLinkButton className="mb-5 has-text-weight-normal has-border-info" href={link}>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="plugins.cloudoguPlatform.login.button.label"
|
||||
components={[<span className="mx-1 has-text-info">cloudogu platform</span>]}
|
||||
/>
|
||||
</ExternalLinkButton>
|
||||
<p className="is-align-self-flex-start is-size-7">
|
||||
<span>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="plugins.cloudoguPlatform.login.description"
|
||||
components={[
|
||||
<a href="https://platform.cloudogu.com/">cloudogu platform</a>,
|
||||
<a href="https://scm-manager.org/data-processing">Data Processing</a>,
|
||||
]}
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloudoguPlatformBanner;
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const CloudoguPlatformTagWrapper = styled.span`
|
||||
border: solid 1px;
|
||||
`;
|
||||
|
||||
const CloudoguPlatformTag = () => (
|
||||
<CloudoguPlatformTagWrapper className="has-text-info has-border-info has-rounded-border p-1 is-size-7">
|
||||
cloudogu platform
|
||||
</CloudoguPlatformTagWrapper>
|
||||
);
|
||||
|
||||
export default CloudoguPlatformTag;
|
||||
@@ -16,19 +16,16 @@
|
||||
|
||||
import React, { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import { Link, Plugin, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types";
|
||||
import { Link, Plugin } from "@scm-manager/ui-types";
|
||||
import { CardColumn, Icon } from "@scm-manager/ui-components";
|
||||
import { PluginAction, PluginModalContent } from "../containers/PluginsOverview";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PluginAvatar from "./PluginAvatar";
|
||||
import classNames from "classnames";
|
||||
import CloudoguPlatformTag from "./CloudoguPlatformTag";
|
||||
import { useKeyboardIteratorTarget } from "@scm-manager/ui-shortcuts";
|
||||
|
||||
type Props = {
|
||||
plugin: Plugin;
|
||||
openModal: (content: PluginModalContent) => void;
|
||||
pluginCenterAuthInfo?: PluginCenterAuthenticationInfo;
|
||||
};
|
||||
|
||||
const ActionbarWrapper = styled.div`
|
||||
@@ -37,7 +34,7 @@ const ActionbarWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const IconWrapperStyle = styled.span.attrs((props) => ({
|
||||
const IconWrapperStyle = styled.span.attrs(() => ({
|
||||
className: "level-item mb-0 p-2 is-clickable",
|
||||
}))`
|
||||
border: 1px solid #cdcdcd; // $dark-25
|
||||
@@ -56,13 +53,11 @@ const IconWrapper: FC<{ action: () => void }> = ({ action, children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PluginEntry: FC<Props> = ({ plugin, openModal, pluginCenterAuthInfo }) => {
|
||||
const PluginEntry: FC<Props> = ({ plugin, openModal }) => {
|
||||
const [t] = useTranslation("admin");
|
||||
const isInstallable = plugin._links.install && (plugin._links.install as Link).href;
|
||||
const isUpdatable = plugin._links.update && (plugin._links.update as Link).href;
|
||||
const isUninstallable = plugin._links.uninstall && (plugin._links.uninstall as Link).href;
|
||||
const isCloudoguPlugin = plugin.type === "CLOUDOGU";
|
||||
const isDefaultPluginCenterLoginAvailable = pluginCenterAuthInfo?.default && !!pluginCenterAuthInfo?._links?.login;
|
||||
const ref = useKeyboardIteratorTarget();
|
||||
|
||||
const evaluateAction = () => {
|
||||
@@ -70,29 +65,16 @@ const PluginEntry: FC<Props> = ({ plugin, openModal, pluginCenterAuthInfo }) =>
|
||||
return () => openModal({ plugin, action: PluginAction.INSTALL });
|
||||
}
|
||||
|
||||
if (isCloudoguPlugin && isDefaultPluginCenterLoginAvailable) {
|
||||
return () => openModal({ plugin, action: PluginAction.CLOUDOGU });
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const pendingInfo = () => (
|
||||
<>
|
||||
<Icon
|
||||
className="fa-lg"
|
||||
name="check"
|
||||
color="info"
|
||||
alt={t("plugins.markedAsPending")}
|
||||
/></>
|
||||
<Icon className="fa-lg" name="check" color="info" alt={t("plugins.markedAsPending")} />
|
||||
</>
|
||||
);
|
||||
const actionBar = () => (
|
||||
<ActionbarWrapper className="is-flex">
|
||||
{isCloudoguPlugin && isDefaultPluginCenterLoginAvailable && (
|
||||
<IconWrapper action={() => openModal({ plugin, action: PluginAction.CLOUDOGU })}>
|
||||
<Icon title={t("plugins.modal.cloudoguInstall")} name="link" color="success-dark" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
{isInstallable && (
|
||||
<IconWrapper action={() => openModal({ plugin, action: PluginAction.INSTALL })}>
|
||||
<Icon title={t("plugins.modal.install")} name="download" color="info" />
|
||||
@@ -121,17 +103,8 @@ const PluginEntry: FC<Props> = ({ plugin, openModal, pluginCenterAuthInfo }) =>
|
||||
description={plugin.description}
|
||||
contentRight={plugin.pending || plugin.markedForUninstall ? pendingInfo() : actionBar()}
|
||||
footerLeft={<small>{plugin.version}</small>}
|
||||
footerRight={null}
|
||||
footerRight={<small className="level-item is-block shorten-text">{plugin.author}</small>}
|
||||
/>
|
||||
<div
|
||||
className={classNames("is-flex", {
|
||||
"is-justify-content-space-between": isCloudoguPlugin,
|
||||
"is-justify-content-end": !isCloudoguPlugin,
|
||||
})}
|
||||
>
|
||||
{isCloudoguPlugin ? <CloudoguPlatformTag /> : null}
|
||||
<small className="level-item is-block shorten-text is-align-self-flex-end">{plugin.author}</small>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,26 +16,18 @@
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { CardColumnGroup } from "@scm-manager/ui-components";
|
||||
import { PluginCenterAuthenticationInfo, PluginGroup } from "@scm-manager/ui-types";
|
||||
import { PluginGroup } from "@scm-manager/ui-types";
|
||||
import PluginEntry from "./PluginEntry";
|
||||
import { PluginModalContent } from "../containers/PluginsOverview";
|
||||
|
||||
type Props = {
|
||||
group: PluginGroup;
|
||||
openModal: (content: PluginModalContent) => void;
|
||||
pluginCenterAuthInfo?: PluginCenterAuthenticationInfo;
|
||||
};
|
||||
|
||||
const PluginGroupEntry: FC<Props> = ({ openModal, group, pluginCenterAuthInfo }) => {
|
||||
const entries = group.plugins.map(plugin => {
|
||||
return (
|
||||
<PluginEntry
|
||||
plugin={plugin}
|
||||
openModal={openModal}
|
||||
key={plugin.name}
|
||||
pluginCenterAuthInfo={pluginCenterAuthInfo}
|
||||
/>
|
||||
);
|
||||
const PluginGroupEntry: FC<Props> = ({ openModal, group }) => {
|
||||
const entries = group.plugins.map((plugin) => {
|
||||
return <PluginEntry plugin={plugin} openModal={openModal} key={plugin.name} />;
|
||||
});
|
||||
return <CardColumnGroup name={group.name} elements={entries} />;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { Plugin, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types";
|
||||
import { Plugin } from "@scm-manager/ui-types";
|
||||
import PluginGroupEntry from "../components/PluginGroupEntry";
|
||||
import groupByCategory from "./groupByCategory";
|
||||
import { PluginModalContent } from "../containers/PluginsOverview";
|
||||
@@ -24,23 +24,15 @@ import { KeyboardIterator } from "@scm-manager/ui-shortcuts";
|
||||
type Props = {
|
||||
plugins: Plugin[];
|
||||
openModal: (content: PluginModalContent) => void;
|
||||
pluginCenterAuthInfo?: PluginCenterAuthenticationInfo;
|
||||
};
|
||||
|
||||
const PluginList: FC<Props> = ({ plugins, openModal, pluginCenterAuthInfo }) => {
|
||||
const PluginList: FC<Props> = ({ plugins, openModal }) => {
|
||||
const groups = groupByCategory(plugins);
|
||||
return (
|
||||
<div className="content is-plugin-page">
|
||||
<KeyboardIterator>
|
||||
{groups.map((group) => {
|
||||
return (
|
||||
<PluginGroupEntry
|
||||
group={group}
|
||||
openModal={openModal}
|
||||
key={group.name}
|
||||
pluginCenterAuthInfo={pluginCenterAuthInfo}
|
||||
/>
|
||||
);
|
||||
return <PluginGroupEntry group={group} openModal={openModal} key={group.name} />;
|
||||
})}
|
||||
</KeyboardIterator>
|
||||
</div>
|
||||
|
||||
@@ -18,12 +18,11 @@ import React, { FC, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { Link, Plugin } from "@scm-manager/ui-types";
|
||||
import { Plugin } from "@scm-manager/ui-types";
|
||||
import { Button, ButtonGroup, Checkbox, ErrorNotification, Modal, Notification } from "@scm-manager/ui-components";
|
||||
import SuccessNotification from "./SuccessNotification";
|
||||
import { useInstallPlugin, usePluginCenterAuthInfo, useUninstallPlugin, useUpdatePlugins } from "@scm-manager/ui-api";
|
||||
import { useInstallPlugin, useUninstallPlugin, useUpdatePlugins } from "@scm-manager/ui-api";
|
||||
import { PluginAction } from "../containers/PluginsOverview";
|
||||
import CloudoguPlatformTag from "./CloudoguPlatformTag";
|
||||
|
||||
type Props = {
|
||||
plugin: Plugin;
|
||||
@@ -48,16 +47,11 @@ const ListChild = styled.div`
|
||||
const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
||||
const [t] = useTranslation("admin");
|
||||
const [shouldRestart, setShouldRestart] = useState<boolean>(false);
|
||||
const {
|
||||
data: pluginCenterAuthInfo,
|
||||
isLoading: isLoadingPluginCenterAuthInfo,
|
||||
error: pluginCenterAuthInfoError
|
||||
} = usePluginCenterAuthInfo();
|
||||
const { isLoading: isInstalling, error: installError, install, isInstalled } = useInstallPlugin();
|
||||
const { isLoading: isUninstalling, error: uninstallError, uninstall, isUninstalled } = useUninstallPlugin();
|
||||
const { isLoading: isUpdating, error: updateError, update, isUpdated } = useUpdatePlugins();
|
||||
const error = installError || uninstallError || updateError || pluginCenterAuthInfoError;
|
||||
const loading = isInstalling || isUninstalling || isUpdating || isLoadingPluginCenterAuthInfo;
|
||||
const error = installError || uninstallError || updateError;
|
||||
const loading = isInstalling || isUninstalling || isUpdating;
|
||||
const isDone = isInstalled || isUninstalled || isUpdated;
|
||||
const initialFocusRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
@@ -70,9 +64,6 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
||||
const handlePluginAction = (e: React.MouseEvent<Element, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
switch (pluginAction) {
|
||||
case PluginAction.CLOUDOGU:
|
||||
window.open((pluginCenterAuthInfo?._links?.login as Link).href, "_self");
|
||||
break;
|
||||
case PluginAction.INSTALL:
|
||||
install(plugin, { restart: shouldRestart });
|
||||
break;
|
||||
@@ -195,7 +186,7 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
||||
disabled={false}
|
||||
/>
|
||||
);
|
||||
} else if (pluginAction !== PluginAction.CLOUDOGU) {
|
||||
} else {
|
||||
return <Notification type="warning">{t("plugins.modal.manualRestartRequired")}</Notification>;
|
||||
}
|
||||
};
|
||||
@@ -213,18 +204,6 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
||||
<ListParent pluginAction={pluginAction}>{t("plugins.modal.author")}:</ListParent>
|
||||
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.author}</ListChild>
|
||||
</div>
|
||||
{pluginAction === PluginAction.CLOUDOGU && (
|
||||
<>
|
||||
<div className="field is-horizontal">
|
||||
<CloudoguPlatformTag />
|
||||
</div>
|
||||
<div className="field is-horizontal">
|
||||
<Notification type="info" className="is-full-width">
|
||||
{t("plugins.modal.cloudoguInstallInfo")}
|
||||
</Notification>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{pluginAction === PluginAction.INSTALL && (
|
||||
<div className="field is-horizontal">
|
||||
<ListParent pluginAction={pluginAction}>{t("plugins.modal.version")}:</ListParent>
|
||||
|
||||
@@ -29,11 +29,8 @@ import {
|
||||
useAvailablePlugins,
|
||||
useInstalledPlugins,
|
||||
usePendingPlugins,
|
||||
usePluginCenterAuthInfo,
|
||||
} from "@scm-manager/ui-api";
|
||||
import PluginModal from "../components/PluginModal";
|
||||
import CloudoguPlatformBanner from "../components/CloudoguPlatformBanner";
|
||||
import PluginCenterAuthInfo from "../components/PluginCenterAuthInfo";
|
||||
import styled from "styled-components";
|
||||
import { Button } from "@scm-manager/ui-buttons";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
@@ -42,7 +39,6 @@ export enum PluginAction {
|
||||
INSTALL = "install",
|
||||
UPDATE = "update",
|
||||
UNINSTALL = "uninstall",
|
||||
CLOUDOGU = "cloudoguInstall",
|
||||
}
|
||||
|
||||
export type PluginModalContent = {
|
||||
@@ -83,7 +79,6 @@ const PluginsOverview: FC<Props> = ({ installed }) => {
|
||||
error: installedPluginsError,
|
||||
} = useInstalledPlugins({ enabled: installed });
|
||||
const { data: pendingPlugins, isLoading: isLoadingPendingPlugins, error: pendingPluginsError } = usePendingPlugins();
|
||||
const pluginCenterAuthInfo = usePluginCenterAuthInfo();
|
||||
const [showPendingModal, setShowPendingModal] = useState(false);
|
||||
const [showExecutePendingModal, setShowExecutePendingModal] = useState(false);
|
||||
const [showUpdateAllModal, setShowUpdateAllModal] = useState(false);
|
||||
@@ -97,9 +92,7 @@ const PluginsOverview: FC<Props> = ({ installed }) => {
|
||||
return (
|
||||
<StickyHeader className="has-background-secondary-least is-flex is-justify-content-space-between is-align-items-baseline has-gap-2">
|
||||
<div className="is-flex-shrink-0">
|
||||
<Title className="is-flex">
|
||||
{t("plugins.title")} <PluginCenterAuthInfo {...pluginCenterAuthInfo} />
|
||||
</Title>
|
||||
<Title>{t("plugins.title")}</Title>
|
||||
<Subtitle subtitle={installed ? t("plugins.installedSubtitle") : t("plugins.availableSubtitle")} />
|
||||
</div>
|
||||
<PluginTopActions>{actions}</PluginTopActions>
|
||||
@@ -166,11 +159,7 @@ const PluginsOverview: FC<Props> = ({ installed }) => {
|
||||
return (
|
||||
<>
|
||||
{pluginCenterStatusNotification}
|
||||
<PluginsList
|
||||
plugins={collection._embedded.plugins}
|
||||
openModal={setPluginModalContent}
|
||||
pluginCenterAuthInfo={pluginCenterAuthInfo.data}
|
||||
/>
|
||||
<PluginsList plugins={collection._embedded.plugins} openModal={setPluginModalContent} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -211,7 +200,6 @@ const PluginsOverview: FC<Props> = ({ installed }) => {
|
||||
return (
|
||||
<>
|
||||
{renderHeader(actions)}
|
||||
{pluginCenterAuthInfo.data?.default ? <CloudoguPlatformBanner info={pluginCenterAuthInfo.data} /> : null}
|
||||
{renderPluginsList()}
|
||||
{renderModals()}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user