mirror of
https://github.com/flarum/framework.git
synced 2025-03-26 00:35:16 +08:00
Global Update (Fixes #2)
This commit is contained in:
parent
77f0dca47e
commit
d878eeb92a
@ -22,7 +22,8 @@ return [
|
||||
->patch('/package-manager/extensions/{id}', 'package-manager.extensions.update', Api\Controller\UpdateExtensionController::class)
|
||||
->delete('/package-manager/extensions/{id}', 'package-manager.extensions.remove', Api\Controller\RemoveExtensionController::class)
|
||||
->post('/package-manager/check-for-updates', 'package-manager.check-for-updates', Api\Controller\CheckForUpdatesController::class)
|
||||
->post('/package-manager/minor-update', 'package-manager.minor-update', Api\Controller\MinorFlarumUpdateController::class),
|
||||
->post('/package-manager/minor-update', 'package-manager.minor-update', Api\Controller\MinorFlarumUpdateController::class)
|
||||
->post('/package-manager/global-update', 'package-manager.global-update', Api\Controller\GlobalUpdateController::class),
|
||||
|
||||
(new Extend\Frontend('admin'))
|
||||
->css(__DIR__ . '/less/admin.less')
|
||||
|
65
extensions/package-manager/js/dist/admin.js
vendored
65
extensions/package-manager/js/dist/admin.js
vendored
@ -352,6 +352,9 @@ __webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var _utils_errorHandler__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../utils/errorHandler */ "./src/admin/utils/errorHandler.ts");
|
||||
/* harmony import */ var flarum_common_utils_classList__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! flarum/common/utils/classList */ "flarum/common/utils/classList");
|
||||
/* harmony import */ var flarum_common_utils_classList__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(flarum_common_utils_classList__WEBPACK_IMPORTED_MODULE_9__);
|
||||
/* harmony import */ var flarum_common_components_LoadingIndicator__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! flarum/common/components/LoadingIndicator */ "flarum/common/components/LoadingIndicator");
|
||||
/* harmony import */ var flarum_common_components_LoadingIndicator__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(flarum_common_components_LoadingIndicator__WEBPACK_IMPORTED_MODULE_10__);
|
||||
|
||||
|
||||
|
||||
|
||||
@ -374,7 +377,7 @@ var Updater = /*#__PURE__*/function (_Component) {
|
||||
}
|
||||
|
||||
_this = _Component.call.apply(_Component, [this].concat(args)) || this;
|
||||
_this.isLoading = false;
|
||||
_this.isLoading = null;
|
||||
_this.lastUpdateCheck = flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.data.lastUpdateCheck || {};
|
||||
return _this;
|
||||
}
|
||||
@ -414,12 +417,23 @@ var Updater = /*#__PURE__*/function (_Component) {
|
||||
className: "PackageManager-lastUpdatedAt-label"
|
||||
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.updater.last_update_checked_at')), m("span", {
|
||||
className: "PackageManager-lastUpdatedAt-value"
|
||||
}, flarum_common_helpers_humanTime__WEBPACK_IMPORTED_MODULE_5___default()((_this$lastUpdateCheck = this.lastUpdateCheck) == null ? void 0 : _this$lastUpdateCheck.checkedAt))) : null, m(flarum_common_components_Button__WEBPACK_IMPORTED_MODULE_4___default.a, {
|
||||
}, flarum_common_helpers_humanTime__WEBPACK_IMPORTED_MODULE_5___default()((_this$lastUpdateCheck = this.lastUpdateCheck) == null ? void 0 : _this$lastUpdateCheck.checkedAt))) : null, m("div", {
|
||||
className: "PackageManager-updaterControls"
|
||||
}, m(flarum_common_components_Button__WEBPACK_IMPORTED_MODULE_4___default.a, {
|
||||
className: "Button",
|
||||
icon: "fas fa-sync-alt",
|
||||
onclick: this.checkForUpdates.bind(this),
|
||||
loading: this.isLoading
|
||||
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.updater.check_for_updates')), extensions.length ? m("div", {
|
||||
loading: this.isLoading === 'check',
|
||||
disabled: this.isLoading !== null && this.isLoading !== 'check'
|
||||
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.updater.check_for_updates')), m(flarum_common_components_Button__WEBPACK_IMPORTED_MODULE_4___default.a, {
|
||||
className: "Button",
|
||||
icon: "fas fa-play",
|
||||
onclick: this.updateGlobally.bind(this),
|
||||
loading: this.isLoading === 'global-update',
|
||||
disabled: this.isLoading !== null && this.isLoading !== 'global-update'
|
||||
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.updater.run_global_update'))), this.isLoading !== null ? m("div", {
|
||||
className: "PackageManager-extensions"
|
||||
}, m(flarum_common_components_LoadingIndicator__WEBPACK_IMPORTED_MODULE_10___default.a, null)) : extensions.length ? m("div", {
|
||||
className: "PackageManager-extensions"
|
||||
}, m("div", {
|
||||
className: "PackageManager-extensions-grid"
|
||||
@ -492,7 +506,7 @@ var Updater = /*#__PURE__*/function (_Component) {
|
||||
_proto.checkForUpdates = function checkForUpdates() {
|
||||
var _this3 = this;
|
||||
|
||||
this.isLoading = true;
|
||||
this.isLoading = 'check';
|
||||
flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.request({
|
||||
method: 'POST',
|
||||
url: flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.forum.attribute('apiUrl') + "/package-manager/check-for-updates",
|
||||
@ -500,13 +514,16 @@ var Updater = /*#__PURE__*/function (_Component) {
|
||||
}).then(function (response) {
|
||||
_this3.lastUpdateCheck = response;
|
||||
})["finally"](function () {
|
||||
_this3.isLoading = false;
|
||||
_this3.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
};
|
||||
|
||||
_proto.updateCoreMinor = function updateCoreMinor() {
|
||||
var _this4 = this;
|
||||
|
||||
flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.modal.show(flarum_admin_components_LoadingModal__WEBPACK_IMPORTED_MODULE_6___default.a);
|
||||
this.isLoading = 'minor-update';
|
||||
flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.request({
|
||||
method: 'POST',
|
||||
url: flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.forum.attribute('apiUrl') + "/package-manager/minor-update",
|
||||
@ -517,12 +534,16 @@ var Updater = /*#__PURE__*/function (_Component) {
|
||||
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.updater.minor_update_successful'));
|
||||
window.location.reload();
|
||||
})["finally"](function () {
|
||||
_this4.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
};
|
||||
|
||||
_proto.updateExtension = function updateExtension(extension) {
|
||||
var _this5 = this;
|
||||
|
||||
flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.modal.show(flarum_admin_components_LoadingModal__WEBPACK_IMPORTED_MODULE_6___default.a);
|
||||
this.isLoading = 'extension-update';
|
||||
flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.request({
|
||||
method: 'PATCH',
|
||||
url: flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.forum.attribute('apiUrl') + "/package-manager/extensions/" + extension.id,
|
||||
@ -535,6 +556,27 @@ var Updater = /*#__PURE__*/function (_Component) {
|
||||
}));
|
||||
window.location.reload();
|
||||
})["finally"](function () {
|
||||
_this5.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
};
|
||||
|
||||
_proto.updateGlobally = function updateGlobally() {
|
||||
var _this6 = this;
|
||||
|
||||
flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.modal.show(flarum_admin_components_LoadingModal__WEBPACK_IMPORTED_MODULE_6___default.a);
|
||||
this.isLoading = 'global-update';
|
||||
flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.request({
|
||||
method: 'POST',
|
||||
url: flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.forum.attribute('apiUrl') + "/package-manager/global-update",
|
||||
errorHandler: _utils_errorHandler__WEBPACK_IMPORTED_MODULE_8__["default"]
|
||||
}).then(function () {
|
||||
flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.alerts.show({
|
||||
type: 'success'
|
||||
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.updater.global_update_successful'));
|
||||
window.location.reload();
|
||||
})["finally"](function () {
|
||||
_this6.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
};
|
||||
@ -751,6 +793,17 @@ module.exports = flarum.core.compat['common/components/Button'];
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "flarum/common/components/LoadingIndicator":
|
||||
/*!***************************************************************************!*\
|
||||
!*** external "flarum.core.compat['common/components/LoadingIndicator']" ***!
|
||||
\***************************************************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = flarum.core.compat['common/components/LoadingIndicator'];
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "flarum/common/components/Modal":
|
||||
/*!****************************************************************!*\
|
||||
!*** external "flarum.core.compat['common/components/Modal']" ***!
|
||||
|
File diff suppressed because one or more lines are too long
@ -4,10 +4,10 @@ import icon from "flarum/common/helpers/icon";
|
||||
import Button from "flarum/common/components/Button";
|
||||
import humanTime from "flarum/common/helpers/humanTime";
|
||||
import LoadingModal from "flarum/admin/components/LoadingModal";
|
||||
import ComposerFailureModal from "./ComposerFailureModal";
|
||||
import Tooltip from "flarum/common/components/Tooltip";
|
||||
import errorHandler from "../utils/errorHandler";
|
||||
import classList from "flarum/common/utils/classList";
|
||||
import LoadingIndicator from "flarum/common/components/LoadingIndicator";
|
||||
|
||||
type UpdatedPackage = {
|
||||
name: string;
|
||||
@ -27,7 +27,7 @@ type LastUpdateCheck = {
|
||||
};
|
||||
|
||||
export default class Updater extends Component {
|
||||
isLoading: boolean = false;
|
||||
isLoading: string|null = null;
|
||||
lastUpdateCheck: LastUpdateCheck = app.data.lastUpdateCheck || {};
|
||||
|
||||
oninit(vnode) {
|
||||
@ -60,21 +60,36 @@ export default class Updater extends Component {
|
||||
<span className="PackageManager-lastUpdatedAt-value">{humanTime(this.lastUpdateCheck?.checkedAt)}</span>
|
||||
</p>
|
||||
) : null}
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-sync-alt"
|
||||
onclick={this.checkForUpdates.bind(this)}
|
||||
loading={this.isLoading}>
|
||||
{app.translator.trans('sycho-package-manager.admin.updater.check_for_updates')}
|
||||
</Button>
|
||||
{extensions.length ? (
|
||||
<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('sycho-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('sycho-package-manager.admin.updater.run_global_update')}
|
||||
</Button>
|
||||
</div>
|
||||
{this.isLoading !== null ? (
|
||||
<div className="PackageManager-extensions">
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
) : (extensions.length ? (
|
||||
<div className="PackageManager-extensions">
|
||||
<div className="PackageManager-extensions-grid">
|
||||
{core ? this.extensionItem(core, true) : null}
|
||||
{extensions.map((extension: any) => this.extensionItem(extension))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
) : null)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -125,7 +140,7 @@ export default class Updater extends Component {
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
this.isLoading = true;
|
||||
this.isLoading = 'check';
|
||||
|
||||
app.request({
|
||||
method: 'POST',
|
||||
@ -134,13 +149,14 @@ export default class Updater extends Component {
|
||||
}).then((response) => {
|
||||
this.lastUpdateCheck = response as LastUpdateCheck;
|
||||
}).finally(() => {
|
||||
this.isLoading = false;
|
||||
this.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
updateCoreMinor() {
|
||||
app.modal.show(LoadingModal);
|
||||
this.isLoading = 'minor-update';
|
||||
|
||||
app.request({
|
||||
method: 'POST',
|
||||
@ -150,12 +166,14 @@ export default class Updater extends Component {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('sycho-package-manager.admin.updater.minor_update_successful'));
|
||||
window.location.reload();
|
||||
}).finally(() => {
|
||||
this.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
updateExtension(extension: any) {
|
||||
app.modal.show(LoadingModal);
|
||||
this.isLoading = 'extension-update';
|
||||
|
||||
app.request({
|
||||
method: 'PATCH',
|
||||
@ -165,6 +183,24 @@ export default class Updater extends Component {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('sycho-package-manager.admin.extensions.successful_update', { extension: extension.extra['flarum-extension'].title }));
|
||||
window.location.reload();
|
||||
}).finally(() => {
|
||||
this.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
updateGlobally() {
|
||||
app.modal.show(LoadingModal);
|
||||
this.isLoading = 'global-update';
|
||||
|
||||
app.request({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/global-update`,
|
||||
errorHandler,
|
||||
}).then(() => {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('sycho-package-manager.admin.updater.global_update_successful'));
|
||||
window.location.reload();
|
||||
}).finally(() => {
|
||||
this.isLoading = null;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
@ -21,6 +21,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.PackageManager-updaterControls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.PackageManager-extensions {
|
||||
&-grid {
|
||||
--gap: 12px;
|
||||
|
@ -25,7 +25,9 @@ sycho-package-manager:
|
||||
updater:
|
||||
check_for_updates: Check for updates
|
||||
flarum: Flarum Core
|
||||
global_update_successful: Successfully updated all packages.
|
||||
last_update_checked_at: "Last Update Check: "
|
||||
minor_update_successful: Flarum successfully updated.
|
||||
run_global_update: Run Global Update
|
||||
updater_title: Updates
|
||||
updater_help: Runs a check for new extension and Flarum updates.
|
||||
|
42
extensions/package-manager/src/Api/Controller/GlobalUpdateController.php
Executable file
42
extensions/package-manager/src/Api/Controller/GlobalUpdateController.php
Executable file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SychO\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use SychO\PackageManager\Command\GlobalUpdate;
|
||||
|
||||
class GlobalUpdateController implements RequestHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
public function __construct(Dispatcher $bus)
|
||||
{
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$this->bus->dispatch(
|
||||
new GlobalUpdate($actor)
|
||||
);
|
||||
|
||||
return new EmptyResponse();
|
||||
}
|
||||
}
|
22
extensions/package-manager/src/Command/GlobalUpdate.php
Normal file
22
extensions/package-manager/src/Command/GlobalUpdate.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SychO\PackageManager\Command;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
||||
class GlobalUpdate
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\User\User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
public function __construct(User $actor)
|
||||
{
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SychO\PackageManager\Command;
|
||||
|
||||
use Composer\Console\Application;
|
||||
use Flarum\Bus\Dispatcher as FlarumDispatcher;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use SychO\PackageManager\Event\FlarumUpdated;
|
||||
use SychO\PackageManager\Exception\ComposerUpdateFailedException;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
|
||||
class GlobalUpdateHandler
|
||||
{
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* @var FlarumDispatcher
|
||||
*/
|
||||
protected $commandDispatcher;
|
||||
|
||||
/**
|
||||
* @param Application $composer
|
||||
* @param Dispatcher $events
|
||||
* @param FlarumDispatcher $commandDispatcher
|
||||
*/
|
||||
public function __construct(Application $composer, Dispatcher $events, FlarumDispatcher $commandDispatcher)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->events = $events;
|
||||
$this->commandDispatcher = $commandDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Flarum\User\Exception\PermissionDeniedException|ComposerUpdateFailedException
|
||||
*/
|
||||
public function handle(GlobalUpdate $command)
|
||||
{
|
||||
$command->actor->assertAdmin();
|
||||
|
||||
$output = new BufferedOutput();
|
||||
$input = new ArrayInput([
|
||||
'command' => 'update',
|
||||
'--prefer-dist' => true,
|
||||
'--no-dev' => true,
|
||||
'-a' => true,
|
||||
'--with-all-dependencies' => true,
|
||||
]);
|
||||
|
||||
$exitCode = $this->composer->run($input, $output);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
throw new ComposerUpdateFailedException('*', $output->fetch());
|
||||
}
|
||||
|
||||
$this->commandDispatcher->dispatch(
|
||||
new CheckForUpdates($command->actor)
|
||||
);
|
||||
|
||||
$this->events->dispatch(
|
||||
new FlarumUpdated(FlarumUpdated::GLOBAL)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SychO\PackageManager\Command;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
@ -70,7 +70,7 @@ class MinorFlarumUpdateHandler
|
||||
$this->lastUpdateCheck->forget('flarum/*', true);
|
||||
|
||||
$this->events->dispatch(
|
||||
new FlarumUpdated()
|
||||
new FlarumUpdated(FlarumUpdated::MINOR)
|
||||
);
|
||||
|
||||
return true;
|
||||
|
@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SychO\PackageManager\Command;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SychO\PackageManager\Command;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SychO\PackageManager\Command;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
@ -4,5 +4,17 @@ namespace SychO\PackageManager\Event;
|
||||
|
||||
class FlarumUpdated
|
||||
{
|
||||
public const GLOBAL = 'global';
|
||||
public const MAJOR = 'major';
|
||||
public const MINOR = 'minor';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
public function __construct(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user