mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 19:40:55 +01:00
Allow Almanac bindings to be disabled and unused interfaces to be removed
Summary: Fixes T9762. Ref T10246. **Disabling Bindings**: Previously, there was no formal way to disable bindings. The internal callers sometimes check some informal property on the binding, but this is a common need and deserves first-class support in the UI. Allow bindings to be disabled. **Deleting Interfaces**: Previously, you could not delete interfaces. Now, you can delete unused interfaces. Also some minor cleanup and slightly less mysterious documentation. Test Plan: Disabled bindings and deleted interfaces. Reviewers: chad Reviewed By: chad Subscribers: yelirekim Maniphest Tasks: T9762, T10246 Differential Revision: https://secure.phabricator.com/D15345
This commit is contained in:
parent
5b9d8aeae7
commit
4c97d88aa4
19 changed files with 357 additions and 14 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_almanac.almanac_binding
|
||||||
|
ADD isDisabled BOOL NOT NULL;
|
|
@ -11,6 +11,7 @@ phutil_register_library_map(array(
|
||||||
'class' => array(
|
'class' => array(
|
||||||
'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php',
|
'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php',
|
||||||
'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php',
|
'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php',
|
||||||
|
'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php',
|
||||||
'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php',
|
'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php',
|
||||||
'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php',
|
'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php',
|
||||||
'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php',
|
'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php',
|
||||||
|
@ -51,6 +52,7 @@ phutil_register_library_map(array(
|
||||||
'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php',
|
'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php',
|
||||||
'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
|
'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
|
||||||
'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php',
|
'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php',
|
||||||
|
'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php',
|
||||||
'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
|
'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
|
||||||
'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php',
|
'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php',
|
||||||
'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php',
|
'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php',
|
||||||
|
@ -3996,6 +3998,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorDestructibleInterface',
|
'PhabricatorDestructibleInterface',
|
||||||
'PhabricatorExtendedPolicyInterface',
|
'PhabricatorExtendedPolicyInterface',
|
||||||
),
|
),
|
||||||
|
'AlmanacBindingDisableController' => 'AlmanacServiceController',
|
||||||
'AlmanacBindingEditController' => 'AlmanacServiceController',
|
'AlmanacBindingEditController' => 'AlmanacServiceController',
|
||||||
'AlmanacBindingEditor' => 'AlmanacEditor',
|
'AlmanacBindingEditor' => 'AlmanacEditor',
|
||||||
'AlmanacBindingPHIDType' => 'PhabricatorPHIDType',
|
'AlmanacBindingPHIDType' => 'PhabricatorPHIDType',
|
||||||
|
@ -4049,8 +4052,10 @@ phutil_register_library_map(array(
|
||||||
'AlmanacDAO',
|
'AlmanacDAO',
|
||||||
'PhabricatorPolicyInterface',
|
'PhabricatorPolicyInterface',
|
||||||
'PhabricatorDestructibleInterface',
|
'PhabricatorDestructibleInterface',
|
||||||
|
'PhabricatorExtendedPolicyInterface',
|
||||||
),
|
),
|
||||||
'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource',
|
'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
|
'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController',
|
||||||
'AlmanacInterfaceEditController' => 'AlmanacDeviceController',
|
'AlmanacInterfaceEditController' => 'AlmanacDeviceController',
|
||||||
'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType',
|
'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType',
|
||||||
'AlmanacInterfaceQuery' => 'AlmanacQuery',
|
'AlmanacInterfaceQuery' => 'AlmanacQuery',
|
||||||
|
|
|
@ -55,9 +55,11 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
||||||
),
|
),
|
||||||
'interface/' => array(
|
'interface/' => array(
|
||||||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacInterfaceEditController',
|
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacInterfaceEditController',
|
||||||
|
'delete/(?:(?P<id>\d+)/)?' => 'AlmanacInterfaceDeleteController',
|
||||||
),
|
),
|
||||||
'binding/' => array(
|
'binding/' => array(
|
||||||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacBindingEditController',
|
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacBindingEditController',
|
||||||
|
'disable/(?:(?P<id>\d+)/)?' => 'AlmanacBindingDisableController',
|
||||||
'(?P<id>\d+)/' => 'AlmanacBindingViewController',
|
'(?P<id>\d+)/' => 'AlmanacBindingViewController',
|
||||||
),
|
),
|
||||||
'network/' => array(
|
'network/' => array(
|
||||||
|
@ -80,6 +82,17 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getCustomCapabilities() {
|
protected function getCustomCapabilities() {
|
||||||
|
$cluster_caption = pht(
|
||||||
|
'This permission is very dangerous. %s',
|
||||||
|
phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => PhabricatorEnv::getDoclink(
|
||||||
|
'User Guide: Phabricator Clusters'),
|
||||||
|
'target' => '_blank',
|
||||||
|
),
|
||||||
|
pht('Learn More')));
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
AlmanacCreateServicesCapability::CAPABILITY => array(
|
AlmanacCreateServicesCapability::CAPABILITY => array(
|
||||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||||
|
@ -94,7 +107,8 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
||||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||||
),
|
),
|
||||||
AlmanacManageClusterServicesCapability::CAPABILITY => array(
|
AlmanacManageClusterServicesCapability::CAPABILITY => array(
|
||||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
'default' => PhabricatorPolicies::POLICY_NOONE,
|
||||||
|
'caption' => $cluster_caption,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class AlmanacBindingDisableController
|
||||||
|
extends AlmanacServiceController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
$binding = id(new AlmanacBindingQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$binding) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $binding->getID();
|
||||||
|
$is_disable = !$binding->getIsDisabled();
|
||||||
|
$done_uri = $binding->getURI();
|
||||||
|
|
||||||
|
if ($is_disable) {
|
||||||
|
$disable_title = pht('Disable Binding');
|
||||||
|
$disable_body = pht('Disable this binding?');
|
||||||
|
$disable_button = pht('Disable Binding');
|
||||||
|
|
||||||
|
$v_disable = 1;
|
||||||
|
} else {
|
||||||
|
$disable_title = pht('Enable Binding');
|
||||||
|
$disable_body = pht('Enable this binding?');
|
||||||
|
$disable_button = pht('Enable Binding');
|
||||||
|
|
||||||
|
$v_disable = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$type_disable = AlmanacBindingTransaction::TYPE_DISABLE;
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new AlmanacBindingTransaction())
|
||||||
|
->setTransactionType($type_disable)
|
||||||
|
->setNewValue($v_disable);
|
||||||
|
|
||||||
|
$editor = id(new AlmanacBindingEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true);
|
||||||
|
|
||||||
|
$editor->applyTransactions($binding, $xactions);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle($disable_title)
|
||||||
|
->appendParagraph($disable_body)
|
||||||
|
->addSubmitButton($disable_button)
|
||||||
|
->addCancelButton($done_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,10 @@ final class AlmanacBindingViewController
|
||||||
->setHeader($title)
|
->setHeader($title)
|
||||||
->setPolicyObject($binding);
|
->setPolicyObject($binding);
|
||||||
|
|
||||||
|
if ($binding->getIsDisabled()) {
|
||||||
|
$header->setStatus('fa-ban', 'red', pht('Disabled'));
|
||||||
|
}
|
||||||
|
|
||||||
$box = id(new PHUIObjectBoxView())
|
$box = id(new PHUIObjectBoxView())
|
||||||
->setHeader($header)
|
->setHeader($header)
|
||||||
->addPropertyList($property_list);
|
->addPropertyList($property_list);
|
||||||
|
@ -114,6 +118,24 @@ final class AlmanacBindingViewController
|
||||||
->setWorkflow(!$can_edit)
|
->setWorkflow(!$can_edit)
|
||||||
->setDisabled(!$can_edit));
|
->setDisabled(!$can_edit));
|
||||||
|
|
||||||
|
if ($binding->getIsDisabled()) {
|
||||||
|
$disable_icon = 'fa-check';
|
||||||
|
$disable_text = pht('Enable Binding');
|
||||||
|
} else {
|
||||||
|
$disable_icon = 'fa-ban';
|
||||||
|
$disable_text = pht('Disable Binding');
|
||||||
|
}
|
||||||
|
|
||||||
|
$disable_href = $this->getApplicationURI("binding/disable/{$id}/");
|
||||||
|
|
||||||
|
$actions->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon($disable_icon)
|
||||||
|
->setName($disable_text)
|
||||||
|
->setHref($disable_href)
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_edit));
|
||||||
|
|
||||||
return $actions;
|
return $actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,8 @@ abstract class AlmanacController
|
||||||
$doc_link = phutil_tag(
|
$doc_link = phutil_tag(
|
||||||
'a',
|
'a',
|
||||||
array(
|
array(
|
||||||
'href' => PhabricatorEnv::getDoclink('Almanac User Guide'),
|
'href' => PhabricatorEnv::getDoclink(
|
||||||
|
'User Guide: Phabricator Clusters'),
|
||||||
'target' => '_blank',
|
'target' => '_blank',
|
||||||
),
|
),
|
||||||
pht('Learn More'));
|
pht('Learn More'));
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class AlmanacInterfaceDeleteController
|
||||||
|
extends AlmanacDeviceController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
$interface = id(new AlmanacInterfaceQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$interface) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$device = $interface->getDevice();
|
||||||
|
$device_uri = $device->getURI();
|
||||||
|
|
||||||
|
if ($interface->loadIsInUse()) {
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Interface In Use'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'You can not delete this interface because it is currently in '.
|
||||||
|
'use. One or more services are bound to it.'))
|
||||||
|
->addCancelButton($device_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE;
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$v_old = array(
|
||||||
|
'id' => $interface->getID(),
|
||||||
|
) + $interface->toAddress()->toDictionary();
|
||||||
|
|
||||||
|
$xactions[] = id(new AlmanacDeviceTransaction())
|
||||||
|
->setTransactionType($type_interface)
|
||||||
|
->setOldValue($v_old)
|
||||||
|
->setNewValue(null);
|
||||||
|
|
||||||
|
$editor = id(new AlmanacDeviceEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true);
|
||||||
|
|
||||||
|
$editor->applyTransactions($device, $xactions);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($device_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Delete Interface'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'Remove interface %s on device %s?',
|
||||||
|
phutil_tag('strong', array(), $interface->renderDisplayAddress()),
|
||||||
|
phutil_tag('strong', array(), $device->getName())))
|
||||||
|
->addCancelButton($device_uri)
|
||||||
|
->addSubmitButton(pht('Delete Interface'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -122,7 +122,8 @@ final class AlmanacServiceViewController
|
||||||
->setNoDataString(
|
->setNoDataString(
|
||||||
pht('This service has not been bound to any device interfaces yet.'))
|
pht('This service has not been bound to any device interfaces yet.'))
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
->setBindings($bindings);
|
->setBindings($bindings)
|
||||||
|
->setHideServiceColumn(true);
|
||||||
|
|
||||||
$header = id(new PHUIHeaderView())
|
$header = id(new PHUIHeaderView())
|
||||||
->setHeader(pht('Service Bindings'))
|
->setHeader(pht('Service Bindings'))
|
||||||
|
|
|
@ -13,6 +13,7 @@ final class AlmanacBindingEditor
|
||||||
$types = parent::getTransactionTypes();
|
$types = parent::getTransactionTypes();
|
||||||
|
|
||||||
$types[] = AlmanacBindingTransaction::TYPE_INTERFACE;
|
$types[] = AlmanacBindingTransaction::TYPE_INTERFACE;
|
||||||
|
$types[] = AlmanacBindingTransaction::TYPE_DISABLE;
|
||||||
|
|
||||||
return $types;
|
return $types;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +24,8 @@ final class AlmanacBindingEditor
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case AlmanacBindingTransaction::TYPE_INTERFACE:
|
case AlmanacBindingTransaction::TYPE_INTERFACE:
|
||||||
return $object->getInterfacePHID();
|
return $object->getInterfacePHID();
|
||||||
|
case AlmanacBindingTransaction::TYPE_DISABLE:
|
||||||
|
return $object->getIsDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||||
|
@ -35,6 +38,8 @@ final class AlmanacBindingEditor
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case AlmanacBindingTransaction::TYPE_INTERFACE:
|
case AlmanacBindingTransaction::TYPE_INTERFACE:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
|
case AlmanacBindingTransaction::TYPE_DISABLE:
|
||||||
|
return (int)$xaction->getNewValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||||
|
@ -53,6 +58,9 @@ final class AlmanacBindingEditor
|
||||||
$object->setDevicePHID($interface->getDevicePHID());
|
$object->setDevicePHID($interface->getDevicePHID());
|
||||||
$object->setInterfacePHID($interface->getPHID());
|
$object->setInterfacePHID($interface->getPHID());
|
||||||
return;
|
return;
|
||||||
|
case AlmanacBindingTransaction::TYPE_DISABLE:
|
||||||
|
$object->setIsDisabled($xaction->getNewValue());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||||
|
@ -63,6 +71,8 @@ final class AlmanacBindingEditor
|
||||||
PhabricatorApplicationTransaction $xaction) {
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
|
case AlmanacBindingTransaction::TYPE_DISABLE:
|
||||||
|
return;
|
||||||
case AlmanacBindingTransaction::TYPE_INTERFACE:
|
case AlmanacBindingTransaction::TYPE_INTERFACE:
|
||||||
$interface_phids = array();
|
$interface_phids = array();
|
||||||
|
|
||||||
|
|
|
@ -310,6 +310,19 @@ final class AlmanacDeviceEditor
|
||||||
pht('You can not edit an invalid or restricted interface.'),
|
pht('You can not edit an invalid or restricted interface.'),
|
||||||
$xaction);
|
$xaction);
|
||||||
$errors[] = $error;
|
$errors[] = $error;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = $xaction->getNewValue();
|
||||||
|
if (!$new) {
|
||||||
|
if ($interface->loadIsInUse()) {
|
||||||
|
$error = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('In Use'),
|
||||||
|
pht('You can not delete an interface which is still in use.'),
|
||||||
|
$xaction);
|
||||||
|
$errors[] = $error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ abstract class AlmanacSearchEngineAttachment
|
||||||
'phid' => $binding->getPHID(),
|
'phid' => $binding->getPHID(),
|
||||||
'properties' => $this->getAlmanacPropertyList($binding),
|
'properties' => $this->getAlmanacPropertyList($binding),
|
||||||
'interface' => $this->getAlmanacInterfaceDictionary($interface),
|
'interface' => $this->getAlmanacInterfaceDictionary($interface),
|
||||||
|
'disabled' => (bool)$binding->getIsDisabled(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ final class AlmanacBinding
|
||||||
protected $devicePHID;
|
protected $devicePHID;
|
||||||
protected $interfacePHID;
|
protected $interfacePHID;
|
||||||
protected $mailKey;
|
protected $mailKey;
|
||||||
|
protected $isDisabled;
|
||||||
|
|
||||||
private $service = self::ATTACHABLE;
|
private $service = self::ATTACHABLE;
|
||||||
private $device = self::ATTACHABLE;
|
private $device = self::ATTACHABLE;
|
||||||
|
@ -22,7 +23,8 @@ final class AlmanacBinding
|
||||||
public static function initializeNewBinding(AlmanacService $service) {
|
public static function initializeNewBinding(AlmanacService $service) {
|
||||||
return id(new AlmanacBinding())
|
return id(new AlmanacBinding())
|
||||||
->setServicePHID($service->getPHID())
|
->setServicePHID($service->getPHID())
|
||||||
->attachAlmanacProperties(array());
|
->attachAlmanacProperties(array())
|
||||||
|
->setIsDisabled(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getConfiguration() {
|
protected function getConfiguration() {
|
||||||
|
@ -30,6 +32,7 @@ final class AlmanacBinding
|
||||||
self::CONFIG_AUX_PHID => true,
|
self::CONFIG_AUX_PHID => true,
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
'mailKey' => 'bytes20',
|
'mailKey' => 'bytes20',
|
||||||
|
'isDisabled' => 'bool',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'key_service' => array(
|
'key_service' => array(
|
||||||
|
|
|
@ -4,6 +4,7 @@ final class AlmanacBindingTransaction
|
||||||
extends AlmanacTransaction {
|
extends AlmanacTransaction {
|
||||||
|
|
||||||
const TYPE_INTERFACE = 'almanac:binding:interface';
|
const TYPE_INTERFACE = 'almanac:binding:interface';
|
||||||
|
const TYPE_DISABLE = 'almanac:binding:disable';
|
||||||
|
|
||||||
public function getApplicationName() {
|
public function getApplicationName() {
|
||||||
return 'almanac';
|
return 'almanac';
|
||||||
|
@ -57,6 +58,17 @@ final class AlmanacBindingTransaction
|
||||||
$this->renderHandleLink($new));
|
$this->renderHandleLink($new));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case self::TYPE_DISABLE:
|
||||||
|
if ($new) {
|
||||||
|
return pht(
|
||||||
|
'%s disabled this binding.',
|
||||||
|
$this->renderHandleLink($author_phid));
|
||||||
|
} else {
|
||||||
|
return pht(
|
||||||
|
'%s enabled this binding.',
|
||||||
|
$this->renderHandleLink($author_phid));
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getTitle();
|
return parent::getTitle();
|
||||||
|
|
|
@ -69,7 +69,7 @@ final class AlmanacDeviceTransaction
|
||||||
return pht(
|
return pht(
|
||||||
'%s removed the interface %s from this device.',
|
'%s removed the interface %s from this device.',
|
||||||
$this->renderHandleLink($author_phid),
|
$this->renderHandleLink($author_phid),
|
||||||
$this->describeInterface($new));
|
$this->describeInterface($old));
|
||||||
} else if ($new) {
|
} else if ($new) {
|
||||||
return pht(
|
return pht(
|
||||||
'%s added the interface %s to this device.',
|
'%s added the interface %s to this device.',
|
||||||
|
|
|
@ -4,7 +4,8 @@ final class AlmanacInterface
|
||||||
extends AlmanacDAO
|
extends AlmanacDAO
|
||||||
implements
|
implements
|
||||||
PhabricatorPolicyInterface,
|
PhabricatorPolicyInterface,
|
||||||
PhabricatorDestructibleInterface {
|
PhabricatorDestructibleInterface,
|
||||||
|
PhabricatorExtendedPolicyInterface {
|
||||||
|
|
||||||
protected $devicePHID;
|
protected $devicePHID;
|
||||||
protected $networkPHID;
|
protected $networkPHID;
|
||||||
|
@ -74,6 +75,16 @@ final class AlmanacInterface
|
||||||
return $this->getAddress().':'.$this->getPort();
|
return $this->getAddress().':'.$this->getPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadIsInUse() {
|
||||||
|
$binding = id(new AlmanacBindingQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withInterfacePHIDs(array($this->getPHID()))
|
||||||
|
->setLimit(1)
|
||||||
|
->executeOne();
|
||||||
|
|
||||||
|
return (bool)$binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
@ -105,6 +116,27 @@ final class AlmanacInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
|
||||||
|
switch ($capability) {
|
||||||
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||||
|
if ($this->getDevice()->isClusterDevice()) {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
new PhabricatorAlmanacApplication(),
|
||||||
|
AlmanacManageClusterServicesCapability::CAPABILITY,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ final class AlmanacBindingTableView extends AphrontView {
|
||||||
private $bindings;
|
private $bindings;
|
||||||
private $noDataString;
|
private $noDataString;
|
||||||
|
|
||||||
|
private $hideServiceColumn;
|
||||||
|
|
||||||
public function setNoDataString($no_data_string) {
|
public function setNoDataString($no_data_string) {
|
||||||
$this->noDataString = $no_data_string;
|
$this->noDataString = $no_data_string;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -23,6 +25,15 @@ final class AlmanacBindingTableView extends AphrontView {
|
||||||
return $this->bindings;
|
return $this->bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setHideServiceColumn($hide_service_column) {
|
||||||
|
$this->hideServiceColumn = $hide_service_column;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHideServiceColumn() {
|
||||||
|
return $this->hideServiceColumn;
|
||||||
|
}
|
||||||
|
|
||||||
public function render() {
|
public function render() {
|
||||||
$bindings = $this->getBindings();
|
$bindings = $this->getBindings();
|
||||||
$viewer = $this->getUser();
|
$viewer = $this->getUser();
|
||||||
|
@ -35,6 +46,22 @@ final class AlmanacBindingTableView extends AphrontView {
|
||||||
}
|
}
|
||||||
$handles = $viewer->loadHandles($phids);
|
$handles = $viewer->loadHandles($phids);
|
||||||
|
|
||||||
|
$icon_disabled = id(new PHUIIconView())
|
||||||
|
->setIcon('fa-ban')
|
||||||
|
->addSigil('has-tooltip')
|
||||||
|
->setMetadata(
|
||||||
|
array(
|
||||||
|
'tip' => pht('Disabled'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$icon_active = id(new PHUIIconView())
|
||||||
|
->setIcon('fa-check')
|
||||||
|
->addSigil('has-tooltip')
|
||||||
|
->setMetadata(
|
||||||
|
array(
|
||||||
|
'tip' => pht('Active'),
|
||||||
|
));
|
||||||
|
|
||||||
$rows = array();
|
$rows = array();
|
||||||
foreach ($bindings as $binding) {
|
foreach ($bindings as $binding) {
|
||||||
$addr = $binding->getInterface()->getAddress();
|
$addr = $binding->getInterface()->getAddress();
|
||||||
|
@ -42,6 +69,7 @@ final class AlmanacBindingTableView extends AphrontView {
|
||||||
|
|
||||||
$rows[] = array(
|
$rows[] = array(
|
||||||
$binding->getID(),
|
$binding->getID(),
|
||||||
|
($binding->getIsDisabled() ? $icon_disabled : $icon_active),
|
||||||
$handles->renderHandle($binding->getServicePHID()),
|
$handles->renderHandle($binding->getServicePHID()),
|
||||||
$handles->renderHandle($binding->getDevicePHID()),
|
$handles->renderHandle($binding->getDevicePHID()),
|
||||||
$handles->renderHandle($binding->getInterface()->getNetworkPHID()),
|
$handles->renderHandle($binding->getInterface()->getNetworkPHID()),
|
||||||
|
@ -61,6 +89,7 @@ final class AlmanacBindingTableView extends AphrontView {
|
||||||
->setHeaders(
|
->setHeaders(
|
||||||
array(
|
array(
|
||||||
pht('ID'),
|
pht('ID'),
|
||||||
|
null,
|
||||||
pht('Service'),
|
pht('Service'),
|
||||||
pht('Device'),
|
pht('Device'),
|
||||||
pht('Network'),
|
pht('Network'),
|
||||||
|
@ -70,11 +99,18 @@ final class AlmanacBindingTableView extends AphrontView {
|
||||||
->setColumnClasses(
|
->setColumnClasses(
|
||||||
array(
|
array(
|
||||||
'',
|
'',
|
||||||
|
'icon',
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'wide',
|
'wide',
|
||||||
'action',
|
'action',
|
||||||
|
))
|
||||||
|
->setColumnVisibility(
|
||||||
|
array(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
!$this->getHideServiceColumn(),
|
||||||
));
|
));
|
||||||
|
|
||||||
return $table;
|
return $table;
|
||||||
|
|
|
@ -27,7 +27,9 @@ final class AlmanacInterfaceTableView extends AphrontView {
|
||||||
$interfaces = $this->getInterfaces();
|
$interfaces = $this->getInterfaces();
|
||||||
$viewer = $this->getUser();
|
$viewer = $this->getUser();
|
||||||
|
|
||||||
if ($this->getCanEdit()) {
|
$can_edit = $this->getCanEdit();
|
||||||
|
|
||||||
|
if ($can_edit) {
|
||||||
$button_class = 'small grey button';
|
$button_class = 'small grey button';
|
||||||
} else {
|
} else {
|
||||||
$button_class = 'small grey button disabled';
|
$button_class = 'small grey button disabled';
|
||||||
|
@ -42,13 +44,22 @@ final class AlmanacInterfaceTableView extends AphrontView {
|
||||||
$handles->renderHandle($interface->getNetworkPHID()),
|
$handles->renderHandle($interface->getNetworkPHID()),
|
||||||
$interface->getAddress(),
|
$interface->getAddress(),
|
||||||
$interface->getPort(),
|
$interface->getPort(),
|
||||||
phutil_tag(
|
javelin_tag(
|
||||||
'a',
|
'a',
|
||||||
array(
|
array(
|
||||||
'class' => $button_class,
|
'class' => $button_class,
|
||||||
'href' => '/almanac/interface/edit/'.$interface->getID().'/',
|
'href' => '/almanac/interface/edit/'.$interface->getID().'/',
|
||||||
|
'sigil' => ($can_edit ? null : 'workflow'),
|
||||||
),
|
),
|
||||||
pht('Edit')),
|
pht('Edit')),
|
||||||
|
javelin_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => $button_class,
|
||||||
|
'href' => '/almanac/interface/delete/'.$interface->getID().'/',
|
||||||
|
'sigil' => 'workflow',
|
||||||
|
),
|
||||||
|
pht('Delete')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +71,7 @@ final class AlmanacInterfaceTableView extends AphrontView {
|
||||||
pht('Address'),
|
pht('Address'),
|
||||||
pht('Port'),
|
pht('Port'),
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
))
|
))
|
||||||
->setColumnClasses(
|
->setColumnClasses(
|
||||||
array(
|
array(
|
||||||
|
@ -68,6 +80,7 @@ final class AlmanacInterfaceTableView extends AphrontView {
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'action',
|
'action',
|
||||||
|
'action',
|
||||||
));
|
));
|
||||||
|
|
||||||
return $table;
|
return $table;
|
||||||
|
|
|
@ -267,6 +267,11 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
|
||||||
|
|
||||||
$free = array();
|
$free = array();
|
||||||
foreach ($bindings as $binding) {
|
foreach ($bindings as $binding) {
|
||||||
|
// Don't consider disabled bindings to be available.
|
||||||
|
if ($binding->getIsDisabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($allocated_phids[$binding->getPHID()])) {
|
if (empty($allocated_phids[$binding->getPHID()])) {
|
||||||
$free[] = $binding;
|
$free[] = $binding;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
@title User Guide: Phabricator Clusters
|
@title User Guide: Phabricator Clusters
|
||||||
@group config
|
@group config
|
||||||
|
|
||||||
Guide on scaling Phabricator across multiple machines, for large installs.
|
Guide on scaling Phabricator across multiple machines.
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
========
|
========
|
||||||
|
@ -9,10 +9,42 @@ Overview
|
||||||
IMPORTANT: Phabricator clustering is in its infancy and does not work at all
|
IMPORTANT: Phabricator clustering is in its infancy and does not work at all
|
||||||
yet. This document is mostly a placeholder.
|
yet. This document is mostly a placeholder.
|
||||||
|
|
||||||
Locking Services
|
IMPORTANT: DO NOT CONFIGURE CLUSTER SERVICES UNLESS YOU HAVE **TWENTY YEARS OF
|
||||||
================
|
EXPERIENCE WITH PHABRICATOR** AND **A MINIMUM OF 17 PHABRICATOR PHDs**. YOU
|
||||||
|
WILL BREAK YOUR INSTALL AND BE UNABLE TO REPAIR IT.
|
||||||
Very briefly, you can set "Can Manage Cluster Services" to "No One" to lock
|
|
||||||
the cluster definition.
|
|
||||||
|
|
||||||
See also @{article:Almanac User Guide}.
|
See also @{article:Almanac User Guide}.
|
||||||
|
|
||||||
|
|
||||||
|
Managing Cluster Configuration
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Cluster configuration is managed primarily from the **Almanac** application.
|
||||||
|
|
||||||
|
To define cluster services and create or edit cluster configuration, you must
|
||||||
|
have the **Can Manage Cluster Services** application permission in Almanac. If
|
||||||
|
you do not have this permission, all cluster services and all connected devices
|
||||||
|
will be locked and not editable.
|
||||||
|
|
||||||
|
The **Can Manage Cluster Services** permission is stronger than service and
|
||||||
|
device policies, and overrides them. You can never edit a cluster service if
|
||||||
|
you don't have this permission, even if the **Can Edit** policy on the service
|
||||||
|
itself is very permissive.
|
||||||
|
|
||||||
|
|
||||||
|
Locking Cluster Configuration
|
||||||
|
=============================
|
||||||
|
|
||||||
|
IMPORTANT: Managing cluster services is **dangerous** and **fragile**.
|
||||||
|
|
||||||
|
If you make a mistake, you can break your install. Because the install is
|
||||||
|
broken, you will be unable to load the web interface in order to repair it.
|
||||||
|
|
||||||
|
IMPORTANT: Currently, broken clusters must be repaired by manually fixing them
|
||||||
|
in the database. There are no instructions available on how to do this, and no
|
||||||
|
tools to help you. Do not configure cluster services.
|
||||||
|
|
||||||
|
If an attacker gains access to an account with permission to manage cluster
|
||||||
|
services, they can add devices they control as database servers. These servers
|
||||||
|
will then receive sensitive data and traffic, and allow the attacker to
|
||||||
|
escalate their access and completely compromise an install.
|
||||||
|
|
Loading…
Reference in a new issue