diff --git a/resources/sql/autopatches/20141017.almanac.binding.sql b/resources/sql/autopatches/20141017.almanac.binding.sql new file mode 100644 index 0000000000..da02069b43 --- /dev/null +++ b/resources/sql/autopatches/20141017.almanac.binding.sql @@ -0,0 +1,14 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_binding ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + servicePHID VARBINARY(64) NOT NULL, + devicePHID VARBINARY(64) NOT NULL, + interfacePHID VARBINARY(64) NOT NULL, + mailKey BINARY(20) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_service` (servicePHID, interfacePHID), + KEY `key_device` (devicePHID), + KEY `key_interface` (interfacePHID) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/autopatches/20141017.almanac.bxaction.sql b/resources/sql/autopatches/20141017.almanac.bxaction.sql new file mode 100644 index 0000000000..12cd214e4a --- /dev/null +++ b/resources/sql/autopatches/20141017.almanac.bxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_bindingtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, + newValue LONGTEXT COLLATE utf8_bin NOT NULL, + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, + metadata LONGTEXT COLLATE utf8_bin NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f921a39557..b8714eaf20 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -10,6 +10,15 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( 'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php', + 'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php', + 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', + 'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php', + 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', + 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', + 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', + 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', + 'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php', + 'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php', 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', @@ -30,6 +39,7 @@ phutil_register_library_map(array( 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php', 'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php', + 'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php', 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', 'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php', @@ -2947,6 +2957,18 @@ phutil_register_library_map(array( ), 'xmap' => array( 'AlmanacAddress' => 'Phobject', + 'AlmanacBinding' => array( + 'AlmanacDAO', + 'PhabricatorPolicyInterface', + ), + 'AlmanacBindingEditController' => 'AlmanacServiceController', + 'AlmanacBindingEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', + 'AlmanacBindingQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacBindingTableView' => 'AphrontView', + 'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacBindingViewController' => 'AlmanacServiceController', 'AlmanacConduitUtil' => 'Phobject', 'AlmanacConsoleController' => 'AlmanacController', 'AlmanacController' => 'PhabricatorController', @@ -2973,6 +2995,7 @@ phutil_register_library_map(array( 'AlmanacDAO', 'PhabricatorPolicyInterface', ), + 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', 'AlmanacInterfaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 59d57342f8..625850b37f 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -47,6 +47,10 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { 'interface/' => array( 'edit/(?:(?P\d+)/)?' => 'AlmanacInterfaceEditController', ), + 'binding/' => array( + 'edit/(?:(?P\d+)/)?' => 'AlmanacBindingEditController', + '(?P\d+)/' => 'AlmanacBindingViewController', + ), 'network/' => array( '(?:query/(?P[^/]+)/)?' => 'AlmanacNetworkListController', 'edit/(?:(?P\d+)/)?' => 'AlmanacNetworkEditController', diff --git a/src/applications/almanac/controller/AlmanacBindingEditController.php b/src/applications/almanac/controller/AlmanacBindingEditController.php new file mode 100644 index 0000000000..35179c833b --- /dev/null +++ b/src/applications/almanac/controller/AlmanacBindingEditController.php @@ -0,0 +1,128 @@ +getViewer(); + + $id = $request->getURIData('id'); + if ($id) { + $binding = id(new AlmanacBindingQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$binding) { + return new Aphront404Response(); + } + + $service = $binding->getService(); + $is_new = false; + + $service_uri = $service->getURI(); + $cancel_uri = $binding->getURI(); + $title = pht('Edit Binding'); + $save_button = pht('Save Changes'); + } else { + $service = id(new AlmanacServiceQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getStr('serviceID'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $binding = AlmanacBinding::initializeNewBinding($service); + $is_new = true; + + $service_uri = $service->getURI(); + $cancel_uri = $service_uri; + $title = pht('Create Binding'); + $save_button = pht('Create Binding'); + } + + $v_interface = array(); + if ($binding->getInterfacePHID()) { + $v_interface = array($binding->getInterfacePHID()); + } + $e_interface = true; + + $validation_exception = null; + if ($request->isFormPost()) { + $v_interface = $request->getArr('interfacePHIDs'); + + $type_interface = AlmanacBindingTransaction::TYPE_INTERFACE; + + $xactions = array(); + + $xactions[] = id(new AlmanacBindingTransaction()) + ->setTransactionType($type_interface) + ->setNewValue(head($v_interface)); + + $editor = id(new AlmanacBindingEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($binding, $xactions); + + $binding_uri = $binding->getURI(); + return id(new AphrontRedirectResponse())->setURI($binding_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_interface = $ex->getShortMessage($type_interface); + } + } + + $interface_handles = array(); + if ($v_interface) { + $interface_handles = $this->loadViewerHandles($v_interface); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setName('interfacePHIDs') + ->setLabel('Interface') + ->setLimit(1) + ->setDatasource(new AlmanacInterfaceDatasource()) + ->setValue($interface_handles) + ->setError($e_interface)) + ->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($service->getName(), $service_uri); + if ($is_new) { + $crumbs->addTextCrumb(pht('Create Binding')); + } else { + $crumbs->addTextCrumb(pht('Edit Binding')); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/almanac/controller/AlmanacBindingViewController.php b/src/applications/almanac/controller/AlmanacBindingViewController.php new file mode 100644 index 0000000000..b7ff42ce03 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacBindingViewController.php @@ -0,0 +1,122 @@ +getViewer(); + + $id = $request->getURIData('id'); + + $binding = id(new AlmanacBindingQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$binding) { + return new Aphront404Response(); + } + + $service = $binding->getService(); + $service_uri = $service->getURI(); + + $title = pht('Binding %s', $binding->getID()); + + $property_list = $this->buildPropertyList($binding); + $action_list = $this->buildActionList($binding); + $property_list->setActionList($action_list); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($title) + ->setPolicyObject($binding); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($property_list); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($service->getName(), $service_uri); + $crumbs->addTextCrumb($title); + + $xactions = id(new AlmanacBindingTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($binding->getPHID())) + ->execute(); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setObjectPHID($binding->getPHID()) + ->setTransactions($xactions) + ->setShouldTerminate(true); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $xaction_view, + ), + array( + 'title' => $title, + )); + } + + private function buildPropertyList(AlmanacBinding $binding) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $handles = $this->loadViewerHandles( + array( + $binding->getServicePHID(), + $binding->getDevicePHID(), + $binding->getInterface()->getNetworkPHID(), + )); + + $properties->addProperty( + pht('Service'), + $handles[$binding->getServicePHID()]->renderLink()); + + $properties->addProperty( + pht('Device'), + $handles[$binding->getDevicePHID()]->renderLink()); + + $properties->addProperty( + pht('Network'), + $handles[$binding->getInterface()->getNetworkPHID()]->renderLink()); + + $properties->addProperty( + pht('Interface'), + $binding->getInterface()->renderDisplayAddress()); + + return $properties; + } + + private function buildActionList(AlmanacBinding $binding) { + $viewer = $this->getViewer(); + $id = $binding->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $binding, + PhabricatorPolicyCapability::CAN_EDIT); + + $actions = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Binding')) + ->setHref($this->getApplicationURI("binding/edit/{$id}/")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + return $actions; + } + +} diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php index e6a8365bed..8218e3eafe 100644 --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -35,6 +35,8 @@ final class AlmanacServiceViewController ->setHeader($header) ->addPropertyList($property_list); + $bindings = $this->buildBindingList($service); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($service->getName()); @@ -53,6 +55,7 @@ final class AlmanacServiceViewController array( $crumbs, $box, + $bindings, $xaction_view, ), array( @@ -92,4 +95,51 @@ final class AlmanacServiceViewController return $actions; } + private function buildBindingList(AlmanacService $service) { + $viewer = $this->getViewer(); + $id = $service->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $service, + PhabricatorPolicyCapability::CAN_EDIT); + + $bindings = id(new AlmanacBindingQuery()) + ->setViewer($viewer) + ->withServicePHIDs(array($service->getPHID())) + ->execute(); + + $phids = array(); + foreach ($bindings as $binding) { + $phids[] = $binding->getServicePHID(); + $phids[] = $binding->getDevicePHID(); + $phids[] = $binding->getInterface()->getNetworkPHID(); + } + $handles = $this->loadViewerHandles($phids); + + $table = id(new AlmanacBindingTableView()) + ->setNoDataString( + pht('This service has not been bound to any device interfaces yet.')) + ->setUser($viewer) + ->setBindings($bindings) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Service Bindings')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($this->getApplicationURI("binding/edit/?serviceID={$id}")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit) + ->setText(pht('Add Binding')) + ->setIcon( + id(new PHUIIconView()) + ->setIconFont('fa-plus'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + } + } diff --git a/src/applications/almanac/editor/AlmanacBindingEditor.php b/src/applications/almanac/editor/AlmanacBindingEditor.php new file mode 100644 index 0000000000..28eb233c6f --- /dev/null +++ b/src/applications/almanac/editor/AlmanacBindingEditor.php @@ -0,0 +1,139 @@ +getTransactionType()) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + return $object->getInterfacePHID(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + $interface = id(new AlmanacInterfaceQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs(array($xaction->getNewValue())) + ->executeOne(); + $object->setDevicePHID($interface->getDevicePHID()); + $object->setInterfacePHID($interface->getPHID()); + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case AlmanacBindingTransaction::TYPE_INTERFACE: + $missing = $this->validateIsEmptyTextField( + $object->getInterfacePHID(), + $xactions); + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Bindings must specify an interface.'), + nonempty(last($xactions), null)); + $error->setIsMissingFieldError(true); + $errors[] = $error; + } else if ($xactions) { + foreach ($xactions as $xaction) { + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs(array($xaction->getNewValue())) + ->execute(); + if (!$interfaces) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can not bind a service to an invalid or restricted '. + 'interface.'), + $xaction); + $errors[] = $error; + } + } + + $final_value = last($xactions)->getNewValue(); + + $binding = id(new AlmanacBindingQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withServicePHIDs(array($object->getServicePHID())) + ->withInterfacePHIDs(array($final_value)) + ->executeOne(); + if ($binding && ($binding->getID() != $object->getID())) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Already Bound'), + pht( + 'You can not bind a service to the same interface multiple '. + 'times.'), + last($xactions)); + $errors[] = $error; + } + } + break; + } + + return $errors; + } + + + +} diff --git a/src/applications/almanac/phid/AlmanacBindingPHIDType.php b/src/applications/almanac/phid/AlmanacBindingPHIDType.php new file mode 100644 index 0000000000..d8fcb510fb --- /dev/null +++ b/src/applications/almanac/phid/AlmanacBindingPHIDType.php @@ -0,0 +1,38 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $binding = $objects[$phid]; + + $id = $binding->getID(); + + $handle->setObjectName(pht('Binding %d', $id)); + $handle->setName(pht('Binding %d', $id)); + } + } + +} diff --git a/src/applications/almanac/phid/AlmanacDevicePHIDType.php b/src/applications/almanac/phid/AlmanacDevicePHIDType.php index 56dc8a5d2f..8a1bb36a90 100644 --- a/src/applications/almanac/phid/AlmanacDevicePHIDType.php +++ b/src/applications/almanac/phid/AlmanacDevicePHIDType.php @@ -33,6 +33,7 @@ final class AlmanacDevicePHIDType extends PhabricatorPHIDType { $handle->setObjectName(pht('Device %d', $id)); $handle->setName($name); + $handle->setURI($device->getURI()); } } diff --git a/src/applications/almanac/phid/AlmanacInterfacePHIDType.php b/src/applications/almanac/phid/AlmanacInterfacePHIDType.php index 609a548c65..67f9b1664a 100644 --- a/src/applications/almanac/phid/AlmanacInterfacePHIDType.php +++ b/src/applications/almanac/phid/AlmanacInterfacePHIDType.php @@ -30,8 +30,20 @@ final class AlmanacInterfacePHIDType extends PhabricatorPHIDType { $id = $interface->getID(); + $device_name = $interface->getDevice()->getName(); + $address = $interface->getAddress(); + $port = $interface->getPort(); + $network = $interface->getNetwork()->getName(); + + $name = pht( + '%s:%s (%s on %s)', + $device_name, + $port, + $address, + $network); + $handle->setObjectName(pht('Interface %d', $id)); - $handle->setName(pht('Interface %d', $id)); + $handle->setName($name); } } diff --git a/src/applications/almanac/phid/AlmanacNetworkPHIDType.php b/src/applications/almanac/phid/AlmanacNetworkPHIDType.php index 8a4be09cd3..e27efa5cd8 100644 --- a/src/applications/almanac/phid/AlmanacNetworkPHIDType.php +++ b/src/applications/almanac/phid/AlmanacNetworkPHIDType.php @@ -33,6 +33,7 @@ final class AlmanacNetworkPHIDType extends PhabricatorPHIDType { $handle->setObjectName(pht('Network %d', $id)); $handle->setName($name); + $handle->setURI($network->getURI()); } } diff --git a/src/applications/almanac/phid/AlmanacServicePHIDType.php b/src/applications/almanac/phid/AlmanacServicePHIDType.php index 19d6634c6b..c64e089ce6 100644 --- a/src/applications/almanac/phid/AlmanacServicePHIDType.php +++ b/src/applications/almanac/phid/AlmanacServicePHIDType.php @@ -33,6 +33,7 @@ final class AlmanacServicePHIDType extends PhabricatorPHIDType { $handle->setObjectName(pht('Service %d', $id)); $handle->setName($name); + $handle->setURI($service->getURI()); } } diff --git a/src/applications/almanac/query/AlmanacBindingQuery.php b/src/applications/almanac/query/AlmanacBindingQuery.php new file mode 100644 index 0000000000..28bb25f598 --- /dev/null +++ b/src/applications/almanac/query/AlmanacBindingQuery.php @@ -0,0 +1,143 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withServicePHIDs(array $phids) { + $this->servicePHIDs = $phids; + return $this; + } + + public function withDevicePHIDs(array $phids) { + $this->devicePHIDs = $phids; + return $this; + } + + public function withInterfacePHIDs(array $phids) { + $this->interfacePHIDs = $phids; + return $this; + } + + protected function loadPage() { + $table = new AlmanacBinding(); + $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 $bindings) { + $service_phids = mpull($bindings, 'getServicePHID'); + $device_phids = mpull($bindings, 'getDevicePHID'); + $interface_phids = mpull($bindings, 'getInterfacePHID'); + + $services = id(new AlmanacServiceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($service_phids) + ->execute(); + $services = mpull($services, null, 'getPHID'); + + $devices = id(new AlmanacDeviceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($device_phids) + ->execute(); + $devices = mpull($devices, null, 'getPHID'); + + $interfaces = id(new AlmanacInterfaceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($interface_phids) + ->execute(); + $interfaces = mpull($interfaces, null, 'getPHID'); + + foreach ($bindings as $key => $binding) { + $service = idx($services, $binding->getServicePHID()); + $device = idx($devices, $binding->getDevicePHID()); + $interface = idx($interfaces, $binding->getInterfacePHID()); + if (!$service || !$device || !$interface) { + $this->didRejectResult($binding); + unset($bindings[$key]); + continue; + } + + $binding->attachService($service); + $binding->attachDevice($device); + $binding->attachInterface($interface); + } + + return $bindings; + } + + 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->servicePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'servicePHID IN (%Ls)', + $this->servicePHIDs); + } + + if ($this->devicePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'devicePHID IN (%Ls)', + $this->devicePHIDs); + } + + if ($this->interfacePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'interfacePHID IN (%Ls)', + $this->interfacePHIDs); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/query/AlmanacBindingTransactionQuery.php b/src/applications/almanac/query/AlmanacBindingTransactionQuery.php new file mode 100644 index 0000000000..2b002e4f4a --- /dev/null +++ b/src/applications/almanac/query/AlmanacBindingTransactionQuery.php @@ -0,0 +1,10 @@ +ids = $ids; @@ -22,6 +23,11 @@ final class AlmanacDeviceQuery return $this; } + public function withDatasourceQuery($query) { + $this->datasourceQuery = $query; + return $this; + } + protected function loadPage() { $table = new AlmanacDevice(); $conn_r = $table->establishConnection('r'); @@ -65,6 +71,13 @@ final class AlmanacDeviceQuery $hashes); } + if ($this->datasourceQuery !== null) { + $where[] = qsprintf( + $conn_r, + 'name LIKE %>', + $this->datasourceQuery); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); diff --git a/src/applications/almanac/storage/AlmanacBinding.php b/src/applications/almanac/storage/AlmanacBinding.php new file mode 100644 index 0000000000..74e7e57de7 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacBinding.php @@ -0,0 +1,112 @@ +setServicePHID($service->getPHID()); + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'mailKey' => 'bytes20', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_service' => array( + 'columns' => array('servicePHID', 'interfacePHID'), + 'unique' => true, + ), + 'key_device' => array( + 'columns' => array('devicePHID'), + ), + 'key_interface' => array( + 'columns' => array('interfacePHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID(AlmanacBindingPHIDType::TYPECONST); + } + + public function save() { + if (!$this->mailKey) { + $this->mailKey = Filesystem::readRandomCharacters(20); + } + return parent::save(); + } + + public function getURI() { + return '/almanac/binding/'.$this->getID().'/'; + } + + public function getService() { + return $this->assertAttached($this->service); + } + + public function attachService(AlmanacService $service) { + $this->service = $service; + return $this; + } + + public function getDevice() { + return $this->assertAttached($this->device); + } + + public function attachDevice(AlmanacDevice $device) { + $this->device = $device; + return $this; + } + + public function getInterface() { + return $this->assertAttached($this->interface); + } + + public function attachInterface(AlmanacInterface $interface) { + $this->interface = $interface; + return $this; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return $this->getService()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getService()->hasAutomaticCapability($capability, $viewer); + } + + public function describeAutomaticCapability($capability) { + return array( + pht('A binding inherits the policies of its service.'), + pht( + 'To view a binding, you must also be able to view its device and '. + 'interface.'), + ); + } + +} diff --git a/src/applications/almanac/storage/AlmanacBindingTransaction.php b/src/applications/almanac/storage/AlmanacBindingTransaction.php new file mode 100644 index 0000000000..4ea4909bb6 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacBindingTransaction.php @@ -0,0 +1,65 @@ +getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_INTERFACE: + if ($old) { + $phids[] = $old; + } + if ($new) { + $phids[] = $new; + } + break; + } + + return $phids; + } + + public function getTitle() { + $author_phid = $this->getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_INTERFACE: + if ($old === null) { + return pht( + '%s created this binding.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s changed this binding from %s to %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + } + break; + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/almanac/storage/AlmanacInterface.php b/src/applications/almanac/storage/AlmanacInterface.php index 2066c655b7..aa95cb4236 100644 --- a/src/applications/almanac/storage/AlmanacInterface.php +++ b/src/applications/almanac/storage/AlmanacInterface.php @@ -52,8 +52,8 @@ final class AlmanacInterface return $this->assertAttached($this->network); } - public function attachNetwork(AlmanacNetwork $device) { - $this->device = $device; + public function attachNetwork(AlmanacNetwork $network) { + $this->network = $network; return $this; } @@ -68,6 +68,10 @@ final class AlmanacInterface return $this->toAddress()->toHash(); } + public function renderDisplayAddress() { + return $this->getAddress().':'.$this->getPort(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php b/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php new file mode 100644 index 0000000000..4a3053d357 --- /dev/null +++ b/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php @@ -0,0 +1,51 @@ +getViewer(); + $raw_query = $this->getRawQuery(); + + $devices = id(new AlmanacDeviceQuery()) + ->setViewer($viewer) + ->withDatasourceQuery($raw_query) + ->execute(); + + if ($devices) { + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($viewer) + ->withDevicePHIDs(mpull($devices, 'getPHID')) + ->execute(); + } else { + $interfaces = array(); + } + + if ($interfaces) { + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs(mpull($interfaces, 'getPHID')) + ->execute(); + } else { + $handles = array(); + } + + $results = array(); + foreach ($handles as $handle) { + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($handle->getName()) + ->setPHID($handle->getPHID()); + } + + return $results; + } + +} diff --git a/src/applications/almanac/view/AlmanacBindingTableView.php b/src/applications/almanac/view/AlmanacBindingTableView.php new file mode 100644 index 0000000000..d6c27f62c9 --- /dev/null +++ b/src/applications/almanac/view/AlmanacBindingTableView.php @@ -0,0 +1,86 @@ +noDataString = $no_data_string; + return $this; + } + + public function getNoDataString() { + return $this->noDataString; + } + + public function setHandles(array $handles) { + $this->handles = $handles; + return $this; + } + + public function getHandles() { + return $this->handles; + } + + public function setBindings(array $bindings) { + $this->bindings = $bindings; + return $this; + } + + public function getBindings() { + return $this->bindings; + } + + public function render() { + $bindings = $this->getBindings(); + $handles = $this->getHandles(); + $viewer = $this->getUser(); + + $rows = array(); + foreach ($bindings as $binding) { + $addr = $binding->getInterface()->getAddress(); + $port = $binding->getInterface()->getPort(); + + $rows[] = array( + $binding->getID(), + $handles[$binding->getServicePHID()]->renderLink(), + $handles[$binding->getDevicePHID()]->renderLink(), + $handles[$binding->getInterface()->getNetworkPHID()]->renderLink(), + $binding->getInterface()->renderDisplayAddress(), + phutil_tag( + 'a', + array( + 'class' => 'small grey button', + 'href' => '/almanac/binding/'.$binding->getID().'/', + ), + pht('Details')), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setNoDataString($this->getNoDataString()) + ->setHeaders( + array( + pht('ID'), + pht('Service'), + pht('Device'), + pht('Network'), + pht('Interface'), + null, + )) + ->setColumnClasses( + array( + '', + '', + '', + '', + 'wide', + 'action', + )); + + return $table; + } + +}