diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php index 3a19eff006..7452b29e21 100644 --- a/src/applications/config/controller/PhabricatorConfigAllController.php +++ b/src/applications/config/controller/PhabricatorConfigAllController.php @@ -1,78 +1,77 @@ getViewer(); $db_values = id(new PhabricatorConfigEntry()) ->loadAllWhere('namespace = %s', 'default'); $db_values = mpull($db_values, null, 'getConfigKey'); $rows = array(); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); ksort($options); foreach ($options as $option) { $key = $option->getKey(); if ($option->getHidden()) { $value = phutil_tag('em', array(), pht('Hidden')); } else { $value = PhabricatorEnv::getEnvConfig($key); $value = PhabricatorConfigJSON::prettyPrintJSON($value); } $db_value = idx($db_values, $key); $rows[] = array( phutil_tag( 'a', array( 'href' => $this->getApplicationURI('edit/'.$key.'/'), ), $key), $value, $db_value && !$db_value->getIsDeleted() ? pht('Customized') : '', ); } $table = id(new AphrontTableView($rows)) ->setColumnClasses( array( '', 'wide', )) ->setHeaders( array( pht('Key'), pht('Value'), pht('Customized'), )); $title = pht('Current Settings'); $header = $this->buildHeaderView($title); $nav = $this->buildSideNavView(); $nav->selectFilter('all/'); $view = $this->buildConfigBoxView( pht('All Settings'), $table); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($view); + ->setFooter($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } } diff --git a/src/applications/config/controller/PhabricatorConfigApplicationController.php b/src/applications/config/controller/PhabricatorConfigApplicationController.php index b4f60982e7..a6b8cd38c6 100644 --- a/src/applications/config/controller/PhabricatorConfigApplicationController.php +++ b/src/applications/config/controller/PhabricatorConfigApplicationController.php @@ -1,58 +1,57 @@ getViewer(); $nav = $this->buildSideNavView(); $nav->selectFilter('application/'); $groups = PhabricatorApplicationConfigOptions::loadAll(); $apps_list = $this->buildConfigOptionsList($groups, 'apps'); $apps_list = $this->buildConfigBoxView(pht('Applications'), $apps_list); $title = pht('Application Settings'); $header = $this->buildHeaderView($title); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($apps_list); + ->setFooter($apps_list); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function buildConfigOptionsList(array $groups, $type) { assert_instances_of($groups, 'PhabricatorApplicationConfigOptions'); $list = new PHUIObjectItemListView(); $list->setBig(true); $groups = msort($groups, 'getName'); foreach ($groups as $group) { if ($group->getGroup() == $type) { $icon = id(new PHUIIconView()) ->setIcon($group->getIcon()) ->setBackground('bg-violet'); $item = id(new PHUIObjectItemView()) ->setHeader($group->getName()) ->setHref('/config/group/'.$group->getKey().'/') ->addAttribute($group->getDescription()) ->setImageIcon($icon); $list->addItem($item); } } return $list; } } diff --git a/src/applications/config/controller/PhabricatorConfigCacheController.php b/src/applications/config/controller/PhabricatorConfigCacheController.php index a23ab1f9c1..36642657c9 100644 --- a/src/applications/config/controller/PhabricatorConfigCacheController.php +++ b/src/applications/config/controller/PhabricatorConfigCacheController.php @@ -1,177 +1,176 @@ getViewer(); $nav = $this->buildSideNavView(); $nav->selectFilter('cache/'); $purge_button = id(new PHUIButtonView()) ->setText(pht('Purge Caches')) ->setHref('/config/cache/purge/') ->setTag('a') ->setWorkflow(true) ->setIcon('fa-exclamation-triangle'); $title = pht('Cache Status'); $header = $this->buildHeaderView($title, $purge_button); $code_box = $this->renderCodeBox(); $data_box = $this->renderDataBox(); $page = array( $code_box, $data_box, ); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($page); + ->setFooter($page); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function renderCodeBox() { $cache = PhabricatorOpcodeCacheSpec::getActiveCacheSpec(); $properties = id(new PHUIPropertyListView()); $this->renderCommonProperties($properties, $cache); return $this->buildConfigBoxView(pht('Opcode Cache'), $properties); } private function renderDataBox() { $cache = PhabricatorDataCacheSpec::getActiveCacheSpec(); $properties = id(new PHUIPropertyListView()); $this->renderCommonProperties($properties, $cache); $table = null; if ($cache->getName() !== null) { $total_memory = $cache->getTotalMemory(); $summary = $cache->getCacheSummary(); $summary = isort($summary, 'total'); $summary = array_reverse($summary, true); $rows = array(); foreach ($summary as $key => $info) { $rows[] = array( $key, pht('%s', new PhutilNumber($info['count'])), phutil_format_bytes($info['max']), phutil_format_bytes($info['total']), sprintf('%.1f%%', (100 * ($info['total'] / $total_memory))), ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Pattern'), pht('Count'), pht('Largest'), pht('Total'), pht('Usage'), )) ->setColumnClasses( array( 'wide', 'n', 'n', 'n', 'n', )); } $properties = $this->buildConfigBoxView(pht('Data Cache'), $properties); $table = $this->buildConfigBoxView(pht('Cache Storage'), $table); return array($properties, $table); } private function renderCommonProperties( PHUIPropertyListView $properties, PhabricatorCacheSpec $cache) { if ($cache->getName() !== null) { $name = $this->renderYes($cache->getName()); } else { $name = $this->renderNo(pht('None')); } $properties->addProperty(pht('Cache'), $name); if ($cache->getIsEnabled()) { $enabled = $this->renderYes(pht('Enabled')); } else { $enabled = $this->renderNo(pht('Not Enabled')); } $properties->addProperty(pht('Enabled'), $enabled); $version = $cache->getVersion(); if ($version) { $properties->addProperty(pht('Version'), $this->renderInfo($version)); } if ($cache->getName() === null) { return; } $mem_total = $cache->getTotalMemory(); $mem_used = $cache->getUsedMemory(); if ($mem_total) { $percent = 100 * ($mem_used / $mem_total); $properties->addProperty( pht('Memory Usage'), pht( '%s of %s', phutil_tag('strong', array(), sprintf('%.1f%%', $percent)), phutil_format_bytes($mem_total))); } $entry_count = $cache->getEntryCount(); if ($entry_count !== null) { $properties->addProperty( pht('Cache Entries'), pht('%s', new PhutilNumber($entry_count))); } } private function renderYes($info) { return array( id(new PHUIIconView())->setIcon('fa-check', 'green'), ' ', $info, ); } private function renderNo($info) { return array( id(new PHUIIconView())->setIcon('fa-times-circle', 'red'), ' ', $info, ); } private function renderInfo($info) { return array( id(new PHUIIconView())->setIcon('fa-info-circle', 'grey'), ' ', $info, ); } } diff --git a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php index 43e5a15b9d..417fa9d3a1 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php @@ -1,242 +1,241 @@ buildSideNavView(); $nav->selectFilter('cluster/databases/'); $title = pht('Cluster Database Status'); $doc_href = PhabricatorEnv::getDoclink('Cluster: Databases'); $button = id(new PHUIButtonView()) ->setIcon('fa-book') ->setHref($doc_href) ->setTag('a') ->setText(pht('Documentation')); $header = $this->buildHeaderView($title, $button); $database_status = $this->buildClusterDatabaseStatus(); $status = $this->buildConfigBoxView(pht('Status'), $database_status); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($status); + ->setFooter($status); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function buildClusterDatabaseStatus() { $viewer = $this->getViewer(); $databases = PhabricatorDatabaseRef::queryAll(); $connection_map = PhabricatorDatabaseRef::getConnectionStatusMap(); $replica_map = PhabricatorDatabaseRef::getReplicaStatusMap(); Javelin::initBehavior('phabricator-tooltips'); $rows = array(); foreach ($databases as $database) { $messages = array(); if ($database->getIsMaster()) { $role_icon = id(new PHUIIconView()) ->setIcon('fa-database sky') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Master'), )); } else { $role_icon = id(new PHUIIconView()) ->setIcon('fa-download') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Replica'), )); } if ($database->getDisabled()) { $conn_icon = 'fa-times'; $conn_color = 'grey'; $conn_label = pht('Disabled'); } else { $status = $database->getConnectionStatus(); $info = idx($connection_map, $status, array()); $conn_icon = idx($info, 'icon'); $conn_color = idx($info, 'color'); $conn_label = idx($info, 'label'); if ($status === PhabricatorDatabaseRef::STATUS_OKAY) { $latency = $database->getConnectionLatency(); $latency = (int)(1000000 * $latency); $conn_label = pht('%s us', new PhutilNumber($latency)); } } $connection = array( id(new PHUIIconView())->setIcon("{$conn_icon} {$conn_color}"), ' ', $conn_label, ); if ($database->getDisabled()) { $replica_icon = 'fa-times'; $replica_color = 'grey'; $replica_label = pht('Disabled'); } else { $status = $database->getReplicaStatus(); $info = idx($replica_map, $status, array()); $replica_icon = idx($info, 'icon'); $replica_color = idx($info, 'color'); $replica_label = idx($info, 'label'); if ($database->getIsMaster()) { if ($status === PhabricatorDatabaseRef::REPLICATION_OKAY) { $replica_icon = 'fa-database'; } } else { switch ($status) { case PhabricatorDatabaseRef::REPLICATION_OKAY: case PhabricatorDatabaseRef::REPLICATION_SLOW: $delay = $database->getReplicaDelay(); if ($delay) { $replica_label = pht('%ss Behind', new PhutilNumber($delay)); } else { $replica_label = pht('Up to Date'); } break; } } } $replication = array( id(new PHUIIconView())->setIcon("{$replica_icon} {$replica_color}"), ' ', $replica_label, ); $health = $database->getHealthRecord(); $health_up = $health->getUpEventCount(); $health_down = $health->getDownEventCount(); if ($health->getIsHealthy()) { $health_icon = id(new PHUIIconView()) ->setIcon('fa-plus green'); } else { $health_icon = id(new PHUIIconView()) ->setIcon('fa-times red'); $messages[] = pht( 'UNHEALTHY: This database has failed recent health checks. Traffic '. 'will not be sent to it until it recovers.'); } $health_count = pht( '%s / %s', new PhutilNumber($health_up), new PhutilNumber($health_up + $health_down)); $health_status = array( $health_icon, ' ', $health_count, ); $conn_message = $database->getConnectionMessage(); if ($conn_message) { $messages[] = $conn_message; } $replica_message = $database->getReplicaMessage(); if ($replica_message) { $messages[] = $replica_message; } $messages = phutil_implode_html(phutil_tag('br'), $messages); $partition = null; if ($database->getIsMaster()) { if ($database->getIsDefaultPartition()) { $partition = id(new PHUIIconView()) ->setIcon('fa-circle sky') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Default Partition'), )); } else { $map = $database->getApplicationMap(); if ($map) { $list = implode(', ', $map); } else { $list = pht('Empty'); } $partition = id(new PHUIIconView()) ->setIcon('fa-adjust sky') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Partition: %s', $list), )); } } $rows[] = array( $role_icon, $partition, $database->getHost(), $database->getPort(), $database->getUser(), $connection, $replication, $health_status, $messages, ); } $table = id(new AphrontTableView($rows)) ->setNoDataString( pht('Phabricator is not configured in cluster mode.')) ->setHeaders( array( null, null, pht('Host'), pht('Port'), pht('User'), pht('Connection'), pht('Replication'), pht('Health'), pht('Messages'), )) ->setColumnClasses( array( null, null, null, null, null, null, null, null, 'wide', )); return $table; } } diff --git a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php index 443b51a903..e9f64d411a 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php @@ -1,176 +1,175 @@ buildSideNavView(); $nav->selectFilter('cluster/notifications/'); $title = pht('Cluster Notifications'); $doc_href = PhabricatorEnv::getDoclink('Cluster: Notifications'); $button = id(new PHUIButtonView()) ->setIcon('fa-book') ->setHref($doc_href) ->setTag('a') ->setText(pht('Documentation')); $header = $this->buildHeaderView($title, $button); $notification_status = $this->buildClusterNotificationStatus(); $status = $this->buildConfigBoxView( pht('Notifications Status'), $notification_status); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($status); + ->setFooter($status); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function buildClusterNotificationStatus() { $viewer = $this->getViewer(); $servers = PhabricatorNotificationServerRef::newRefs(); Javelin::initBehavior('phabricator-tooltips'); $rows = array(); foreach ($servers as $server) { if ($server->isAdminServer()) { $type_icon = 'fa-database sky'; $type_tip = pht('Admin Server'); } else { $type_icon = 'fa-bell sky'; $type_tip = pht('Client Server'); } $type_icon = id(new PHUIIconView()) ->setIcon($type_icon) ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => $type_tip, )); $messages = array(); $details = array(); if ($server->isAdminServer()) { try { $details = $server->loadServerStatus(); $status_icon = 'fa-exchange green'; $status_label = pht('Version %s', idx($details, 'version')); } catch (Exception $ex) { $status_icon = 'fa-times red'; $status_label = pht('Connection Error'); $messages[] = $ex->getMessage(); } } else { try { $server->testClient(); $status_icon = 'fa-exchange green'; $status_label = pht('Connected'); } catch (Exception $ex) { $status_icon = 'fa-times red'; $status_label = pht('Connection Error'); $messages[] = $ex->getMessage(); } } if ($details) { $uptime = idx($details, 'uptime'); $uptime = $uptime / 1000; $uptime = phutil_format_relative_time_detailed($uptime); $clients = pht( '%s Active / %s Total', new PhutilNumber(idx($details, 'clients.active')), new PhutilNumber(idx($details, 'clients.total'))); $stats = pht( '%s In / %s Out', new PhutilNumber(idx($details, 'messages.in')), new PhutilNumber(idx($details, 'messages.out'))); if (idx($details, 'history.size')) { $history = pht( '%s Held / %sms', new PhutilNumber(idx($details, 'history.size')), new PhutilNumber(idx($details, 'history.age'))); } else { $history = pht('No Messages'); } } else { $uptime = null; $clients = null; $stats = null; $history = null; } $status_view = array( id(new PHUIIconView())->setIcon($status_icon), ' ', $status_label, ); $messages = phutil_implode_html(phutil_tag('br'), $messages); $rows[] = array( $type_icon, $server->getProtocol(), $server->getHost(), $server->getPort(), $status_view, $uptime, $clients, $stats, $history, $messages, ); } $table = id(new AphrontTableView($rows)) ->setNoDataString( pht('No notification servers are configured.')) ->setHeaders( array( null, pht('Proto'), pht('Host'), pht('Port'), pht('Status'), pht('Uptime'), pht('Clients'), pht('Messages'), pht('History'), null, )) ->setColumnClasses( array( null, null, null, null, null, null, null, null, null, 'wide', )); return $table; } } diff --git a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php index 471c4cedf0..eb83a28a2a 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php @@ -1,420 +1,420 @@ buildSideNavView(); $nav->selectFilter('cluster/repositories/'); $title = pht('Cluster Repository Status'); $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); $button = id(new PHUIButtonView()) ->setIcon('fa-book') ->setHref($doc_href) ->setTag('a') ->setText(pht('Documentation')); $header = $this->buildHeaderView($title, $button); $repository_status = $this->buildClusterRepositoryStatus(); $repo_status = $this->buildConfigBoxView( pht('Repository Status'), $repository_status); $repository_errors = $this->buildClusterRepositoryErrors(); $repo_errors = $this->buildConfigBoxView( pht('Repository Errors'), $repository_errors); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn(array( - $repo_status, - $repo_errors, - )); + ->setFooter( + array( + $repo_status, + $repo_errors, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function buildClusterRepositoryStatus() { $viewer = $this->getViewer(); Javelin::initBehavior('phabricator-tooltips'); $all_services = id(new AlmanacServiceQuery()) ->setViewer($viewer) ->withServiceTypes( array( AlmanacClusterRepositoryServiceType::SERVICETYPE, )) ->needBindings(true) ->needProperties(true) ->execute(); $all_services = mpull($all_services, null, 'getPHID'); $all_repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withTypes( array( PhabricatorRepositoryType::REPOSITORY_TYPE_GIT, )) ->execute(); $all_repositories = mpull($all_repositories, null, 'getPHID'); $all_versions = id(new PhabricatorRepositoryWorkingCopyVersion()) ->loadAll(); $all_devices = $this->getDevices($all_services, false); $all_active_devices = $this->getDevices($all_services, true); $leader_versions = $this->getLeaderVersionsByRepository( $all_repositories, $all_versions, $all_active_devices); $push_times = $this->loadLeaderPushTimes($leader_versions); $repository_groups = mgroup($all_repositories, 'getAlmanacServicePHID'); $repository_versions = mgroup($all_versions, 'getRepositoryPHID'); $rows = array(); foreach ($all_services as $service) { $service_phid = $service->getPHID(); if ($service->getAlmanacPropertyValue('closed')) { $status_icon = 'fa-folder'; $status_tip = pht('Closed'); } else { $status_icon = 'fa-folder-open green'; $status_tip = pht('Open'); } $status_icon = id(new PHUIIconView()) ->setIcon($status_icon) ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => $status_tip, )); $devices = idx($all_devices, $service_phid, array()); $active_devices = idx($all_active_devices, $service_phid, array()); $device_icon = 'fa-server green'; $device_label = pht( '%s Active', phutil_count($active_devices)); $device_status = array( id(new PHUIIconView())->setIcon($device_icon), ' ', $device_label, ); $repositories = idx($repository_groups, $service_phid, array()); $repository_status = pht( '%s', phutil_count($repositories)); $no_leader = array(); $full_sync = array(); $partial_sync = array(); $no_sync = array(); $lag = array(); // Threshold in seconds before we start complaining that repositories // are not synchronized when there is only one leader. $threshold = phutil_units('5 minutes in seconds'); $messages = array(); foreach ($repositories as $repository) { $repository_phid = $repository->getPHID(); $leader_version = idx($leader_versions, $repository_phid); if ($leader_version === null) { $no_leader[] = $repository; $messages[] = pht( 'Repository %s has an ambiguous leader.', $viewer->renderHandle($repository_phid)->render()); continue; } $versions = idx($repository_versions, $repository_phid, array()); // Filter out any versions for devices which are no longer active. foreach ($versions as $key => $version) { $version_device_phid = $version->getDevicePHID(); if (empty($active_devices[$version_device_phid])) { unset($versions[$key]); } } $leaders = 0; foreach ($versions as $version) { if ($version->getRepositoryVersion() == $leader_version) { $leaders++; } } if ($leaders == count($active_devices)) { $full_sync[] = $repository; } else { $push_epoch = idx($push_times, $repository_phid); if ($push_epoch) { $duration = (PhabricatorTime::getNow() - $push_epoch); $lag[] = $duration; } else { $duration = null; } if ($leaders >= 2 || ($duration && ($duration < $threshold))) { $partial_sync[] = $repository; } else { $no_sync[] = $repository; if ($push_epoch) { $messages[] = pht( 'Repository %s has unreplicated changes (for %s).', $viewer->renderHandle($repository_phid)->render(), phutil_format_relative_time($duration)); } else { $messages[] = pht( 'Repository %s has unreplicated changes.', $viewer->renderHandle($repository_phid)->render()); } } } } $with_lag = false; if ($no_leader) { $replication_icon = 'fa-times red'; $replication_label = pht('Ambiguous Leader'); } else if ($no_sync) { $replication_icon = 'fa-refresh yellow'; $replication_label = pht('Unsynchronized'); $with_lag = true; } else if ($partial_sync) { $replication_icon = 'fa-refresh green'; $replication_label = pht('Partial'); $with_lag = true; } else if ($full_sync) { $replication_icon = 'fa-check green'; $replication_label = pht('Synchronized'); } else { $replication_icon = 'fa-times grey'; $replication_label = pht('No Repositories'); } if ($with_lag && $lag) { $lag_status = phutil_format_relative_time(max($lag)); $lag_status = pht(' (%s)', $lag_status); } else { $lag_status = null; } $replication_status = array( id(new PHUIIconView())->setIcon($replication_icon), ' ', $replication_label, $lag_status, ); $messages = phutil_implode_html(phutil_tag('br'), $messages); $rows[] = array( $status_icon, $viewer->renderHandle($service->getPHID()), $device_status, $repository_status, $replication_status, $messages, ); } return id(new AphrontTableView($rows)) ->setNoDataString( pht('No repository cluster services are configured.')) ->setHeaders( array( null, pht('Service'), pht('Devices'), pht('Repos'), pht('Sync'), pht('Messages'), )) ->setColumnClasses( array( null, 'pri', null, null, null, 'wide', )); } private function getDevices( array $all_services, $only_active) { $devices = array(); foreach ($all_services as $service) { $map = array(); foreach ($service->getBindings() as $binding) { if ($only_active && $binding->getIsDisabled()) { continue; } $device = $binding->getDevice(); $device_phid = $device->getPHID(); $map[$device_phid] = $device; } $devices[$service->getPHID()] = $map; } return $devices; } private function getLeaderVersionsByRepository( array $all_repositories, array $all_versions, array $active_devices) { $version_map = mgroup($all_versions, 'getRepositoryPHID'); $result = array(); foreach ($all_repositories as $repository_phid => $repository) { $service_phid = $repository->getAlmanacServicePHID(); if (!$service_phid) { continue; } $devices = idx($active_devices, $service_phid); if (!$devices) { continue; } $versions = idx($version_map, $repository_phid, array()); $versions = mpull($versions, null, 'getDevicePHID'); $versions = array_select_keys($versions, array_keys($devices)); if (!$versions) { continue; } $leader = (int)max(mpull($versions, 'getRepositoryVersion')); $result[$repository_phid] = $leader; } return $result; } private function loadLeaderPushTimes(array $leader_versions) { $viewer = $this->getViewer(); if (!$leader_versions) { return array(); } $events = id(new PhabricatorRepositoryPushEventQuery()) ->setViewer($viewer) ->withIDs($leader_versions) ->execute(); $events = mpull($events, null, 'getID'); $result = array(); foreach ($leader_versions as $key => $version) { $event = idx($events, $version); if (!$event) { continue; } $result[$key] = $event->getEpoch(); } return $result; } private function buildClusterRepositoryErrors() { $viewer = $this->getViewer(); $messages = id(new PhabricatorRepositoryStatusMessage())->loadAllWhere( 'statusCode IN (%Ls)', array( PhabricatorRepositoryStatusMessage::CODE_ERROR, )); $repository_ids = mpull($messages, 'getRepositoryID'); if ($repository_ids) { // NOTE: We're bypassing policies when loading repositories because we // want to show errors exist even if the viewer can't see the repository. // We use handles to describe the repository below, so the viewer won't // actually be able to see any particulars if they can't see the // repository. $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withIDs($repository_ids) ->execute(); $repositories = mpull($repositories, null, 'getID'); } $rows = array(); foreach ($messages as $message) { $repository = idx($repositories, $message->getRepositoryID()); if (!$repository) { continue; } if (!$repository->isTracked()) { continue; } $icon = id(new PHUIIconView()) ->setIcon('fa-exclamation-triangle red'); $rows[] = array( $icon, $viewer->renderHandle($repository->getPHID()), phutil_tag( 'a', array( 'href' => $repository->getPathURI('manage/status/'), ), $message->getStatusTypeName()), ); } return id(new AphrontTableView($rows)) ->setNoDataString( pht('No active repositories have outstanding errors.')) ->setHeaders( array( null, pht('Repository'), pht('Error'), )) ->setColumnClasses( array( null, 'pri', 'wide', )); } } diff --git a/src/applications/config/controller/PhabricatorConfigClusterSearchController.php b/src/applications/config/controller/PhabricatorConfigClusterSearchController.php index cd00ef73a0..55caeb1cad 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterSearchController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterSearchController.php @@ -1,127 +1,126 @@ buildSideNavView(); $nav->selectFilter('cluster/search/'); $title = pht('Cluster Search'); $doc_href = PhabricatorEnv::getDoclink('Cluster: Search'); $button = id(new PHUIButtonView()) ->setIcon('fa-book') ->setHref($doc_href) ->setTag('a') ->setText(pht('Documentation')); $header = $this->buildHeaderView($title, $button); $search_status = $this->buildClusterSearchStatus(); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($search_status); + ->setFooter($search_status); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function buildClusterSearchStatus() { $viewer = $this->getViewer(); $services = PhabricatorSearchService::getAllServices(); Javelin::initBehavior('phabricator-tooltips'); $view = array(); foreach ($services as $service) { $view[] = $this->renderStatusView($service); } return $view; } private function renderStatusView($service) { $head = array_merge( array(pht('Type')), array_keys($service->getStatusViewColumns()), array(pht('Status'))); $rows = array(); $status_map = PhabricatorSearchService::getConnectionStatusMap(); $stats = false; $stats_view = false; foreach ($service->getHosts() as $host) { try { $status = $host->getConnectionStatus(); $status = idx($status_map, $status, array()); } catch (Exception $ex) { $status['icon'] = 'fa-times'; $status['label'] = pht('Connection Error'); $status['color'] = 'red'; $host->didHealthCheck(false); } if (!$stats_view) { try { $stats = $host->getEngine()->getIndexStats($host); $stats_view = $this->renderIndexStats($stats); } catch (Exception $e) { $stats_view = false; } } $type_icon = 'fa-search sky'; $type_tip = $host->getDisplayName(); $type_icon = id(new PHUIIconView()) ->setIcon($type_icon); $status_view = array( id(new PHUIIconView())->setIcon($status['icon'].' '.$status['color']), ' ', $status['label'], ); $row = array(array($type_icon, ' ', $type_tip)); $row = array_merge($row, array_values( $host->getStatusViewColumns())); $row[] = $status_view; $rows[] = $row; } $table = id(new AphrontTableView($rows)) ->setNoDataString(pht('No search servers are configured.')) ->setHeaders($head); $view = $this->buildConfigBoxView(pht('Search Servers'), $table); $stats = null; if ($stats_view->hasAnyProperties()) { $stats = $this->buildConfigBoxView( pht('%s Stats', $service->getDisplayName()), $stats_view); } return array($stats, $view); } private function renderIndexStats($stats) { $view = id(new PHUIPropertyListView()); if ($stats !== false) { foreach ($stats as $label => $val) { $view->addProperty($label, $val); } } return $view; } } diff --git a/src/applications/config/controller/PhabricatorConfigController.php b/src/applications/config/controller/PhabricatorConfigController.php index ad5ea6cd27..1b6a5af8b5 100644 --- a/src/applications/config/controller/PhabricatorConfigController.php +++ b/src/applications/config/controller/PhabricatorConfigController.php @@ -1,95 +1,93 @@ setBaseURI(new PhutilURI($this->getApplicationURI())); $nav->addFilter('/', pht('Core Settings'), null, 'fa-gear'); $nav->addFilter('application/', pht('Application Settings'), null, 'fa-globe'); $nav->addFilter('history/', pht('Settings History'), null, 'fa-history'); $nav->addFilter('version/', pht('Version Information'), null, 'fa-download'); $nav->addFilter('all/', pht('All Settings'), null, 'fa-list-ul'); $nav->addLabel(pht('Setup')); $nav->addFilter('issue/', pht('Setup Issues'), null, 'fa-warning'); $nav->addFilter(null, pht('Installation Guide'), $guide_href, 'fa-book'); $nav->addLabel(pht('Database')); $nav->addFilter('database/', pht('Database Status'), null, 'fa-heartbeat'); $nav->addFilter('dbissue/', pht('Database Issues'), null, 'fa-exclamation-circle'); $nav->addLabel(pht('Cache')); $nav->addFilter('cache/', pht('Cache Status'), null, 'fa-home'); $nav->addLabel(pht('Cluster')); $nav->addFilter('cluster/databases/', pht('Database Servers'), null, 'fa-database'); $nav->addFilter('cluster/notifications/', pht('Notification Servers'), null, 'fa-bell-o'); $nav->addFilter('cluster/repositories/', pht('Repository Servers'), null, 'fa-code'); $nav->addFilter('cluster/search/', pht('Search Servers'), null, 'fa-search'); $nav->addLabel(pht('Modules')); $modules = PhabricatorConfigModule::getAllModules(); foreach ($modules as $key => $module) { $nav->addFilter('module/'.$key.'/', $module->getModuleName(), null, 'fa-puzzle-piece'); } return $nav; } public function buildApplicationMenu() { return $this->buildSideNavView(null, true)->getMenu(); } public function buildHeaderView($text, $action = null) { $viewer = $this->getViewer(); $file = PhabricatorFile::loadBuiltin($viewer, 'projects/v3/manage.png'); $image = $file->getBestURI($file); $header = id(new PHUIHeaderView()) ->setHeader($text) ->setProfileHeader(true) ->setImage($image); if ($action) { $header->addActionLink($action); } return $header; } public function buildConfigBoxView($title, $content, $action = null) { $header = id(new PHUIHeaderView()) ->setHeader($title); if ($action) { $header->addActionItem($action); } $view = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($content) ->setBackground(PHUIObjectBoxView::WHITE_CONFIG); return $view; } } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php index 708a708043..fa6bcab5e6 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php @@ -1,180 +1,179 @@ getViewer(); $query = new PhabricatorConfigSchemaQuery(); $actual = $query->loadActualSchemata(); $expect = $query->loadExpectedSchemata(); $comp_servers = $query->buildComparisonSchemata($expect, $actual); // Collect all open issues. $issues = array(); foreach ($comp_servers as $ref_name => $comp) { foreach ($comp->getDatabases() as $database_name => $database) { foreach ($database->getLocalIssues() as $issue) { $issues[] = array( $ref_name, $database_name, null, null, null, $issue, ); } foreach ($database->getTables() as $table_name => $table) { foreach ($table->getLocalIssues() as $issue) { $issues[] = array( $ref_name, $database_name, $table_name, null, null, $issue, ); } foreach ($table->getColumns() as $column_name => $column) { foreach ($column->getLocalIssues() as $issue) { $issues[] = array( $ref_name, $database_name, $table_name, 'column', $column_name, $issue, ); } } foreach ($table->getKeys() as $key_name => $key) { foreach ($key->getLocalIssues() as $issue) { $issues[] = array( $ref_name, $database_name, $table_name, 'key', $key_name, $issue, ); } } } } } // Sort all open issues so that the most severe issues appear first. $order = array(); $counts = array(); foreach ($issues as $key => $issue) { $const = $issue[5]; $status = PhabricatorConfigStorageSchema::getIssueStatus($const); $severity = PhabricatorConfigStorageSchema::getStatusSeverity($status); $order[$key] = sprintf( '~%d~%s%s%s', 9 - $severity, $issue[1], $issue[2], $issue[4]); if (empty($counts[$status])) { $counts[$status] = 0; } $counts[$status]++; } asort($order); $issues = array_select_keys($issues, array_keys($order)); // Render the issues. $rows = array(); foreach ($issues as $issue) { $const = $issue[5]; $uri = $this->getApplicationURI('/database/'.$issue[0].'/'.$issue[1].'/'); $database_link = phutil_tag( 'a', array( 'href' => $uri, ), $issue[1]); $rows[] = array( $this->renderIcon( PhabricatorConfigStorageSchema::getIssueStatus($const)), $issue[0], $database_link, $issue[2], $issue[3], $issue[4], PhabricatorConfigStorageSchema::getIssueDescription($const), ); } $table = id(new AphrontTableView($rows)) ->setNoDataString( pht('No databases have any issues.')) ->setHeaders( array( null, pht('Server'), pht('Database'), pht('Table'), pht('Type'), pht('Column/Key'), pht('Issue'), )) ->setColumnClasses( array( null, null, null, null, null, null, 'wide', )); $errors = array(); if (isset($counts[PhabricatorConfigStorageSchema::STATUS_FAIL])) { $errors[] = pht( 'Detected %s serious issue(s) with the schemata.', new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_FAIL])); } if (isset($counts[PhabricatorConfigStorageSchema::STATUS_WARN])) { $errors[] = pht( 'Detected %s warning(s) with the schemata.', new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_WARN])); } $title = pht('Database Issues'); $header = $this->buildHeaderView($title); $nav = $this->buildSideNavView(); $nav->selectFilter('dbissue/'); $view = $this->buildConfigBoxView(pht('Issues'), $table); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($view); + ->setFooter($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php index 760317ae80..6831a048d5 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -1,865 +1,864 @@ getViewer(); $this->database = $request->getURIData('database'); $this->table = $request->getURIData('table'); $this->column = $request->getURIData('column'); $this->key = $request->getURIData('key'); $this->ref = $request->getURIData('ref'); $query = new PhabricatorConfigSchemaQuery(); $actual = $query->loadActualSchemata(); $expect = $query->loadExpectedSchemata(); $comp = $query->buildComparisonSchemata($expect, $actual); if ($this->ref !== null) { $server_actual = idx($actual, $this->ref); if (!$server_actual) { return new Aphront404Response(); } $server_comparison = $comp[$this->ref]; $server_expect = $expect[$this->ref]; if ($this->column) { return $this->renderColumn( $server_comparison, $server_expect, $server_actual, $this->database, $this->table, $this->column); } else if ($this->key) { return $this->renderKey( $server_comparison, $server_expect, $server_actual, $this->database, $this->table, $this->key); } else if ($this->table) { return $this->renderTable( $server_comparison, $server_expect, $server_actual, $this->database, $this->table); } else if ($this->database) { return $this->renderDatabase( $server_comparison, $server_expect, $server_actual, $this->database); } } return $this->renderServers( $comp, $expect, $actual); } private function buildResponse($title, $body) { $nav = $this->buildSideNavView(); $nav->selectFilter('database/'); if (!$title) { $title = pht('Database Status'); } $ref = $this->ref; $database = $this->database; $table = $this->table; $column = $this->column; $key = $this->key; $links = array(); $links[] = array( pht('Database Status'), 'database/', ); if ($database) { $links[] = array( $database, "database/{$ref}/{$database}/", ); } if ($table) { $links[] = array( $table, "database/{$ref}/{$database}/{$table}/", ); } if ($column) { $links[] = array( $column, "database/{$ref}/{$database}/{$table}/col/{$column}/", ); } if ($key) { $links[] = array( $key, "database/{$ref}/{$database}/{$table}/key/{$key}/", ); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $last_key = last_key($links); foreach ($links as $link_key => $link) { list($name, $href) = $link; if ($link_key == $last_key) { $crumbs->addTextCrumb($name); } else { $crumbs->addTextCrumb($name, $this->getApplicationURI($href)); } } $doc_link = PhabricatorEnv::getDoclink('Managing Storage Adjustments'); $button = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-book') ->setHref($doc_link) ->setText(pht('Documentation')); $header = $this->buildHeaderView($title, $button); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($body); + ->setFooter($body); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function renderServers( array $comp_servers, array $expect_servers, array $actual_servers) { $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; $rows = array(); foreach ($comp_servers as $ref_key => $comp) { $actual = $actual_servers[$ref_key]; $expect = $expect_servers[$ref_key]; foreach ($comp->getDatabases() as $database_name => $database) { $actual_database = $actual->getDatabase($database_name); if ($actual_database) { $charset = $actual_database->getCharacterSet(); $collation = $actual_database->getCollation(); } else { $charset = null; $collation = null; } $status = $database->getStatus(); $issues = $database->getIssues(); $uri = $this->getURI( array( 'ref' => $ref_key, 'database' => $database_name, )); $rows[] = array( $this->renderIcon($status), $ref_key, phutil_tag( 'a', array( 'href' => $uri, ), $database_name), $this->renderAttr($charset, $database->hasIssue($charset_issue)), $this->renderAttr($collation, $database->hasIssue($collation_issue)), ); } } $table = id(new AphrontTableView($rows)) ->setHeaders( array( null, pht('Server'), pht('Database'), pht('Charset'), pht('Collation'), )) ->setColumnClasses( array( null, null, 'wide pri', null, null, )); $title = pht('Database Status'); $properties = $this->buildProperties( array( ), $comp->getIssues()); $properties = $this->buildConfigBoxView(pht('Properties'), $properties); $table = $this->buildConfigBoxView(pht('Database'), $table); return $this->buildResponse($title, array($properties, $table)); } private function renderDatabase( PhabricatorConfigServerSchema $comp, PhabricatorConfigServerSchema $expect, PhabricatorConfigServerSchema $actual, $database_name) { $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; $database = $comp->getDatabase($database_name); if (!$database) { return new Aphront404Response(); } $rows = array(); foreach ($database->getTables() as $table_name => $table) { $status = $table->getStatus(); $uri = $this->getURI( array( 'table' => $table_name, )); $rows[] = array( $this->renderIcon($status), phutil_tag( 'a', array( 'href' => $uri, ), $table_name), $this->renderAttr( $table->getCollation(), $table->hasIssue($collation_issue)), $table->getPersistenceTypeDisplayName(), ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( null, pht('Table'), pht('Collation'), pht('Persistence'), )) ->setColumnClasses( array( null, 'wide pri', null, null, )); $title = $database_name; $actual_database = $actual->getDatabase($database_name); if ($actual_database) { $actual_charset = $actual_database->getCharacterSet(); $actual_collation = $actual_database->getCollation(); } else { $actual_charset = null; $actual_collation = null; } $expect_database = $expect->getDatabase($database_name); if ($expect_database) { $expect_charset = $expect_database->getCharacterSet(); $expect_collation = $expect_database->getCollation(); } else { $expect_charset = null; $expect_collation = null; } $properties = $this->buildProperties( array( array( pht('Server'), $this->ref, ), array( pht('Character Set'), $actual_charset, ), array( pht('Expected Character Set'), $expect_charset, ), array( pht('Collation'), $actual_collation, ), array( pht('Expected Collation'), $expect_collation, ), ), $database->getIssues()); $properties = $this->buildConfigBoxView(pht('Properties'), $properties); $table = $this->buildConfigBoxView(pht('Database'), $table); return $this->buildResponse($title, array($properties, $table)); } private function renderTable( PhabricatorConfigServerSchema $comp, PhabricatorConfigServerSchema $expect, PhabricatorConfigServerSchema $actual, $database_name, $table_name) { $type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE; $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; $nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE; $unique_issue = PhabricatorConfigStorageSchema::ISSUE_UNIQUE; $columns_issue = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS; $longkey_issue = PhabricatorConfigStorageSchema::ISSUE_LONGKEY; $auto_issue = PhabricatorConfigStorageSchema::ISSUE_AUTOINCREMENT; $database = $comp->getDatabase($database_name); if (!$database) { return new Aphront404Response(); } $table = $database->getTable($table_name); if (!$table) { return new Aphront404Response(); } $actual_database = $actual->getDatabase($database_name); $actual_table = null; if ($actual_database) { $actual_table = $actual_database->getTable($table_name); } $expect_database = $expect->getDatabase($database_name); $expect_table = null; if ($expect_database) { $expect_table = $expect_database->getTable($table_name); } $rows = array(); foreach ($table->getColumns() as $column_name => $column) { $expect_column = null; if ($expect_table) { $expect_column = $expect_table->getColumn($column_name); } $status = $column->getStatus(); $data_type = null; if ($expect_column) { $data_type = $expect_column->getDataType(); } $uri = $this->getURI( array( 'column' => $column_name, )); $rows[] = array( $this->renderIcon($status), phutil_tag( 'a', array( 'href' => $uri, ), $column_name), $data_type, $this->renderAttr( $column->getColumnType(), $column->hasIssue($type_issue)), $this->renderAttr( $this->renderBoolean($column->getNullable()), $column->hasIssue($nullable_issue)), $this->renderAttr( $this->renderBoolean($column->getAutoIncrement()), $column->hasIssue($auto_issue)), $this->renderAttr( $column->getCharacterSet(), $column->hasIssue($charset_issue)), $this->renderAttr( $column->getCollation(), $column->hasIssue($collation_issue)), ); } $table_view = id(new AphrontTableView($rows)) ->setHeaders( array( null, pht('Column'), pht('Data Type'), pht('Column Type'), pht('Nullable'), pht('Autoincrement'), pht('Character Set'), pht('Collation'), )) ->setColumnClasses( array( null, 'wide pri', null, null, null, null, null, )); $key_rows = array(); foreach ($table->getKeys() as $key_name => $key) { $expect_key = null; if ($expect_table) { $expect_key = $expect_table->getKey($key_name); } $status = $key->getStatus(); $size = 0; foreach ($key->getColumnNames() as $column_spec) { list($column_name, $prefix) = $key->getKeyColumnAndPrefix($column_spec); $column = $table->getColumn($column_name); if (!$column) { $size = 0; break; } $size += $column->getKeyByteLength($prefix); } $size_formatted = null; if ($size) { $size_formatted = $this->renderAttr( $size, $key->hasIssue($longkey_issue)); } $uri = $this->getURI( array( 'key' => $key_name, )); $key_rows[] = array( $this->renderIcon($status), phutil_tag( 'a', array( 'href' => $uri, ), $key_name), $this->renderAttr( implode(', ', $key->getColumnNames()), $key->hasIssue($columns_issue)), $this->renderAttr( $this->renderBoolean($key->getUnique()), $key->hasIssue($unique_issue)), $size_formatted, ); } $keys_view = id(new AphrontTableView($key_rows)) ->setHeaders( array( null, pht('Key'), pht('Columns'), pht('Unique'), pht('Size'), )) ->setColumnClasses( array( null, 'wide pri', null, null, null, )); $title = pht('%s.%s', $database_name, $table_name); if ($actual_table) { $actual_collation = $actual_table->getCollation(); } else { $actual_collation = null; } if ($expect_table) { $expect_collation = $expect_table->getCollation(); } else { $expect_collation = null; } $properties = $this->buildProperties( array( array( pht('Server'), $this->ref, ), array( pht('Collation'), $actual_collation, ), array( pht('Expected Collation'), $expect_collation, ), ), $table->getIssues()); $box_header = pht('%s.%s', $database_name, $table_name); $properties = $this->buildConfigBoxView(pht('Properties'), $properties); $table = $this->buildConfigBoxView(pht('Database'), $table_view); $keys = $this->buildConfigBoxView(pht('Keys'), $keys_view); return $this->buildResponse( $title, array($properties, $table, $keys)); } private function renderColumn( PhabricatorConfigServerSchema $comp, PhabricatorConfigServerSchema $expect, PhabricatorConfigServerSchema $actual, $database_name, $table_name, $column_name) { $database = $comp->getDatabase($database_name); if (!$database) { return new Aphront404Response(); } $table = $database->getTable($table_name); if (!$table) { return new Aphront404Response(); } $column = $table->getColumn($column_name); if (!$column) { return new Aphront404Response(); } $actual_database = $actual->getDatabase($database_name); $actual_table = null; $actual_column = null; if ($actual_database) { $actual_table = $actual_database->getTable($table_name); if ($actual_table) { $actual_column = $actual_table->getColumn($column_name); } } $expect_database = $expect->getDatabase($database_name); $expect_table = null; $expect_column = null; if ($expect_database) { $expect_table = $expect_database->getTable($table_name); if ($expect_table) { $expect_column = $expect_table->getColumn($column_name); } } if ($actual_column) { $actual_coltype = $actual_column->getColumnType(); $actual_charset = $actual_column->getCharacterSet(); $actual_collation = $actual_column->getCollation(); $actual_nullable = $actual_column->getNullable(); $actual_auto = $actual_column->getAutoIncrement(); } else { $actual_coltype = null; $actual_charset = null; $actual_collation = null; $actual_nullable = null; $actual_auto = null; } if ($expect_column) { $data_type = $expect_column->getDataType(); $expect_coltype = $expect_column->getColumnType(); $expect_charset = $expect_column->getCharacterSet(); $expect_collation = $expect_column->getCollation(); $expect_nullable = $expect_column->getNullable(); $expect_auto = $expect_column->getAutoIncrement(); } else { $data_type = null; $expect_coltype = null; $expect_charset = null; $expect_collation = null; $expect_nullable = null; $expect_auto = null; } $title = pht( '%s.%s.%s', $database_name, $table_name, $column_name); $properties = $this->buildProperties( array( array( pht('Server'), $this->ref, ), array( pht('Data Type'), $data_type, ), array( pht('Column Type'), $actual_coltype, ), array( pht('Expected Column Type'), $expect_coltype, ), array( pht('Character Set'), $actual_charset, ), array( pht('Expected Character Set'), $expect_charset, ), array( pht('Collation'), $actual_collation, ), array( pht('Expected Collation'), $expect_collation, ), array( pht('Nullable'), $this->renderBoolean($actual_nullable), ), array( pht('Expected Nullable'), $this->renderBoolean($expect_nullable), ), array( pht('Autoincrement'), $this->renderBoolean($actual_auto), ), array( pht('Expected Autoincrement'), $this->renderBoolean($expect_auto), ), ), $column->getIssues()); $properties = $this->buildConfigBoxView(pht('Properties'), $properties); return $this->buildResponse($title, $properties); } private function renderKey( PhabricatorConfigServerSchema $comp, PhabricatorConfigServerSchema $expect, PhabricatorConfigServerSchema $actual, $database_name, $table_name, $key_name) { $database = $comp->getDatabase($database_name); if (!$database) { return new Aphront404Response(); } $table = $database->getTable($table_name); if (!$table) { return new Aphront404Response(); } $key = $table->getKey($key_name); if (!$key) { return new Aphront404Response(); } $actual_database = $actual->getDatabase($database_name); $actual_table = null; $actual_key = null; if ($actual_database) { $actual_table = $actual_database->getTable($table_name); if ($actual_table) { $actual_key = $actual_table->getKey($key_name); } } $expect_database = $expect->getDatabase($database_name); $expect_table = null; $expect_key = null; if ($expect_database) { $expect_table = $expect_database->getTable($table_name); if ($expect_table) { $expect_key = $expect_table->getKey($key_name); } } if ($actual_key) { $actual_columns = $actual_key->getColumnNames(); $actual_unique = $actual_key->getUnique(); } else { $actual_columns = array(); $actual_unique = null; } if ($expect_key) { $expect_columns = $expect_key->getColumnNames(); $expect_unique = $expect_key->getUnique(); } else { $expect_columns = array(); $expect_unique = null; } $title = pht( '%s.%s (%s)', $database_name, $table_name, $key_name); $properties = $this->buildProperties( array( array( pht('Server'), $this->ref, ), array( pht('Unique'), $this->renderBoolean($actual_unique), ), array( pht('Expected Unique'), $this->renderBoolean($expect_unique), ), array( pht('Columns'), implode(', ', $actual_columns), ), array( pht('Expected Columns'), implode(', ', $expect_columns), ), ), $key->getIssues()); $properties = $this->buildConfigBoxView(pht('Properties'), $properties); return $this->buildResponse($title, $properties); } private function buildProperties(array $properties, array $issues) { $view = id(new PHUIPropertyListView()) ->setUser($this->getRequest()->getUser()); foreach ($properties as $property) { list($key, $value) = $property; $view->addProperty($key, $value); } $status_view = new PHUIStatusListView(); if (!$issues) { $status_view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('No Schema Issues'))); } else { foreach ($issues as $issue) { $note = PhabricatorConfigStorageSchema::getIssueDescription($issue); $status = PhabricatorConfigStorageSchema::getIssueStatus($issue); switch ($status) { case PhabricatorConfigStorageSchema::STATUS_WARN: $icon = PHUIStatusItemView::ICON_WARNING; $color = 'yellow'; break; case PhabricatorConfigStorageSchema::STATUS_FAIL: default: $icon = PHUIStatusItemView::ICON_REJECT; $color = 'red'; break; } $item = id(new PHUIStatusItemView()) ->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue)) ->setIcon($icon, $color) ->setNote($note); $status_view->addItem($item); } } $view->addProperty(pht('Schema Status'), $status_view); return phutil_tag_div('config-page-property', $view); } private function getURI(array $properties) { $defaults = array( 'ref' => $this->ref, 'database' => $this->database, 'table' => $this->table, 'column' => $this->column, 'key' => $this->key, ); $properties = $properties + $defaults; $properties = array_select_keys($properties, array_keys($defaults)); $parts = array(); foreach ($properties as $key => $property) { if (!strlen($property)) { continue; } if ($key == 'column') { $parts[] = 'col'; } else if ($key == 'key') { $parts[] = 'key'; } $parts[] = $property; } if ($parts) { $parts = implode('/', $parts).'/'; } else { $parts = null; } return $this->getApplicationURI('/database/'.$parts); } } diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 224705e181..381b54e046 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -1,491 +1,491 @@ getViewer(); $key = $request->getURIData('key'); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); if (empty($options[$key])) { $ancient = PhabricatorExtraConfigSetupCheck::getAncientConfig(); if (isset($ancient[$key])) { $desc = pht( "This configuration has been removed. You can safely delete ". "it.\n\n%s", $ancient[$key]); } else { $desc = pht( 'This configuration option is unknown. It may be misspelled, '. 'or have existed in a previous version of Phabricator.'); } // This may be a dead config entry, which existed in the past but no // longer exists. Allow it to be edited so it can be reviewed and // deleted. $option = id(new PhabricatorConfigOption()) ->setKey($key) ->setType('wild') ->setDefault(null) ->setDescription($desc); $group = null; $group_uri = $this->getApplicationURI(); } else { $option = $options[$key]; $group = $option->getGroup(); $group_uri = $this->getApplicationURI('group/'.$group->getKey().'/'); } $issue = $request->getStr('issue'); if ($issue) { // If the user came here from an open setup issue, send them back. $done_uri = $this->getApplicationURI('issue/'.$issue.'/'); } else { $done_uri = $group_uri; } // Check if the config key is already stored in the database. // Grab the value if it is. $config_entry = id(new PhabricatorConfigEntry()) ->loadOneWhere( 'configKey = %s AND namespace = %s', $key, 'default'); if (!$config_entry) { $config_entry = id(new PhabricatorConfigEntry()) ->setConfigKey($key) ->setNamespace('default') ->setIsDeleted(true); $config_entry->setPHID($config_entry->generatePHID()); } $e_value = null; $errors = array(); if ($request->isFormPost() && !$option->getLocked()) { $result = $this->readRequest( $option, $request); list($e_value, $value_errors, $display_value, $xaction) = $result; $errors = array_merge($errors, $value_errors); if (!$errors) { $editor = id(new PhabricatorConfigEditor()) ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); try { $editor->applyTransactions($config_entry, array($xaction)); return id(new AphrontRedirectResponse())->setURI($done_uri); } catch (PhabricatorConfigValidationException $ex) { $e_value = pht('Invalid'); $errors[] = $ex->getMessage(); } } } else { if ($config_entry->getIsDeleted()) { $display_value = null; } else { $display_value = $this->getDisplayValue( $option, $config_entry, $config_entry->getValue()); } } $form = id(new AphrontFormView()) ->setEncType('multipart/form-data'); $error_view = null; if ($errors) { $error_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_ERROR) ->setErrors($errors); } $doc_href = PhabricatorEnv::getDoclink( 'Configuration Guide: Locked and Hidden Configuration'); $doc_link = phutil_tag( 'a', array( 'href' => $doc_href, 'target' => '_blank', ), pht('Learn more about locked and hidden options.')); $status_items = array(); $tag = null; if ($option->getHidden()) { $tag = id(new PHUITagView()) ->setName(pht('Hidden')) ->setColor(PHUITagView::COLOR_GREY) ->setBorder(PHUITagView::BORDER_NONE) ->setType(PHUITagView::TYPE_SHADE); $message = pht( 'This configuration is hidden and can not be edited or viewed from '. 'the web interface.'); $status_items[] = id(new PHUIInfoView()) ->appendChild(array($message, ' ', $doc_link)); } else if ($option->getLocked()) { $tag = id(new PHUITagView()) ->setName(pht('Locked')) ->setColor(PHUITagView::COLOR_RED) ->setBorder(PHUITagView::BORDER_NONE) ->setType(PHUITagView::TYPE_SHADE); $message = $option->getLockedMessage(); $status_items[] = id(new PHUIInfoView()) ->appendChild(array($message, ' ', $doc_link)); } if ($option->getHidden() || $option->getLocked()) { $controls = array(); } else { $controls = $this->renderControls( $option, $display_value, $e_value); } $form ->setUser($viewer) ->addHiddenInput('issue', $request->getStr('issue')); $description = $option->newDescriptionRemarkupView($viewer); if ($description) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Description')) ->setValue($description)); } if ($group) { $extra = $group->renderContextualDescription( $option, $request); if ($extra !== null) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setValue($extra)); } } foreach ($controls as $control) { $form->appendControl($control); } if (!$option->getLocked()) { $form->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($done_uri) ->setValue(pht('Save Config Entry'))); } $current_config = null; if (!$option->getHidden()) { $current_config = $this->renderDefaults($option, $config_entry); $current_config = $this->buildConfigBoxView( pht('Current Configuration'), $current_config); } $examples = $this->renderExamples($option); if ($examples) { $examples = $this->buildConfigBoxView( pht('Examples'), $examples); } $title = $key; $box_header = array(); if ($group) { $box_header[] = phutil_tag( 'a', array( 'href' => $group_uri, ), $group->getName()); $box_header[] = " \xC2\xBB "; } $box_header[] = $key; $crumbs = $this->buildApplicationCrumbs(); if ($group) { $crumbs->addTextCrumb($group->getName(), $group_uri); } $crumbs->addTextCrumb($key, '/config/edit/'.$key); $crumbs->setBorder(true); $form_box = $this->buildConfigBoxView($box_header, $form, $tag); $timeline = $this->buildTransactionTimeline( $config_entry, new PhabricatorConfigTransactionQuery()); $timeline->setShouldTerminate(true); $nav = $this->buildSideNavView(); $nav->selectFilter($group_uri); $header = $this->buildHeaderView($title); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn(array( + ->setFooter( + array( $error_view, $form_box, $status_items, $examples, $current_config, )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($view); } private function readRequest( PhabricatorConfigOption $option, AphrontRequest $request) { $type = $option->newOptionType(); if ($type) { $is_set = $type->isValuePresentInRequest($option, $request); if ($is_set) { $value = $type->readValueFromRequest($option, $request); $errors = array(); try { $canonical_value = $type->newValueFromRequestValue( $option, $value); $type->validateStoredValue($option, $canonical_value); $xaction = $type->newTransaction($option, $canonical_value); } catch (PhabricatorConfigValidationException $ex) { $errors[] = $ex->getMessage(); $xaction = null; } catch (Exception $ex) { // NOTE: Some older validators throw bare exceptions. Purely in good // taste, it would be nice to convert these at some point. $errors[] = $ex->getMessage(); $xaction = null; } return array( $errors ? pht('Invalid') : null, $errors, $value, $xaction, ); } else { $delete_xaction = id(new PhabricatorConfigTransaction()) ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) ->setNewValue( array( 'deleted' => true, 'value' => null, )); return array( null, array(), null, $delete_xaction, ); } } // TODO: If we missed on the new `PhabricatorConfigType` map, fall back // to the old semi-modular, semi-hacky way of doing things. $xaction = new PhabricatorConfigTransaction(); $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT); $e_value = null; $errors = array(); if ($option->isCustomType()) { $info = $option->getCustomObject()->readRequest($option, $request); list($e_value, $errors, $set_value, $value) = $info; } else { throw new Exception( pht( 'Unknown configuration option type "%s".', $option->getType())); } if (!$errors) { $xaction->setNewValue( array( 'deleted' => false, 'value' => $set_value, )); } else { $xaction = null; } return array($e_value, $errors, $value, $xaction); } private function getDisplayValue( PhabricatorConfigOption $option, PhabricatorConfigEntry $entry, $value) { $type = $option->newOptionType(); if ($type) { return $type->newDisplayValue($option, $value); } if ($option->isCustomType()) { return $option->getCustomObject()->getDisplayValue( $option, $entry, $value); } throw new Exception( pht( 'Unknown configuration option type "%s".', $option->getType())); } private function renderControls( PhabricatorConfigOption $option, $display_value, $e_value) { $type = $option->newOptionType(); if ($type) { return $type->newControls( $option, $display_value, $e_value); } if ($option->isCustomType()) { $controls = $option->getCustomObject()->renderControls( $option, $display_value, $e_value); } else { throw new Exception( pht( 'Unknown configuration option type "%s".', $option->getType())); } return $controls; } private function renderExamples(PhabricatorConfigOption $option) { $examples = $option->getExamples(); if (!$examples) { return null; } $table = array(); $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( phutil_tag('th', array(), pht('Example')), phutil_tag('th', array(), pht('Value')), )); foreach ($examples as $example) { list($value, $description) = $example; if ($value === null) { $value = phutil_tag('em', array(), pht('(empty)')); } else { if (is_array($value)) { $value = implode("\n", $value); } } $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( phutil_tag('th', array(), $description), phutil_tag('td', array(), $value), )); } require_celerity_resource('config-options-css'); return phutil_tag( 'table', array( 'class' => 'config-option-table', 'cellspacing' => '0', 'cellpadding' => '0', ), $table); } private function renderDefaults( PhabricatorConfigOption $option, PhabricatorConfigEntry $entry) { $stack = PhabricatorEnv::getConfigSourceStack(); $stack = $stack->getStack(); $table = array(); $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( phutil_tag('th', array(), pht('Source')), phutil_tag('th', array(), pht('Value')), )); $is_effective_value = true; foreach ($stack as $key => $source) { $row_classes = array( 'column-labels', ); $value = $source->getKeys( array( $option->getKey(), )); if (!array_key_exists($option->getKey(), $value)) { $value = phutil_tag('em', array(), pht('(No Value Configured)')); } else { $value = $this->getDisplayValue( $option, $entry, $value[$option->getKey()]); if ($is_effective_value) { $is_effective_value = false; $row_classes[] = 'config-options-effective-value'; } } $table[] = phutil_tag( 'tr', array( 'class' => implode(' ', $row_classes), ), array( phutil_tag('th', array(), $source->getName()), phutil_tag('td', array(), $value), )); } require_celerity_resource('config-options-css'); return phutil_tag( 'table', array( 'class' => 'config-option-table', 'cellspacing' => '0', 'cellpadding' => '0', ), $table); } } diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php index 7a3f77dfea..f981c1c1a1 100644 --- a/src/applications/config/controller/PhabricatorConfigGroupController.php +++ b/src/applications/config/controller/PhabricatorConfigGroupController.php @@ -1,113 +1,112 @@ getViewer(); $group_key = $request->getURIData('key'); $groups = PhabricatorApplicationConfigOptions::loadAll(); $options = idx($groups, $group_key); if (!$options) { return new Aphront404Response(); } $group_uri = PhabricatorConfigGroupConstants::getGroupFullURI( $options->getGroup()); $group_name = PhabricatorConfigGroupConstants::getGroupShortName( $options->getGroup()); $nav = $this->buildSideNavView(); $nav->selectFilter($group_uri); $title = pht('%s Configuration', $options->getName()); $header = $this->buildHeaderView($title); $list = $this->buildOptionList($options->getOptions()); $group_url = phutil_tag('a', array('href' => $group_uri), $group_name); $box_header = pht("%s \xC2\xBB %s", $group_url, $options->getName()); $view = $this->buildConfigBoxView($box_header, $list); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($group_name, $group_uri) ->addTextCrumb($options->getName()) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($view); + ->setFooter($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function buildOptionList(array $options) { assert_instances_of($options, 'PhabricatorConfigOption'); require_celerity_resource('config-options-css'); $db_values = array(); if ($options) { $db_values = id(new PhabricatorConfigEntry())->loadAllWhere( 'configKey IN (%Ls) AND namespace = %s', mpull($options, 'getKey'), 'default'); $db_values = mpull($db_values, null, 'getConfigKey'); } $list = new PHUIObjectItemListView(); $list->setBig(true); foreach ($options as $option) { $summary = $option->getSummary(); $item = id(new PHUIObjectItemView()) ->setHeader($option->getKey()) ->setHref('/config/edit/'.$option->getKey().'/') ->addAttribute($summary); $color = null; $db_value = idx($db_values, $option->getKey()); if ($db_value && !$db_value->getIsDeleted()) { $item->setEffect('visited'); $color = 'violet'; } if ($option->getHidden()) { $item->setStatusIcon('fa-eye-slash grey', pht('Hidden')); $item->setDisabled(true); } else if ($option->getLocked()) { $item->setStatusIcon('fa-lock '.$color, pht('Locked')); } else if ($color) { $item->setStatusIcon('fa-pencil '.$color, pht('Editable')); } else { $item->setStatusIcon('fa-pencil-square-o '.$color, pht('Editable')); } if (!$option->getHidden()) { $current_value = PhabricatorEnv::getEnvConfig($option->getKey()); $current_value = PhabricatorConfigJSON::prettyPrintJSON( $current_value); $current_value = phutil_tag( 'div', array( 'class' => 'config-options-current-value '.$color, ), array( $current_value, )); $item->setSideColumn($current_value); } $list->addItem($item); } return $list; } } diff --git a/src/applications/config/controller/PhabricatorConfigHistoryController.php b/src/applications/config/controller/PhabricatorConfigHistoryController.php index 9157ecb8bb..495102b6a2 100644 --- a/src/applications/config/controller/PhabricatorConfigHistoryController.php +++ b/src/applications/config/controller/PhabricatorConfigHistoryController.php @@ -1,49 +1,48 @@ getViewer(); $id = $request->getURIData('id'); $xactions = id(new PhabricatorConfigTransactionQuery()) ->setViewer($viewer) ->needComments(true) ->execute(); $object = new PhabricatorConfigEntry(); $xaction = $object->getApplicationTransactionTemplate(); $timeline = id(new PhabricatorApplicationTransactionView()) ->setViewer($viewer) ->setTransactions($xactions) ->setRenderAsFeed(true) ->setObjectPHID(PhabricatorPHIDConstants::PHID_VOID); $timeline->setShouldTerminate(true); $title = pht('Settings History'); $header = $this->buildHeaderView($title); $nav = $this->buildSideNavView(); $nav->selectFilter('history/'); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($timeline); + ->setFooter($timeline); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } } diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php index 0ca94abe04..6518ccec97 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -1,115 +1,114 @@ getViewer(); $nav = $this->buildSideNavView(); $nav->selectFilter('issue/'); $engine = new PhabricatorSetupEngine(); $response = $engine->execute(); if ($response) { return $response; } $issues = $engine->getIssues(); $important = $this->buildIssueList( $issues, PhabricatorSetupCheck::GROUP_IMPORTANT, 'fa-warning'); $php = $this->buildIssueList( $issues, PhabricatorSetupCheck::GROUP_PHP, 'fa-code'); $mysql = $this->buildIssueList( $issues, PhabricatorSetupCheck::GROUP_MYSQL, 'fa-database'); $other = $this->buildIssueList( $issues, PhabricatorSetupCheck::GROUP_OTHER, 'fa-question-circle'); $title = pht('Setup Issues'); $header = $this->buildHeaderView($title); if (!$issues) { $issue_list = id(new PHUIInfoView()) ->setTitle(pht('No Issues')) ->appendChild( pht('Your install has no current setup issues to resolve.')) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); } else { $issue_list = array( $important, $php, $mysql, $other, ); $issue_list = $this->buildConfigBoxView(pht('Issues'), $issue_list); } $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($issue_list); + ->setFooter($issue_list); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function buildIssueList(array $issues, $group, $fonticon) { assert_instances_of($issues, 'PhabricatorSetupIssue'); $list = new PHUIObjectItemListView(); $list->setBig(true); $ignored_items = array(); $items = 0; foreach ($issues as $issue) { if ($issue->getGroup() == $group) { $items++; $href = $this->getApplicationURI('/issue/'.$issue->getIssueKey().'/'); $item = id(new PHUIObjectItemView()) ->setHeader($issue->getName()) ->setHref($href) ->addAttribute($issue->getSummary()); if (!$issue->getIsIgnored()) { $icon = id(new PHUIIconView()) ->setIcon($fonticon) ->setBackground('bg-sky'); $item->setImageIcon($icon); $list->addItem($item); } else { $icon = id(new PHUIIconView()) ->setIcon('fa-eye-slash') ->setBackground('bg-grey'); $item->setDisabled(true); $item->setImageIcon($icon); $ignored_items[] = $item; } } } foreach ($ignored_items as $item) { $list->addItem($item); } if ($items == 0) { return null; } else { return $list; } } } diff --git a/src/applications/config/controller/PhabricatorConfigIssueViewController.php b/src/applications/config/controller/PhabricatorConfigIssueViewController.php index 29c9078413..2967169e38 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueViewController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueViewController.php @@ -1,76 +1,75 @@ getViewer(); $issue_key = $request->getURIData('key'); $engine = new PhabricatorSetupEngine(); $response = $engine->execute(); if ($response) { return $response; } $issues = $engine->getIssues(); $nav = $this->buildSideNavView(); $nav->selectFilter('issue/'); if (empty($issues[$issue_key])) { $content = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Issue Resolved')) ->appendChild(pht('This setup issue has been resolved. ')) ->appendChild( phutil_tag( 'a', array( 'href' => $this->getApplicationURI('issue/'), ), pht('Return to Open Issue List'))); $title = pht('Resolved Issue'); } else { $issue = $issues[$issue_key]; $content = $this->renderIssue($issue); $title = $issue->getShortName(); } $header = $this->buildHeaderView($title); $crumbs = $this ->buildApplicationCrumbs() ->setBorder(true) ->addTextCrumb(pht('Setup Issues'), $this->getApplicationURI('issue/')) ->addTextCrumb($title, $request->getRequestURI()) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($content); + ->setFooter($content); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function renderIssue(PhabricatorSetupIssue $issue) { require_celerity_resource('setup-issue-css'); $view = new PhabricatorSetupIssueView(); $view->setIssue($issue); $container = phutil_tag( 'div', array( 'class' => 'setup-issue-background', ), $view->render()); return $container; } } diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php index 1a136ea416..38a0afc328 100644 --- a/src/applications/config/controller/PhabricatorConfigListController.php +++ b/src/applications/config/controller/PhabricatorConfigListController.php @@ -1,58 +1,57 @@ getViewer(); $nav = $this->buildSideNavView(); $nav->selectFilter('/'); $groups = PhabricatorApplicationConfigOptions::loadAll(); $core_list = $this->buildConfigOptionsList($groups, 'core'); $core_list = $this->buildConfigBoxView(pht('Core'), $core_list); $title = pht('Core Settings'); $header = $this->buildHeaderView($title); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($core_list); + ->setFooter($core_list); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } private function buildConfigOptionsList(array $groups, $type) { assert_instances_of($groups, 'PhabricatorApplicationConfigOptions'); $list = new PHUIObjectItemListView(); $list->setBig(true); $groups = msort($groups, 'getName'); foreach ($groups as $group) { if ($group->getGroup() == $type) { $icon = id(new PHUIIconView()) ->setIcon($group->getIcon()) ->setBackground('bg-blue'); $item = id(new PHUIObjectItemView()) ->setHeader($group->getName()) ->setHref('/config/group/'.$group->getKey().'/') ->addAttribute($group->getDescription()) ->setImageIcon($icon); $list->addItem($item); } } return $list; } } diff --git a/src/applications/config/controller/PhabricatorConfigModuleController.php b/src/applications/config/controller/PhabricatorConfigModuleController.php index 63cc5b3843..fe919c57e4 100644 --- a/src/applications/config/controller/PhabricatorConfigModuleController.php +++ b/src/applications/config/controller/PhabricatorConfigModuleController.php @@ -1,41 +1,40 @@ getViewer(); $key = $request->getURIData('module'); $all_modules = PhabricatorConfigModule::getAllModules(); if (empty($all_modules[$key])) { return new Aphront404Response(); } $module = $all_modules[$key]; $content = $module->renderModuleStatus($request); $title = $module->getModuleName(); $nav = $this->buildSideNavView(); $nav->selectFilter('module/'.$key.'/'); $header = $this->buildHeaderView($title); $view = $this->buildConfigBoxView($title, $content); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($view); + ->setFooter($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); } } diff --git a/src/applications/config/controller/PhabricatorConfigVersionController.php b/src/applications/config/controller/PhabricatorConfigVersionController.php index a9571a1f85..153d363062 100644 --- a/src/applications/config/controller/PhabricatorConfigVersionController.php +++ b/src/applications/config/controller/PhabricatorConfigVersionController.php @@ -1,244 +1,242 @@ getViewer(); $title = pht('Version Information'); $versions = $this->renderModuleStatus($viewer); $nav = $this->buildSideNavView(); $nav->selectFilter('version/'); $header = $this->buildHeaderView($title); $view = $this->buildConfigBoxView( pht('Installed Versions'), $versions); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setNavigation($nav) - ->setFixed(true) - ->setMainColumn($view); + ->setFooter($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($nav) ->appendChild($content); - } public function renderModuleStatus($viewer) { $versions = $this->loadVersions($viewer); $version_property_list = id(new PHUIPropertyListView()); foreach ($versions as $name => $info) { $version = $info['version']; if ($info['branchpoint']) { $display = pht( '%s (branched from %s on %s)', $version, $info['branchpoint'], $info['upstream']); } else { $display = $version; } $version_property_list->addProperty($name, $display); } $phabricator_root = dirname(phutil_get_library_root('phabricator')); $version_path = $phabricator_root.'/conf/local/VERSION'; if (Filesystem::pathExists($version_path)) { $version_from_file = Filesystem::readFile($version_path); $version_property_list->addProperty( pht('Local Version'), $version_from_file); } $version_property_list->addProperty('php', phpversion()); $binaries = PhutilBinaryAnalyzer::getAllBinaries(); foreach ($binaries as $binary) { if (!$binary->isBinaryAvailable()) { $binary_info = pht('Not Available'); } else { $version = $binary->getBinaryVersion(); $path = $binary->getBinaryPath(); if ($path === null && $version === null) { $binary_info = pht('-'); } else if ($path === null) { $binary_info = $version; } else if ($version === null) { $binary_info = pht('- at %s', $path); } else { $binary_info = pht('%s at %s', $version, $path); } } $version_property_list->addProperty( $binary->getBinaryName(), $binary_info); } return $version_property_list; } private function loadVersions(PhabricatorUser $viewer) { $specs = array( 'phabricator', 'arcanist', 'phutil', ); $all_libraries = PhutilBootloader::getInstance()->getAllLibraries(); // This puts the core libraries at the top: $other_libraries = array_diff($all_libraries, $specs); $specs = array_merge($specs, $other_libraries); $log_futures = array(); $remote_futures = array(); foreach ($specs as $lib) { $root = dirname(phutil_get_library_root($lib)); $log_command = csprintf( 'git log --format=%s -n 1 --', '%H %ct'); $remote_command = csprintf( 'git remote -v'); $log_futures[$lib] = id(new ExecFuture('%C', $log_command)) ->setCWD($root); $remote_futures[$lib] = id(new ExecFuture('%C', $remote_command)) ->setCWD($root); } $all_futures = array_merge($log_futures, $remote_futures); id(new FutureIterator($all_futures)) ->resolveAll(); // A repository may have a bunch of remotes, but we're only going to look // for remotes we host to try to figure out where this repository branched. $upstream_pattern = '(github\.com/phacility/|secure\.phabricator\.com/)'; $upstream_futures = array(); $lib_upstreams = array(); foreach ($specs as $lib) { $remote_future = $remote_futures[$lib]; list($err, $stdout) = $remote_future->resolve(); if ($err) { // If this fails for whatever reason, just move on. continue; } // These look like this, with a tab separating the first two fields: // remote-name http://remote.uri/ (push) $upstreams = array(); $remotes = phutil_split_lines($stdout, false); foreach ($remotes as $remote) { $remote_pattern = '/^([^\t]+)\t([^ ]+) \(([^)]+)\)\z/'; $matches = null; if (!preg_match($remote_pattern, $remote, $matches)) { continue; } // Remote URIs are either "push" or "fetch": we only care about "fetch" // URIs. $type = $matches[3]; if ($type != 'fetch') { continue; } $uri = $matches[2]; $is_upstream = preg_match($upstream_pattern, $uri); if (!$is_upstream) { continue; } $name = $matches[1]; $upstreams[$name] = $name; } // If we have several suitable upstreams, try to pick the one named // "origin", if it exists. Otherwise, just pick the first one. if (isset($upstreams['origin'])) { $upstream = $upstreams['origin']; } else if ($upstreams) { $upstream = head($upstreams); } else { $upstream = null; } if (!$upstream) { continue; } $lib_upstreams[$lib] = $upstream; $merge_base_command = csprintf( 'git merge-base HEAD %s/master --', $upstream); $root = dirname(phutil_get_library_root($lib)); $upstream_futures[$lib] = id(new ExecFuture('%C', $merge_base_command)) ->setCWD($root); } if ($upstream_futures) { id(new FutureIterator($upstream_futures)) ->resolveAll(); } $results = array(); foreach ($log_futures as $lib => $future) { list($err, $stdout) = $future->resolve(); if (!$err) { list($hash, $epoch) = explode(' ', $stdout); $version = pht('%s (%s)', $hash, phabricator_date($epoch, $viewer)); } else { $version = pht('Unknown'); } $result = array( 'version' => $version, 'upstream' => null, 'branchpoint' => null, ); $upstream_future = idx($upstream_futures, $lib); if ($upstream_future) { list($err, $stdout) = $upstream_future->resolve(); if (!$err) { $branchpoint = trim($stdout); if (strlen($branchpoint)) { // We only list a branchpoint if it differs from HEAD. if ($branchpoint != $hash) { $result['upstream'] = $lib_upstreams[$lib]; $result['branchpoint'] = trim($stdout); } } } } $results[$lib] = $result; } return $results; } }