diff --git a/resources/sql/autopatches/20210316.almanac.03.device-status.sql b/resources/sql/autopatches/20210316.almanac.03.device-status.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20210316.almanac.03.device-status.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_almanac.almanac_device + ADD status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20210316.almanac.04.device-status-value.sql b/resources/sql/autopatches/20210316.almanac.04.device-status-value.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20210316.almanac.04.device-status-value.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_almanac.almanac_device + SET status = 'active' WHERE status = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -62,6 +62,8 @@ 'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php', 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.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', 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php', @@ -6089,6 +6091,8 @@ 'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacDeviceSetPropertyTransaction' => 'AlmanacDeviceTransactionType', + 'AlmanacDeviceStatus' => 'Phobject', + 'AlmanacDeviceStatusTransaction' => 'AlmanacDeviceTransactionType', 'AlmanacDeviceTransaction' => 'AlmanacModularTransaction', 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacDeviceTransactionType' => 'AlmanacTransactionType', diff --git a/src/applications/almanac/constants/AlmanacDeviceStatus.php b/src/applications/almanac/constants/AlmanacDeviceStatus.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/constants/AlmanacDeviceStatus.php @@ -0,0 +1,89 @@ +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', + ), + ); + } + + +} diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php --- a/src/applications/almanac/controller/AlmanacDeviceViewController.php +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -31,6 +31,14 @@ ->setPolicyObject($device) ->setHeaderIcon('fa-server'); + $status = $device->getStatusObject(); + if ($status->hasStatusTag()) { + $header->setStatus( + $status->getStatusTagIcon(), + $status->getStatusTagColor(), + $status->getName()); + } + $issue = null; if ($device->isClusterDevice()) { $issue = $this->addClusterMessage( diff --git a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php --- a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php @@ -76,6 +76,8 @@ } protected function buildCustomEditFields($object) { + $status_map = $this->getDeviceStatusMap($object); + return array( id(new PhabricatorTextEditField()) ->setKey('name') @@ -84,7 +86,32 @@ ->setTransactionType(AlmanacDeviceNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->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; + } + } diff --git a/src/applications/almanac/phid/AlmanacInterfacePHIDType.php b/src/applications/almanac/phid/AlmanacInterfacePHIDType.php --- a/src/applications/almanac/phid/AlmanacInterfacePHIDType.php +++ b/src/applications/almanac/phid/AlmanacInterfacePHIDType.php @@ -34,7 +34,8 @@ $id = $interface->getID(); - $device_name = $interface->getDevice()->getName(); + $device = $interface->getDevice(); + $device_name = $device->getName(); $address = $interface->getAddress(); $port = $interface->getPort(); $network = $interface->getNetwork()->getName(); @@ -48,6 +49,10 @@ $handle->setObjectName(pht('Interface %d', $id)); $handle->setName($name); + + if ($device->isDisabled()) { + $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); + } } } diff --git a/src/applications/almanac/query/AlmanacDeviceQuery.php b/src/applications/almanac/query/AlmanacDeviceQuery.php --- a/src/applications/almanac/query/AlmanacDeviceQuery.php +++ b/src/applications/almanac/query/AlmanacDeviceQuery.php @@ -9,6 +9,7 @@ private $namePrefix; private $nameSuffix; private $isClusterDevice; + private $statuses; public function withIDs(array $ids) { $this->ids = $ids; @@ -35,6 +36,11 @@ return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function withNameNgrams($ngrams) { return $this->withNgramsConstraint( new AlmanacDeviceNameNgrams(), @@ -103,6 +109,13 @@ (int)$this->isClusterDevice); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'device.status IN (%Ls)', + $this->statuses); + } + return $where; } diff --git a/src/applications/almanac/query/AlmanacDeviceSearchEngine.php b/src/applications/almanac/query/AlmanacDeviceSearchEngine.php --- a/src/applications/almanac/query/AlmanacDeviceSearchEngine.php +++ b/src/applications/almanac/query/AlmanacDeviceSearchEngine.php @@ -16,6 +16,9 @@ } protected function buildCustomSearchFields() { + $status_options = AlmanacDeviceStatus::getStatusMap(); + $status_options = mpull($status_options, 'getName'); + return array( id(new PhabricatorSearchTextField()) ->setLabel(pht('Name Contains')) @@ -25,6 +28,11 @@ ->setLabel(pht('Exact Names')) ->setKey('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()) ->setLabel(pht('Cluster Device')) ->setKey('isClusterDevice') @@ -50,6 +58,10 @@ $query->withIsClusterDevice($map['isClusterDevice']); } + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } + return $query; } @@ -99,6 +111,19 @@ $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); } diff --git a/src/applications/almanac/storage/AlmanacDevice.php b/src/applications/almanac/storage/AlmanacDevice.php --- a/src/applications/almanac/storage/AlmanacDevice.php +++ b/src/applications/almanac/storage/AlmanacDevice.php @@ -17,6 +17,7 @@ protected $nameIndex; protected $viewPolicy; protected $editPolicy; + protected $status; protected $isBoundToClusterService; private $almanacProperties = self::ATTACHABLE; @@ -25,6 +26,7 @@ return id(new AlmanacDevice()) ->setViewPolicy(PhabricatorPolicies::POLICY_USER) ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) + ->setStatus(AlmanacDeviceStatus::ACTIVE) ->attachAlmanacProperties(array()) ->setIsBoundToClusterService(0); } @@ -35,6 +37,7 @@ self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'nameIndex' => 'bytes12', + 'status' => 'text32', 'isBoundToClusterService' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( @@ -100,6 +103,18 @@ 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 )------------------------------------------- */ @@ -263,12 +278,22 @@ ->setKey('name') ->setType('string') ->setDescription(pht('The name of the device.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('map') + ->setDescription(pht('Device status information.')), ); } public function getFieldValuesForConduit() { + $status = $this->getStatusObject(); + return array( 'name' => $this->getName(), + 'status' => array( + 'value' => $status->getValue(), + 'name' => $status->getName(), + ), ); } diff --git a/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php b/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php --- a/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php +++ b/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php @@ -46,9 +46,16 @@ $results = array(); foreach ($handles as $handle) { + if ($handle->isClosed()) { + $closed = pht('Disabled'); + } else { + $closed = null; + } + $results[] = id(new PhabricatorTypeaheadResult()) ->setName($handle->getName()) - ->setPHID($handle->getPHID()); + ->setPHID($handle->getPHID()) + ->setClosed($closed); } return $results; diff --git a/src/applications/almanac/xaction/AlmanacDeviceStatusTransaction.php b/src/applications/almanac/xaction/AlmanacDeviceStatusTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacDeviceStatusTransaction.php @@ -0,0 +1,61 @@ +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; + } + +} diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -197,6 +197,10 @@ return $this->status; } + public function isClosed() { + return ($this->status === self::STATUS_CLOSED); + } + public function setFullName($full_name) { $this->fullName = $full_name; return $this;