1
0
Fork 0
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:
epriestley 2014-10-27 13:39:36 -07:00
parent de40bc0c1d
commit d5b70e2c1c
21 changed files with 1039 additions and 3 deletions

View 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;

View 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;

View file

@ -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',

View file

@ -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',

View file

@ -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,
));
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View 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;
}
}

View 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));
}
}
}

View file

@ -33,6 +33,7 @@ final class AlmanacDevicePHIDType extends PhabricatorPHIDType {
$handle->setObjectName(pht('Device %d', $id));
$handle->setName($name);
$handle->setURI($device->getURI());
}
}

View file

@ -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);
}
}

View file

@ -33,6 +33,7 @@ final class AlmanacNetworkPHIDType extends PhabricatorPHIDType {
$handle->setObjectName(pht('Network %d', $id));
$handle->setName($name);
$handle->setURI($network->getURI());
}
}

View file

@ -33,6 +33,7 @@ final class AlmanacServicePHIDType extends PhabricatorPHIDType {
$handle->setObjectName(pht('Service %d', $id));
$handle->setName($name);
$handle->setURI($service->getURI());
}
}

View 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';
}
}

View file

@ -0,0 +1,10 @@
<?php
final class AlmanacBindingTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new AlmanacBindingTransaction();
}
}

View file

@ -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);

View 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.'),
);
}
}

View file

@ -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();
}
}

View file

@ -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 )----------------------------------------- */

View file

@ -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;
}
}

View 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;
}
}