mirror of
https://github.com/flarum/core.git
synced 2025-08-20 15:21:49 +02:00
feat(em): port extension manager from 1.0 (#3959)
* feat(em): port extension manager from 1.0 * Apply fixes from StyleCI * chore: phpstan --------- Co-authored-by: StyleCI Bot <bot@styleci.io>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||
import Mithril from 'mithril';
|
||||
import app from 'flarum/admin/app';
|
||||
import Select from 'flarum/common/components/Select';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
|
||||
export interface IAuthMethodModalAttrs extends IInternalModalAttrs {
|
||||
onsubmit: (type: string, host: string, token: string) => void;
|
||||
type?: string;
|
||||
host?: string;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export default class AuthMethodModal<CustomAttrs extends IAuthMethodModalAttrs = IAuthMethodModalAttrs> extends Modal<CustomAttrs> {
|
||||
protected type!: Stream<string>;
|
||||
protected host!: Stream<string>;
|
||||
protected token!: Stream<string>;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.type = Stream(this.attrs.type || 'bearer');
|
||||
this.host = Stream(this.attrs.host || '');
|
||||
this.token = Stream(this.attrs.token || '');
|
||||
}
|
||||
|
||||
className(): string {
|
||||
return 'AuthMethodModal Modal--small';
|
||||
}
|
||||
|
||||
title(): Mithril.Children {
|
||||
const context = this.attrs.host ? 'edit' : 'add';
|
||||
return app.translator.trans(`flarum-extension-manager.admin.auth_config.${context}_label`);
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
const types = {
|
||||
'github-oauth': app.translator.trans('flarum-extension-manager.admin.auth_config.types.github-oauth'),
|
||||
'gitlab-oauth': app.translator.trans('flarum-extension-manager.admin.auth_config.types.gitlab-oauth'),
|
||||
'gitlab-token': app.translator.trans('flarum-extension-manager.admin.auth_config.types.gitlab-token'),
|
||||
bearer: app.translator.trans('flarum-extension-manager.admin.auth_config.types.bearer'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.type_label')}</label>
|
||||
<Select options={types} value={this.type()} onchange={this.type} />
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.host_label')}</label>
|
||||
<input
|
||||
className="FormControl"
|
||||
bidi={this.host}
|
||||
placeholder={app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.host_placeholder')}
|
||||
/>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.token_label')}</label>
|
||||
<textarea
|
||||
className="FormControl"
|
||||
oninput={(e: InputEvent) => this.token((e.target as HTMLTextAreaElement).value)}
|
||||
rows="6"
|
||||
placeholder={
|
||||
this.token().startsWith('unchanged:')
|
||||
? extractText(app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.unchanged_token_placeholder'))
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{this.token().startsWith('unchanged:') ? '' : this.token()}
|
||||
</textarea>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary" onclick={this.submit.bind(this)}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.attrs.onsubmit(this.type(), this.host(), this.token());
|
||||
this.hide();
|
||||
}
|
||||
}
|
@@ -0,0 +1,120 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import type Mithril from 'mithril';
|
||||
import ConfigureJson, { IConfigureJson } from './ConfigureJson';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import AuthMethodModal from './AuthMethodModal';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
|
||||
export default class ConfigureAuth extends ConfigureJson<IConfigureJson> {
|
||||
protected type = 'auth';
|
||||
|
||||
title(): Mithril.Children {
|
||||
return app.translator.trans('flarum-extension-manager.admin.auth_config.title');
|
||||
}
|
||||
|
||||
className(): string {
|
||||
return 'ConfigureAuth';
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
const authSettings = Object.keys(this.settings);
|
||||
const hasAuthSettings =
|
||||
authSettings.length &&
|
||||
authSettings.every((type) => {
|
||||
const data = this.settings[type]();
|
||||
|
||||
return Array.isArray(data) ? data.length : Object.keys(data).length;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="ExtensionManager-SettingsGroups-content">
|
||||
{hasAuthSettings ? (
|
||||
authSettings.map((type) => {
|
||||
const hosts = this.settings[type]();
|
||||
|
||||
return (
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans(`flarum-extension-manager.admin.auth_config.types.${type}`)}</label>
|
||||
<div className="ConfigureAuth-hosts">
|
||||
{Object.keys(hosts).map((host) => {
|
||||
const data = hosts[host] as string | Record<string, string>;
|
||||
|
||||
return (
|
||||
<div className="ButtonGroup ButtonGroup--full">
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-key"
|
||||
onclick={() =>
|
||||
app.modal.show(AuthMethodModal, {
|
||||
type,
|
||||
host,
|
||||
token: data,
|
||||
onsubmit: this.onchange.bind(this, host),
|
||||
})
|
||||
}
|
||||
>
|
||||
{host}
|
||||
</Button>
|
||||
<Button
|
||||
className="Button Button--icon"
|
||||
icon="fas fa-trash"
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.auth_config.delete_label')}
|
||||
onclick={() => {
|
||||
if (confirm(extractText(app.translator.trans('flarum-extension-manager.admin.auth_config.delete_confirmation')))) {
|
||||
const newType = { ...this.setting(type)() };
|
||||
delete newType[host];
|
||||
|
||||
if (Object.keys(newType).length) {
|
||||
this.setting(type)(newType);
|
||||
} else {
|
||||
delete this.settings[type];
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<span className="helpText">{app.translator.trans('flarum-extension-manager.admin.auth_config.no_auth_methods_configured')}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submitButton(): Mithril.Children[] {
|
||||
const items = super.submitButton();
|
||||
|
||||
items.push(
|
||||
<Button
|
||||
className="Button"
|
||||
loading={this.loading}
|
||||
onclick={() =>
|
||||
app.modal.show(AuthMethodModal, {
|
||||
onsubmit: this.onchange.bind(this, null),
|
||||
})
|
||||
}
|
||||
>
|
||||
{app.translator.trans('flarum-extension-manager.admin.auth_config.add_label')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
onchange(oldHost: string | null, type: string, host: string, token: string) {
|
||||
const data = { ...this.setting(type)() };
|
||||
|
||||
if (oldHost) {
|
||||
delete data[oldHost];
|
||||
}
|
||||
|
||||
data[host] = token;
|
||||
|
||||
this.setting(type)(data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import type Mithril from 'mithril';
|
||||
import ConfigureJson, { type IConfigureJson } from './ConfigureJson';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import RepositoryModal from './RepositoryModal';
|
||||
|
||||
export type Repository = {
|
||||
type: 'composer' | 'vcs' | 'path';
|
||||
url: string;
|
||||
};
|
||||
|
||||
export default class ConfigureComposer extends ConfigureJson<IConfigureJson> {
|
||||
protected type = 'composer';
|
||||
|
||||
title(): Mithril.Children {
|
||||
return app.translator.trans('flarum-extension-manager.admin.composer.title');
|
||||
}
|
||||
|
||||
className(): string {
|
||||
return 'ConfigureComposer';
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
return (
|
||||
<div className="Form ExtensionManager-SettingsGroups-content">
|
||||
{this.attrs.buildSettingComponent.call(this, {
|
||||
setting: 'minimum-stability',
|
||||
label: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.label'),
|
||||
help: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.help'),
|
||||
type: 'select',
|
||||
options: {
|
||||
stable: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.stable'),
|
||||
RC: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.rc'),
|
||||
beta: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.beta'),
|
||||
alpha: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.alpha'),
|
||||
dev: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.dev'),
|
||||
},
|
||||
})}
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.composer.repositories.label')}</label>
|
||||
<div className="helpText">{app.translator.trans('flarum-extension-manager.admin.composer.repositories.help')}</div>
|
||||
<div className="ConfigureComposer-repositories">
|
||||
{Object.keys(this.setting('repositories')() || {}).map((name) => {
|
||||
const repository = this.setting('repositories')()[name] as Repository;
|
||||
|
||||
return (
|
||||
<div className="ButtonGroup ButtonGroup--full">
|
||||
<Button
|
||||
className="Button"
|
||||
icon={
|
||||
{
|
||||
composer: 'fas fa-cubes',
|
||||
vcs: 'fas fa-code-branch',
|
||||
path: 'fas fa-folder',
|
||||
}[repository.type]
|
||||
}
|
||||
onclick={() =>
|
||||
app.modal.show(RepositoryModal, {
|
||||
name,
|
||||
repository,
|
||||
onsubmit: (repository: Repository, newName: string) => {
|
||||
const repositories = this.setting('repositories')();
|
||||
delete repositories[name];
|
||||
|
||||
this.setting('repositories')(repositories);
|
||||
|
||||
this.onchange(repository, newName);
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{name} ({repository.type})
|
||||
</Button>
|
||||
<Button
|
||||
className="Button Button--icon"
|
||||
icon="fas fa-trash"
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.composer.delete_repository_label')}
|
||||
onclick={() => {
|
||||
if (confirm(extractText(app.translator.trans('flarum-extension-manager.admin.composer.delete_repository_confirmation')))) {
|
||||
const repositories = { ...this.setting('repositories')() };
|
||||
delete repositories[name];
|
||||
|
||||
this.setting('repositories')(repositories);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submitButton(): Mithril.Children[] {
|
||||
const items = super.submitButton();
|
||||
|
||||
items.push(
|
||||
<Button className="Button" onclick={() => app.modal.show(RepositoryModal, { onsubmit: this.onchange.bind(this) })}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.composer.add_repository_label')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
onchange(repository: Repository, name: string) {
|
||||
this.setting('repositories')({
|
||||
...this.setting('repositories')(),
|
||||
[name]: repository,
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import type Mithril from 'mithril';
|
||||
import Component, { type ComponentAttrs } from 'flarum/common/Component';
|
||||
import { CommonSettingsItemOptions, type SettingsComponentOptions } from '@flarum/core/src/admin/components/AdminPage';
|
||||
import AdminPage from 'flarum/admin/components/AdminPage';
|
||||
import type ItemList from 'flarum/common/utils/ItemList';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
export interface IConfigureJson extends ComponentAttrs {
|
||||
buildSettingComponent: (entry: ((this: this) => Mithril.Children) | SettingsComponentOptions) => Mithril.Children;
|
||||
}
|
||||
|
||||
export default abstract class ConfigureJson<CustomAttrs extends IConfigureJson = IConfigureJson> extends Component<CustomAttrs> {
|
||||
protected settings: Record<string, Stream<any>> = {};
|
||||
protected initialSettings: Record<string, any> | null = null;
|
||||
protected loading: boolean = false;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.submit(true);
|
||||
}
|
||||
|
||||
protected abstract type: string;
|
||||
abstract title(): Mithril.Children;
|
||||
abstract content(): Mithril.Children;
|
||||
|
||||
className(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
view(): Mithril.Children {
|
||||
return (
|
||||
<div className={classList('FormSection', this.className())}>
|
||||
<label>{this.title()}</label>
|
||||
{this.content()}
|
||||
<div className="Form-group Form-controls">{this.submitButton()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submitButton(): Mithril.Children[] {
|
||||
return [
|
||||
<Button className="Button Button--primary" loading={this.loading} onclick={() => this.submit(false)} disabled={!this.isDirty()}>
|
||||
{app.translator.trans('core.admin.settings.submit_button')}
|
||||
</Button>,
|
||||
];
|
||||
}
|
||||
|
||||
customSettingComponents(): ItemList<(attributes: CommonSettingsItemOptions) => Mithril.Children> {
|
||||
return AdminPage.prototype.customSettingComponents();
|
||||
}
|
||||
|
||||
setting(key: string) {
|
||||
return this.settings[key] ?? (this.settings[key] = Stream());
|
||||
}
|
||||
|
||||
submit(readOnly: boolean) {
|
||||
this.loading = true;
|
||||
|
||||
const configuration: any = {};
|
||||
|
||||
Object.keys(this.settings).forEach((key) => {
|
||||
configuration[key] = this.settings[key]();
|
||||
});
|
||||
|
||||
app
|
||||
.request({
|
||||
method: 'POST',
|
||||
url: app.forum.attribute('apiUrl') + '/extension-manager/composer',
|
||||
body: {
|
||||
type: this.type,
|
||||
data: readOnly ? null : configuration,
|
||||
},
|
||||
})
|
||||
.then(({ data }: any) => {
|
||||
Object.keys(data).forEach((key) => {
|
||||
this.settings[key] = Stream(data[key]);
|
||||
});
|
||||
|
||||
this.initialSettings = Array.isArray(data) ? {} : data;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
isDirty() {
|
||||
return JSON.stringify(this.initialSettings) !== JSON.stringify(this.settings);
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ import { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Installer from './Installer';
|
||||
import Updater from './Updater';
|
||||
import Mithril from 'mithril';
|
||||
import Form from 'flarum/common/components/Form';
|
||||
|
||||
export default class ControlSection extends Component<ComponentAttrs> {
|
||||
oninit(vnode: Mithril.Vnode<ComponentAttrs, this>) {
|
||||
@@ -14,22 +15,22 @@ export default class ControlSection extends Component<ComponentAttrs> {
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div className="ExtensionPage-permissions PackageManager-controlSection">
|
||||
<div className="ExtensionPage-permissions ExtensionManager-controlSection">
|
||||
<div className="ExtensionPage-permissions-header">
|
||||
<div className="container">
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-package-manager.admin.sections.control.title')}</h2>
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-extension-manager.admin.sections.control.title')}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container">
|
||||
{app.data['flarum-package-manager.writable_dirs'] ? (
|
||||
<>
|
||||
{app.data['flarum-extension-manager.writable_dirs'] ? (
|
||||
<Form>
|
||||
<Installer />
|
||||
<Updater />
|
||||
</>
|
||||
</Form>
|
||||
) : (
|
||||
<div className="Form-group">
|
||||
<Alert type="warning" dismissible={false}>
|
||||
{app.translator.trans('flarum-package-manager.admin.file_permissions')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.file_permissions')}
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
@@ -10,11 +10,17 @@ import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import { UpdatedPackage } from '../states/ControlSectionState';
|
||||
import WhyNotModal from './WhyNotModal';
|
||||
import Label from './Label';
|
||||
import Dropdown from 'flarum/common/components/Dropdown';
|
||||
|
||||
export interface ExtensionItemAttrs extends ComponentAttrs {
|
||||
extension: Extension;
|
||||
updates: UpdatedPackage;
|
||||
onClickUpdate: CallableFunction;
|
||||
onClickUpdate:
|
||||
| CallableFunction
|
||||
| {
|
||||
soft: CallableFunction;
|
||||
hard: CallableFunction;
|
||||
};
|
||||
whyNotWarning?: boolean;
|
||||
isCore?: boolean;
|
||||
updatable?: boolean;
|
||||
@@ -29,43 +35,56 @@ export default class ExtensionItem<Attrs extends ExtensionItemAttrs = ExtensionI
|
||||
return (
|
||||
<div
|
||||
className={classList({
|
||||
'PackageManager-extension': true,
|
||||
'PackageManager-extension--core': isCore,
|
||||
'PackageManager-extension--danger': isDanger,
|
||||
'ExtensionManager-extension': true,
|
||||
'ExtensionManager-extension--core': isCore,
|
||||
'ExtensionManager-extension--danger': isDanger,
|
||||
})}
|
||||
>
|
||||
<div className="PackageManager-extension-icon ExtensionIcon" style={extension.icon}>
|
||||
<div className="ExtensionManager-extension-icon ExtensionIcon" style={extension.icon}>
|
||||
{extension.icon ? <Icon name={extension.icon.name} /> : ''}
|
||||
</div>
|
||||
<div className="PackageManager-extension-info">
|
||||
<div className="PackageManager-extension-name">{extension.extra['flarum-extension'].title}</div>
|
||||
<div className="PackageManager-extension-version">
|
||||
<span className="PackageManager-extension-version-current">{this.version(updates['version'])}</span>
|
||||
<div className="ExtensionManager-extension-info">
|
||||
<div className="ExtensionManager-extension-name">{extension.extra['flarum-extension'].title}</div>
|
||||
<div className="ExtensionManager-extension-version">
|
||||
<span className="ExtensionManager-extension-version-current">{this.version(updates['version'])}</span>
|
||||
{latestVersion ? (
|
||||
<Label className="PackageManager-extension-version-latest" type={updates['latest-minor'] ? 'success' : 'warning'}>
|
||||
<Label className="ExtensionManager-extension-version-latest" type={updates['latest-minor'] ? 'success' : 'warning'}>
|
||||
{this.version(latestVersion)}
|
||||
</Label>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="PackageManager-extension-controls">
|
||||
{onClickUpdate ? (
|
||||
<Tooltip text={app.translator.trans('flarum-package-manager.admin.extensions.update')}>
|
||||
<div className="ExtensionManager-extension-controls">
|
||||
{onClickUpdate && typeof onClickUpdate === 'function' ? (
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.extensions.update')}>
|
||||
<Button
|
||||
icon="fas fa-arrow-alt-circle-up"
|
||||
className="Button Button--icon Button--flat"
|
||||
onclick={onClickUpdate}
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.extensions.update')}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.extensions.update')}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : onClickUpdate ? (
|
||||
<Dropdown
|
||||
buttonClassName="Button Button--icon Button--flat"
|
||||
icon="fas fa-arrow-alt-circle-up"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.extensions.update')}
|
||||
>
|
||||
<Button icon="fas fa-arrow-alt-circle-up" className="Button" onclick={onClickUpdate.soft}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.update_soft_label')}
|
||||
</Button>
|
||||
<Button icon="fas fa-arrow-alt-circle-up" className="Button" onclick={onClickUpdate.hard} disabled={!updates['direct-dependency']}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.update_hard_label')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
{whyNotWarning ? (
|
||||
<Tooltip text={app.translator.trans('flarum-package-manager.admin.extensions.check_why_it_failed_updating')}>
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.extensions.check_why_it_failed_updating')}>
|
||||
<Button
|
||||
icon="fas fa-exclamation-circle"
|
||||
className="Button Button--icon Button--flat Button--danger"
|
||||
onclick={() => app.modal.show(WhyNotModal, { package: extension.name })}
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.extensions.check_why_it_failed_updating')}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.extensions.check_why_it_failed_updating')}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
@@ -75,6 +94,6 @@ export default class ExtensionItem<Attrs extends ExtensionItemAttrs = ExtensionI
|
||||
}
|
||||
|
||||
version(v: string): string {
|
||||
return 'v' + v.replace('v', '');
|
||||
return v.charAt(0) === 'v' ? v.substring(1) : v;
|
||||
}
|
||||
}
|
||||
|
@@ -3,11 +3,6 @@ import app from 'flarum/admin/app';
|
||||
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
|
||||
import errorHandler from '../utils/errorHandler';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import { AsyncBackendResponse } from '../shims';
|
||||
|
||||
export interface InstallerAttrs extends ComponentAttrs {}
|
||||
|
||||
@@ -24,23 +19,25 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
|
||||
view(): Mithril.Children {
|
||||
return (
|
||||
<div className="Form-group PackageManager-installer">
|
||||
<label htmlFor="install-extension">{app.translator.trans('flarum-package-manager.admin.extensions.install')}</label>
|
||||
<p className="helpText">
|
||||
{app.translator.trans('flarum-package-manager.admin.extensions.install_help', {
|
||||
<div className="Form-group ExtensionManager-installer">
|
||||
<label htmlFor="install-extension">{app.translator.trans('flarum-extension-manager.admin.extensions.install')}</label>
|
||||
<div className="helpText">
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.install_help', {
|
||||
extiverse: <a href="https://extiverse.com">extiverse.com</a>,
|
||||
semantic_link: <a href="https://devhints.io/semver" />,
|
||||
code: <code />,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="FormControl-container">
|
||||
<input className="FormControl" id="install-extension" placeholder="vendor/package-name" bidi={this.packageName} />
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-download"
|
||||
onclick={this.onsubmit.bind(this)}
|
||||
loading={app.packageManager.control.isLoading('extension-install')}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('extension-install')}
|
||||
loading={app.extensionManager.control.isLoading('extension-install')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.extensions.proceed')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.proceed')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,35 +51,6 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
}
|
||||
|
||||
onsubmit(): void {
|
||||
app.packageManager.control.setLoading('extension-install');
|
||||
app.modal.show(LoadingModal);
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse & { id: number }>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions`,
|
||||
body: {
|
||||
data: this.data(),
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
const extensionId = response.id;
|
||||
app.alerts.show(
|
||||
{ type: 'success' },
|
||||
app.translator.trans('flarum-package-manager.admin.extensions.successful_install', { extension: extensionId })
|
||||
);
|
||||
window.location.href = `${app.forum.attribute('adminUrl')}#/extension/${extensionId}`;
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
app.packageManager.control.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
app.extensionManager.control.requirePackage(this.data());
|
||||
}
|
||||
}
|
||||
|
@@ -3,16 +3,12 @@ import app from 'flarum/admin/app';
|
||||
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import Tooltip from 'flarum/common/components/Tooltip';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import RequestError from 'flarum/common/utils/RequestError';
|
||||
|
||||
import { UpdatedPackage, UpdateState } from '../states/ControlSectionState';
|
||||
import errorHandler from '../utils/errorHandler';
|
||||
import WhyNotModal from './WhyNotModal';
|
||||
import ExtensionItem from './ExtensionItem';
|
||||
import { AsyncBackendResponse } from '../shims';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
export interface MajorUpdaterAttrs extends ComponentAttrs {
|
||||
coreUpdate: UpdatedPackage;
|
||||
@@ -33,32 +29,39 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
view(): Mithril.Children {
|
||||
// @todo move Form-group--danger class to core for reuse
|
||||
return (
|
||||
<div className="Form-group Form-group--danger PackageManager-majorUpdate">
|
||||
<img alt="flarum logo" src={app.forum.attribute('baseUrl') + '/assets/extensions/flarum-package-manager/flarum.svg'} />
|
||||
<label>{app.translator.trans('flarum-package-manager.admin.major_updater.title', { version: this.attrs.coreUpdate['latest-major'] })}</label>
|
||||
<p className="helpText">{app.translator.trans('flarum-package-manager.admin.major_updater.description')}</p>
|
||||
<div className="PackageManager-updaterControls">
|
||||
<Tooltip text={app.translator.trans('flarum-package-manager.admin.major_updater.dry_run_help')}>
|
||||
<div
|
||||
className={classList('Form-group Form-group--danger ExtensionManager-majorUpdate', {
|
||||
'ExtensionManager-majorUpdate--failed': this.updateState.status === 'failure',
|
||||
'ExtensionManager-majorUpdate--incompatibleExtensions': this.updateState.incompatibleExtensions.length,
|
||||
})}
|
||||
>
|
||||
<img alt="flarum logo" src={app.forum.attribute('baseUrl') + '/assets/extensions/flarum-extension-manager/flarum.svg'} />
|
||||
<label>
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.title', { version: this.attrs.coreUpdate['latest-major'] })}
|
||||
</label>
|
||||
<p className="helpText">{app.translator.trans('flarum-extension-manager.admin.major_updater.description')}</p>
|
||||
<div className="ExtensionManager-updaterControls">
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.major_updater.dry_run_help')}>
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-vial"
|
||||
onclick={this.update.bind(this, true)}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('major-update-dry-run')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.dry_run')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.dry_run')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
className="Button Button--danger"
|
||||
icon="fas fa-play"
|
||||
onclick={this.update.bind(this, false)}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('major-update')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.update')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.update')}
|
||||
</Button>
|
||||
</div>
|
||||
{this.updateState.incompatibleExtensions.length ? (
|
||||
<div className="PackageManager-majorUpdate-incompatibleExtensions PackageManager-extensions-grid">
|
||||
<div className="ExtensionManager-majorUpdate-incompatibleExtensions ExtensionManager-extensions-grid">
|
||||
{this.updateState.incompatibleExtensions.map((extension: string) => (
|
||||
<ExtensionItem
|
||||
extension={app.data.extensions[extension.replace('flarum-', '').replace('flarum-ext-', '').replace('/', '-')]}
|
||||
@@ -72,20 +75,20 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
{this.updateState.status === 'failure' ? (
|
||||
<Alert
|
||||
type="error"
|
||||
className="PackageManager-majorUpdate-failure"
|
||||
className="ExtensionManager-majorUpdate-failure"
|
||||
dismissible={false}
|
||||
controls={[
|
||||
<Button
|
||||
className="Button Button--text PackageManager-majorUpdate-failure-details"
|
||||
className="Button Button--text ExtensionManager-majorUpdate-failure-details"
|
||||
icon="fas fa-question-circle"
|
||||
onclick={() => app.modal.show(WhyNotModal, { package: 'flarum/core' })}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.failure.why')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.failure.why')}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<p className="PackageManager-majorUpdate-failure-desc">
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.failure.desc')}
|
||||
<p className="ExtensionManager-majorUpdate-failure-desc">
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.failure.desc')}
|
||||
</p>
|
||||
</Alert>
|
||||
) : null}
|
||||
@@ -94,34 +97,6 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
}
|
||||
|
||||
update(dryRun: boolean) {
|
||||
app.packageManager.control.setLoading(dryRun ? 'major-update-dry-run' : 'major-update');
|
||||
app.modal.show(LoadingModal);
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/major-update`,
|
||||
body: {
|
||||
data: { dryRun },
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.catch((e: RequestError) => {
|
||||
app.modal.close();
|
||||
this.updateState.status = 'failure';
|
||||
this.updateState.incompatibleExtensions = e.response?.errors?.pop()?.incompatible_extensions as string[];
|
||||
})
|
||||
.finally(() => {
|
||||
app.packageManager.control.setLoading(null);
|
||||
m.redraw();
|
||||
});
|
||||
app.extensionManager.control.majorUpdate({ dryRun });
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ export default class Pagination extends Component<PaginationAttrs> {
|
||||
return (
|
||||
<nav className="Pagination UserListPage-gridPagination">
|
||||
<Button
|
||||
disabled={!this.attrs.list.hasPrev()}
|
||||
disabled={!this.attrs.list.hasPrev() || app.extensionManager.control.isLoading()}
|
||||
title={app.translator.trans('core.admin.users.pagination.back_button')}
|
||||
onclick={() => this.attrs.list.prev()}
|
||||
icon="fas fa-chevron-left"
|
||||
@@ -28,7 +28,7 @@ export default class Pagination extends Component<PaginationAttrs> {
|
||||
})}
|
||||
</span>
|
||||
<Button
|
||||
disabled={!this.attrs.list.hasNext()}
|
||||
disabled={!this.attrs.list.hasNext() || app.extensionManager.control.isLoading()}
|
||||
title={app.translator.trans('core.admin.users.pagination.next_button')}
|
||||
onclick={() => this.attrs.list.next()}
|
||||
icon="fas fa-chevron-right"
|
||||
|
@@ -8,6 +8,7 @@ import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import Icon from 'flarum/common/components/Icon';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import Link from 'flarum/common/components/Link';
|
||||
|
||||
import Label from './Label';
|
||||
import TaskOutputModal from './TaskOutputModal';
|
||||
@@ -24,20 +25,21 @@ export default class QueueSection extends Component<{}> {
|
||||
oninit(vnode: Mithril.Vnode<{}, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
app.packageManager.queue.load();
|
||||
app.extensionManager.queue.load();
|
||||
}
|
||||
|
||||
view() {
|
||||
return (
|
||||
<section id="PackageManager-queueSection" className="ExtensionPage-permissions PackageManager-queueSection">
|
||||
<div className="ExtensionPage-permissions-header PackageManager-queueSection-header">
|
||||
<section id="ExtensionManager-queueSection" className="ExtensionPage-permissions ExtensionManager-queueSection">
|
||||
<div className="ExtensionPage-permissions-header ExtensionManager-queueSection-header">
|
||||
<div className="container">
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-package-manager.admin.sections.queue.title')}</h2>
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-extension-manager.admin.sections.queue.title')}</h2>
|
||||
<Button
|
||||
className="Button Button--icon"
|
||||
icon="fas fa-sync-alt"
|
||||
onclick={() => app.packageManager.queue.load()}
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.sections.queue.refresh')}
|
||||
onclick={() => app.extensionManager.queue.load()}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.sections.queue.refresh')}
|
||||
disabled={app.extensionManager.control.isLoading()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,12 +54,12 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'operation',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.operation')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.operation')),
|
||||
content: (task) => (
|
||||
<div className="PackageManager-queueTable-operation">
|
||||
<span className="PackageManager-queueTable-operation-icon">{this.operationIcon(task.operation())}</span>
|
||||
<span className="PackageManager-queueTable-operation-name">
|
||||
{app.translator.trans(`flarum-package-manager.admin.sections.queue.operations.${task.operation()}`)}
|
||||
<div className="ExtensionManager-queueTable-operation">
|
||||
<span className="ExtensionManager-queueTable-operation-icon">{this.operationIcon(task.operation())}</span>
|
||||
<span className="ExtensionManager-queueTable-operation-name">
|
||||
{app.translator.trans(`flarum-extension-manager.admin.sections.queue.operations.${task.operation()}`)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
@@ -68,20 +70,20 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'package',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.package')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.package')),
|
||||
content: (task) => {
|
||||
const extension: Extension | null = app.data.extensions[task.package()?.replace(/(\/flarum-|\/flarum-ext-|\/)/g, '-')];
|
||||
|
||||
return extension ? (
|
||||
<div className="PackageManager-queueTable-package">
|
||||
<div className="PackageManager-queueTable-package-icon ExtensionIcon" style={extension.icon}>
|
||||
<Link className="ExtensionManager-queueTable-package" href={app.route('extension', { id: extension.id })}>
|
||||
<div className="ExtensionManager-queueTable-package-icon ExtensionIcon" style={extension.icon}>
|
||||
{!!extension.icon && <Icon name={extension.icon.name} />}
|
||||
</div>
|
||||
<div className="PackageManager-queueTable-package-details">
|
||||
<span className="PackageManager-queueTable-package-title">{extension.extra['flarum-extension'].title}</span>
|
||||
<span className="PackageManager-queueTable-package-name">{task.package()}</span>
|
||||
<div className="ExtensionManager-queueTable-package-details">
|
||||
<span className="ExtensionManager-queueTable-package-title">{extension.extra['flarum-extension'].title}</span>
|
||||
<span className="ExtensionManager-queueTable-package-name">{task.package()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
task.package()
|
||||
);
|
||||
@@ -93,14 +95,17 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'status',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.status')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.status')),
|
||||
content: (task) => (
|
||||
<Label
|
||||
className="PackageManager-queueTable-status"
|
||||
type={{ running: 'neutral', failure: 'error', pending: 'warning', success: 'success' }[task.status()]}
|
||||
>
|
||||
{app.translator.trans(`flarum-package-manager.admin.sections.queue.statuses.${task.status()}`)}
|
||||
</Label>
|
||||
<>
|
||||
<Label
|
||||
className="ExtensionManager-queueTable-status"
|
||||
type={{ running: 'neutral', failure: 'error', pending: 'warning', success: 'success' }[task.status()]}
|
||||
>
|
||||
{app.translator.trans(`flarum-extension-manager.admin.sections.queue.statuses.${task.status()}`)}
|
||||
</Label>
|
||||
{['pending', 'running'].includes(task.status()) && <LoadingIndicator size="small" display="inline" />}
|
||||
</>
|
||||
),
|
||||
},
|
||||
70
|
||||
@@ -109,10 +114,10 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'elapsedTime',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.elapsed_time')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.elapsed_time')),
|
||||
content: (task) =>
|
||||
!task.startedAt() ? (
|
||||
app.translator.trans('flarum-package-manager.admin.sections.queue.task_just_started')
|
||||
!task.startedAt() || !task.finishedAt() ? (
|
||||
app.translator.trans('flarum-extension-manager.admin.sections.queue.task_just_started')
|
||||
) : (
|
||||
<Tooltip text={`${dayjs(task.startedAt()).format('LL LTS')} ${dayjs(task.finishedAt()).format('LL LTS')}`}>
|
||||
<span>{humanDuration(task.startedAt(), task.finishedAt())}</span>
|
||||
@@ -125,7 +130,7 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'memoryUsed',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.peak_memory_used')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.peak_memory_used')),
|
||||
content: (task) => <span>{task.peakMemoryUsed()}</span>,
|
||||
},
|
||||
60
|
||||
@@ -134,15 +139,16 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'details',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.details')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.details')),
|
||||
content: (task) => (
|
||||
<Button
|
||||
className="Button Button--icon Table-controls-item"
|
||||
icon="fas fa-file-alt"
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.sections.queue.columns.details')}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.details')}
|
||||
// @todo fix in core
|
||||
// @ts-ignore
|
||||
onclick={() => app.modal.show(TaskOutputModal, { task })}
|
||||
disabled={['pending', 'running'].includes(task.status())}
|
||||
/>
|
||||
),
|
||||
className: 'Table-controls',
|
||||
@@ -154,21 +160,21 @@ export default class QueueSection extends Component<{}> {
|
||||
}
|
||||
|
||||
queueTable() {
|
||||
const tasks = app.packageManager.queue.getItems();
|
||||
const tasks = app.extensionManager.queue.getItems();
|
||||
|
||||
if (!tasks) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
if (tasks && !tasks.length) {
|
||||
return <h3 className="ExtensionPage-subHeader">{app.translator.trans('flarum-package-manager.admin.sections.queue.none')}</h3>;
|
||||
return <h3 className="ExtensionPage-subHeader">{app.translator.trans('flarum-extension-manager.admin.sections.queue.none')}</h3>;
|
||||
}
|
||||
|
||||
const columns = this.columns();
|
||||
|
||||
return (
|
||||
<>
|
||||
<table className="Table PackageManager-queueTable">
|
||||
<table className="Table ExtensionManager-queueTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.toArray().map((item, index) => (
|
||||
@@ -193,23 +199,27 @@ export default class QueueSection extends Component<{}> {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pagination list={app.packageManager.queue} />
|
||||
<Pagination list={app.extensionManager.queue} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
operationIcon(operation: TaskOperations): Mithril.Children {
|
||||
const iconName = {
|
||||
update_check: 'fas fa-sync-alt',
|
||||
update_major: 'fas fa-play',
|
||||
update_minor: 'fas fa-play',
|
||||
update_global: 'fas fa-play',
|
||||
extension_install: 'fas fa-download',
|
||||
extension_remove: 'fas fa-times',
|
||||
extension_update: 'fas fa-arrow-alt-circle-up',
|
||||
why_not: 'fas fa-exclamation-circle',
|
||||
}[operation];
|
||||
|
||||
return <Icon name={iconName} />;
|
||||
return (
|
||||
<Icon
|
||||
name={
|
||||
{
|
||||
update_check: 'fas fa-sync-alt',
|
||||
update_major: 'fas fa-play',
|
||||
update_minor: 'fas fa-play',
|
||||
update_global: 'fas fa-play',
|
||||
extension_install: 'fas fa-download',
|
||||
extension_remove: 'fas fa-times',
|
||||
extension_update: 'fas fa-arrow-alt-circle-up',
|
||||
why_not: 'fas fa-exclamation-circle',
|
||||
}[operation]
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,77 @@
|
||||
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||
import Mithril from 'mithril';
|
||||
import app from 'flarum/admin/app';
|
||||
import Select from 'flarum/common/components/Select';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import { type Repository } from './ConfigureComposer';
|
||||
|
||||
export interface IRepositoryModalAttrs extends IInternalModalAttrs {
|
||||
onsubmit: (repository: Repository, key: string) => void;
|
||||
name?: string;
|
||||
repository?: Repository;
|
||||
}
|
||||
|
||||
export default class RepositoryModal<CustomAttrs extends IRepositoryModalAttrs = IRepositoryModalAttrs> extends Modal<CustomAttrs> {
|
||||
protected name!: Stream<string>;
|
||||
protected repository!: Stream<Repository>;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.name = Stream(this.attrs.name || '');
|
||||
this.repository = Stream(this.attrs.repository || { type: 'composer', url: '' });
|
||||
}
|
||||
|
||||
className(): string {
|
||||
return 'RepositoryModal Modal--small';
|
||||
}
|
||||
|
||||
title(): Mithril.Children {
|
||||
const context = this.attrs.repository ? 'edit' : 'add';
|
||||
return app.translator.trans(`flarum-extension-manager.admin.composer.${context}_repository_label`);
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
const types = {
|
||||
composer: app.translator.trans('flarum-extension-manager.admin.composer.repositories.types.composer'),
|
||||
vcs: app.translator.trans('flarum-extension-manager.admin.composer.repositories.types.vcs'),
|
||||
path: app.translator.trans('flarum-extension-manager.admin.composer.repositories.types.path'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.composer.repositories.add_modal.name_label')}</label>
|
||||
<input className="FormControl" bidi={this.name} />
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.composer.repositories.add_modal.type_label')}</label>
|
||||
<Select
|
||||
options={types}
|
||||
value={this.repository().type}
|
||||
onchange={(value: 'composer' | 'vcs' | 'path') => this.repository({ ...this.repository(), type: value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.composer.repositories.add_modal.url')}</label>
|
||||
<input
|
||||
className="FormControl"
|
||||
onchange={(e: Event) => this.repository({ ...this.repository(), url: (e.target as HTMLInputElement).value })}
|
||||
value={this.repository().url}
|
||||
/>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary" onclick={this.submit.bind(this)}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.composer.repositories.add_modal.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.attrs.onsubmit(this.repository(), this.name());
|
||||
this.hide();
|
||||
}
|
||||
}
|
@@ -5,8 +5,45 @@ import ItemList from 'flarum/common/utils/ItemList';
|
||||
|
||||
import QueueSection from './QueueSection';
|
||||
import ControlSection from './ControlSection';
|
||||
import ConfigureComposer from './ConfigureComposer';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import listItems from 'flarum/common/helpers/listItems';
|
||||
import ConfigureAuth from './ConfigureAuth';
|
||||
|
||||
export default class SettingsPage extends ExtensionPage {
|
||||
content() {
|
||||
const settings = app.extensionData.getSettings(this.extension.id);
|
||||
|
||||
const warnings = [app.translator.trans('flarum-extension-manager.admin.settings.access_warning')];
|
||||
|
||||
if (app.data.debugEnabled) warnings.push(app.translator.trans('flarum-extension-manager.admin.settings.debug_mode_warning'));
|
||||
|
||||
return (
|
||||
<div className="ExtensionPage-settings">
|
||||
<div className="container">
|
||||
<div className="ExtensionManager-warnings Form-group">
|
||||
<Alert className="ExtensionManager-primaryWarning" type="warning" dismissible={false}>
|
||||
<ul>{listItems(warnings)}</ul>
|
||||
</Alert>
|
||||
</div>
|
||||
{settings ? (
|
||||
<div className="FormSectionGroup ExtensionManager-SettingsGroups">
|
||||
<div className="FormSection">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.settings.title')}</label>
|
||||
<div className="Form">{settings.map(this.buildSettingComponent.bind(this))}</div>
|
||||
<div className="Form-group Form--controls">{this.submitButton()}</div>
|
||||
</div>
|
||||
<ConfigureComposer buildSettingComponent={this.buildSettingComponent} />
|
||||
<ConfigureAuth buildSettingComponent={this.buildSettingComponent} />
|
||||
</div>
|
||||
) : (
|
||||
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h3>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
sections(vnode: Mithril.VnodeDOM<ExtensionPageAttrs, this>): ItemList<unknown> {
|
||||
const items = super.sections(vnode);
|
||||
|
||||
@@ -14,12 +51,17 @@ export default class SettingsPage extends ExtensionPage {
|
||||
|
||||
items.add('control', <ControlSection />, 8);
|
||||
|
||||
if (parseInt(app.data.settings['flarum-package-manager.queue_jobs'])) {
|
||||
if (app.data.settings['flarum-extension-manager.queue_jobs'] !== '0' && app.data.settings['flarum-extension-manager.queue_jobs']) {
|
||||
items.add('queue', <QueueSection />, 5);
|
||||
}
|
||||
|
||||
items.setPriority('permissions', 0);
|
||||
items.remove('permissions');
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
onsaved() {
|
||||
super.onsaved();
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
|
@@ -12,21 +12,33 @@ export default class TaskOutputModal<CustomAttrs extends TaskOutputModalAttrs =
|
||||
}
|
||||
|
||||
title() {
|
||||
return app.translator.trans(`flarum-package-manager.admin.sections.queue.operations.${this.attrs.task.operation()}`);
|
||||
return app.translator.trans(`flarum-extension-manager.admin.sections.queue.operations.${this.attrs.task.operation()}`);
|
||||
}
|
||||
|
||||
content() {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="TaskOutputModal-data">
|
||||
{this.attrs.task.status() === 'failure' && (
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.sections.queue.output_modal.guessed_cause')}</label>
|
||||
<div className="FormControl TaskOutputModal-data-guessed-cause">
|
||||
{(this.attrs.task.guessedCause() &&
|
||||
app.translator.trans('flarum-extension-manager.admin.exceptions.guessed_cause.' + this.attrs.task.guessedCause())) ||
|
||||
app.translator.trans('flarum-extension-manager.admin.sections.queue.output_modal.cause_unknown')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-package-manager.admin.sections.queue.output_modal.command')}</label>
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.sections.queue.output_modal.command')}</label>
|
||||
<div className="FormControl TaskOutputModal-data-command">
|
||||
<code>$ composer {this.attrs.task.command()}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-package-manager.admin.sections.queue.output_modal.output')}</label>
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.sections.queue.output_modal.output')}</label>
|
||||
<div className="FormControl TaskOutputModal-data-output">
|
||||
<code>
|
||||
<pre>{this.attrs.task.output()}</pre>
|
||||
|
@@ -6,7 +6,6 @@ import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||
import MajorUpdater from './MajorUpdater';
|
||||
import ExtensionItem from './ExtensionItem';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
|
||||
export interface IUpdaterAttrs extends ComponentAttrs {}
|
||||
@@ -15,30 +14,30 @@ export type UpdaterLoadingTypes = 'check' | 'minor-update' | 'global-update' | '
|
||||
|
||||
export default class Updater extends Component<IUpdaterAttrs> {
|
||||
view() {
|
||||
const core = app.packageManager.control.coreUpdate;
|
||||
const core = app.extensionManager.control.coreUpdate;
|
||||
|
||||
return [
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-package-manager.admin.updater.updater_title')}</label>
|
||||
<p className="helpText">{app.translator.trans('flarum-package-manager.admin.updater.updater_help')}</p>
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.updater.updater_title')}</label>
|
||||
<div className="helpText">{app.translator.trans('flarum-extension-manager.admin.updater.updater_help')}</div>
|
||||
{this.lastUpdateCheckView()}
|
||||
<div className="PackageManager-updaterControls">{this.controlItems().toArray()}</div>
|
||||
<div className="ExtensionManager-updaterControls">{this.controlItems().toArray()}</div>
|
||||
{this.availableUpdatesView()}
|
||||
</div>,
|
||||
core && core.package['latest-major'] ? (
|
||||
<MajorUpdater coreUpdate={core.package} updateState={app.packageManager.control.lastUpdateRun.major} />
|
||||
<MajorUpdater coreUpdate={core.package} updateState={app.extensionManager.control.lastUpdateRun.major} />
|
||||
) : null,
|
||||
];
|
||||
}
|
||||
|
||||
lastUpdateCheckView() {
|
||||
return (
|
||||
(app.packageManager.control.lastUpdateCheck?.checkedAt && (
|
||||
<p className="PackageManager-lastUpdatedAt">
|
||||
<span className="PackageManager-lastUpdatedAt-label">
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.last_update_checked_at')}
|
||||
(app.extensionManager.control.lastUpdateCheck?.checkedAt && (
|
||||
<p className="ExtensionManager-lastUpdatedAt">
|
||||
<span className="ExtensionManager-lastUpdatedAt-label">
|
||||
{app.translator.trans('flarum-extension-manager.admin.updater.last_update_checked_at')}
|
||||
</span>
|
||||
<span className="PackageManager-lastUpdatedAt-value">{humanTime(app.packageManager.control.lastUpdateCheck.checkedAt)}</span>
|
||||
<span className="ExtensionManager-lastUpdatedAt-value">{humanTime(app.extensionManager.control.lastUpdateCheck.checkedAt)}</span>
|
||||
</p>
|
||||
)) ||
|
||||
null
|
||||
@@ -46,33 +45,33 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
}
|
||||
|
||||
availableUpdatesView() {
|
||||
const state = app.packageManager.control;
|
||||
const state = app.extensionManager.control;
|
||||
|
||||
if (app.packageManager.control.isLoading()) {
|
||||
if (app.extensionManager.control.isLoading('check') || app.extensionManager.control.isLoading('global-update')) {
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<div className="ExtensionManager-extensions">
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!(state.extensionUpdates.length || state.coreUpdate)) {
|
||||
const hasMinorCoreUpdate = state.coreUpdate && state.coreUpdate.package['latest-minor'];
|
||||
|
||||
if (!(state.extensionUpdates.length || hasMinorCoreUpdate)) {
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<Alert type="success" dismissible={false}>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.up_to_date')}
|
||||
</Alert>
|
||||
<div className="ExtensionManager-extensions">
|
||||
<span className="helpText">{app.translator.trans('flarum-extension-manager.admin.updater.up_to_date')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<div className="PackageManager-extensions-grid">
|
||||
{state.coreUpdate ? (
|
||||
<div className="ExtensionManager-extensions">
|
||||
<div className="ExtensionManager-extensions-grid">
|
||||
{hasMinorCoreUpdate ? (
|
||||
<ExtensionItem
|
||||
extension={state.coreUpdate.extension}
|
||||
updates={state.coreUpdate.package}
|
||||
extension={state.coreUpdate!.extension}
|
||||
updates={state.coreUpdate!.package}
|
||||
isCore={true}
|
||||
onClickUpdate={() => state.updateCoreMinor()}
|
||||
whyNotWarning={state.lastUpdateRun.limitedPackages().includes('flarum/core')}
|
||||
@@ -82,7 +81,10 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
<ExtensionItem
|
||||
extension={extension}
|
||||
updates={state.packageUpdates[extension.id]}
|
||||
onClickUpdate={() => state.updateExtension(extension)}
|
||||
onClickUpdate={{
|
||||
soft: () => state.updateExtension(extension, 'soft'),
|
||||
hard: () => state.updateExtension(extension, 'hard'),
|
||||
}}
|
||||
whyNotWarning={state.lastUpdateRun.limitedPackages().includes(extension.name)}
|
||||
/>
|
||||
))}
|
||||
@@ -99,11 +101,11 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-sync-alt"
|
||||
onclick={() => app.packageManager.control.checkForUpdates()}
|
||||
loading={app.packageManager.control.isLoading('check')}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('check')}
|
||||
onclick={() => app.extensionManager.control.checkForUpdates()}
|
||||
loading={app.extensionManager.control.isLoading('check')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.check_for_updates')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.updater.check_for_updates')}
|
||||
</Button>,
|
||||
100
|
||||
);
|
||||
@@ -113,11 +115,11 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-play"
|
||||
onclick={() => app.packageManager.control.updateGlobally()}
|
||||
loading={app.packageManager.control.isLoading('global-update')}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('global-update')}
|
||||
onclick={() => app.extensionManager.control.updateGlobally()}
|
||||
loading={app.extensionManager.control.isLoading('global-update')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.run_global_update')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.updater.run_global_update')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
|
@@ -24,7 +24,7 @@ export default class WhyNotModal<CustomAttrs extends WhyNotModalAttrs = WhyNotMo
|
||||
}
|
||||
|
||||
title() {
|
||||
return app.translator.trans('flarum-package-manager.admin.why_not_modal.title');
|
||||
return app.translator.trans('flarum-extension-manager.admin.why_not_modal.title');
|
||||
}
|
||||
|
||||
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
|
||||
@@ -41,7 +41,7 @@ export default class WhyNotModal<CustomAttrs extends WhyNotModalAttrs = WhyNotMo
|
||||
app
|
||||
.request<WhyNotResponse>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/why-not`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/why-not`,
|
||||
body: {
|
||||
data: {
|
||||
package: this.attrs.package,
|
||||
|
@@ -4,35 +4,30 @@ import ExtensionPage from 'flarum/admin/components/ExtensionPage';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
import isExtensionEnabled from 'flarum/admin/utils/isExtensionEnabled';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
import Task from './models/Task';
|
||||
import jumpToQueue from './utils/jumpToQueue';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import { AsyncBackendResponse } from './shims';
|
||||
import PackageManagerState from './states/PackageManagerState';
|
||||
import ExtensionManagerState from './states/ExtensionManagerState';
|
||||
|
||||
app.initializers.add('flarum-package-manager', (app) => {
|
||||
app.store.models['package-manager-tasks'] = Task;
|
||||
app.initializers.add('flarum-extension-manager', (app) => {
|
||||
app.store.models['extension-manager-tasks'] = Task;
|
||||
|
||||
app.packageManager = new PackageManagerState();
|
||||
app.extensionManager = new ExtensionManagerState();
|
||||
|
||||
if (app.data['flarum-extension-manager.using_sync_queue']) {
|
||||
app.data.settings['flarum-extension-manager.queue_jobs'] = '0';
|
||||
}
|
||||
|
||||
app.extensionData
|
||||
.for('flarum-package-manager')
|
||||
.registerSetting(() => (
|
||||
<div className="Form-group">
|
||||
<Alert type="warning" dismissible={false}>
|
||||
{app.translator.trans('flarum-package-manager.admin.settings.access_warning')}
|
||||
</Alert>
|
||||
</div>
|
||||
))
|
||||
.for('flarum-extension-manager')
|
||||
.registerSetting({
|
||||
setting: 'flarum-package-manager.queue_jobs',
|
||||
label: app.translator.trans('flarum-package-manager.admin.settings.queue_jobs'),
|
||||
setting: 'flarum-extension-manager.queue_jobs',
|
||||
label: app.translator.trans('flarum-extension-manager.admin.settings.queue_jobs'),
|
||||
help: m.trust(
|
||||
extractText(
|
||||
app.translator.trans('flarum-package-manager.admin.settings.queue_jobs_help', {
|
||||
app.translator.trans('flarum-extension-manager.admin.settings.queue_jobs_help', {
|
||||
basic_impl_link: 'https://discuss.flarum.org/d/28151-database-queue-the-simplest-queue-even-for-shared-hosting',
|
||||
adv_impl_link: 'https://discuss.flarum.org/d/21873-redis-sessions-cache-queues',
|
||||
php_version: `<strong>${app.data.phpVersion}</strong>`,
|
||||
@@ -40,14 +35,19 @@ app.initializers.add('flarum-package-manager', (app) => {
|
||||
})
|
||||
)
|
||||
),
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
disabled: app.data['flarum-package-manager.using_sync_queue'],
|
||||
disabled: app.data['flarum-extension-manager.using_sync_queue'],
|
||||
})
|
||||
.registerSetting({
|
||||
setting: 'flarum-extension-manager.task_retention_days',
|
||||
label: app.translator.trans('flarum-extension-manager.admin.settings.task_retention_days'),
|
||||
help: app.translator.trans('flarum-extension-manager.admin.settings.task_retention_days_help'),
|
||||
type: 'number',
|
||||
})
|
||||
.registerPage(SettingsPage);
|
||||
|
||||
extend(ExtensionPage.prototype, 'topItems', function (items) {
|
||||
if (this.extension.id === 'flarum-package-manager' || isExtensionEnabled(this.extension.id)) {
|
||||
if (this.extension.id === 'flarum-extension-manager' || isExtensionEnabled(this.extension.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,14 +61,14 @@ app.initializers.add('flarum-package-manager', (app) => {
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions/${this.extension.id}`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/extensions/${this.extension.id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.extensions.successful_remove'));
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-extension-manager.admin.extensions.successful_remove'));
|
||||
window.location = app.forum.attribute('adminUrl');
|
||||
}
|
||||
})
|
||||
@@ -77,7 +77,7 @@ app.initializers.add('flarum-package-manager', (app) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.remove')}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
@@ -32,6 +32,10 @@ export default class Task extends Model {
|
||||
return Model.attribute<string>('output').call(this);
|
||||
}
|
||||
|
||||
guessedCause() {
|
||||
return Model.attribute<string>('guessedCause').call(this);
|
||||
}
|
||||
|
||||
createdAt() {
|
||||
return Model.attribute('createdAt', Model.transformDate).call(this);
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import PackageManagerState from './states/PackageManagerState';
|
||||
import 'dayjs/plugin/relativeTime';
|
||||
import ExtensionManagerState from './states/ExtensionManagerState';
|
||||
|
||||
export interface AsyncBackendResponse {
|
||||
processing: boolean;
|
||||
@@ -6,6 +7,6 @@ export interface AsyncBackendResponse {
|
||||
|
||||
declare module 'flarum/admin/AdminApplication' {
|
||||
export default interface AdminApplication {
|
||||
packageManager: PackageManagerState;
|
||||
extensionManager: ExtensionManagerState;
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import errorHandler from '../utils/errorHandler';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import RequestError from 'flarum/common/utils/RequestError';
|
||||
|
||||
export type UpdatedPackage = {
|
||||
name: string;
|
||||
@@ -16,6 +17,8 @@ export type UpdatedPackage = {
|
||||
'latest-minor': string | null;
|
||||
'latest-major': string | null;
|
||||
'latest-status': string;
|
||||
'required-as': string;
|
||||
'direct-dependency': boolean;
|
||||
description: string;
|
||||
};
|
||||
|
||||
@@ -43,7 +46,7 @@ export type LastUpdateRun = {
|
||||
limitedPackages: () => string[];
|
||||
};
|
||||
|
||||
export type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes;
|
||||
export type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes | 'queued-action';
|
||||
|
||||
export type CoreUpdate = {
|
||||
package: UpdatedPackage;
|
||||
@@ -58,7 +61,7 @@ export default class ControlSectionState {
|
||||
public extensionUpdates!: Extension[];
|
||||
public coreUpdate: CoreUpdate | null = null;
|
||||
get lastUpdateRun(): LastUpdateRun {
|
||||
const lastUpdateRun = JSON.parse(app.data.settings['flarum-package-manager.last_update_run']) as LastUpdateRun;
|
||||
const lastUpdateRun = JSON.parse(app.data.settings['flarum-extension-manager.last_update_run']) as LastUpdateRun;
|
||||
|
||||
lastUpdateRun.limitedPackages = () => [
|
||||
...lastUpdateRun.major.limitedPackages,
|
||||
@@ -70,7 +73,7 @@ export default class ControlSectionState {
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.lastUpdateCheck = JSON.parse(app.data.settings['flarum-package-manager.last_update_check']) as LastUpdateCheck;
|
||||
this.lastUpdateCheck = JSON.parse(app.data.settings['flarum-extension-manager.last_update_check']) as LastUpdateCheck;
|
||||
this.extensionUpdates = this.formatExtensionUpdates(this.lastUpdateCheck);
|
||||
this.coreUpdate = this.formatCoreUpdate(this.lastUpdateCheck);
|
||||
}
|
||||
@@ -79,21 +82,53 @@ export default class ControlSectionState {
|
||||
return (name && this.loading === name) || (!name && this.loading !== null);
|
||||
}
|
||||
|
||||
isLoadingOtherThan(name: LoadingTypes): boolean {
|
||||
return this.loading !== null && this.loading !== name;
|
||||
hasOperationRunning(): boolean {
|
||||
return this.isLoading() || app.extensionManager.queue.hasPending();
|
||||
}
|
||||
|
||||
setLoading(name: LoadingTypes): void {
|
||||
this.loading = name;
|
||||
}
|
||||
|
||||
requirePackage(data: any) {
|
||||
app.extensionManager.control.setLoading('extension-install');
|
||||
app.modal.show(LoadingModal);
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse & { id: number }>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/extensions`,
|
||||
body: {
|
||||
data,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
const extensionId = response.id;
|
||||
app.alerts.show(
|
||||
{ type: 'success' },
|
||||
app.translator.trans('flarum-extension-manager.admin.extensions.successful_install', { extension: extensionId })
|
||||
);
|
||||
window.location.href = `${app.forum.attribute('adminUrl')}#/extension/${extensionId}`;
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
this.setLoading('check');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | LastUpdateCheck>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/check-for-updates`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/check-for-updates`,
|
||||
})
|
||||
.then((response) => {
|
||||
if ((response as AsyncBackendResponse).processing) {
|
||||
@@ -102,51 +137,55 @@ export default class ControlSectionState {
|
||||
this.lastUpdateCheck = response as LastUpdateCheck;
|
||||
this.extensionUpdates = this.formatExtensionUpdates(response as LastUpdateCheck);
|
||||
this.coreUpdate = this.formatCoreUpdate(response as LastUpdateCheck);
|
||||
this.setLoading(null);
|
||||
m.redraw();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
updateCoreMinor() {
|
||||
if (confirm(extractText(app.translator.trans('flarum-package-manager.admin.minor_update_confirmation.content')))) {
|
||||
if (confirm(extractText(app.translator.trans('flarum-extension-manager.admin.minor_update_confirmation.content')))) {
|
||||
app.modal.show(LoadingModal);
|
||||
this.setLoading('minor-update');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/minor-update`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/minor-update`,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.update_successful'));
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-extension-manager.admin.update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateExtension(extension: Extension) {
|
||||
updateExtension(extension: Extension, updateMode: 'soft' | 'hard') {
|
||||
app.modal.show(LoadingModal);
|
||||
this.setLoading('extension-update');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'PATCH',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions/${extension.id}`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/extensions/${extension.id}`,
|
||||
body: {
|
||||
data: {
|
||||
updateMode,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
@@ -154,7 +193,7 @@ export default class ControlSectionState {
|
||||
} else {
|
||||
app.alerts.show(
|
||||
{ type: 'success' },
|
||||
app.translator.trans('flarum-package-manager.admin.extensions.successful_update', {
|
||||
app.translator.trans('flarum-extension-manager.admin.extensions.successful_update', {
|
||||
extension: extension.extra['flarum-extension'].title,
|
||||
})
|
||||
);
|
||||
@@ -163,7 +202,6 @@ export default class ControlSectionState {
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
@@ -176,19 +214,18 @@ export default class ControlSectionState {
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/global-update`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/global-update`,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.updater.global_update_successful'));
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-extension-manager.admin.updater.global_update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
@@ -226,14 +263,46 @@ export default class ControlSectionState {
|
||||
version: app.data.settings.version,
|
||||
icon: {
|
||||
// @ts-ignore
|
||||
backgroundImage: `url(${app.data.resources[0]['attributes']['baseUrl']}/assets/extensions/flarum-package-manager/flarum.svg`,
|
||||
backgroundImage: `url(${app.data.resources[0]['attributes']['baseUrl']}/assets/extensions/flarum-extension-manager/flarum.svg`,
|
||||
},
|
||||
extra: {
|
||||
'flarum-extension': {
|
||||
title: extractText(app.translator.trans('flarum-package-manager.admin.updater.flarum')),
|
||||
title: extractText(app.translator.trans('flarum-extension-manager.admin.updater.flarum')),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
majorUpdate({ dryRun }: { dryRun: boolean }) {
|
||||
app.extensionManager.control.setLoading(dryRun ? 'major-update-dry-run' : 'major-update');
|
||||
app.modal.show(LoadingModal);
|
||||
const updateState = this.lastUpdateRun.major;
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/major-update`,
|
||||
body: {
|
||||
data: { dryRun },
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-extension-manager.admin.update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.catch((e: RequestError) => {
|
||||
app.modal.close();
|
||||
updateState.status = 'failure';
|
||||
updateState.incompatibleExtensions = e.response?.errors?.pop()?.incompatible_extensions as string[];
|
||||
})
|
||||
.finally(() => {
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,7 @@
|
||||
import QueueState from './QueueState';
|
||||
import ControlSectionState from './ControlSectionState';
|
||||
|
||||
export default class ExtensionManagerState {
|
||||
public queue: QueueState = new QueueState();
|
||||
public control: ControlSectionState = new ControlSectionState();
|
||||
}
|
@@ -3,12 +3,13 @@ import Task from '../models/Task';
|
||||
import { ApiQueryParamsPlural } from 'flarum/common/Store';
|
||||
|
||||
export default class QueueState {
|
||||
private polling: any = null;
|
||||
private tasks: Task[] | null = null;
|
||||
private limit = 20;
|
||||
private offset = 0;
|
||||
private total = 0;
|
||||
|
||||
load(params?: ApiQueryParamsPlural) {
|
||||
load(params?: ApiQueryParamsPlural, actionTaken = false): Promise<Task[]> {
|
||||
this.tasks = null;
|
||||
params = {
|
||||
page: {
|
||||
@@ -19,12 +20,26 @@ export default class QueueState {
|
||||
...params,
|
||||
};
|
||||
|
||||
return app.store.find<Task[]>('package-manager-tasks', params || {}).then((data) => {
|
||||
return app.store.find<Task[]>('extension-manager-tasks', params || {}).then((data) => {
|
||||
this.tasks = data;
|
||||
this.total = data.payload.meta?.total!;
|
||||
this.total = data.payload.meta?.total;
|
||||
|
||||
m.redraw();
|
||||
|
||||
// Check if there is a pending or running task
|
||||
const pendingTask = data?.find((task) => task.status() === 'pending' || task.status() === 'running');
|
||||
|
||||
if (pendingTask) {
|
||||
this.pollQueue(actionTaken);
|
||||
} else if (actionTaken) {
|
||||
app.extensionManager.control.setLoading(null);
|
||||
|
||||
// Refresh the page
|
||||
window.location.reload();
|
||||
} else if (app.extensionManager.control.isLoading()) {
|
||||
app.extensionManager.control.setLoading(null);
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
@@ -62,4 +77,18 @@ export default class QueueState {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
pollQueue(actionTaken = false): void {
|
||||
if (this.polling) {
|
||||
clearTimeout(this.polling);
|
||||
}
|
||||
|
||||
this.polling = setTimeout(() => {
|
||||
this.load({}, actionTaken);
|
||||
}, 6000);
|
||||
}
|
||||
|
||||
hasPending() {
|
||||
return !!this.tasks?.find((task) => task.status() === 'pending' || task.status() === 'running');
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +1,33 @@
|
||||
import app from 'flarum/admin/app';
|
||||
|
||||
export default function (e: any) {
|
||||
app.extensionManager.control.setLoading(null);
|
||||
|
||||
const error = e.response.errors[0];
|
||||
|
||||
if (!['composer_command_failure', 'extension_already_installed', 'extension_not_installed'].includes(error.code)) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
app.alerts.clear();
|
||||
|
||||
switch (error.code) {
|
||||
case 'composer_command_failure':
|
||||
if (error.guessed_cause) {
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans(`flarum-package-manager.admin.exceptions.guessed_cause.${error.guessed_cause}`));
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans(`flarum-extension-manager.admin.exceptions.guessed_cause.${error.guessed_cause}`));
|
||||
app.modal.close();
|
||||
} else {
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.composer_command_failure'));
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-extension-manager.admin.exceptions.composer_command_failure'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'extension_already_installed':
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.extension_already_installed'));
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-extension-manager.admin.exceptions.extension_already_installed'));
|
||||
app.modal.close();
|
||||
break;
|
||||
|
||||
case 'extension_not_installed':
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.extension_not_installed'));
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-extension-manager.admin.exceptions.extension_not_installed'));
|
||||
app.modal.close();
|
||||
}
|
||||
}
|
||||
|
@@ -5,9 +5,12 @@ window.jumpToQueue = jumpToQueue;
|
||||
|
||||
export default function jumpToQueue(): void {
|
||||
app.modal.close();
|
||||
m.route.set(app.route('extension', { id: 'flarum-package-manager' }));
|
||||
app.packageManager.queue.load();
|
||||
|
||||
m.route.set(app.route('extension', { id: 'flarum-extension-manager' }));
|
||||
|
||||
app.extensionManager.queue.load({}, true);
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('PackageManager-queueSection')?.scrollIntoView({ block: 'nearest' });
|
||||
document.getElementById('ExtensionManager-queueSection')?.scrollIntoView({ block: 'nearest' });
|
||||
}, 200);
|
||||
}
|
||||
|
Reference in New Issue
Block a user