diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index bf95a57009..5df54593ee 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -1,100 +1,102 @@ pht('Drydock User Guide'), 'href' => PhabricatorEnv::getDoclink('Drydock User Guide'), ), ); } public function getRoutes() { return array( '/drydock/' => array( '' => 'DrydockConsoleController', 'blueprint/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockBlueprintListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockBlueprintViewController', '(?Pdisable|enable)/' => 'DrydockBlueprintDisableController', 'resources/(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', ), 'create/' => 'DrydockBlueprintCreateController', 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', ), 'resource/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockResourceViewController', 'release/' => 'DrydockResourceReleaseController', + 'leases/(?:query/(?P[^/]+)/)?' => + 'DrydockLeaseListController', ), ), 'lease/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockLeaseViewController', 'release/' => 'DrydockLeaseReleaseController', ), ), 'log/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLogListController', ), ), ); } protected function getCustomCapabilities() { return array( DrydockDefaultViewCapability::CAPABILITY => array( 'template' => DrydockBlueprintPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), DrydockDefaultEditCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, 'template' => DrydockBlueprintPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), DrydockCreateBlueprintsCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/drydock/controller/DrydockLeaseController.php b/src/applications/drydock/controller/DrydockLeaseController.php index d520fc66ee..d5a6335454 100644 --- a/src/applications/drydock/controller/DrydockLeaseController.php +++ b/src/applications/drydock/controller/DrydockLeaseController.php @@ -1,27 +1,62 @@ resource = $resource; + return $this; + } + + public function getResource() { + return $this->resource; + } + public function buildSideNavView() { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - id(new DrydockLeaseSearchEngine()) - ->setViewer($this->getRequest()->getUser()) - ->addNavigationItems($nav->getMenu()); + $engine = id(new DrydockLeaseSearchEngine()) + ->setViewer($this->getRequest()->getUser()); + + if ($this->getResource()) { + $engine->setResource($this->getResource()); + } + + $engine->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Leases'), - $this->getApplicationURI('lease/')); + + $resource = $this->getResource(); + if ($resource) { + $id = $resource->getID(); + + $crumbs->addTextCrumb( + pht('Resources'), + $this->getApplicationURI('resource/')); + + $crumbs->addTextCrumb( + $resource->getName(), + $this->getApplicationURI("resource/{$id}/")); + + $crumbs->addTextCrumb( + pht('Leases'), + $this->getApplicationURI("resource/{$id}/leases/")); + + } else { + $crumbs->addTextCrumb( + pht('Leases'), + $this->getApplicationURI('lease/')); + } return $crumbs; } } diff --git a/src/applications/drydock/controller/DrydockLeaseListController.php b/src/applications/drydock/controller/DrydockLeaseListController.php index e370467a5f..321e1d4ae7 100644 --- a/src/applications/drydock/controller/DrydockLeaseListController.php +++ b/src/applications/drydock/controller/DrydockLeaseListController.php @@ -1,21 +1,36 @@ getViewer(); - $querykey = $request->getURIData('queryKey'); + $query_key = $request->getURIData('queryKey'); + + $engine = new DrydockLeaseSearchEngine(); + + $id = $request->getURIData('id'); + if ($id) { + $resource = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$resource) { + return new Aphront404Response(); + } + $this->setResource($resource); + $engine->setResource($resource); + } $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new DrydockLeaseSearchEngine()) + ->setQueryKey($query_key) + ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } } diff --git a/src/applications/drydock/controller/DrydockResourceController.php b/src/applications/drydock/controller/DrydockResourceController.php index 8675b5bf59..120d6d7cb0 100644 --- a/src/applications/drydock/controller/DrydockResourceController.php +++ b/src/applications/drydock/controller/DrydockResourceController.php @@ -1,55 +1,60 @@ blueprint = $blueprint; return $this; } public function getBlueprint() { return $this->blueprint; } public function buildSideNavView() { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - id(new DrydockResourceSearchEngine()) - ->setViewer($this->getViewer()) - ->addNavigationItems($nav->getMenu()); + $engine = id(new DrydockResourceSearchEngine()) + ->setViewer($this->getViewer()); + + if ($this->getBlueprint()) { + $engine->setBlueprint($this->getBlueprint()); + } + + $engine->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $blueprint = $this->getBlueprint(); if ($blueprint) { $id = $blueprint->getID(); $crumbs->addTextCrumb( pht('Blueprints'), $this->getApplicationURI('blueprint/')); $crumbs->addTextCrumb( $blueprint->getBlueprintName(), $this->getApplicationURI("blueprint/{$id}/")); $crumbs->addTextCrumb( pht('Resources'), $this->getApplicationURI("blueprint/{$id}/resources/")); } else { $crumbs->addTextCrumb( pht('Resources'), $this->getApplicationURI('resource/')); } return $crumbs; } } diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index be1db24a0c..0641ced96d 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -1,152 +1,178 @@ getViewer(); $id = $request->getURIData('id'); $resource = id(new DrydockResourceQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$resource) { return new Aphront404Response(); } $title = pht('Resource %s %s', $resource->getID(), $resource->getName()); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($resource) ->setHeader($title); $actions = $this->buildActionListView($resource); $properties = $this->buildPropertyListView($resource, $actions); $resource_uri = 'resource/'.$resource->getID().'/'; $resource_uri = $this->getApplicationURI($resource_uri); - $leases = id(new DrydockLeaseQuery()) - ->setViewer($viewer) - ->withResourcePHIDs(array($resource->getPHID())) - ->execute(); - - $lease_list = id(new DrydockLeaseListView()) - ->setUser($viewer) - ->setLeases($leases) - ->render(); - $lease_list->setNoDataString(pht('This resource has no leases.')); - $pager = new PHUIPagerView(); $pager->setURI(new PhutilURI($resource_uri), 'offset'); $pager->setOffset($request->getInt('offset')); $logs = id(new DrydockLogQuery()) ->setViewer($viewer) ->withResourceIDs(array($resource->getID())) ->executeWithOffsetPager($pager); $log_table = id(new DrydockLogListView()) ->setUser($viewer) ->setLogs($logs) ->render(); $log_table->appendChild($pager); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); $locks = $this->buildLocksTab($resource->getPHID()); $commands = $this->buildCommandsTab($resource->getPHID()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - $lease_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Leases')) - ->setObjectList($lease_list); + $lease_box = $this->buildLeaseBox($resource); $log_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Resource Logs')) ->setTable($log_table); return $this->buildApplicationPage( array( $crumbs, $object_box, $lease_box, $log_box, ), array( 'title' => $title, )); } private function buildActionListView(DrydockResource $resource) { $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($resource); $can_release = $resource->canRelease(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $resource, PhabricatorPolicyCapability::CAN_EDIT); $uri = '/resource/'.$resource->getID().'/release/'; $uri = $this->getApplicationURI($uri); $view->addAction( id(new PhabricatorActionView()) ->setHref($uri) ->setName(pht('Release Resource')) ->setIcon('fa-times') ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); return $view; } private function buildPropertyListView( DrydockResource $resource, PhabricatorActionListView $actions) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setActionList($actions); $status = $resource->getStatus(); $status = DrydockResourceStatus::getNameForStatus($status); $view->addProperty( pht('Status'), $status); $view->addProperty( pht('Resource Type'), $resource->getType()); $view->addProperty( pht('Blueprint'), $viewer->renderHandle($resource->getBlueprintPHID())); $attributes = $resource->getAttributes(); if ($attributes) { $view->addSectionHeader( pht('Attributes'), 'fa-list-ul'); foreach ($attributes as $key => $value) { $view->addProperty($key, $value); } } return $view; } + private function buildLeaseBox(DrydockResource $resource) { + $viewer = $this->getViewer(); + + $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withResourcePHIDs(array($resource->getPHID())) + ->withStatuses( + array( + DrydockLeaseStatus::STATUS_PENDING, + DrydockLeaseStatus::STATUS_ACQUIRED, + DrydockLeaseStatus::STATUS_ACTIVE, + )) + ->setLimit(100) + ->execute(); + + $id = $resource->getID(); + $leases_uri = "resource/{$id}/leases/query/all/"; + $leases_uri = $this->getApplicationURI($leases_uri); + + $lease_header = id(new PHUIHeaderView()) + ->setHeader(pht('Active Leases')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($leases_uri) + ->setIconFont('fa-search') + ->setText(pht('View All Leases'))); + + $lease_list = id(new DrydockLeaseListView()) + ->setUser($viewer) + ->setLeases($leases) + ->render() + ->setNoDataString(pht('This resource has no active leases.')); + + return id(new PHUIObjectBoxView()) + ->setHeader($lease_header) + ->setObjectList($lease_list); + } + } diff --git a/src/applications/drydock/query/DrydockLeaseSearchEngine.php b/src/applications/drydock/query/DrydockLeaseSearchEngine.php index 7d85ddbe70..3b551023b2 100644 --- a/src/applications/drydock/query/DrydockLeaseSearchEngine.php +++ b/src/applications/drydock/query/DrydockLeaseSearchEngine.php @@ -1,81 +1,105 @@ resource = $resource; + return $this; + } + + public function getResource() { + return $this->resource; + } + public function getResultTypeDescription() { return pht('Drydock Leases'); } public function getApplicationClassName() { return 'PhabricatorDrydockApplication'; } public function newQuery() { - return new DrydockLeaseQuery(); + $query = new DrydockLeaseQuery(); + + $resource = $this->getResource(); + if ($resource) { + $query->withResourcePHIDs(array($resource->getPHID())); + } + + return $query; } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if ($map['statuses']) { $query->withStatuses($map['statuses']); } return $query; } protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Statuses')) ->setKey('statuses') ->setOptions(DrydockLeaseStatus::getStatusMap()), ); } protected function getURI($path) { - return '/drydock/lease/'.$path; + $resource = $this->getResource(); + if ($resource) { + $id = $resource->getID(); + return "/drydock/resource/{$id}/leases/".$path; + } else { + return '/drydock/lease/'.$path; + } } protected function getBuiltinQueryNames() { return array( 'active' => pht('Active Leases'), 'all' => pht('All Leases'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'active': return $query->setParameter( 'statuses', array( DrydockLeaseStatus::STATUS_PENDING, DrydockLeaseStatus::STATUS_ACQUIRED, DrydockLeaseStatus::STATUS_ACTIVE, )); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $leases, PhabricatorSavedQuery $saved, array $handles) { $list = id(new DrydockLeaseListView()) ->setUser($this->requireViewer()) ->setLeases($leases); return id(new PhabricatorApplicationSearchResultView()) ->setContent($list); } }