mirror of
https://github.com/Icinga/icingaweb2-module-director.git
synced 2025-07-31 01:34:12 +02:00
parent
9611381956
commit
8bcc20e004
@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Db\Branch;
|
|||||||
use Icinga\Module\Director\Data\Db\DbObject;
|
use Icinga\Module\Director\Data\Db\DbObject;
|
||||||
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
|
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
|
||||||
use Icinga\Module\Director\Db;
|
use Icinga\Module\Director\Db;
|
||||||
|
use Icinga\Module\Director\Objects\DirectorActivityLog;
|
||||||
use Ramsey\Uuid\UuidInterface;
|
use Ramsey\Uuid\UuidInterface;
|
||||||
|
|
||||||
class BranchMerger
|
class BranchMerger
|
||||||
@ -82,9 +83,10 @@ class BranchMerger
|
|||||||
/**
|
/**
|
||||||
* @throws MergeError
|
* @throws MergeError
|
||||||
*/
|
*/
|
||||||
public function merge()
|
public function merge($comment)
|
||||||
{
|
{
|
||||||
$this->connection->runFailSafeTransaction(function () {
|
$this->connection->runFailSafeTransaction(function () use ($comment) {
|
||||||
|
$formerActivityId = (int) DirectorActivityLog::loadLatest($this->connection)->get('id');
|
||||||
$query = $this->db->select()
|
$query = $this->db->select()
|
||||||
->from(BranchActivity::DB_TABLE)
|
->from(BranchActivity::DB_TABLE)
|
||||||
->where('branch_uuid = ?', $this->connection->quoteBinary($this->branchUuid->getBytes()))
|
->where('branch_uuid = ?', $this->connection->quoteBinary($this->branchUuid->getBytes()))
|
||||||
@ -95,6 +97,15 @@ class BranchMerger
|
|||||||
$this->applyModification($activity);
|
$this->applyModification($activity);
|
||||||
}
|
}
|
||||||
(new BranchStore($this->connection))->deleteByUuid($this->branchUuid);
|
(new BranchStore($this->connection))->deleteByUuid($this->branchUuid);
|
||||||
|
$currentActivityId = (int) DirectorActivityLog::loadLatest($this->connection)->get('id');
|
||||||
|
$firstActivityId = (int) $this->db->fetchOne(
|
||||||
|
$this->db->select()->from('director_activity_log', 'MIN(id)')->where('id > ?', $formerActivityId)
|
||||||
|
);
|
||||||
|
$this->db->insert('director_activity_log_remark', [
|
||||||
|
'first_related_activity' => $firstActivityId,
|
||||||
|
'last_related_activity' => $currentActivityId,
|
||||||
|
'remark' => $comment,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,11 @@
|
|||||||
namespace Icinga\Module\Director\Web\Table;
|
namespace Icinga\Module\Director\Web\Table;
|
||||||
|
|
||||||
use gipfl\Format\LocalTimeFormat;
|
use gipfl\Format\LocalTimeFormat;
|
||||||
use Icinga\Module\Director\Util;
|
|
||||||
use ipl\Html\BaseHtmlElement;
|
|
||||||
use gipfl\IcingaWeb2\Link;
|
use gipfl\IcingaWeb2\Link;
|
||||||
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
|
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
|
||||||
|
use Icinga\Module\Director\Util;
|
||||||
|
use ipl\Html\Html;
|
||||||
|
use ipl\Html\HtmlElement;
|
||||||
|
|
||||||
class ActivityLogTable extends ZfQueryBasedTable
|
class ActivityLogTable extends ZfQueryBasedTable
|
||||||
{
|
{
|
||||||
@ -20,12 +21,6 @@ class ActivityLogTable extends ZfQueryBasedTable
|
|||||||
|
|
||||||
protected $hasObjectFilter = false;
|
protected $hasObjectFilter = false;
|
||||||
|
|
||||||
/** @var BaseHtmlElement */
|
|
||||||
protected $currentHead;
|
|
||||||
|
|
||||||
/** @var BaseHtmlElement */
|
|
||||||
protected $currentBody;
|
|
||||||
|
|
||||||
protected $searchColumns = [
|
protected $searchColumns = [
|
||||||
'author',
|
'author',
|
||||||
'object_name',
|
'object_name',
|
||||||
@ -35,6 +30,17 @@ class ActivityLogTable extends ZfQueryBasedTable
|
|||||||
/** @var LocalTimeFormat */
|
/** @var LocalTimeFormat */
|
||||||
protected $timeFormat;
|
protected $timeFormat;
|
||||||
|
|
||||||
|
protected $ranges = [];
|
||||||
|
|
||||||
|
/** @var ?object */
|
||||||
|
protected $currentRange = null;
|
||||||
|
/** @var ?HtmlElement */
|
||||||
|
protected $currentRangeCell = null;
|
||||||
|
/** @var int */
|
||||||
|
protected $rangeRows = 0;
|
||||||
|
protected $continueRange = false;
|
||||||
|
protected $currentRow;
|
||||||
|
|
||||||
public function __construct($db)
|
public function __construct($db)
|
||||||
{
|
{
|
||||||
parent::__construct($db);
|
parent::__construct($db);
|
||||||
@ -52,8 +58,27 @@ class ActivityLogTable extends ZfQueryBasedTable
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function fetchQueryRows()
|
||||||
|
{
|
||||||
|
$rows = parent::fetchQueryRows();
|
||||||
|
// Hint -> DESC, that's why they are inverted
|
||||||
|
$last = $rows[0]->id;
|
||||||
|
$first = $rows[count($rows) - 1]->id;
|
||||||
|
$db = $this->db();
|
||||||
|
$this->ranges = $db->fetchAll(
|
||||||
|
$db->select()
|
||||||
|
->from('director_activity_log_remark')
|
||||||
|
->where('first_related_activity <= ?', $last)
|
||||||
|
->where('last_related_activity >= ?', $first)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function renderRow($row)
|
public function renderRow($row)
|
||||||
{
|
{
|
||||||
|
$this->currentRow = $row;
|
||||||
$this->splitByDay($row->ts_change_time);
|
$this->splitByDay($row->ts_change_time);
|
||||||
$action = 'action-' . $row->action. ' ';
|
$action = 'action-' . $row->action. ' ';
|
||||||
if ($row->id > $this->lastDeployedId) {
|
if ($row->id > $this->lastDeployedId) {
|
||||||
@ -62,10 +87,109 @@ class ActivityLogTable extends ZfQueryBasedTable
|
|||||||
$action .= 'deployed';
|
$action .= 'deployed';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this::tr([
|
$columns = [
|
||||||
$this::td($this->makeLink($row))->setSeparator(' '),
|
$this::td($this->makeLink($row))->setSeparator(' '),
|
||||||
$this::td($this->timeFormat->getTime($row->ts_change_time))
|
];
|
||||||
])->addAttributes(['class' => $action]);
|
if (! $this->hasObjectFilter) {
|
||||||
|
$columns[] = $this->makeRangeInfo($row->id);
|
||||||
|
}
|
||||||
|
$columns[] = $this::td($this->timeFormat->getTime($row->ts_change_time));
|
||||||
|
|
||||||
|
return $this::tr($columns)->addAttributes(['class' => $action]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hint: cloned from parent class and modified
|
||||||
|
* @param int $timestamp
|
||||||
|
*/
|
||||||
|
protected function renderDayIfNew($timestamp)
|
||||||
|
{
|
||||||
|
$day = $this->getDateFormatter()->getFullDay($timestamp);
|
||||||
|
|
||||||
|
if ($this->lastDay !== $day) {
|
||||||
|
$this->nextHeader()->add(
|
||||||
|
$this::th($day, [
|
||||||
|
'colspan' => $this->hasObjectFilter ? 2 : 3,
|
||||||
|
'class' => 'table-header-day'
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->lastDay = $day;
|
||||||
|
if ($this->currentRangeCell) {
|
||||||
|
if ($this->currentRange->first_related_activity <= $this->currentRow->id) {
|
||||||
|
$this->currentRangeCell->addAttributes(['class' => 'continuing']);
|
||||||
|
$this->continueRange = true;
|
||||||
|
} else {
|
||||||
|
$this->continueRange = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->currentRangeCell = null;
|
||||||
|
$this->currentRange = null;
|
||||||
|
$this->rangeRows = 0;
|
||||||
|
$this->nextBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function makeRangeInfo($id)
|
||||||
|
{
|
||||||
|
$range = $this->getRangeForId($id);
|
||||||
|
if ($range === null) {
|
||||||
|
if ($this->currentRangeCell) {
|
||||||
|
$this->currentRangeCell->getAttributes()->remove('class', 'continuing');
|
||||||
|
}
|
||||||
|
$this->currentRange = null;
|
||||||
|
$this->currentRangeCell = null;
|
||||||
|
$this->rangeRows = 0;
|
||||||
|
return $this::td();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($range === $this->currentRange) {
|
||||||
|
$this->growCurrentRange();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$this->startRange($range);
|
||||||
|
|
||||||
|
return $this->currentRangeCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function startRange($range)
|
||||||
|
{
|
||||||
|
$this->currentRangeCell = $this::td($this->renderRangeComment($range), [
|
||||||
|
'colspan' => $this->rangeRows = 1,
|
||||||
|
'class' => 'comment-cell'
|
||||||
|
]);
|
||||||
|
if ($this->continueRange) {
|
||||||
|
$this->currentRangeCell->addAttributes(['class' => 'continued']);
|
||||||
|
$this->continueRange = false;
|
||||||
|
}
|
||||||
|
$this->currentRange = $range;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderRangeComment($range)
|
||||||
|
{
|
||||||
|
// The only purpose of this container is to avoid hovered rows from influencing
|
||||||
|
// the comments background color, as we're using the alpha channel to lighten it
|
||||||
|
// This can be replaced once we get theme-safe colors for such messages
|
||||||
|
return Html::tag('div', [
|
||||||
|
'class' => 'range-comment-container',
|
||||||
|
], Link::create($this->continueRange ? '' : $range->remark, '#', null, ['class' => 'range-comment']));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function growCurrentRange()
|
||||||
|
{
|
||||||
|
$this->rangeRows++;
|
||||||
|
$this->currentRangeCell->setAttribute('rowspan', $this->rangeRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRangeForId($id)
|
||||||
|
{
|
||||||
|
foreach ($this->ranges as $range) {
|
||||||
|
if ($id >= $range->first_related_activity && $id <= $range->last_related_activity) {
|
||||||
|
return $range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeLink($row)
|
protected function makeLink($row)
|
||||||
|
@ -1297,6 +1297,9 @@ th.table-header-day {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table.activity-log {
|
table.activity-log {
|
||||||
|
td {
|
||||||
|
max-height: 2em;
|
||||||
|
}
|
||||||
tr th:first-child {
|
tr th:first-child {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
@ -1304,6 +1307,7 @@ table.activity-log {
|
|||||||
tr td:last-child {
|
tr td:last-child {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
width:10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr td:first-child {
|
tr td:first-child {
|
||||||
@ -1362,6 +1366,57 @@ table.activity-log {
|
|||||||
tr.undeployed td:first-child::before {
|
tr.undeployed td:first-child::before {
|
||||||
color: @gray;
|
color: @gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.range-comment-container {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
background: @body-bg-color;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
a.range-comment {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
padding: 0.2em 1em;
|
||||||
|
vertical-align: middle;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
overflow-y:auto;
|
||||||
|
overflow-x:hidden;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
background: fade(@color-warning-handled, 40%);
|
||||||
|
&:hover {
|
||||||
|
background: fade(@color-warning-handled, 60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td.comment-cell {
|
||||||
|
padding: 0;
|
||||||
|
min-width: 10em;
|
||||||
|
position: relative;
|
||||||
|
&.continuing div.range-comment-container {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
a.range-comment {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.continued div.range-comment-container {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
a.range-comment {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.branch_modified {
|
tr.branch_modified {
|
||||||
|
20
schema/mysql-migrations/upgrade_178.sql
Normal file
20
schema/mysql-migrations/upgrade_178.sql
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE director_activity_log_remark (
|
||||||
|
first_related_activity BIGINT(20) UNSIGNED NOT NULL,
|
||||||
|
last_related_activity BIGINT(20) UNSIGNED NOT NULL,
|
||||||
|
remark TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (first_related_activity, last_related_activity),
|
||||||
|
CONSTRAINT activity_log_remark_begin
|
||||||
|
FOREIGN KEY first_related_activity (first_related_activity)
|
||||||
|
REFERENCES director_activity_log (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT activity_log_remark_end
|
||||||
|
FOREIGN KEY last_related_activity (last_related_activity)
|
||||||
|
REFERENCES director_activity_log (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
INSERT INTO director_schema_migration
|
||||||
|
(schema_version, migration_time)
|
||||||
|
VALUES ('178', NOW());
|
@ -49,6 +49,23 @@ CREATE TABLE director_activity_log (
|
|||||||
INDEX checksum (checksum)
|
INDEX checksum (checksum)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
CREATE TABLE director_activity_log_remark (
|
||||||
|
first_related_activity BIGINT(20) UNSIGNED NOT NULL,
|
||||||
|
last_related_activity BIGINT(20) UNSIGNED NOT NULL,
|
||||||
|
remark TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (first_related_activity, last_related_activity),
|
||||||
|
CONSTRAINT activity_log_remark_begin
|
||||||
|
FOREIGN KEY first_related_activity (first_related_activity)
|
||||||
|
REFERENCES director_activity_log (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT activity_log_remark_end
|
||||||
|
FOREIGN KEY last_related_activity (last_related_activity)
|
||||||
|
REFERENCES director_activity_log (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
CREATE TABLE director_basket (
|
CREATE TABLE director_basket (
|
||||||
uuid VARBINARY(16) NOT NULL,
|
uuid VARBINARY(16) NOT NULL,
|
||||||
basket_name VARCHAR(64) NOT NULL,
|
basket_name VARCHAR(64) NOT NULL,
|
||||||
@ -2397,4 +2414,4 @@ CREATE TABLE branched_icinga_dependency (
|
|||||||
|
|
||||||
INSERT INTO director_schema_migration
|
INSERT INTO director_schema_migration
|
||||||
(schema_version, migration_time)
|
(schema_version, migration_time)
|
||||||
VALUES (176, NOW());
|
VALUES (178, NOW());
|
||||||
|
23
schema/pgsql-migrations/upgrade_178.sql
Normal file
23
schema/pgsql-migrations/upgrade_178.sql
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
CREATE TABLE director_activity_log_remark (
|
||||||
|
first_related_activity bigint NOT NULL,
|
||||||
|
last_related_activity bigint NOT NULL,
|
||||||
|
remark TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (first_related_activity, last_related_activity),
|
||||||
|
CONSTRAINT activity_log_remark_begin
|
||||||
|
FOREIGN KEY (first_related_activity)
|
||||||
|
REFERENCES director_activity_log (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT activity_log_remark_end
|
||||||
|
FOREIGN KEY (last_related_activity)
|
||||||
|
REFERENCES director_activity_log (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX first_related_activity ON director_activity_log_remark (first_related_activity);
|
||||||
|
CREATE INDEX last_related_activity ON director_activity_log_remark (last_related_activity);
|
||||||
|
|
||||||
|
INSERT INTO director_schema_migration
|
||||||
|
(schema_version, migration_time)
|
||||||
|
VALUES (178, NOW());
|
@ -97,6 +97,26 @@ CREATE INDEX activity_log_author ON director_activity_log (author);
|
|||||||
COMMENT ON COLUMN director_activity_log.old_properties IS 'Property hash, JSON';
|
COMMENT ON COLUMN director_activity_log.old_properties IS 'Property hash, JSON';
|
||||||
COMMENT ON COLUMN director_activity_log.new_properties IS 'Property hash, JSON';
|
COMMENT ON COLUMN director_activity_log.new_properties IS 'Property hash, JSON';
|
||||||
|
|
||||||
|
CREATE TABLE director_activity_log_remark (
|
||||||
|
first_related_activity bigint NOT NULL,
|
||||||
|
last_related_activity bigint NOT NULL,
|
||||||
|
remark TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (first_related_activity, last_related_activity),
|
||||||
|
CONSTRAINT activity_log_remark_begin
|
||||||
|
FOREIGN KEY (first_related_activity)
|
||||||
|
REFERENCES director_activity_log (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT activity_log_remark_end
|
||||||
|
FOREIGN KEY (last_related_activity)
|
||||||
|
REFERENCES director_activity_log (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX first_related_activity ON director_activity_log_remark (first_related_activity);
|
||||||
|
CREATE INDEX last_related_activity ON director_activity_log_remark (last_related_activity);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE director_basket (
|
CREATE TABLE director_basket (
|
||||||
uuid bytea CHECK(LENGTH(uuid) = 16) NOT NULL,
|
uuid bytea CHECK(LENGTH(uuid) = 16) NOT NULL,
|
||||||
@ -2743,4 +2763,4 @@ CREATE INDEX branched_dependency_search_object_name ON branched_icinga_dependenc
|
|||||||
|
|
||||||
INSERT INTO director_schema_migration
|
INSERT INTO director_schema_migration
|
||||||
(schema_version, migration_time)
|
(schema_version, migration_time)
|
||||||
VALUES (176, NOW());
|
VALUES (178, NOW());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user