mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-14 16:51:08 +01:00
Add AlmanacBinding, to bind a service to an interface
Summary: Ref T5833. Allows you to bind a service (like `db.example.com`) to one or more interfaces (for example, to specify a pool with one read/write host and two read-only hosts). You can't configure which hosts have which properties yet, but you can add all the relevant interfaces to the service. Next diff will start supporting service, binding, and device properties like "is writable", "is active", etc., so that Almanac will be able to express operations like "change which database is writable", "disable writes", "bring a device down", etc. Test Plan: See screenshots. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T5833 Differential Revision: https://secure.phabricator.com/D10745
This commit is contained in:
parent
de40bc0c1d
commit
d5b70e2c1c
21 changed files with 1039 additions and 3 deletions
14
resources/sql/autopatches/20141017.almanac.binding.sql
Normal file
14
resources/sql/autopatches/20141017.almanac.binding.sql
Normal file
|
@ -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;
|
19
resources/sql/autopatches/20141017.almanac.bxaction.sql
Normal file
19
resources/sql/autopatches/20141017.almanac.bxaction.sql
Normal file
|
@ -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;
|
|
@ -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',
|
||||
|
|
|
@ -47,6 +47,10 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
'interface/' => array(
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacInterfaceEditController',
|
||||
),
|
||||
'binding/' => array(
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacBindingEditController',
|
||||
'(?P<id>\d+)/' => 'AlmanacBindingViewController',
|
||||
),
|
||||
'network/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacNetworkListController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController',
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingEditController
|
||||
extends AlmanacServiceController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->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,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingViewController
|
||||
extends AlmanacServiceController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
139
src/applications/almanac/editor/AlmanacBindingEditor.php
Normal file
139
src/applications/almanac/editor/AlmanacBindingEditor.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Almanac Binding');
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = AlmanacBindingTransaction::TYPE_INTERFACE;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
38
src/applications/almanac/phid/AlmanacBindingPHIDType.php
Normal file
38
src/applications/almanac/phid/AlmanacBindingPHIDType.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'ABND';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Almanac Binding');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new AlmanacBinding();
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new AlmanacBindingQuery())
|
||||
->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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -33,6 +33,7 @@ final class AlmanacDevicePHIDType extends PhabricatorPHIDType {
|
|||
|
||||
$handle->setObjectName(pht('Device %d', $id));
|
||||
$handle->setName($name);
|
||||
$handle->setURI($device->getURI());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ final class AlmanacNetworkPHIDType extends PhabricatorPHIDType {
|
|||
|
||||
$handle->setObjectName(pht('Network %d', $id));
|
||||
$handle->setName($name);
|
||||
$handle->setURI($network->getURI());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ final class AlmanacServicePHIDType extends PhabricatorPHIDType {
|
|||
|
||||
$handle->setObjectName(pht('Service %d', $id));
|
||||
$handle->setName($name);
|
||||
$handle->setURI($service->getURI());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
143
src/applications/almanac/query/AlmanacBindingQuery.php
Normal file
143
src/applications/almanac/query/AlmanacBindingQuery.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $servicePHIDs;
|
||||
private $devicePHIDs;
|
||||
private $interfacePHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->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';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new AlmanacBindingTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ final class AlmanacDeviceQuery
|
|||
private $ids;
|
||||
private $phids;
|
||||
private $names;
|
||||
private $datasourceQuery;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->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);
|
||||
|
|
112
src/applications/almanac/storage/AlmanacBinding.php
Normal file
112
src/applications/almanac/storage/AlmanacBinding.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBinding
|
||||
extends AlmanacDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $servicePHID;
|
||||
protected $devicePHID;
|
||||
protected $interfacePHID;
|
||||
protected $mailKey;
|
||||
|
||||
private $service = self::ATTACHABLE;
|
||||
private $device = self::ATTACHABLE;
|
||||
private $interface = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewBinding(AlmanacService $service) {
|
||||
return id(new AlmanacBinding())
|
||||
->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.'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_INTERFACE = 'almanac:binding:interface';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'almanac';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return AlmanacBindingPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
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;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacInterfaceDatasource
|
||||
extends PhabricatorTypeaheadDatasource {
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type an interface name...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
|
||||
public function loadResults() {
|
||||
$viewer = $this->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;
|
||||
}
|
||||
|
||||
}
|
86
src/applications/almanac/view/AlmanacBindingTableView.php
Normal file
86
src/applications/almanac/view/AlmanacBindingTableView.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingTableView extends AphrontView {
|
||||
|
||||
private $bindings;
|
||||
private $handles;
|
||||
private $noDataString;
|
||||
|
||||
public function setNoDataString($no_data_string) {
|
||||
$this->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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue