mirror of
https://github.com/flarum/framework.git
synced 2025-03-21 12:35:15 +08:00
chore(package-manager): last tweaks before beta tag
chore: fix workflow errors chore: fix workflow errors chore: avoid updating an extension that wasn't directly required chore: prevent job overlap chore: reorganize code, separate state from view fix: update checking ui display chore: minor improvements Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>
This commit is contained in:
parent
082117d8bc
commit
335c602cea
@ -41,7 +41,8 @@ return [
|
||||
$paths = resolve(Paths::class);
|
||||
|
||||
$document->payload['flarum-package-manager.writable_dirs'] = is_writable($paths->vendor)
|
||||
&& is_writable($paths->storage.'/.composer')
|
||||
&& is_writable($paths->storage)
|
||||
&& (! file_exists($paths->storage.'/.composer') || is_writable($paths->storage.'/.composer'))
|
||||
&& is_writable($paths->base.'/composer.json')
|
||||
&& is_writable($paths->base.'/composer.lock');
|
||||
|
||||
|
@ -1,11 +1,17 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import Component from 'flarum/common/Component';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import { ComponentAttrs } from 'flarum/common/Component';
|
||||
|
||||
import Installer from './Installer';
|
||||
import Updater from './Updater';
|
||||
import Mithril from 'mithril';
|
||||
|
||||
export default class ControlSection extends Component<ComponentAttrs> {
|
||||
oninit(vnode: Mithril.Vnode<ComponentAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
}
|
||||
|
||||
export default class ControlSection extends Component {
|
||||
view() {
|
||||
return (
|
||||
<div className="ExtensionPage-permissions PackageManager-controlSection">
|
||||
|
@ -7,7 +7,7 @@ import Tooltip from 'flarum/common/components/Tooltip';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
|
||||
import { UpdatedPackage } from './Updater';
|
||||
import { UpdatedPackage } from '../states/ControlSectionState';
|
||||
import WhyNotModal from './WhyNotModal';
|
||||
import Label from './Label';
|
||||
|
||||
@ -40,7 +40,7 @@ export default class ExtensionItem<Attrs extends ExtensionItemAttrs = ExtensionI
|
||||
<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(extension.version)}</span>
|
||||
<span className="PackageManager-extension-version-current">{this.version(updates['version'])}</span>
|
||||
{latestVersion ? (
|
||||
<Label className="PackageManager-extension-version-latest" type={updates['latest-minor'] ? 'success' : 'warning'}>
|
||||
{this.version(latestVersion)}
|
||||
|
@ -9,11 +9,12 @@ import errorHandler from '../utils/errorHandler';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import { AsyncBackendResponse } from '../shims';
|
||||
|
||||
interface InstallerAttrs extends ComponentAttrs {}
|
||||
export interface InstallerAttrs extends ComponentAttrs {}
|
||||
|
||||
export type InstallerLoadingTypes = 'extension-install' | null;
|
||||
|
||||
export default class Installer extends Component<InstallerAttrs> {
|
||||
packageName!: Stream<string>;
|
||||
isLoading: boolean = false;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<InstallerAttrs, this>): void {
|
||||
super.oninit(vnode);
|
||||
@ -32,7 +33,13 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
</p>
|
||||
<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={this.isLoading}>
|
||||
<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')}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.extensions.proceed')}
|
||||
</Button>
|
||||
</div>
|
||||
@ -47,7 +54,7 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
}
|
||||
|
||||
onsubmit(): void {
|
||||
this.isLoading = true;
|
||||
app.packageManager.control.setLoading('extension-install');
|
||||
app.modal.show(LoadingModal);
|
||||
|
||||
app
|
||||
@ -73,7 +80,7 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
app.packageManager.control.setLoading(null);
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
@ -7,20 +7,21 @@ 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 './Updater';
|
||||
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';
|
||||
|
||||
interface MajorUpdaterAttrs extends ComponentAttrs {
|
||||
export interface MajorUpdaterAttrs extends ComponentAttrs {
|
||||
coreUpdate: UpdatedPackage;
|
||||
updateState: UpdateState;
|
||||
}
|
||||
|
||||
export type MajorUpdaterLoadingTypes = 'major-update' | 'major-update-dry-run';
|
||||
|
||||
export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttrs> extends Component<T> {
|
||||
isLoading: string | null = null;
|
||||
updateState!: UpdateState;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<T, this>) {
|
||||
@ -29,7 +30,7 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
this.updateState = this.attrs.updateState;
|
||||
}
|
||||
|
||||
view(vnode: Mithril.Vnode<T, this>): Mithril.Children {
|
||||
view(): Mithril.Children {
|
||||
// @todo move Form-group--danger class to core for reuse
|
||||
return (
|
||||
<div className="Form-group Form-group--danger PackageManager-majorUpdate">
|
||||
@ -38,11 +39,21 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
<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')}>
|
||||
<Button className="Button" icon="fas fa-vial" onclick={this.update.bind(this, true)}>
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-vial"
|
||||
onclick={this.update.bind(this, true)}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('major-update-dry-run')}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.dry_run')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button className="Button Button--danger" icon="fas fa-play" onclick={this.update.bind(this, false)}>
|
||||
<Button
|
||||
className="Button Button--danger"
|
||||
icon="fas fa-play"
|
||||
onclick={this.update.bind(this, false)}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('major-update')}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.update')}
|
||||
</Button>
|
||||
</div>
|
||||
@ -83,7 +94,7 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
}
|
||||
|
||||
update(dryRun: boolean) {
|
||||
this.isLoading = `update-${dryRun ? 'dry-run' : 'run'}`;
|
||||
app.packageManager.control.setLoading(dryRun ? 'major-update-dry-run' : 'major-update');
|
||||
app.modal.show(LoadingModal);
|
||||
|
||||
app
|
||||
@ -109,7 +120,7 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
this.updateState.incompatibleExtensions = e.response?.errors?.pop()?.incompatible_extensions as string[];
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = null;
|
||||
app.packageManager.control.setLoading(null);
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export default class QueueSection extends Component<{}> {
|
||||
oninit(vnode: Mithril.Vnode<{}, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
app.packageManagerQueue.load();
|
||||
app.packageManager.queue.load();
|
||||
}
|
||||
|
||||
view() {
|
||||
@ -36,7 +36,7 @@ export default class QueueSection extends Component<{}> {
|
||||
<Button
|
||||
className="Button Button--icon"
|
||||
icon="fas fa-sync-alt"
|
||||
onclick={() => app.packageManagerQueue.load()}
|
||||
onclick={() => app.packageManager.queue.load()}
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.sections.queue.refresh')}
|
||||
/>
|
||||
</div>
|
||||
@ -154,7 +154,7 @@ export default class QueueSection extends Component<{}> {
|
||||
}
|
||||
|
||||
queueTable() {
|
||||
const tasks = app.packageManagerQueue.getItems();
|
||||
const tasks = app.packageManager.queue.getItems();
|
||||
|
||||
if (!tasks) {
|
||||
return <LoadingIndicator />;
|
||||
@ -193,7 +193,7 @@ export default class QueueSection extends Component<{}> {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pagination list={app.packageManagerQueue} />
|
||||
<Pagination list={app.packageManager.queue} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -8,16 +8,16 @@ import ControlSection from './ControlSection';
|
||||
|
||||
export default class SettingsPage extends ExtensionPage {
|
||||
sections(vnode: Mithril.VnodeDOM<ExtensionPageAttrs, this>): ItemList<unknown> {
|
||||
// @todo add core feature to register sections
|
||||
const items = super.sections(vnode);
|
||||
|
||||
if (app.data.settings['flarum-package-manager.queue_jobs']) {
|
||||
items.add('queue', <QueueSection />, 5);
|
||||
}
|
||||
items.setPriority('content', 10);
|
||||
|
||||
items.add('control', <ControlSection />, 8);
|
||||
|
||||
items.setPriority('content', 10);
|
||||
if (parseInt(app.data.settings['flarum-package-manager.queue_jobs'])) {
|
||||
items.add('queue', <QueueSection />, 5);
|
||||
}
|
||||
|
||||
items.setPriority('permissions', 0);
|
||||
|
||||
return items;
|
||||
|
@ -1,278 +1,126 @@
|
||||
import Mithril from 'mithril';
|
||||
import app from 'flarum/admin/app';
|
||||
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import humanTime from 'flarum/common/helpers/humanTime';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
import errorHandler from '../utils/errorHandler';
|
||||
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||
import MajorUpdater from './MajorUpdater';
|
||||
import ExtensionItem from './ExtensionItem';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import { AsyncBackendResponse } from '../shims';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import ItemList from '@flarum/core/src/common/utils/ItemList';
|
||||
|
||||
export type UpdatedPackage = {
|
||||
name: string;
|
||||
version: string;
|
||||
latest: string;
|
||||
'latest-minor': string | null;
|
||||
'latest-major': string | null;
|
||||
'latest-status': string;
|
||||
description: string;
|
||||
};
|
||||
export interface IUpdaterAttrs extends ComponentAttrs {}
|
||||
|
||||
export type ComposerUpdates = {
|
||||
installed: UpdatedPackage[];
|
||||
};
|
||||
|
||||
export type LastUpdateCheck = {
|
||||
checkedAt: Date | null;
|
||||
updates: ComposerUpdates;
|
||||
};
|
||||
|
||||
type UpdateType = 'major' | 'minor' | 'global';
|
||||
type UpdateStatus = 'success' | 'failure' | null;
|
||||
export type UpdateState = {
|
||||
ranAt: Date | null;
|
||||
status: UpdateStatus;
|
||||
limitedPackages: string[];
|
||||
incompatibleExtensions: string[];
|
||||
};
|
||||
|
||||
export type LastUpdateRun = {
|
||||
[key in UpdateType]: UpdateState;
|
||||
} & {
|
||||
limitedPackages: () => string[];
|
||||
};
|
||||
|
||||
interface UpdaterAttrs extends ComponentAttrs {}
|
||||
|
||||
export default class Updater extends Component<UpdaterAttrs> {
|
||||
isLoading: string | null = null;
|
||||
packageUpdates: Record<string, UpdatedPackage> = {};
|
||||
lastUpdateCheck: LastUpdateCheck = JSON.parse(app.data.settings['flarum-package-manager.last_update_check']) as LastUpdateCheck;
|
||||
get lastUpdateRun(): LastUpdateRun {
|
||||
const lastUpdateRun = JSON.parse(app.data.settings['flarum-package-manager.last_update_run']) as LastUpdateRun;
|
||||
|
||||
lastUpdateRun.limitedPackages = () => [
|
||||
...lastUpdateRun.major.limitedPackages,
|
||||
...lastUpdateRun.minor.limitedPackages,
|
||||
...lastUpdateRun.global.limitedPackages,
|
||||
];
|
||||
|
||||
return lastUpdateRun;
|
||||
}
|
||||
|
||||
oninit(vnode: Mithril.Vnode<UpdaterAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
}
|
||||
export type UpdaterLoadingTypes = 'check' | 'minor-update' | 'global-update' | 'extension-update' | null;
|
||||
|
||||
export default class Updater extends Component<IUpdaterAttrs> {
|
||||
view() {
|
||||
const extensions = this.getExtensionUpdates();
|
||||
let coreUpdate: UpdatedPackage | undefined = this.getCoreUpdate();
|
||||
let core: any;
|
||||
|
||||
if (coreUpdate) {
|
||||
core = {
|
||||
id: 'flarum-core',
|
||||
name: 'flarum/core',
|
||||
version: app.data.settings.version,
|
||||
icon: {
|
||||
backgroundImage: `url(${app.forum.attribute('baseUrl')}/assets/extensions/flarum-package-manager/flarum.svg`,
|
||||
},
|
||||
extra: {
|
||||
'flarum-extension': {
|
||||
title: app.translator.trans('flarum-package-manager.admin.updater.flarum'),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const core = app.packageManager.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>
|
||||
{this.lastUpdateCheck?.checkedAt && (
|
||||
<p className="PackageManager-lastUpdatedAt">
|
||||
<span className="PackageManager-lastUpdatedAt-label">
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.last_update_checked_at')}
|
||||
</span>
|
||||
<span className="PackageManager-lastUpdatedAt-value">{humanTime(this.lastUpdateCheck.checkedAt)}</span>
|
||||
</p>
|
||||
)}
|
||||
<div className="PackageManager-updaterControls">
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-sync-alt"
|
||||
onclick={this.checkForUpdates.bind(this)}
|
||||
loading={this.isLoading === 'check'}
|
||||
disabled={this.isLoading !== null && this.isLoading !== 'check'}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.check_for_updates')}
|
||||
</Button>
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-play"
|
||||
onclick={this.updateGlobally.bind(this)}
|
||||
loading={this.isLoading === 'global-update'}
|
||||
disabled={this.isLoading !== null && this.isLoading !== 'global-update'}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.run_global_update')}
|
||||
</Button>
|
||||
</div>
|
||||
{this.isLoading !== null ? (
|
||||
<div className="PackageManager-extensions">
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
) : extensions.length || core ? (
|
||||
<div className="PackageManager-extensions">
|
||||
<div className="PackageManager-extensions-grid">
|
||||
{core ? (
|
||||
<ExtensionItem
|
||||
extension={core}
|
||||
updates={coreUpdate}
|
||||
isCore={true}
|
||||
onClickUpdate={this.updateCoreMinor.bind(this)}
|
||||
whyNotWarning={this.lastUpdateRun.limitedPackages().includes('flarum/core')}
|
||||
/>
|
||||
) : null}
|
||||
{extensions.map((extension: Extension) => (
|
||||
<ExtensionItem
|
||||
extension={extension}
|
||||
updates={this.packageUpdates[extension.id]}
|
||||
onClickUpdate={this.updateExtension.bind(this, extension)}
|
||||
whyNotWarning={this.lastUpdateRun.limitedPackages().includes(extension.name)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.lastUpdateCheckView()}
|
||||
<div className="PackageManager-updaterControls">{this.controlItems().toArray()}</div>
|
||||
{this.availableUpdatesView()}
|
||||
</div>,
|
||||
coreUpdate && coreUpdate['latest-major'] ? <MajorUpdater coreUpdate={coreUpdate} updateState={this.lastUpdateRun.major} /> : null,
|
||||
core && core.package['latest-major'] ? (
|
||||
<MajorUpdater coreUpdate={core.package} updateState={app.packageManager.control.lastUpdateRun.major} />
|
||||
) : null,
|
||||
];
|
||||
}
|
||||
|
||||
getExtensionUpdates(): Extension[] {
|
||||
this.lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => {
|
||||
const id = composerPackage.name.replace('/', '-').replace(/(flarum-ext-)|(flarum-)/, '');
|
||||
|
||||
const extension = app.data.extensions[id];
|
||||
const safeToUpdate = ['semver-safe-update', 'update-possible'].includes(composerPackage['latest-status']);
|
||||
|
||||
if (extension && safeToUpdate) {
|
||||
this.packageUpdates[extension.id] = composerPackage;
|
||||
}
|
||||
|
||||
return extension && safeToUpdate;
|
||||
});
|
||||
|
||||
return (Object.values(app.data.extensions) as Extension[]).filter((extension: Extension) => this.packageUpdates[extension.id]);
|
||||
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')}
|
||||
</span>
|
||||
<span className="PackageManager-lastUpdatedAt-value">{humanTime(app.packageManager.control.lastUpdateCheck.checkedAt)}</span>
|
||||
</p>
|
||||
)) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
getCoreUpdate(): UpdatedPackage | undefined {
|
||||
return this.lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => composerPackage.name === 'flarum/core').pop();
|
||||
}
|
||||
availableUpdatesView() {
|
||||
const state = app.packageManager.control;
|
||||
|
||||
checkForUpdates() {
|
||||
this.isLoading = 'check';
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | LastUpdateCheck>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/check-for-updates`,
|
||||
errorHandler,
|
||||
})
|
||||
.then((response) => {
|
||||
if ((response as AsyncBackendResponse).processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
this.lastUpdateCheck = response as LastUpdateCheck;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
updateCoreMinor() {
|
||||
if (confirm(extractText(app.translator.trans('flarum-package-manager.admin.minor_update_confirmation.content')))) {
|
||||
app.modal.show(LoadingModal);
|
||||
this.isLoading = 'minor-update';
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/minor-update`,
|
||||
errorHandler,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
if (app.packageManager.control.isLoading()) {
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!(state.extensionUpdates.length || state.coreUpdate)) {
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<Alert type="success" dismissible={false}>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.up_to_date')}
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<div className="PackageManager-extensions-grid">
|
||||
{state.coreUpdate ? (
|
||||
<ExtensionItem
|
||||
extension={state.coreUpdate.extension}
|
||||
updates={state.coreUpdate.package}
|
||||
isCore={true}
|
||||
onClickUpdate={() => state.updateCoreMinor()}
|
||||
whyNotWarning={state.lastUpdateRun.limitedPackages().includes('flarum/core')}
|
||||
/>
|
||||
) : null}
|
||||
{state.extensionUpdates.map((extension: Extension) => (
|
||||
<ExtensionItem
|
||||
extension={extension}
|
||||
updates={state.packageUpdates[extension.id]}
|
||||
onClickUpdate={() => state.updateExtension(extension)}
|
||||
whyNotWarning={state.lastUpdateRun.limitedPackages().includes(extension.name)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
updateExtension(extension: any) {
|
||||
app.modal.show(LoadingModal);
|
||||
this.isLoading = 'extension-update';
|
||||
controlItems() {
|
||||
const items = new ItemList();
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'PATCH',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions/${extension.id}`,
|
||||
errorHandler,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show(
|
||||
{ type: 'success' },
|
||||
app.translator.trans('flarum-package-manager.admin.extensions.successful_update', {
|
||||
extension: extension.extra['flarum-extension'].title,
|
||||
})
|
||||
);
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
items.add(
|
||||
'updateCheck',
|
||||
<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')}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.check_for_updates')}
|
||||
</Button>,
|
||||
100
|
||||
);
|
||||
|
||||
updateGlobally() {
|
||||
app.modal.show(LoadingModal);
|
||||
this.isLoading = 'global-update';
|
||||
items.add(
|
||||
'globalUpdate',
|
||||
<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')}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.run_global_update')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/global-update`,
|
||||
errorHandler,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.updater.global_update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
@ -8,14 +8,14 @@ import SettingsPage from './components/SettingsPage';
|
||||
|
||||
import Task from './models/Task';
|
||||
import jumpToQueue from './utils/jumpToQueue';
|
||||
import QueueState from './states/QueueState';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import { AsyncBackendResponse } from './shims';
|
||||
import PackageManagerState from './states/PackageManagerState';
|
||||
|
||||
app.initializers.add('flarum-package-manager', (app) => {
|
||||
app.store.models['package-manager-tasks'] = Task;
|
||||
|
||||
app.packageManagerQueue = new QueueState();
|
||||
app.packageManager = new PackageManagerState();
|
||||
|
||||
app.extensionData
|
||||
.for('flarum-package-manager')
|
||||
|
@ -1,4 +1,4 @@
|
||||
import QueueState from './states/QueueState';
|
||||
import PackageManagerState from './states/PackageManagerState';
|
||||
|
||||
export interface AsyncBackendResponse {
|
||||
processing: boolean;
|
||||
@ -6,6 +6,6 @@ export interface AsyncBackendResponse {
|
||||
|
||||
declare module 'flarum/admin/AdminApplication' {
|
||||
export default interface AdminApplication {
|
||||
packageManagerQueue: QueueState;
|
||||
packageManager: PackageManagerState;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,239 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
import { UpdaterLoadingTypes } from '../components/Updater';
|
||||
import { InstallerLoadingTypes } from '../components/Installer';
|
||||
import { MajorUpdaterLoadingTypes } from '../components/MajorUpdater';
|
||||
import { AsyncBackendResponse } from '../shims';
|
||||
import errorHandler from '../utils/errorHandler';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
|
||||
export type UpdatedPackage = {
|
||||
name: string;
|
||||
version: string;
|
||||
latest: string;
|
||||
'latest-minor': string | null;
|
||||
'latest-major': string | null;
|
||||
'latest-status': string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type ComposerUpdates = {
|
||||
installed: UpdatedPackage[];
|
||||
};
|
||||
|
||||
export type LastUpdateCheck = {
|
||||
checkedAt: Date | null;
|
||||
updates: ComposerUpdates;
|
||||
};
|
||||
|
||||
type UpdateType = 'major' | 'minor' | 'global';
|
||||
type UpdateStatus = 'success' | 'failure' | null;
|
||||
export type UpdateState = {
|
||||
ranAt: Date | null;
|
||||
status: UpdateStatus;
|
||||
limitedPackages: string[];
|
||||
incompatibleExtensions: string[];
|
||||
};
|
||||
|
||||
export type LastUpdateRun = {
|
||||
[key in UpdateType]: UpdateState;
|
||||
} & {
|
||||
limitedPackages: () => string[];
|
||||
};
|
||||
|
||||
export type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes;
|
||||
|
||||
export type CoreUpdate = {
|
||||
package: UpdatedPackage;
|
||||
extension: Extension;
|
||||
};
|
||||
|
||||
export default class ControlSectionState {
|
||||
loading: LoadingTypes = null;
|
||||
|
||||
public packageUpdates: Record<string, UpdatedPackage> = {};
|
||||
public lastUpdateCheck!: LastUpdateCheck;
|
||||
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;
|
||||
|
||||
lastUpdateRun.limitedPackages = () => [
|
||||
...lastUpdateRun.major.limitedPackages,
|
||||
...lastUpdateRun.minor.limitedPackages,
|
||||
...lastUpdateRun.global.limitedPackages,
|
||||
];
|
||||
|
||||
return lastUpdateRun;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.lastUpdateCheck = JSON.parse(app.data.settings['flarum-package-manager.last_update_check']) as LastUpdateCheck;
|
||||
this.extensionUpdates = this.formatExtensionUpdates(this.lastUpdateCheck);
|
||||
this.coreUpdate = this.formatCoreUpdate(this.lastUpdateCheck);
|
||||
}
|
||||
|
||||
isLoading(name: LoadingTypes = null): boolean {
|
||||
return (name && this.loading === name) || (!name && this.loading !== null);
|
||||
}
|
||||
|
||||
isLoadingOtherThan(name: LoadingTypes): boolean {
|
||||
return this.loading !== null && this.loading !== name;
|
||||
}
|
||||
|
||||
setLoading(name: LoadingTypes): void {
|
||||
this.loading = name;
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
this.setLoading('check');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | LastUpdateCheck>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/check-for-updates`,
|
||||
errorHandler,
|
||||
})
|
||||
.then((response) => {
|
||||
if ((response as AsyncBackendResponse).processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
this.lastUpdateCheck = response as LastUpdateCheck;
|
||||
this.extensionUpdates = this.formatExtensionUpdates(response as LastUpdateCheck);
|
||||
this.coreUpdate = this.formatCoreUpdate(response as LastUpdateCheck);
|
||||
m.redraw();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
updateCoreMinor() {
|
||||
if (confirm(extractText(app.translator.trans('flarum-package-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`,
|
||||
errorHandler,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateExtension(extension: Extension) {
|
||||
app.modal.show(LoadingModal);
|
||||
this.setLoading('extension-update');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'PATCH',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions/${extension.id}`,
|
||||
errorHandler,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show(
|
||||
{ type: 'success' },
|
||||
app.translator.trans('flarum-package-manager.admin.extensions.successful_update', {
|
||||
extension: extension.extra['flarum-extension'].title,
|
||||
})
|
||||
);
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
updateGlobally() {
|
||||
app.modal.show(LoadingModal);
|
||||
this.setLoading('global-update');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/global-update`,
|
||||
errorHandler,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.updater.global_update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
formatExtensionUpdates(lastUpdateCheck: LastUpdateCheck): Extension[] {
|
||||
this.packageUpdates = {};
|
||||
|
||||
lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => {
|
||||
const id = composerPackage.name.replace('/', '-').replace(/(flarum-ext-)|(flarum-)/, '');
|
||||
|
||||
const extension = app.data.extensions[id];
|
||||
const safeToUpdate = ['semver-safe-update', 'update-possible'].includes(composerPackage['latest-status']);
|
||||
|
||||
if (extension && safeToUpdate) {
|
||||
this.packageUpdates[extension.id] = composerPackage;
|
||||
}
|
||||
|
||||
return extension && safeToUpdate;
|
||||
});
|
||||
|
||||
return (Object.values(app.data.extensions) as Extension[]).filter((extension: Extension) => this.packageUpdates[extension.id]);
|
||||
}
|
||||
|
||||
formatCoreUpdate(lastUpdateCheck: LastUpdateCheck): CoreUpdate | null {
|
||||
const core = lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => composerPackage.name === 'flarum/core').pop();
|
||||
|
||||
if (!core) return null;
|
||||
|
||||
return {
|
||||
package: core,
|
||||
extension: {
|
||||
id: 'flarum-core',
|
||||
name: 'flarum/core',
|
||||
version: app.data.settings.version,
|
||||
icon: {
|
||||
// @ts-ignore
|
||||
backgroundImage: `url(${app.forum.attribute('baseUrl')}/assets/extensions/flarum-package-manager/flarum.svg`,
|
||||
},
|
||||
extra: {
|
||||
'flarum-extension': {
|
||||
title: extractText(app.translator.trans('flarum-package-manager.admin.updater.flarum')),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import QueueState from './QueueState';
|
||||
import ControlSectionState from './ControlSectionState';
|
||||
|
||||
export default class PackageManagerState {
|
||||
public queue: QueueState = new QueueState();
|
||||
public control: ControlSectionState = new ControlSectionState();
|
||||
}
|
@ -4,7 +4,7 @@ import { ApiQueryParamsPlural } from 'flarum/common/Store';
|
||||
|
||||
export default class QueueState {
|
||||
private tasks: Task[] | null = null;
|
||||
private limit = 5;
|
||||
private limit = 20;
|
||||
private offset = 0;
|
||||
private total = 0;
|
||||
|
||||
|
@ -6,7 +6,7 @@ window.jumpToQueue = jumpToQueue;
|
||||
export default function jumpToQueue(): void {
|
||||
app.modal.close();
|
||||
m.route.set(app.route('extension', { id: 'flarum-package-manager' }));
|
||||
app.packageManagerQueue.load();
|
||||
app.packageManager.queue.load();
|
||||
setTimeout(() => {
|
||||
document.getElementById('PackageManager-queueSection')?.scrollIntoView({ block: 'nearest' });
|
||||
}, 200);
|
||||
|
@ -11,6 +11,7 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
grid-area: controls;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.PackageManager-extensions {
|
||||
@ -19,7 +20,6 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, calc(~"100% / 3 - var(--gap)"));
|
||||
gap: var(--gap);
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ flarum-package-manager:
|
||||
update: Update
|
||||
|
||||
file_permissions: >
|
||||
The package manager requires read and write permissions on the following files and directories: composer.json, composer.lock, vendor, storage/.composer
|
||||
The package manager requires read and write permissions on the following files and directories: composer.json, composer.lock, vendor, storage, storage/.composer
|
||||
|
||||
major_updater:
|
||||
description: Major Flarum updates are not backwards compatible, meaning that some of your currently installed extensions, and manually made modifications might not work with this new version.
|
||||
@ -78,6 +78,7 @@ flarum-package-manager:
|
||||
Make sure the PHP version used for the queue is {php_version}. Make sure <a href='{folder_perms_link}'>folder permissions</a> are correctly configured.
|
||||
|
||||
updater:
|
||||
up_to_date: Everything is up to date!
|
||||
check_for_updates: Check for updates
|
||||
flarum: Flarum Core
|
||||
global_update_successful: Successfully updated all packages.
|
||||
|
@ -10,6 +10,8 @@
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Foundation\Paths;
|
||||
use Flarum\Foundation\ValidationException;
|
||||
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||
use Flarum\PackageManager\Exception\ComposerUpdateFailedException;
|
||||
use Flarum\PackageManager\Exception\ExtensionNotInstalledException;
|
||||
@ -46,18 +48,25 @@ class UpdateExtensionHandler
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* @var Paths
|
||||
*/
|
||||
protected $paths;
|
||||
|
||||
public function __construct(
|
||||
ComposerAdapter $composer,
|
||||
ExtensionManager $extensions,
|
||||
UpdateExtensionValidator $validator,
|
||||
LastUpdateCheck $lastUpdateCheck,
|
||||
Dispatcher $events
|
||||
Dispatcher $events,
|
||||
Paths $paths
|
||||
) {
|
||||
$this->composer = $composer;
|
||||
$this->extensions = $extensions;
|
||||
$this->validator = $validator;
|
||||
$this->lastUpdateCheck = $lastUpdateCheck;
|
||||
$this->events = $events;
|
||||
$this->paths = $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,6 +85,17 @@ class UpdateExtensionHandler
|
||||
throw new ExtensionNotInstalledException($command->extensionId);
|
||||
}
|
||||
|
||||
$rootComposer = json_decode(file_get_contents("{$this->paths->base}/composer.json"), true);
|
||||
|
||||
// If this was installed as a requirement for another extension,
|
||||
// don't update it directly.
|
||||
// @TODO communicate this in the UI.
|
||||
if (! isset($rootComposer['require'][$extension->name]) && ! empty($extension->getExtensionDependencyIds())) {
|
||||
throw new ValidationException([
|
||||
'message' => "Cannot update $extension->name. It was installed as a requirement for other extensions: ".implode(', ', $extension->getExtensionDependencyIds()).'. Update those extensions instead.'
|
||||
]);
|
||||
}
|
||||
|
||||
$output = $this->composer->run(
|
||||
new StringInput("require $extension->name:*"),
|
||||
$command->task ?? null
|
||||
|
@ -12,6 +12,7 @@ namespace Flarum\PackageManager\Job;
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\PackageManager\Command\BusinessCommandInterface;
|
||||
use Flarum\Queue\AbstractJob;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Throwable;
|
||||
|
||||
class ComposerCommandJob extends AbstractJob
|
||||
@ -62,4 +63,11 @@ class ComposerCommandJob extends AbstractJob
|
||||
|
||||
$this->fail($exception);
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
{
|
||||
return [
|
||||
new WithoutOverlapping(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user