mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-23 15:22:41 +01:00
Allow long daemon log messages to be expanded
Summary: Ref T3557. We summarize long messages, but don't let you see the entire message. This is occasionally inconvenient, and I'm planning to add more prefix junk to some messages for T2569. Provide a link you can click to see the full message. This isn't javascripted because a ton of these can make the page ridiculously enormous and it seems unlikely you'd care much about all of them. Test Plan: {F51261} {F51262} Reviewers: btrahan Reviewed By: btrahan CC: aran, chad Maniphest Tasks: T3557 Differential Revision: https://secure.phabricator.com/D6546
This commit is contained in:
parent
e793a3657a
commit
36c1359230
8 changed files with 175 additions and 73 deletions
|
@ -917,7 +917,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'aphront-table-view-css' =>
|
'aphront-table-view-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/d3c44c69/rsrc/css/aphront/table-view.css',
|
'uri' => '/res/be5ca6be/rsrc/css/aphront/table-view.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -4182,7 +4182,7 @@ celerity_register_resource_map(array(
|
||||||
), array(
|
), array(
|
||||||
'packages' =>
|
'packages' =>
|
||||||
array(
|
array(
|
||||||
'72b33589' =>
|
'570cc49c' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'core.pkg.css',
|
'name' => 'core.pkg.css',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
|
@ -4230,7 +4230,7 @@ celerity_register_resource_map(array(
|
||||||
40 => 'phabricator-property-list-view-css',
|
40 => 'phabricator-property-list-view-css',
|
||||||
41 => 'phabricator-tag-view-css',
|
41 => 'phabricator-tag-view-css',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/72b33589/core.pkg.css',
|
'uri' => '/res/pkg/570cc49c/core.pkg.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
),
|
),
|
||||||
'75ccea43' =>
|
'75ccea43' =>
|
||||||
|
@ -4421,16 +4421,16 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'reverse' =>
|
'reverse' =>
|
||||||
array(
|
array(
|
||||||
'aphront-dialog-view-css' => '72b33589',
|
'aphront-dialog-view-css' => '570cc49c',
|
||||||
'aphront-error-view-css' => '72b33589',
|
'aphront-error-view-css' => '570cc49c',
|
||||||
'aphront-form-view-css' => '72b33589',
|
'aphront-form-view-css' => '570cc49c',
|
||||||
'aphront-list-filter-view-css' => '72b33589',
|
'aphront-list-filter-view-css' => '570cc49c',
|
||||||
'aphront-pager-view-css' => '72b33589',
|
'aphront-pager-view-css' => '570cc49c',
|
||||||
'aphront-panel-view-css' => '72b33589',
|
'aphront-panel-view-css' => '570cc49c',
|
||||||
'aphront-table-view-css' => '72b33589',
|
'aphront-table-view-css' => '570cc49c',
|
||||||
'aphront-tokenizer-control-css' => '72b33589',
|
'aphront-tokenizer-control-css' => '570cc49c',
|
||||||
'aphront-tooltip-css' => '72b33589',
|
'aphront-tooltip-css' => '570cc49c',
|
||||||
'aphront-typeahead-control-css' => '72b33589',
|
'aphront-typeahead-control-css' => '570cc49c',
|
||||||
'differential-changeset-view-css' => 'dd27a69b',
|
'differential-changeset-view-css' => 'dd27a69b',
|
||||||
'differential-core-view-css' => 'dd27a69b',
|
'differential-core-view-css' => 'dd27a69b',
|
||||||
'differential-inline-comment-editor' => '48040be9',
|
'differential-inline-comment-editor' => '48040be9',
|
||||||
|
@ -4444,7 +4444,7 @@ celerity_register_resource_map(array(
|
||||||
'differential-table-of-contents-css' => 'dd27a69b',
|
'differential-table-of-contents-css' => 'dd27a69b',
|
||||||
'diffusion-commit-view-css' => 'c8ce2d88',
|
'diffusion-commit-view-css' => 'c8ce2d88',
|
||||||
'diffusion-icons-css' => 'c8ce2d88',
|
'diffusion-icons-css' => 'c8ce2d88',
|
||||||
'global-drag-and-drop-css' => '72b33589',
|
'global-drag-and-drop-css' => '570cc49c',
|
||||||
'inline-comment-summary-css' => 'dd27a69b',
|
'inline-comment-summary-css' => 'dd27a69b',
|
||||||
'javelin-aphlict' => '75ccea43',
|
'javelin-aphlict' => '75ccea43',
|
||||||
'javelin-behavior' => 'a9f14d76',
|
'javelin-behavior' => 'a9f14d76',
|
||||||
|
@ -4517,55 +4517,55 @@ celerity_register_resource_map(array(
|
||||||
'javelin-util' => 'a9f14d76',
|
'javelin-util' => 'a9f14d76',
|
||||||
'javelin-vector' => 'a9f14d76',
|
'javelin-vector' => 'a9f14d76',
|
||||||
'javelin-workflow' => 'a9f14d76',
|
'javelin-workflow' => 'a9f14d76',
|
||||||
'lightbox-attachment-css' => '72b33589',
|
'lightbox-attachment-css' => '570cc49c',
|
||||||
'maniphest-task-summary-css' => '06bacb9a',
|
'maniphest-task-summary-css' => '06bacb9a',
|
||||||
'maniphest-transaction-detail-css' => '06bacb9a',
|
'maniphest-transaction-detail-css' => '06bacb9a',
|
||||||
'phabricator-action-list-view-css' => '72b33589',
|
'phabricator-action-list-view-css' => '570cc49c',
|
||||||
'phabricator-application-launch-view-css' => '72b33589',
|
'phabricator-application-launch-view-css' => '570cc49c',
|
||||||
'phabricator-busy' => '75ccea43',
|
'phabricator-busy' => '75ccea43',
|
||||||
'phabricator-content-source-view-css' => 'dd27a69b',
|
'phabricator-content-source-view-css' => 'dd27a69b',
|
||||||
'phabricator-core-css' => '72b33589',
|
'phabricator-core-css' => '570cc49c',
|
||||||
'phabricator-crumbs-view-css' => '72b33589',
|
'phabricator-crumbs-view-css' => '570cc49c',
|
||||||
'phabricator-drag-and-drop-file-upload' => '48040be9',
|
'phabricator-drag-and-drop-file-upload' => '48040be9',
|
||||||
'phabricator-dropdown-menu' => '75ccea43',
|
'phabricator-dropdown-menu' => '75ccea43',
|
||||||
'phabricator-file-upload' => '75ccea43',
|
'phabricator-file-upload' => '75ccea43',
|
||||||
'phabricator-filetree-view-css' => '72b33589',
|
'phabricator-filetree-view-css' => '570cc49c',
|
||||||
'phabricator-flag-css' => '72b33589',
|
'phabricator-flag-css' => '570cc49c',
|
||||||
'phabricator-form-view-css' => '72b33589',
|
'phabricator-form-view-css' => '570cc49c',
|
||||||
'phabricator-header-view-css' => '72b33589',
|
'phabricator-header-view-css' => '570cc49c',
|
||||||
'phabricator-hovercard' => '75ccea43',
|
'phabricator-hovercard' => '75ccea43',
|
||||||
'phabricator-jump-nav' => '72b33589',
|
'phabricator-jump-nav' => '570cc49c',
|
||||||
'phabricator-keyboard-shortcut' => '75ccea43',
|
'phabricator-keyboard-shortcut' => '75ccea43',
|
||||||
'phabricator-keyboard-shortcut-manager' => '75ccea43',
|
'phabricator-keyboard-shortcut-manager' => '75ccea43',
|
||||||
'phabricator-main-menu-view' => '72b33589',
|
'phabricator-main-menu-view' => '570cc49c',
|
||||||
'phabricator-menu-item' => '75ccea43',
|
'phabricator-menu-item' => '75ccea43',
|
||||||
'phabricator-nav-view-css' => '72b33589',
|
'phabricator-nav-view-css' => '570cc49c',
|
||||||
'phabricator-notification' => '75ccea43',
|
'phabricator-notification' => '75ccea43',
|
||||||
'phabricator-notification-css' => '72b33589',
|
'phabricator-notification-css' => '570cc49c',
|
||||||
'phabricator-notification-menu-css' => '72b33589',
|
'phabricator-notification-menu-css' => '570cc49c',
|
||||||
'phabricator-object-item-list-view-css' => '72b33589',
|
'phabricator-object-item-list-view-css' => '570cc49c',
|
||||||
'phabricator-object-selector-css' => 'dd27a69b',
|
'phabricator-object-selector-css' => 'dd27a69b',
|
||||||
'phabricator-phtize' => '75ccea43',
|
'phabricator-phtize' => '75ccea43',
|
||||||
'phabricator-prefab' => '75ccea43',
|
'phabricator-prefab' => '75ccea43',
|
||||||
'phabricator-project-tag-css' => '06bacb9a',
|
'phabricator-project-tag-css' => '06bacb9a',
|
||||||
'phabricator-property-list-view-css' => '72b33589',
|
'phabricator-property-list-view-css' => '570cc49c',
|
||||||
'phabricator-remarkup-css' => '72b33589',
|
'phabricator-remarkup-css' => '570cc49c',
|
||||||
'phabricator-shaped-request' => '48040be9',
|
'phabricator-shaped-request' => '48040be9',
|
||||||
'phabricator-side-menu-view-css' => '72b33589',
|
'phabricator-side-menu-view-css' => '570cc49c',
|
||||||
'phabricator-standard-page-view' => '72b33589',
|
'phabricator-standard-page-view' => '570cc49c',
|
||||||
'phabricator-tag-view-css' => '72b33589',
|
'phabricator-tag-view-css' => '570cc49c',
|
||||||
'phabricator-textareautils' => '75ccea43',
|
'phabricator-textareautils' => '75ccea43',
|
||||||
'phabricator-tooltip' => '75ccea43',
|
'phabricator-tooltip' => '75ccea43',
|
||||||
'phabricator-transaction-view-css' => '72b33589',
|
'phabricator-transaction-view-css' => '570cc49c',
|
||||||
'phabricator-zindex-css' => '72b33589',
|
'phabricator-zindex-css' => '570cc49c',
|
||||||
'phui-button-css' => '72b33589',
|
'phui-button-css' => '570cc49c',
|
||||||
'phui-form-css' => '72b33589',
|
'phui-form-css' => '570cc49c',
|
||||||
'phui-icon-view-css' => '72b33589',
|
'phui-icon-view-css' => '570cc49c',
|
||||||
'phui-spacing-css' => '72b33589',
|
'phui-spacing-css' => '570cc49c',
|
||||||
'sprite-apps-large-css' => '72b33589',
|
'sprite-apps-large-css' => '570cc49c',
|
||||||
'sprite-gradient-css' => '72b33589',
|
'sprite-gradient-css' => '570cc49c',
|
||||||
'sprite-icons-css' => '72b33589',
|
'sprite-icons-css' => '570cc49c',
|
||||||
'sprite-menu-css' => '72b33589',
|
'sprite-menu-css' => '570cc49c',
|
||||||
'syntax-highlighting-css' => '72b33589',
|
'syntax-highlighting-css' => '570cc49c',
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
|
@ -1023,6 +1023,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorDaemonEventListener' => 'applications/daemon/event/PhabricatorDaemonEventListener.php',
|
'PhabricatorDaemonEventListener' => 'applications/daemon/event/PhabricatorDaemonEventListener.php',
|
||||||
'PhabricatorDaemonLog' => 'applications/daemon/storage/PhabricatorDaemonLog.php',
|
'PhabricatorDaemonLog' => 'applications/daemon/storage/PhabricatorDaemonLog.php',
|
||||||
'PhabricatorDaemonLogEvent' => 'applications/daemon/storage/PhabricatorDaemonLogEvent.php',
|
'PhabricatorDaemonLogEvent' => 'applications/daemon/storage/PhabricatorDaemonLogEvent.php',
|
||||||
|
'PhabricatorDaemonLogEventViewController' => 'applications/daemon/controller/PhabricatorDaemonLogEventViewController.php',
|
||||||
'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/PhabricatorDaemonLogEventsView.php',
|
'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/PhabricatorDaemonLogEventsView.php',
|
||||||
'PhabricatorDaemonLogListController' => 'applications/daemon/controller/PhabricatorDaemonLogListController.php',
|
'PhabricatorDaemonLogListController' => 'applications/daemon/controller/PhabricatorDaemonLogListController.php',
|
||||||
'PhabricatorDaemonLogListView' => 'applications/daemon/view/PhabricatorDaemonLogListView.php',
|
'PhabricatorDaemonLogListView' => 'applications/daemon/view/PhabricatorDaemonLogListView.php',
|
||||||
|
@ -3029,6 +3030,7 @@ phutil_register_library_map(array(
|
||||||
1 => 'PhabricatorPolicyInterface',
|
1 => 'PhabricatorPolicyInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO',
|
'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO',
|
||||||
|
'PhabricatorDaemonLogEventViewController' => 'PhabricatorDaemonController',
|
||||||
'PhabricatorDaemonLogEventsView' => 'AphrontView',
|
'PhabricatorDaemonLogEventsView' => 'AphrontView',
|
||||||
'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController',
|
'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController',
|
||||||
'PhabricatorDaemonLogListView' => 'AphrontView',
|
'PhabricatorDaemonLogListView' => 'AphrontView',
|
||||||
|
|
|
@ -48,6 +48,7 @@ final class PhabricatorApplicationDaemons extends PhabricatorApplication {
|
||||||
'combined/' => 'PhabricatorDaemonCombinedLogController',
|
'combined/' => 'PhabricatorDaemonCombinedLogController',
|
||||||
'(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogViewController',
|
'(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogViewController',
|
||||||
),
|
),
|
||||||
|
'event/(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogEventViewController',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorDaemonLogEventViewController
|
||||||
|
extends PhabricatorDaemonController {
|
||||||
|
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
public function willProcessRequest(array $data) {
|
||||||
|
$this->id = $data['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
|
||||||
|
$event = id(new PhabricatorDaemonLogEvent())->load($this->id);
|
||||||
|
if (!$event) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$event_view = id(new PhabricatorDaemonLogEventsView())
|
||||||
|
->setEvents(array($event))
|
||||||
|
->setUser($request->getUser())
|
||||||
|
->setCombinedLog(true)
|
||||||
|
->setShowFullMessage(true);
|
||||||
|
|
||||||
|
$log_panel = new AphrontPanelView();
|
||||||
|
$log_panel->appendChild($event_view);
|
||||||
|
$log_panel->setNoBackground();
|
||||||
|
|
||||||
|
$daemon_id = $event->getLogID();
|
||||||
|
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$crumbs->addCrumb(
|
||||||
|
id(new PhabricatorCrumbView())
|
||||||
|
->setName(pht('Daemon %s', $daemon_id))
|
||||||
|
->setHref($this->getApplicationURI("log/{$daemon_id}/")));
|
||||||
|
$crumbs->addCrumb(
|
||||||
|
id(new PhabricatorCrumbView())
|
||||||
|
->setName(pht('Event %s', $event->getID())));
|
||||||
|
|
||||||
|
|
||||||
|
return $this->buildApplicationPage(
|
||||||
|
array(
|
||||||
|
$crumbs,
|
||||||
|
$log_panel,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => pht('Combined Daemon Log'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,7 +13,10 @@ final class PhabricatorDaemonLogViewController
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
$user = $request->getUser();
|
$user = $request->getUser();
|
||||||
|
|
||||||
$log = id(new PhabricatorDaemonLog())->load($this->id);
|
$log = id(new PhabricatorDaemonLogQuery())
|
||||||
|
->setViewer($user)
|
||||||
|
->withIDs(array($this->id))
|
||||||
|
->executeOne();
|
||||||
if (!$log) {
|
if (!$log) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ final class PhabricatorDaemonLogQuery
|
||||||
const STATUS_ALL = 'status-all';
|
const STATUS_ALL = 'status-all';
|
||||||
const STATUS_ALIVE = 'status-alive';
|
const STATUS_ALIVE = 'status-alive';
|
||||||
|
|
||||||
|
private $ids;
|
||||||
private $status = self::STATUS_ALL;
|
private $status = self::STATUS_ALL;
|
||||||
|
|
||||||
public static function getTimeUntilUnknown() {
|
public static function getTimeUntilUnknown() {
|
||||||
|
@ -16,6 +17,11 @@ final class PhabricatorDaemonLogQuery
|
||||||
return 30 * PhutilDaemonOverseer::HEARTBEAT_WAIT;
|
return 30 * PhutilDaemonOverseer::HEARTBEAT_WAIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withIDs(array $ids) {
|
||||||
|
$this->ids = $ids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function withStatus($status) {
|
public function withStatus($status) {
|
||||||
$this->status = $status;
|
$this->status = $status;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -89,6 +95,13 @@ final class PhabricatorDaemonLogQuery
|
||||||
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||||
$where = array();
|
$where = array();
|
||||||
|
|
||||||
|
if ($this->ids) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'id IN (%Ld)',
|
||||||
|
$this->ids);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->getStatusConstants()) {
|
if ($this->getStatusConstants()) {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn_r,
|
$conn_r,
|
||||||
|
|
|
@ -4,6 +4,13 @@ final class PhabricatorDaemonLogEventsView extends AphrontView {
|
||||||
|
|
||||||
private $events;
|
private $events;
|
||||||
private $combinedLog;
|
private $combinedLog;
|
||||||
|
private $showFullMessage;
|
||||||
|
|
||||||
|
|
||||||
|
public function setShowFullMessage($show_full_message) {
|
||||||
|
$this->showFullMessage = $show_full_message;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function setEvents(array $events) {
|
public function setEvents(array $events) {
|
||||||
assert_instances_of($events, 'PhabricatorDaemonLogEvent');
|
assert_instances_of($events, 'PhabricatorDaemonLogEvent');
|
||||||
|
@ -31,7 +38,9 @@ final class PhabricatorDaemonLogEventsView extends AphrontView {
|
||||||
// truncation if that doesn't get things short enough.
|
// truncation if that doesn't get things short enough.
|
||||||
|
|
||||||
$message = $event->getMessage();
|
$message = $event->getMessage();
|
||||||
|
$more = null;
|
||||||
|
|
||||||
|
if (!$this->showFullMessage) {
|
||||||
$more_lines = null;
|
$more_lines = null;
|
||||||
$more_chars = null;
|
$more_chars = null;
|
||||||
$line_limit = 12;
|
$line_limit = 12;
|
||||||
|
@ -50,20 +59,36 @@ final class PhabricatorDaemonLogEventsView extends AphrontView {
|
||||||
$message = implode('', $message);
|
$message = implode('', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
$more = null;
|
|
||||||
if ($more_chars) {
|
if ($more_chars) {
|
||||||
$more = number_format($more_chars);
|
$more = new PhutilNumber($more_chars);
|
||||||
$more = "\n<... {$more} more characters ...>";
|
$more = pht("Show %d more character(s)...", $more);
|
||||||
} else if ($more_lines) {
|
} else if ($more_lines) {
|
||||||
$more = number_format($more_lines);
|
$more = new PhutilNumber($more_lines);
|
||||||
$more = "\n<... {$more} more lines ...>";
|
$more = pht("Show %d more line(s)...", $more);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($more) {
|
||||||
|
$id = $event->getID();
|
||||||
|
$more = array(
|
||||||
|
"\n...\n",
|
||||||
|
phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => "/daemon/event/{$id}/",
|
||||||
|
),
|
||||||
|
$more),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$row = array(
|
$row = array(
|
||||||
$event->getLogType(),
|
$event->getLogType(),
|
||||||
phabricator_date($event->getEpoch(), $this->user),
|
phabricator_date($event->getEpoch(), $this->user),
|
||||||
phabricator_time($event->getEpoch(), $this->user),
|
phabricator_time($event->getEpoch(), $this->user),
|
||||||
phutil_escape_html_newlines($message.$more),
|
array(
|
||||||
|
$message,
|
||||||
|
$more,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($this->combinedLog) {
|
if ($this->combinedLog) {
|
||||||
|
@ -84,7 +109,7 @@ final class PhabricatorDaemonLogEventsView extends AphrontView {
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'right',
|
'right',
|
||||||
'wide wrap',
|
'wide prewrap',
|
||||||
);
|
);
|
||||||
|
|
||||||
$headers = array(
|
$headers = array(
|
||||||
|
|
|
@ -168,6 +168,12 @@ th.aphront-table-view-sortable-selected {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aphront-table-view td.prewrap {
|
||||||
|
font-family: "Monaco", monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.aphront-table-view td.narrow {
|
.aphront-table-view td.narrow {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue