1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

Build AlmanacInterface

Summary: Ref T5833. An interface is an IP (maybe v4, maybe v6) and port on a specified network (public internet, VPN, NAT block, etc).

Test Plan: See screenshots.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5833

Differential Revision: https://secure.phabricator.com/D10718
This commit is contained in:
epriestley 2014-10-17 05:04:24 -07:00
parent 3e704f6c78
commit 3e516e40f3
12 changed files with 835 additions and 0 deletions

View file

@ -0,0 +1,13 @@
CREATE TABLE {$NAMESPACE}_almanac.almanac_interface (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
devicePHID VARBINARY(64) NOT NULL,
networkPHID VARBINARY(64) NOT NULL,
address VARCHAR(128) NOT NULL COLLATE utf8_bin,
port INT UNSIGNED NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_location` (networkPHID, address, port),
KEY `key_device` (devicePHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;

View file

@ -9,6 +9,7 @@
phutil_register_library_map(array(
'__library_version__' => 2,
'class' => array(
'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php',
'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php',
'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
@ -28,6 +29,11 @@ phutil_register_library_map(array(
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php',
'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php',
'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php',
'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php',
'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php',
'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php',
'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php',
@ -2940,6 +2946,7 @@ phutil_register_library_map(array(
'require_celerity_resource' => 'applications/celerity/api.php',
),
'xmap' => array(
'AlmanacAddress' => 'Phobject',
'AlmanacConduitUtil' => 'Phobject',
'AlmanacConsoleController' => 'AlmanacController',
'AlmanacController' => 'PhabricatorController',
@ -2962,6 +2969,14 @@ phutil_register_library_map(array(
'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction',
'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacDeviceViewController' => 'AlmanacDeviceController',
'AlmanacInterface' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
),
'AlmanacInterfaceEditController' => 'AlmanacDeviceController',
'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType',
'AlmanacInterfaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacInterfaceTableView' => 'AphrontView',
'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow',
'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow',
'AlmanacNames' => 'Phobject',

View file

@ -44,6 +44,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController',
),
'interface/' => array(
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacInterfaceEditController',
),
'network/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacNetworkListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController',

View file

@ -35,6 +35,8 @@ final class AlmanacDeviceViewController
->setHeader($header)
->addPropertyList($property_list);
$interfaces = $this->buildInterfaceList($device);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($device->getName());
@ -53,6 +55,7 @@ final class AlmanacDeviceViewController
array(
$crumbs,
$box,
$interfaces,
$xaction_view,
),
array(
@ -92,4 +95,48 @@ final class AlmanacDeviceViewController
return $actions;
}
private function buildInterfaceList(AlmanacDevice $device) {
$viewer = $this->getViewer();
$id = $device->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$device,
PhabricatorPolicyCapability::CAN_EDIT);
$interfaces = id(new AlmanacInterfaceQuery())
->setViewer($viewer)
->withDevicePHIDs(array($device->getPHID()))
->execute();
$phids = array();
foreach ($interfaces as $interface) {
$phids[] = $interface->getNetworkPHID();
$phids[] = $interface->getDevicePHID();
}
$handles = $this->loadViewerHandles($phids);
$table = id(new AlmanacInterfaceTableView())
->setUser($viewer)
->setInterfaces($interfaces)
->setHandles($handles);
$header = id(new PHUIHeaderView())
->setHeader(pht('Device Interfaces'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($this->getApplicationURI("interface/edit/?deviceID={$id}"))
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit)
->setText(pht('Add Interface'))
->setIcon(
id(new PHUIIconView())
->setIconFont('fa-plus')));
return id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($table);
}
}

View file

@ -0,0 +1,155 @@
<?php
final class AlmanacInterfaceEditController
extends AlmanacDeviceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if ($id) {
$interface = id(new AlmanacInterfaceQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$interface) {
return new Aphront404Response();
}
$device = $interface->getDevice();
$is_new = false;
$title = pht('Edit Interface');
$save_button = pht('Save Changes');
} else {
$device = id(new AlmanacDeviceQuery())
->setViewer($viewer)
->withIDs(array($request->getStr('deviceID')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$device) {
return new Aphront404Response();
}
$interface = AlmanacInterface::initializeNewInterface();
$is_new = true;
$title = pht('Create Interface');
$save_button = pht('Create Interface');
}
$device_uri = $device->getURI();
$cancel_uri = $device_uri;
$v_network = $interface->getNetworkPHID();
$v_address = $interface->getAddress();
$e_address = true;
$v_port = $interface->getPort();
$validation_exception = null;
if ($request->isFormPost()) {
$v_network = $request->getStr('networkPHID');
$v_address = $request->getStr('address');
$v_port = $request->getStr('port');
$type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE;
$address = AlmanacAddress::newFromParts($v_network, $v_address, $v_port);
$xaction = id(new AlmanacDeviceTransaction())
->setTransactionType($type_interface)
->setNewValue($address->toDictionary());
if ($interface->getID()) {
$xaction->setOldValue(array(
'id' => $interface->getID(),
) + $interface->toAddress()->toDictionary());
} else {
$xaction->setOldValue(array());
}
$xactions = array();
$xactions[] = $xaction;
$editor = id(new AlmanacDeviceEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
try {
$editor->applyTransactions($device, $xactions);
$device_uri = $device->getURI();
return id(new AphrontRedirectResponse())->setURI($device_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_address = $ex->getShortMessage($type_interface);
}
}
$networks = id(new AlmanacNetworkQuery())
->setViewer($viewer)
->execute();
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Network'))
->setName('networkPHID')
->setValue($v_network)
->setOptions(mpull($networks, 'getName', 'getPHID')))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Address'))
->setName('address')
->setValue($v_address)
->setError($e_address))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Port'))
->setName('port')
->setValue($v_port)
->setError($e_address))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($save_button));
$box = id(new PHUIObjectBoxView())
->setValidationException($validation_exception)
->setHeaderText($title)
->appendChild($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($device->getName(), $device_uri);
if ($is_new) {
$crumbs->addTextCrumb(pht('Create Interface'));
} else {
$crumbs->addTextCrumb(pht('Edit Interface'));
}
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
}
}

View file

@ -15,6 +15,7 @@ final class AlmanacDeviceEditor
$types = parent::getTransactionTypes();
$types[] = AlmanacDeviceTransaction::TYPE_NAME;
$types[] = AlmanacDeviceTransaction::TYPE_INTERFACE;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@ -38,6 +39,7 @@ final class AlmanacDeviceEditor
switch ($xaction->getTransactionType()) {
case AlmanacDeviceTransaction::TYPE_NAME:
case AlmanacDeviceTransaction::TYPE_INTERFACE:
return $xaction->getNewValue();
}
@ -52,6 +54,7 @@ final class AlmanacDeviceEditor
case AlmanacDeviceTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case AlmanacDeviceTransaction::TYPE_INTERFACE:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return;
@ -69,6 +72,32 @@ final class AlmanacDeviceEditor
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return;
case AlmanacDeviceTransaction::TYPE_INTERFACE:
$old = $xaction->getOldValue();
if ($old) {
$interface = id(new AlmanacInterfaceQuery())
->setViewer($this->requireActor())
->withIDs(array($old['id']))
->executeOne();
if (!$interface) {
throw new Exception(pht('Unable to load interface!'));
}
} else {
$interface = AlmanacInterface::initializeNewInterface()
->setDevicePHID($object->getPHID());
}
$new = $xaction->getNewValue();
if ($new) {
$interface
->setNetworkPHID($new['networkPHID'])
->setAddress($new['address'])
->setPort((int)$new['port'])
->save();
} else {
$interface->delete();
}
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
@ -134,6 +163,125 @@ final class AlmanacDeviceEditor
}
break;
case AlmanacDeviceTransaction::TYPE_INTERFACE:
// We want to make sure that all the affected networks are visible to
// the actor, any edited interfaces exist, and that the actual address
// components are valid.
$network_phids = array();
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if ($old) {
$network_phids[] = $old['networkPHID'];
}
if ($new) {
$network_phids[] = $new['networkPHID'];
$address = $new['address'];
if (!strlen($address)) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('Interfaces must have an address.'),
$xaction);
$errors[] = $error;
} else {
// TODO: Validate addresses, but IPv6 addresses are not trival
// to validate.
}
$port = $new['port'];
if (!strlen($port)) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('Interfaces must have a port.'),
$xaction);
$errors[] = $error;
} else if ((int)$port < 1 || (int)$port > 65535) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Port numbers must be between 1 and 65535, inclusive.'),
$xaction);
$errors[] = $error;
}
}
}
if ($network_phids) {
$networks = id(new AlmanacNetworkQuery())
->setViewer($this->requireActor())
->withPHIDs($network_phids)
->execute();
$networks = mpull($networks, null, 'getPHID');
} else {
$networks = array();
}
$addresses = array();
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
if ($old) {
$network = idx($networks, $old['networkPHID']);
if (!$network) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'You can not edit an interface which belongs to a '.
'nonexistent or restricted network.'),
$xaction);
$errors[] = $error;
}
$addresses[] = $old['id'];
}
$new = $xaction->getNewValue();
if ($new) {
$network = idx($networks, $new['networkPHID']);
if (!$network) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'You can not add an interface on a nonexistent or '.
'restricted network.'),
$xaction);
$errors[] = $error;
}
}
}
if ($addresses) {
$interfaces = id(new AlmanacInterfaceQuery())
->setViewer($this->requireActor())
->withDevicePHIDs(array($object->getPHID()))
->withIDs($addresses)
->execute();
$interfaces = mpull($interfaces, null, 'getID');
} else {
$interfaces = array();
}
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
if ($old) {
$interface = idx($interfaces, $old['id']);
if (!$interface) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('You can not edit an invalid or restricted interface.'),
$xaction);
$errors[] = $error;
}
}
}
break;
}
return $errors;

View file

@ -0,0 +1,38 @@
<?php
final class AlmanacInterfacePHIDType extends PhabricatorPHIDType {
const TYPECONST = 'AINT';
public function getTypeName() {
return pht('Almanac Interface');
}
public function newObject() {
return new AlmanacInterface();
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new AlmanacInterfaceQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$interface = $objects[$phid];
$id = $interface->getID();
$handle->setObjectName(pht('Interface %d', $id));
$handle->setName(pht('Interface %d', $id));
}
}
}

View file

@ -0,0 +1,139 @@
<?php
final class AlmanacInterfaceQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $networkPHIDs;
private $devicePHIDs;
private $addresses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withNetworkPHIDs(array $phids) {
$this->networkPHIDs = $phids;
return $this;
}
public function withDevicePHIDs(array $phids) {
$this->devicePHIDs = $phids;
return $this;
}
public function withAddresses(array $addresses) {
$this->addresses = $addresses;
return $this;
}
protected function loadPage() {
$table = new AlmanacInterface();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
protected function willFilterPage(array $interfaces) {
$network_phids = mpull($interfaces, 'getNetworkPHID');
$device_phids = mpull($interfaces, 'getDevicePHID');
$networks = id(new AlmanacNetworkQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($network_phids)
->execute();
$networks = mpull($networks, null, 'getPHID');
$devices = id(new AlmanacDeviceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($device_phids)
->execute();
$devices = mpull($devices, null, 'getPHID');
foreach ($interfaces as $key => $interface) {
$network = idx($networks, $interface->getNetworkPHID());
$device = idx($devices, $interface->getDevicePHID());
if (!$network || !$device) {
$this->didRejectResult($interface);
unset($interfaces[$key]);
continue;
}
$interface->attachNetwork($network);
$interface->attachDevice($device);
}
return $interfaces;
}
protected function buildWhereClause($conn_r) {
$where = array();
if ($this->ids !== null) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
if ($this->networkPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'networkPHID IN (%Ls)',
$this->networkPHIDs);
}
if ($this->devicePHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'devicePHID IN (%Ls)',
$this->devicePHIDs);
}
if ($this->addresses !== null) {
$parts = array();
foreach ($this->addresses as $address) {
$parts[] = qsprintf(
$conn_r,
'(networkPHID = %s AND address = %s AND port = %d)',
$address->getNetworkPHID(),
$address->getAddress(),
$address->getPort());
}
$where[] = implode(' OR ', $parts);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
public function getQueryApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
}

View file

@ -4,6 +4,7 @@ final class AlmanacDeviceTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'almanac:device:name';
const TYPE_INTERFACE = 'almanac:device:interface';
public function getApplicationName() {
return 'almanac';
@ -17,6 +18,26 @@ final class AlmanacDeviceTransaction
return null;
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_INTERFACE:
if ($old) {
$phids[] = $old['networkPHID'];
}
if ($new) {
$phids[] = $new['networkPHID'];
}
break;
}
return $phids;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
@ -37,9 +58,43 @@ final class AlmanacDeviceTransaction
$new);
}
break;
case self::TYPE_INTERFACE:
if ($old && $new) {
return pht(
'%s changed interface %s on this device to %s.',
$this->renderHandleLink($author_phid),
$this->describeInterface($old),
$this->describeInterface($new));
} else if ($old) {
return pht(
'%s removed the interface %s from this device.',
$this->renderHandleLink($author_phid),
$this->describeInterface($new));
} else if ($new) {
return pht(
'%s added the interface %s to this device.',
$this->renderHandleLink($author_phid),
$this->describeInterface($new));
}
}
return parent::getTitle();
}
public function shouldGenerateOldValue() {
switch ($this->getTransactionType()) {
case self::TYPE_INTERFACE:
return false;
}
return parent::shouldGenerateOldValue();
}
private function describeInterface(array $info) {
return pht(
'%s:%s (%s)',
$info['address'],
$info['port'],
$this->renderHandleLink($info['networkPHID']));
}
}

View file

@ -0,0 +1,99 @@
<?php
final class AlmanacInterface
extends AlmanacDAO
implements PhabricatorPolicyInterface {
protected $devicePHID;
protected $networkPHID;
protected $address;
protected $port;
private $device = self::ATTACHABLE;
private $network = self::ATTACHABLE;
public static function initializeNewInterface() {
return id(new AlmanacInterface());
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'address' => 'text64',
'port' => 'uint32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_location' => array(
'columns' => array('networkPHID', 'address', 'port'),
),
'key_device' => array(
'columns' => array('devicePHID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
AlmanacInterfacePHIDType::TYPECONST);
}
public function getDevice() {
return $this->assertAttached($this->device);
}
public function attachDevice(AlmanacDevice $device) {
$this->device = $device;
return $this;
}
public function getNetwork() {
return $this->assertAttached($this->network);
}
public function attachNetwork(AlmanacNetwork $device) {
$this->device = $device;
return $this;
}
public function toAddress() {
return AlmanacAddress::newFromParts(
$this->getNetworkPHID(),
$this->getAddress(),
$this->getPort());
}
public function getAddressHash() {
return $this->toAddress()->toHash();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getDevice()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getDevice()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return array(
pht('An interface inherits the policies of the device it belongs to.'),
pht(
'You must be able to view the network an interface resides on to '.
'view the interface.'),
);
}
}

View file

@ -0,0 +1,54 @@
<?php
final class AlmanacAddress extends Phobject {
private $networkPHID;
private $address;
private $port;
private function __construct() {
// <private>
}
public function getNetworkPHID() {
return $this->networkPHID;
}
public function getAddress() {
return $this->address;
}
public function getPort() {
return $this->port;
}
public static function newFromDictionary(array $dictionary) {
return self::newFromParts(
$dictionary['networkPHID'],
$dictionary['address'],
$dictionary['port']);
}
public static function newFromParts($network_phid, $address, $port) {
$addr = new AlmanacAddress();
$addr->networkPHID = $network_phid;
$addr->address = $address;
$addr->port = (int)$port;
return $addr;
}
public function toDictionary() {
return array(
'networkPHID' => $this->getNetworkPHID(),
'address' => $this->getAddress(),
'port' => $this->getPort(),
);
}
public function toHash() {
return PhabricatorHash::digestForIndex(json_encode($this->toDictionary()));
}
}

View file

@ -0,0 +1,69 @@
<?php
final class AlmanacInterfaceTableView extends AphrontView {
private $interfaces;
private $handles;
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getHandles() {
return $this->handles;
}
public function setInterfaces(array $interfaces) {
$this->interfaces = $interfaces;
return $this;
}
public function getInterfaces() {
return $this->interfaces;
}
public function render() {
$interfaces = $this->getInterfaces();
$handles = $this->getHandles();
$viewer = $this->getUser();
$rows = array();
foreach ($interfaces as $interface) {
$rows[] = array(
$interface->getID(),
$handles[$interface->getNetworkPHID()]->renderLink(),
$interface->getAddress(),
$interface->getPort(),
phutil_tag(
'a',
array(
'class' => 'small grey button',
'href' => '/almanac/interface/edit/'.$interface->getID().'/',
),
pht('Edit')),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('ID'),
pht('Network'),
pht('Address'),
pht('Port'),
null,
))
->setColumnClasses(
array(
'',
'wide',
'',
'',
'action',
));
return $table;
}
}