1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 02:02:41 +01:00

Add a "status" property to Almanac devices

Summary:
Ref T13641. Add a "status" property with most of the relevant support code.

This currently has no impact on use of the device or bindings by Diffusion or Drydock: they ignore the status of devices bound to services.

Test Plan:
  - Created a new device.
  - Changed the status of a device via web and API.
  - Queried for devices via API.
  - Searched for active and disabled devices.
  - Viewed UI in list view, detail view.
  - Used typeahead to add a new binding to an interface on a disabled device, got disabled hint in typeahead UI.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13641

Differential Revision: https://secure.phabricator.com/D21627
This commit is contained in:
epriestley 2021-03-16 11:05:33 -07:00
parent 9003a45369
commit 5d64fb1815
13 changed files with 274 additions and 2 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
ADD status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_almanac.almanac_device
SET status = 'active' WHERE status = '';

View file

@ -62,6 +62,8 @@ phutil_register_library_map(array(
'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php', 'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php',
'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
'AlmanacDeviceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php', 'AlmanacDeviceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php',
'AlmanacDeviceStatus' => 'applications/almanac/constants/AlmanacDeviceStatus.php',
'AlmanacDeviceStatusTransaction' => 'applications/almanac/xaction/AlmanacDeviceStatusTransaction.php',
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php', 'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php',
@ -6089,6 +6091,8 @@ phutil_register_library_map(array(
'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacDeviceSetPropertyTransaction' => 'AlmanacDeviceTransactionType', 'AlmanacDeviceSetPropertyTransaction' => 'AlmanacDeviceTransactionType',
'AlmanacDeviceStatus' => 'Phobject',
'AlmanacDeviceStatusTransaction' => 'AlmanacDeviceTransactionType',
'AlmanacDeviceTransaction' => 'AlmanacModularTransaction', 'AlmanacDeviceTransaction' => 'AlmanacModularTransaction',
'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacDeviceTransactionType' => 'AlmanacTransactionType', 'AlmanacDeviceTransactionType' => 'AlmanacTransactionType',

View file

@ -0,0 +1,89 @@
<?php
final class AlmanacDeviceStatus
extends Phobject {
const ACTIVE = 'active';
const DISABLED = 'disabled';
private $value;
public static function newStatusFromValue($value) {
$status = new self();
$status->value = $value;
return $status;
}
public function getValue() {
return $this->value;
}
public function getName() {
$name = $this->getDeviceStatusProperty('name');
if ($name === null) {
$name = pht('Unknown Almanac Device Status ("%s")', $this->getValue());
}
return $name;
}
public function getIconIcon() {
return $this->getDeviceStatusProperty('icon.icon');
}
public function getIconColor() {
return $this->getDeviceStatusProperty('icon.color');
}
public function isDisabled() {
return ($this->getValue() === self::DISABLED);
}
public function hasStatusTag() {
return ($this->getStatusTagIcon() !== null);
}
public function getStatusTagIcon() {
return $this->getDeviceStatusProperty('status-tag.icon');
}
public function getStatusTagColor() {
return $this->getDeviceStatusProperty('status-tag.color');
}
public static function getStatusMap() {
$result = array();
foreach (self::newDeviceStatusMap() as $status_value => $ignored) {
$result[$status_value] = self::newStatusFromValue($status_value);
}
return $result;
}
private function getDeviceStatusProperty($key, $default = null) {
$map = self::newDeviceStatusMap();
$properties = idx($map, $this->getValue(), array());
return idx($properties, $key, $default);
}
private static function newDeviceStatusMap() {
return array(
self::ACTIVE => array(
'name' => pht('Active'),
'icon.icon' => 'fa-server',
'icon.color' => 'green',
),
self::DISABLED => array(
'name' => pht('Disabled'),
'icon.icon' => 'fa-times',
'icon.color' => 'grey',
'status-tag.icon' => 'fa-times',
'status-tag.color' => 'indigo',
),
);
}
}

View file

@ -31,6 +31,14 @@ final class AlmanacDeviceViewController
->setPolicyObject($device) ->setPolicyObject($device)
->setHeaderIcon('fa-server'); ->setHeaderIcon('fa-server');
$status = $device->getStatusObject();
if ($status->hasStatusTag()) {
$header->setStatus(
$status->getStatusTagIcon(),
$status->getStatusTagColor(),
$status->getName());
}
$issue = null; $issue = null;
if ($device->isClusterDevice()) { if ($device->isClusterDevice()) {
$issue = $this->addClusterMessage( $issue = $this->addClusterMessage(

View file

@ -76,6 +76,8 @@ final class AlmanacDeviceEditEngine
} }
protected function buildCustomEditFields($object) { protected function buildCustomEditFields($object) {
$status_map = $this->getDeviceStatusMap($object);
return array( return array(
id(new PhabricatorTextEditField()) id(new PhabricatorTextEditField())
->setKey('name') ->setKey('name')
@ -84,7 +86,32 @@ final class AlmanacDeviceEditEngine
->setTransactionType(AlmanacDeviceNameTransaction::TRANSACTIONTYPE) ->setTransactionType(AlmanacDeviceNameTransaction::TRANSACTIONTYPE)
->setIsRequired(true) ->setIsRequired(true)
->setValue($object->getName()), ->setValue($object->getName()),
id(new PhabricatorSelectEditField())
->setKey('status')
->setLabel(pht('Status'))
->setDescription(pht('Device status.'))
->setTransactionType(AlmanacDeviceStatusTransaction::TRANSACTIONTYPE)
->setOptions($status_map)
->setValue($object->getStatus()),
); );
} }
private function getDeviceStatusMap(AlmanacDevice $device) {
$status_map = AlmanacDeviceStatus::getStatusMap();
// If the device currently has an unknown status, add it to the list for
// the dropdown.
$status_value = $device->getStatus();
if (!isset($status_map[$status_value])) {
$status_map = array(
$status_value => AlmanacDeviceStatus::newStatusFromValue($status_value),
) + $status_map;
}
$status_map = mpull($status_map, 'getName');
return $status_map;
}
} }

View file

@ -34,7 +34,8 @@ final class AlmanacInterfacePHIDType extends PhabricatorPHIDType {
$id = $interface->getID(); $id = $interface->getID();
$device_name = $interface->getDevice()->getName(); $device = $interface->getDevice();
$device_name = $device->getName();
$address = $interface->getAddress(); $address = $interface->getAddress();
$port = $interface->getPort(); $port = $interface->getPort();
$network = $interface->getNetwork()->getName(); $network = $interface->getNetwork()->getName();
@ -48,6 +49,10 @@ final class AlmanacInterfacePHIDType extends PhabricatorPHIDType {
$handle->setObjectName(pht('Interface %d', $id)); $handle->setObjectName(pht('Interface %d', $id));
$handle->setName($name); $handle->setName($name);
if ($device->isDisabled()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
} }
} }

View file

@ -9,6 +9,7 @@ final class AlmanacDeviceQuery
private $namePrefix; private $namePrefix;
private $nameSuffix; private $nameSuffix;
private $isClusterDevice; private $isClusterDevice;
private $statuses;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -35,6 +36,11 @@ final class AlmanacDeviceQuery
return $this; return $this;
} }
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withNameNgrams($ngrams) { public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint( return $this->withNgramsConstraint(
new AlmanacDeviceNameNgrams(), new AlmanacDeviceNameNgrams(),
@ -103,6 +109,13 @@ final class AlmanacDeviceQuery
(int)$this->isClusterDevice); (int)$this->isClusterDevice);
} }
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'device.status IN (%Ls)',
$this->statuses);
}
return $where; return $where;
} }

View file

@ -16,6 +16,9 @@ final class AlmanacDeviceSearchEngine
} }
protected function buildCustomSearchFields() { protected function buildCustomSearchFields() {
$status_options = AlmanacDeviceStatus::getStatusMap();
$status_options = mpull($status_options, 'getName');
return array( return array(
id(new PhabricatorSearchTextField()) id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains')) ->setLabel(pht('Name Contains'))
@ -25,6 +28,11 @@ final class AlmanacDeviceSearchEngine
->setLabel(pht('Exact Names')) ->setLabel(pht('Exact Names'))
->setKey('names') ->setKey('names')
->setDescription(pht('Search for devices with specific names.')), ->setDescription(pht('Search for devices with specific names.')),
id(new PhabricatorSearchCheckboxesField())
->setLabel(pht('Statuses'))
->setKey('statuses')
->setDescription(pht('Search for devices with given statuses.'))
->setOptions($status_options),
id(new PhabricatorSearchThreeStateField()) id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Cluster Device')) ->setLabel(pht('Cluster Device'))
->setKey('isClusterDevice') ->setKey('isClusterDevice')
@ -50,6 +58,10 @@ final class AlmanacDeviceSearchEngine
$query->withIsClusterDevice($map['isClusterDevice']); $query->withIsClusterDevice($map['isClusterDevice']);
} }
if ($map['statuses']) {
$query->withStatuses($map['statuses']);
}
return $query; return $query;
} }
@ -99,6 +111,19 @@ final class AlmanacDeviceSearchEngine
$item->addIcon('fa-sitemap', pht('Cluster Device')); $item->addIcon('fa-sitemap', pht('Cluster Device'));
} }
if ($device->isDisabled()) {
$item->setDisabled(true);
}
$status = $device->getStatusObject();
$icon_icon = $status->getIconIcon();
$icon_color = $status->getIconColor();
$icon_label = $status->getName();
$item->setStatusIcon(
"{$icon_icon} {$icon_color}",
$icon_label);
$list->addItem($item); $list->addItem($item);
} }

