diff --git a/resources/sql/autopatches/20141016.almanac.interface.sql b/resources/sql/autopatches/20141016.almanac.interface.sql new file mode 100644 index 0000000000..a23450e1bd --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.interface.sql @@ -0,0 +1,13 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_interface ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + devicePHID VARBINARY(64) NOT NULL, + networkPHID VARBINARY(64) NOT NULL, + address VARCHAR(128) NOT NULL COLLATE utf8_bin, + port INT UNSIGNED NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_location` (networkPHID, address, port), + KEY `key_device` (devicePHID) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1963ba3e70..f921a39557 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -9,6 +9,7 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( + 'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php', 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', @@ -28,6 +29,11 @@ phutil_register_library_map(array( 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php', + 'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php', + 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', + 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', + 'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php', + 'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php', 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', 'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php', @@ -2940,6 +2946,7 @@ phutil_register_library_map(array( 'require_celerity_resource' => 'applications/celerity/api.php', ), 'xmap' => array( + 'AlmanacAddress' => 'Phobject', 'AlmanacConduitUtil' => 'Phobject', 'AlmanacConsoleController' => 'AlmanacController', 'AlmanacController' => 'PhabricatorController', @@ -2962,6 +2969,14 @@ phutil_register_library_map(array( 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacDeviceViewController' => 'AlmanacDeviceController', + 'AlmanacInterface' => array( + 'AlmanacDAO', + 'PhabricatorPolicyInterface', + ), + 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', + 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', + 'AlmanacInterfaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacInterfaceTableView' => 'AphrontView', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', 'AlmanacNames' => 'Phobject', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 6d9b2ee95d..59d57342f8 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -44,6 +44,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { 'edit/(?:(?P\d+)/)?' => 'AlmanacDeviceEditController', 'view/(?P[^/]+)/' => 'AlmanacDeviceViewController', ), + 'interface/' => array( + 'edit/(?:(?P\d+)/)?' => 'AlmanacInterfaceEditController', + ), 'network/' => array( '(?:query/(?P[^/]+)/)?' => 'AlmanacNetworkListController', 'edit/(?:(?P\d+)/)?' => 'AlmanacNetworkEditController', diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php index 3b9c581059..15c9102487 100644 --- a/src/applications/almanac/controller/AlmanacDeviceViewController.php +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -35,6 +35,8 @@ final class AlmanacDeviceViewController ->setHeader($header) ->addPropertyList($property_list); + $interfaces = $this->buildInterfaceList($device); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($device->getName()); @@ -53,6 +55,7 @@ final class AlmanacDeviceViewController array( $crumbs, $box, + $interfaces, $xaction_view, ), array( @@ -92,4 +95,48 @@ final class AlmanacDeviceViewController return $actions; } + private function buildInterfaceList(AlmanacDevice $device) { + $viewer = $this->getViewer(); + $id = $device->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $device, + PhabricatorPolicyCapability::CAN_EDIT); + + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($viewer) + ->withDevicePHIDs(array($device->getPHID())) + ->execute(); + + $phids = array(); + foreach ($interfaces as $interface) { + $phids[] = $interface->getNetworkPHID(); + $phids[] = $interface->getDevicePHID(); + } + $handles = $this->loadViewerHandles($phids); + + $table = id(new AlmanacInterfaceTableView()) + ->setUser($viewer) + ->setInterfaces($interfaces) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Device Interfaces')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($this->getApplicationURI("interface/edit/?deviceID={$id}")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit) + ->setText(pht('Add Interface')) + ->setIcon( + id(new PHUIIconView()) + ->setIconFont('fa-plus'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + } + } diff --git a/src/applications/almanac/controller/AlmanacInterfaceEditController.php b/src/applications/almanac/controller/AlmanacInterfaceEditController.php new file mode 100644 index 0000000000..9d9cb652e2 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacInterfaceEditController.php @@ -0,0 +1,155 @@ +getViewer(); + + $id = $request->getURIData('id'); + if ($id) { + $interface = id(new AlmanacInterfaceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$interface) { + return new Aphront404Response(); + } + + $device = $interface->getDevice(); + + $is_new = false; + $title = pht('Edit Interface'); + $save_button = pht('Save Changes'); + } else { + $device = id(new AlmanacDeviceQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getStr('deviceID'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$device) { + return new Aphront404Response(); + } + + $interface = AlmanacInterface::initializeNewInterface(); + $is_new = true; + + $title = pht('Create Interface'); + $save_button = pht('Create Interface'); + } + + $device_uri = $device->getURI(); + $cancel_uri = $device_uri; + + $v_network = $interface->getNetworkPHID(); + + $v_address = $interface->getAddress(); + $e_address = true; + + $v_port = $interface->getPort(); + + $validation_exception = null; + + if ($request->isFormPost()) { + $v_network = $request->getStr('networkPHID'); + $v_address = $request->getStr('address'); + $v_port = $request->getStr('port'); + + $type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE; + + $address = AlmanacAddress::newFromParts($v_network, $v_address, $v_port); + + $xaction = id(new AlmanacDeviceTransaction()) + ->setTransactionType($type_interface) + ->setNewValue($address->toDictionary()); + + if ($interface->getID()) { + $xaction->setOldValue(array( + 'id' => $interface->getID(), + ) + $interface->toAddress()->toDictionary()); + } else { + $xaction->setOldValue(array()); + } + + $xactions = array(); + $xactions[] = $xaction; + + $editor = id(new AlmanacDeviceEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + try { + $editor->applyTransactions($device, $xactions); + + $device_uri = $device->getURI(); + return id(new AphrontRedirectResponse())->setURI($device_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_address = $ex->getShortMessage($type_interface); + } + } + + $networks = id(new AlmanacNetworkQuery()) + ->setViewer($viewer) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Network')) + ->setName('networkPHID') + ->setValue($v_network) + ->setOptions(mpull($networks, 'getName', 'getPHID'))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Address')) + ->setName('address') + ->setValue($v_address) + ->setError($e_address)) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Port')) + ->setName('port') + ->setValue($v_port) + ->setError($e_address)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue($save_button)); + + $box = id(new PHUIObjectBoxView()) + ->setValidationException($validation_exception) + ->setHeaderText($title) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($device->getName(), $device_uri); + if ($is_new) { + $crumbs->addTextCrumb(pht('Create Interface')); + } else { + $crumbs->addTextCrumb(pht('Edit Interface')); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php index 04c4abf810..fbf8c29bbb 100644 --- a/src/applications/almanac/editor/AlmanacDeviceEditor.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php @@ -15,6 +15,7 @@ final class AlmanacDeviceEditor $types = parent::getTransactionTypes(); $types[] = AlmanacDeviceTransaction::TYPE_NAME; + $types[] = AlmanacDeviceTransaction::TYPE_INTERFACE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -38,6 +39,7 @@ final class AlmanacDeviceEditor switch ($xaction->getTransactionType()) { case AlmanacDeviceTransaction::TYPE_NAME: + case AlmanacDeviceTransaction::TYPE_INTERFACE: return $xaction->getNewValue(); } @@ -52,6 +54,7 @@ final class AlmanacDeviceEditor case AlmanacDeviceTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; + case AlmanacDeviceTransaction::TYPE_INTERFACE: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: return; @@ -69,6 +72,32 @@ final class AlmanacDeviceEditor case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: return; + case AlmanacDeviceTransaction::TYPE_INTERFACE: + $old = $xaction->getOldValue(); + if ($old) { + $interface = id(new AlmanacInterfaceQuery()) + ->setViewer($this->requireActor()) + ->withIDs(array($old['id'])) + ->executeOne(); + if (!$interface) { + throw new Exception(pht('Unable to load interface!')); + } + } else { + $interface = AlmanacInterface::initializeNewInterface() + ->setDevicePHID($object->getPHID()); + } + + $new = $xaction->getNewValue(); + if ($new) { + $interface + ->setNetworkPHID($new['networkPHID']) + ->setAddress($new['address']) + ->setPort((int)$new['port']) + ->save(); + } else { + $interface->delete(); + } + return; } return parent::applyCustomExternalTransaction($object, $xaction); @@ -134,6 +163,125 @@ final class AlmanacDeviceEditor } break; + case AlmanacDeviceTransaction::TYPE_INTERFACE: + // We want to make sure that all the affected networks are visible to + // the actor, any edited interfaces exist, and that the actual address + // components are valid. + + $network_phids = array(); + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + if ($old) { + $network_phids[] = $old['networkPHID']; + } + if ($new) { + $network_phids[] = $new['networkPHID']; + + $address = $new['address']; + if (!strlen($address)) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('Interfaces must have an address.'), + $xaction); + $errors[] = $error; + } else { + // TODO: Validate addresses, but IPv6 addresses are not trival + // to validate. + } + + $port = $new['port']; + if (!strlen($port)) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('Interfaces must have a port.'), + $xaction); + $errors[] = $error; + } else if ((int)$port < 1 || (int)$port > 65535) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Port numbers must be between 1 and 65535, inclusive.'), + $xaction); + $errors[] = $error; + } + } + } + + if ($network_phids) { + $networks = id(new AlmanacNetworkQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs($network_phids) + ->execute(); + $networks = mpull($networks, null, 'getPHID'); + } else { + $networks = array(); + } + + $addresses = array(); + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + if ($old) { + $network = idx($networks, $old['networkPHID']); + if (!$network) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can not edit an interface which belongs to a '. + 'nonexistent or restricted network.'), + $xaction); + $errors[] = $error; + } + + $addresses[] = $old['id']; + } + + $new = $xaction->getNewValue(); + if ($new) { + $network = idx($networks, $new['networkPHID']); + if (!$network) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can not add an interface on a nonexistent or '. + 'restricted network.'), + $xaction); + $errors[] = $error; + } + } + } + + if ($addresses) { + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($this->requireActor()) + ->withDevicePHIDs(array($object->getPHID())) + ->withIDs($addresses) + ->execute(); + $interfaces = mpull($interfaces, null, 'getID'); + } else { + $interfaces = array(); + } + + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + if ($old) { + $interface = idx($interfaces, $old['id']); + if (!$interface) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You can not edit an invalid or restricted interface.'), + $xaction); + $errors[] = $error; + } + } + } + break; } return $errors; diff --git a/src/applications/almanac/phid/AlmanacInterfacePHIDType.php b/src/applications/almanac/phid/AlmanacInterfacePHIDType.php new file mode 100644 index 0000000000..609a548c65 --- /dev/null +++ b/src/applications/almanac/phid/AlmanacInterfacePHIDType.php @@ -0,0 +1,38 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $interface = $objects[$phid]; + + $id = $interface->getID(); + + $handle->setObjectName(pht('Interface %d', $id)); + $handle->setName(pht('Interface %d', $id)); + } + } + +} diff --git a/src/applications/almanac/query/AlmanacInterfaceQuery.php b/src/applications/almanac/query/AlmanacInterfaceQuery.php new file mode 100644 index 0000000000..7e2a867d29 --- /dev/null +++ b/src/applications/almanac/query/AlmanacInterfaceQuery.php @@ -0,0 +1,139 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withNetworkPHIDs(array $phids) { + $this->networkPHIDs = $phids; + return $this; + } + + public function withDevicePHIDs(array $phids) { + $this->devicePHIDs = $phids; + return $this; + } + + public function withAddresses(array $addresses) { + $this->addresses = $addresses; + return $this; + } + + protected function loadPage() { + $table = new AlmanacInterface(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function willFilterPage(array $interfaces) { + $network_phids = mpull($interfaces, 'getNetworkPHID'); + $device_phids = mpull($interfaces, 'getDevicePHID'); + + $networks = id(new AlmanacNetworkQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($network_phids) + ->execute(); + $networks = mpull($networks, null, 'getPHID'); + + $devices = id(new AlmanacDeviceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($device_phids) + ->execute(); + $devices = mpull($devices, null, 'getPHID'); + + foreach ($interfaces as $key => $interface) { + $network = idx($networks, $interface->getNetworkPHID()); + $device = idx($devices, $interface->getDevicePHID()); + if (!$network || !$device) { + $this->didRejectResult($interface); + unset($interfaces[$key]); + continue; + } + + $interface->attachNetwork($network); + $interface->attachDevice($device); + } + + return $interfaces; + } + + protected function buildWhereClause($conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->networkPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'networkPHID IN (%Ls)', + $this->networkPHIDs); + } + + if ($this->devicePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'devicePHID IN (%Ls)', + $this->devicePHIDs); + } + + if ($this->addresses !== null) { + $parts = array(); + foreach ($this->addresses as $address) { + $parts[] = qsprintf( + $conn_r, + '(networkPHID = %s AND address = %s AND port = %d)', + $address->getNetworkPHID(), + $address->getAddress(), + $address->getPort()); + } + $where[] = implode(' OR ', $parts); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/storage/AlmanacDeviceTransaction.php b/src/applications/almanac/storage/AlmanacDeviceTransaction.php index 0068ed6548..b80b74af18 100644 --- a/src/applications/almanac/storage/AlmanacDeviceTransaction.php +++ b/src/applications/almanac/storage/AlmanacDeviceTransaction.php @@ -4,6 +4,7 @@ final class AlmanacDeviceTransaction extends PhabricatorApplicationTransaction { const TYPE_NAME = 'almanac:device:name'; + const TYPE_INTERFACE = 'almanac:device:interface'; public function getApplicationName() { return 'almanac'; @@ -17,6 +18,26 @@ final class AlmanacDeviceTransaction return null; } + public function getRequiredHandlePHIDs() { + $phids = parent::getRequiredHandlePHIDs(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_INTERFACE: + if ($old) { + $phids[] = $old['networkPHID']; + } + if ($new) { + $phids[] = $new['networkPHID']; + } + break; + } + + return $phids; + } + public function getTitle() { $author_phid = $this->getAuthorPHID(); @@ -37,9 +58,43 @@ final class AlmanacDeviceTransaction $new); } break; + case self::TYPE_INTERFACE: + if ($old && $new) { + return pht( + '%s changed interface %s on this device to %s.', + $this->renderHandleLink($author_phid), + $this->describeInterface($old), + $this->describeInterface($new)); + } else if ($old) { + return pht( + '%s removed the interface %s from this device.', + $this->renderHandleLink($author_phid), + $this->describeInterface($new)); + } else if ($new) { + return pht( + '%s added the interface %s to this device.', + $this->renderHandleLink($author_phid), + $this->describeInterface($new)); + } } return parent::getTitle(); } + public function shouldGenerateOldValue() { + switch ($this->getTransactionType()) { + case self::TYPE_INTERFACE: + return false; + } + return parent::shouldGenerateOldValue(); + } + + private function describeInterface(array $info) { + return pht( + '%s:%s (%s)', + $info['address'], + $info['port'], + $this->renderHandleLink($info['networkPHID'])); + } + } diff --git a/src/applications/almanac/storage/AlmanacInterface.php b/src/applications/almanac/storage/AlmanacInterface.php new file mode 100644 index 0000000000..2066c655b7 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacInterface.php @@ -0,0 +1,99 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'address' => 'text64', + 'port' => 'uint32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_location' => array( + 'columns' => array('networkPHID', 'address', 'port'), + ), + 'key_device' => array( + 'columns' => array('devicePHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + AlmanacInterfacePHIDType::TYPECONST); + } + + public function getDevice() { + return $this->assertAttached($this->device); + } + + public function attachDevice(AlmanacDevice $device) { + $this->device = $device; + return $this; + } + + public function getNetwork() { + return $this->assertAttached($this->network); + } + + public function attachNetwork(AlmanacNetwork $device) { + $this->device = $device; + return $this; + } + + public function toAddress() { + return AlmanacAddress::newFromParts( + $this->getNetworkPHID(), + $this->getAddress(), + $this->getPort()); + } + + public function getAddressHash() { + return $this->toAddress()->toHash(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return $this->getDevice()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getDevice()->hasAutomaticCapability($capability, $viewer); + } + + public function describeAutomaticCapability($capability) { + return array( + pht('An interface inherits the policies of the device it belongs to.'), + pht( + 'You must be able to view the network an interface resides on to '. + 'view the interface.'), + ); + } + +} diff --git a/src/applications/almanac/util/AlmanacAddress.php b/src/applications/almanac/util/AlmanacAddress.php new file mode 100644 index 0000000000..2d33579a32 --- /dev/null +++ b/src/applications/almanac/util/AlmanacAddress.php @@ -0,0 +1,54 @@ + + } + + public function getNetworkPHID() { + return $this->networkPHID; + } + + public function getAddress() { + return $this->address; + } + + public function getPort() { + return $this->port; + } + + public static function newFromDictionary(array $dictionary) { + return self::newFromParts( + $dictionary['networkPHID'], + $dictionary['address'], + $dictionary['port']); + } + + public static function newFromParts($network_phid, $address, $port) { + $addr = new AlmanacAddress(); + + $addr->networkPHID = $network_phid; + $addr->address = $address; + $addr->port = (int)$port; + + return $addr; + } + + public function toDictionary() { + return array( + 'networkPHID' => $this->getNetworkPHID(), + 'address' => $this->getAddress(), + 'port' => $this->getPort(), + ); + } + + public function toHash() { + return PhabricatorHash::digestForIndex(json_encode($this->toDictionary())); + } + +} diff --git a/src/applications/almanac/view/AlmanacInterfaceTableView.php b/src/applications/almanac/view/AlmanacInterfaceTableView.php new file mode 100644 index 0000000000..68822677b0 --- /dev/null +++ b/src/applications/almanac/view/AlmanacInterfaceTableView.php @@ -0,0 +1,69 @@ +handles = $handles; + return $this; + } + + public function getHandles() { + return $this->handles; + } + + public function setInterfaces(array $interfaces) { + $this->interfaces = $interfaces; + return $this; + } + + public function getInterfaces() { + return $this->interfaces; + } + + public function render() { + $interfaces = $this->getInterfaces(); + $handles = $this->getHandles(); + $viewer = $this->getUser(); + + $rows = array(); + foreach ($interfaces as $interface) { + $rows[] = array( + $interface->getID(), + $handles[$interface->getNetworkPHID()]->renderLink(), + $interface->getAddress(), + $interface->getPort(), + phutil_tag( + 'a', + array( + 'class' => 'small grey button', + 'href' => '/almanac/interface/edit/'.$interface->getID().'/', + ), + pht('Edit')), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('ID'), + pht('Network'), + pht('Address'), + pht('Port'), + null, + )) + ->setColumnClasses( + array( + '', + 'wide', + '', + '', + 'action', + )); + + return $table; + } + +}