aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-10-20 15:45:41 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-10-23 08:01:13 -0700
commitafdec5241d0bdeb88583f520c75d6f4de2d583d7 (patch)
tree1a7a89d7b7d6ad0c6488a113813b1fa3aab0fca6 /server
parentee4d391a17cddca6478e95db65601b398af3f78f (diff)
downloadsonarqube-afdec5241d0bdeb88583f520c75d6f4de2d583d7.tar.gz
sonarqube-afdec5241d0bdeb88583f520c75d6f4de2d583d7.zip
Display last available version editions in read only when current edition is not found
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/App.tsx70
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx122
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx76
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap10
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/utils.ts11
-rw-r--r--server/sonar-web/src/main/less/components/alerts.less5
13 files changed, 192 insertions, 150 deletions
diff --git a/server/sonar-web/src/main/js/apps/marketplace/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
index c5012002c95..c57f3de78fd 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
@@ -39,7 +39,14 @@ import {
import { Edition, EditionStatus, getEditionsList, getEditionStatus } from '../../api/marketplace';
import { RawQuery } from '../../helpers/query';
import { translate } from '../../helpers/l10n';
-import { getEditionsForVersion, filterPlugins, parseQuery, Query, serializeQuery } from './utils';
+import {
+ getEditionsForLastVersion,
+ getEditionsForVersion,
+ filterPlugins,
+ parseQuery,
+ Query,
+ serializeQuery
+} from './utils';
export interface Props {
editionsUrl: string;
@@ -51,6 +58,7 @@ export interface Props {
interface State {
editions?: Edition[];
+ editionsReadOnly: boolean;
editionStatus?: EditionStatus;
loadingEditions: boolean;
loadingPlugins: boolean;
@@ -73,6 +81,7 @@ export default class App extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
+ editionsReadOnly: false,
loadingEditions: true,
loadingPlugins: true,
pending: {
@@ -113,37 +122,26 @@ export default class App extends React.PureComponent<Props, State> {
fetchAllPlugins = () => {
this.setState({ loadingPlugins: true });
- Promise.all([getInstalledPluginsWithUpdates(), getAvailablePlugins()]).then(
- ([installed, available]) => {
- if (this.mounted) {
- this.setState({
- loadingPlugins: false,
- plugins: sortBy(uniqBy([...installed, ...available.plugins], 'key'), 'name')
- });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loadingPlugins: false });
- }
+ Promise.all([
+ getInstalledPluginsWithUpdates(),
+ getAvailablePlugins()
+ ]).then(([installed, available]) => {
+ if (this.mounted) {
+ this.setState({
+ loadingPlugins: false,
+ plugins: sortBy(uniqBy([...installed, ...available.plugins], 'key'), 'name')
+ });
}
- );
+ }, this.stopLoadingPlugins);
};
fetchUpdatesOnly = () => {
this.setState({ loadingPlugins: true });
- getPluginUpdates().then(
- plugins => {
- if (this.mounted) {
- this.setState({ loadingPlugins: false, plugins });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loadingPlugins: false });
- }
+ getPluginUpdates().then(plugins => {
+ if (this.mounted) {
+ this.setState({ loadingPlugins: false, plugins });
}
- );
+ }, this.stopLoadingPlugins);
};
fetchPendingPlugins = () =>
@@ -171,10 +169,16 @@ export default class App extends React.PureComponent<Props, State> {
getEditionsList(this.props.editionsUrl).then(
editionsPerVersion => {
if (this.mounted) {
- this.setState({
+ const newState = {
editions: getEditionsForVersion(editionsPerVersion, this.props.sonarqubeVersion),
+ editionsReadOnly: false,
loadingEditions: false
- });
+ };
+ if (!newState.editions) {
+ newState.editions = getEditionsForLastVersion(editionsPerVersion);
+ newState.editionsReadOnly = true;
+ }
+ this.setState(newState);
}
},
() => {
@@ -204,6 +208,12 @@ export default class App extends React.PureComponent<Props, State> {
this.context.router.push({ pathname: this.props.location.pathname, query });
};
+ stopLoadingPlugins = () => {
+ if (this.mounted) {
+ this.setState({ loadingPlugins: false });
+ }
+ };
+
render() {
const { standaloneMode } = this.props;
const { editions, editionStatus, loadingPlugins, plugins, pending } = this.state;
@@ -222,7 +232,7 @@ export default class App extends React.PureComponent<Props, State> {
updateEditionStatus={this.updateEditionStatus}
/>
)}
- {!standaloneMode && (
+ {standaloneMode && (
<PendingActions refreshPending={this.fetchPendingPlugins} pending={pending} />
)}
</div>
@@ -232,7 +242,7 @@ export default class App extends React.PureComponent<Props, State> {
loading={this.state.loadingEditions}
editionStatus={editionStatus}
editionsUrl={this.props.editionsUrl}
- readOnly={!standaloneMode}
+ readOnly={!standaloneMode || this.state.editionsReadOnly}
sonarqubeVersion={this.props.sonarqubeVersion}
updateCenterActive={this.props.updateCenterActive}
updateEditionStatus={this.updateEditionStatus}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
index 4bc679d4d2a..79498c13948 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
@@ -74,6 +74,7 @@ export default class PluginsList extends React.PureComponent<Props> {
/>
);
}
+ return null;
};
render() {
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
index d637c7116ae..27130a159a4 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
@@ -48,7 +48,7 @@ export default class EditionBox extends React.PureComponent<Props> {
if (isInstalled) {
return (
<span className="marketplace-edition-badge badge badge-normal-size">
- <CheckIcon size={14} className="little-spacer-right text-text-top" />
+ <CheckIcon size={14} className="little-spacer-right text-bottom" />
{translate('marketplace.installed')}
</span>
);
@@ -59,6 +59,8 @@ export default class EditionBox extends React.PureComponent<Props> {
render() {
const { edition, editionStatus, readOnly } = this.props;
const isInstalled = editionStatus && editionStatus.currentEditionKey === edition.key;
+ const uninstallInProgress =
+ editionStatus && editionStatus.installationStatus === 'UNINSTALL_IN_PROGRESS';
const installInProgress =
editionStatus &&
['AUTOMATIC_IN_PROGRESS', 'AUTOMATIC_READY'].includes(editionStatus.installationStatus);
@@ -74,20 +76,20 @@ export default class EditionBox extends React.PureComponent<Props> {
{translate('marketplace.learn_more')}
</a>
{!readOnly &&
- !isInstalled && (
- <button disabled={installInProgress} onClick={this.handleInstall}>
- {translate('marketplace.install')}
- </button>
- )}
- {!readOnly &&
- isInstalled && (
- <button
- className="button-red"
- disabled={installInProgress}
- onClick={this.props.onUninstall}>
- {translate('marketplace.uninstall')}
- </button>
- )}
+ (isInstalled ? (
+ <button
+ className="button-red"
+ disabled={installInProgress || uninstallInProgress}
+ onClick={this.props.onUninstall}>
+ {translate('marketplace.uninstall')}
+ </button>
+ ) : (
+ <button
+ disabled={installInProgress || uninstallInProgress}
+ onClick={this.handleInstall}>
+ {translate('marketplace.install')}
+ </button>
+ ))}
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx
index 2430200ac27..f21e8a30588 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx
@@ -49,8 +49,67 @@ export default class EditionsStatusNotif extends React.PureComponent<Props, Stat
);
};
- renderStatusAlert() {
+ renderRestartMsg(edition?: Edition) {
const { editionStatus, readOnly } = this.props;
+ return (
+ <div className="alert alert-success">
+ <span>
+ {edition ? (
+ translateWithParameters(
+ 'marketplace.status_x.' + editionStatus.installationStatus,
+ edition.name
+ )
+ ) : (
+ translate('marketplace.status', editionStatus.installationStatus)
+ )}
+ </span>
+ {!readOnly && (
+ <button className="js-restart spacer-left" onClick={this.handleOpenRestart}>
+ {translate('marketplace.restart')}
+ </button>
+ )}
+ {!readOnly && this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
+ </div>
+ );
+ }
+
+ renderManualMsg(edition?: Edition) {
+ const { editionStatus } = this.props;
+ return (
+ <div className="alert alert-danger">
+ {edition ? (
+ translateWithParameters(
+ 'marketplace.status_x.' + editionStatus.installationStatus,
+ edition.name
+ )
+ ) : (
+ translate('marketplace.status', editionStatus.installationStatus)
+ )}
+ <p className="spacer-left">
+ {edition && (
+ <a
+ className="button spacer-right"
+ download={`sonarqube-${edition.name}.zip`}
+ href={edition.downloadUrl}
+ target="_blank">
+ {translate('marketplace.download_package')}
+ </a>
+ )}
+ <a
+ href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
+ target="_blank">
+ {translate('marketplace.how_to_install')}
+ </a>
+ </p>
+ <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
+ {translate('marketplace.how_to_install')}
+ </a>
+ </div>
+ );
+ }
+
+ renderStatusAlert() {
+ const { editionStatus } = this.props;
const { installationStatus, nextEditionKey } = editionStatus;
const nextEdition =
this.props.editions && this.props.editions.find(edition => edition.key === nextEditionKey);
@@ -65,59 +124,9 @@ export default class EditionsStatusNotif extends React.PureComponent<Props, Stat
);
case 'AUTOMATIC_READY':
case 'UNINSTALL_IN_PROGRESS':
- return (
- <div className="alert alert-success">
- <span>
- {nextEdition ? (
- translateWithParameters(
- 'marketplace.status_x.' + installationStatus,
- nextEdition.name
- )
- ) : (
- translate('marketplace.status', installationStatus)
- )}
- </span>
- {!readOnly && (
- <button className="js-restart spacer-left" onClick={this.handleOpenRestart}>
- {translate('marketplace.restart')}
- </button>
- )}
- {!readOnly &&
- this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
- </div>
- );
+ return this.renderRestartMsg(nextEdition);
case 'MANUAL_IN_PROGRESS':
- return (
- <div className="alert alert-danger">
- {nextEdition ? (
- translateWithParameters(
- 'marketplace.status_x.' + installationStatus,
- nextEdition.name
- )
- ) : (
- translate('marketplace.status', installationStatus)
- )}
- <p className="spacer-left">
- {nextEdition && (
- <a
- className="button spacer-right"
- download={`sonarqube-${nextEdition.name}.zip`}
- href={nextEdition.downloadUrl}
- target="_blank">
- {translate('marketplace.download_package')}
- </a>
- )}
- <a
- href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
- target="_blank">
- {translate('marketplace.how_to_install')}
- </a>
- </p>
- <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
- {translate('marketplace.how_to_install')}
- </a>
- </div>
- );
+ return this.renderManualMsg(nextEdition);
}
return null;
}
@@ -127,12 +136,9 @@ export default class EditionsStatusNotif extends React.PureComponent<Props, Stat
return (
<div>
{installError && (
- <div className="alert alert-danger">
+ <div className="alert alert-danger alert-cancel">
{installError}
- <a
- className="pull-right button-link text-danger"
- href="#"
- onClick={this.handleDismissError}>
+ <a className="button-link text-danger" href="#" onClick={this.handleDismissError}>
<CloseIcon />
</a>
</div>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx
index 10def5d07bd..58b58f371a7 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx
@@ -130,11 +130,11 @@ export default class LicenseEditionSet extends React.PureComponent<Props, State>
<textarea
autoFocus={true}
id="set-license"
- className="display-block"
- cols={62}
+ className="display-block input-super-large"
onChange={this.handleLicenseChange}
required={true}
- rows={6}
+ rows={8}
+ style={{ resize: 'none' }}
value={license}
/>
{previewStatus && (
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
index c895d0aedd1..501631edadc 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
@@ -69,47 +69,53 @@ export default class PluginActions extends React.PureComponent<Props, State> {
handleUninstall = () => this.doPluginAction(uninstallPlugin);
handleTermsCheck = (checked: boolean) => this.setState({ acceptTerms: checked });
+ renderBundled() {
+ const { plugin } = this.props;
+
+ return (
+ <div className="js-actions">
+ {isPluginAvailable(plugin) && (
+ <div>
+ <p className="little-spacer-bottom">
+ {translate('marketplace.available_under_commercial_license')}
+ </p>
+ <a href={plugin.homepageUrl} target="_blank">
+ {translate('marketplace.learn_more')}
+ </a>
+ </div>
+ )}
+ {isPluginInstalled(plugin) && (
+ <p>
+ <CheckIcon className="little-spacer-right" />
+ {translate('marketplace.installed')}
+ </p>
+ )}
+ {isPluginInstalled(plugin) &&
+ plugin.updates &&
+ plugin.updates.length > 0 && (
+ <div className="spacer-top button-group">
+ {plugin.updates.map((update, idx) => (
+ <PluginUpdateButton
+ key={idx}
+ onClick={this.handleUpdate}
+ update={update}
+ disabled={this.state.loading}
+ />
+ ))}
+ </div>
+ )}
+ </div>
+ );
+ }
+
render() {
const { plugin } = this.props;
- const { loading } = this.state;
if (plugin.editionBundled) {
- return (
- <div className="js-actions">
- {isPluginAvailable(plugin) && (
- <div>
- <p className="little-spacer-bottom">
- {translate('marketplace.available_under_commercial_license')}
- </p>
- <a href={plugin.homepageUrl} target="_blank">
- {translate('marketplace.learn_more')}
- </a>
- </div>
- )}
- {isPluginInstalled(plugin) && (
- <p>
- <CheckIcon className="little-spacer-right" />
- {translate('marketplace.installed')}
- </p>
- )}
- {isPluginInstalled(plugin) &&
- plugin.updates &&
- plugin.updates.length > 0 && (
- <div className="spacer-top button-group">
- {plugin.updates.map((update, idx) => (
- <PluginUpdateButton
- key={idx}
- onClick={this.handleUpdate}
- update={update}
- disabled={loading}
- />
- ))}
- </div>
- )}
- </div>
- );
+ return this.renderBundled();
}
+ const { loading } = this.state;
return (
<div className="js-actions">
{isPluginAvailable(plugin) &&
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx
index 705d02f3907..37b76edc83e 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx
@@ -57,6 +57,7 @@ export default class UninstallEditionForm extends React.PureComponent<Props, Sta
.then(() => {
this.props.updateEditionStatus({
...this.props.editionStatus,
+ currentEditionKey: undefined,
installationStatus: 'UNINSTALL_IN_PROGRESS'
});
this.props.onClose();
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx
index 3f52d0cce13..be7f50ce171 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx
@@ -53,7 +53,7 @@ it('should update the edition status after uninstall', async () => {
expect(uninstallEdition).toHaveBeenCalled();
await new Promise(setImmediate);
expect(updateEditionStatus).toHaveBeenCalledWith({
- currentEditionKey: 'foo',
+ currentEditionKey: undefined,
installationStatus: 'UNINSTALL_IN_PROGRESS'
});
});
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap
index 7b8a8eaa902..b7dbb58a562 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap
@@ -123,7 +123,7 @@ exports[`should display installed badge 1`] = `
className="marketplace-edition-badge badge badge-normal-size"
>
<CheckIcon
- className="little-spacer-right text-text-top"
+ className="little-spacer-right text-bottom"
size={14}
/>
marketplace.installed
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap
index e52e16c6fa7..a76a31c61d2 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap
@@ -36,11 +36,11 @@ exports[`should display an in progress notif 1`] = `
exports[`should display install errors 1`] = `
<div>
<div
- className="alert alert-danger"
+ className="alert alert-danger alert-cancel"
>
Foo error
<a
- className="pull-right button-link text-danger"
+ className="button-link text-danger"
href="#"
onClick={[Function]}
>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap
index 2e49983f7d3..4534dd328d2 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap
@@ -39,12 +39,16 @@ exports[`should display correctly 1`] = `
</label>
<textarea
autoFocus={true}
- className="display-block"
- cols={62}
+ className="display-block input-super-large"
id="set-license"
onChange={[Function]}
required={true}
- rows={6}
+ rows={8}
+ style={
+ Object {
+ "resize": "none",
+ }
+ }
value=""
/>
<a
diff --git a/server/sonar-web/src/main/js/apps/marketplace/utils.ts b/server/sonar-web/src/main/js/apps/marketplace/utils.ts
index 1f9216c1c2e..31835b2ec56 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/utils.ts
+++ b/server/sonar-web/src/main/js/apps/marketplace/utils.ts
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { memoize } from 'lodash';
+import { memoize, sortBy } from 'lodash';
import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins';
import { Edition, EditionsPerVersion } from '../../api/marketplace';
import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query';
@@ -52,6 +52,15 @@ export function filterPlugins(plugins: Plugin[], search: string): Plugin[] {
});
}
+export function getEditionsForLastVersion(editions: EditionsPerVersion): Edition[] {
+ const sortedVersion = sortBy(Object.keys(editions), [
+ (version: string) => -Number(version.split('.')[0]),
+ (version: string) => -Number(version.split('.')[1] || 0),
+ (version: string) => -Number(version.split('.')[2] || 0)
+ ]);
+ return editions[sortedVersion[0]];
+}
+
export function getEditionsForVersion(
editions: EditionsPerVersion,
version: string
diff --git a/server/sonar-web/src/main/less/components/alerts.less b/server/sonar-web/src/main/less/components/alerts.less
index 01bdc89ce1c..1cc1c516fe2 100644
--- a/server/sonar-web/src/main/less/components/alerts.less
+++ b/server/sonar-web/src/main/less/components/alerts.less
@@ -66,9 +66,12 @@
.alert-emphasis-variant(#3c763d, #dff0d8, #d6e9c6);
}
-.page-notifs .alert {
+.alert-cancel {
display: flex;
justify-content: space-between;
+}
+
+.page-notifs .alert {
padding: 8px 10px;
}