View file

@ -17,6 +17,7 @@ final class AlmanacDevice
protected $nameIndex; protected $nameIndex;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
protected $status;
protected $isBoundToClusterService; protected $isBoundToClusterService;
private $almanacProperties = self::ATTACHABLE; private $almanacProperties = self::ATTACHABLE;
@ -25,6 +26,7 @@ final class AlmanacDevice
return id(new AlmanacDevice()) return id(new AlmanacDevice())
->setViewPolicy(PhabricatorPolicies::POLICY_USER) ->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN)
->setStatus(AlmanacDeviceStatus::ACTIVE)
->attachAlmanacProperties(array()) ->attachAlmanacProperties(array())
->setIsBoundToClusterService(0); ->setIsBoundToClusterService(0);
} }
@ -35,6 +37,7 @@ final class AlmanacDevice
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text128', 'name' => 'text128',
'nameIndex' => 'bytes12', 'nameIndex' => 'bytes12',
'status' => 'text32',
'isBoundToClusterService' => 'bool', 'isBoundToClusterService' => 'bool',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
@ -100,6 +103,18 @@ final class AlmanacDevice
return $this->getIsBoundToClusterService(); return $this->getIsBoundToClusterService();
} }
public function getStatusObject() {
return $this->newStatusObject();
}
private function newStatusObject() {
return AlmanacDeviceStatus::newStatusFromValue($this->getStatus());
}
public function isDisabled() {
return $this->getStatusObject()->isDisabled();
}
/* -( AlmanacPropertyInterface )------------------------------------------- */ /* -( AlmanacPropertyInterface )------------------------------------------- */
@ -263,12 +278,22 @@ final class AlmanacDevice
->setKey('name') ->setKey('name')
->setType('string') ->setType('string')
->setDescription(pht('The name of the device.')), ->setDescription(pht('The name of the device.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('status')
->setType('map<string, wild>')
->setDescription(pht('Device status information.')),
); );
} }
public function getFieldValuesForConduit() { public function getFieldValuesForConduit() {
$status = $this->getStatusObject();
return array( return array(
'name' => $this->getName(), 'name' => $this->getName(),
'status' => array(
'value' => $status->getValue(),
'name' => $status->getName(),
),
); );
} }

View file

@ -46,9 +46,16 @@ final class AlmanacInterfaceDatasource
$results = array(); $results = array();
foreach ($handles as $handle) { foreach ($handles as $handle) {
if ($handle->isClosed()) {
$closed = pht('Disabled');
} else {
$closed = null;
}
$results[] = id(new PhabricatorTypeaheadResult()) $results[] = id(new PhabricatorTypeaheadResult())
->setName($handle->getName()) ->setName($handle->getName())
->setPHID($handle->getPHID()); ->setPHID($handle->getPHID())
->setClosed($closed);
} }
return $results; return $results;

View file

@ -0,0 +1,61 @@
<?php
final class AlmanacDeviceStatusTransaction
extends AlmanacDeviceTransactionType {
const TRANSACTIONTYPE = 'almanac:device:status';
public function generateOldValue($object) {
return $object->getStatus();
}
public function applyInternalEffects($object, $value) {
$object->setStatus($value);
}
public function getTitle() {
$old_value = $this->getOldValue();
$new_value = $this->getNewValue();
$old_status = AlmanacDeviceStatus::newStatusFromValue($old_value);
$new_status = AlmanacDeviceStatus::newStatusFromValue($new_value);
$old_name = $old_status->getName();
$new_name = $new_status->getName();
return pht(
'%s changed the status of this device from %s to %s.',
$this->renderAuthor(),
$this->renderValue($old_name),
$this->renderValue($new_name));
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$status_map = AlmanacDeviceStatus::getStatusMap();
$old_value = $this->generateOldValue($object);
foreach ($xactions as $xaction) {
$new_value = $xaction->getNewValue();
if ($new_value === $old_value) {
continue;
}
if (!isset($status_map[$new_value])) {
$errors[] = $this->newInvalidError(
pht(
'Almanac device status "%s" is unrecognized. Valid status '.
'values are: %s.',
$new_value,
implode(', ', array_keys($status_map))),
$xaction);
continue;
}
}
return $errors;
}
}

View file

@ -197,6 +197,10 @@ final class PhabricatorObjectHandle
return $this->status; return $this->status;
} }
public function isClosed() {
return ($this->status === self::STATUS_CLOSED);
}
public function setFullName($full_name) { public function setFullName($full_name) {
$this->fullName = $full_name; $this->fullName = $full_name;
return $this; return $this;