Path: blob/master/src/applications/config/controller/services/PhabricatorConfigClusterRepositoriesController.php
12262 views
<?php12final class PhabricatorConfigClusterRepositoriesController3extends PhabricatorConfigServicesController {45public function handleRequest(AphrontRequest $request) {6$title = pht('Repository Services');78$doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');9$button = id(new PHUIButtonView())10->setIcon('fa-book')11->setHref($doc_href)12->setTag('a')13->setText(pht('Documentation'));1415$header = $this->buildHeaderView($title, $button);1617$repository_status = $this->buildClusterRepositoryStatus();18$repo_status = $this->buildConfigBoxView(19pht('Repository Status'), $repository_status);2021$repository_errors = $this->buildClusterRepositoryErrors();22$repo_errors = $this->buildConfigBoxView(23pht('Repository Errors'), $repository_errors);2425$crumbs = $this->newCrumbs()26->addTextCrumb($title);2728$content = id(new PHUITwoColumnView())29->setHeader($header)30->setFooter(31array(32$repo_status,33$repo_errors,34));3536$nav = $this->newNavigation('repository-servers');3738return $this->newPage()39->setTitle($title)40->setCrumbs($crumbs)41->setNavigation($nav)42->appendChild($content);43}4445private function buildClusterRepositoryStatus() {46$viewer = $this->getViewer();4748Javelin::initBehavior('phabricator-tooltips');4950$all_services = id(new AlmanacServiceQuery())51->setViewer($viewer)52->withServiceTypes(53array(54AlmanacClusterRepositoryServiceType::SERVICETYPE,55))56->needBindings(true)57->needProperties(true)58->execute();59$all_services = mpull($all_services, null, 'getPHID');6061$all_repositories = id(new PhabricatorRepositoryQuery())62->setViewer($viewer)63->withTypes(64array(65PhabricatorRepositoryType::REPOSITORY_TYPE_GIT,66))67->execute();68$all_repositories = mpull($all_repositories, null, 'getPHID');6970$all_versions = id(new PhabricatorRepositoryWorkingCopyVersion())71->loadAll();7273$all_devices = $this->getDevices($all_services, false);74$all_active_devices = $this->getDevices($all_services, true);7576$leader_versions = $this->getLeaderVersionsByRepository(77$all_repositories,78$all_versions,79$all_active_devices);8081$push_times = $this->loadLeaderPushTimes($leader_versions);8283$repository_groups = mgroup($all_repositories, 'getAlmanacServicePHID');84$repository_versions = mgroup($all_versions, 'getRepositoryPHID');8586$rows = array();87foreach ($all_services as $service) {88$service_phid = $service->getPHID();8990if ($service->getAlmanacPropertyValue('closed')) {91$status_icon = 'fa-folder';92$status_tip = pht('Closed');93} else {94$status_icon = 'fa-folder-open green';95$status_tip = pht('Open');96}9798$status_icon = id(new PHUIIconView())99->setIcon($status_icon)100->addSigil('has-tooltip')101->setMetadata(102array(103'tip' => $status_tip,104));105106$devices = idx($all_devices, $service_phid, array());107$active_devices = idx($all_active_devices, $service_phid, array());108109$device_icon = 'fa-server green';110111$device_label = pht(112'%s Active',113phutil_count($active_devices));114115$device_status = array(116id(new PHUIIconView())->setIcon($device_icon),117' ',118$device_label,119);120121$repositories = idx($repository_groups, $service_phid, array());122123$repository_status = pht(124'%s',125phutil_count($repositories));126127$no_leader = array();128$full_sync = array();129$partial_sync = array();130$no_sync = array();131$lag = array();132133// Threshold in seconds before we start complaining that repositories134// are not synchronized when there is only one leader.135$threshold = phutil_units('5 minutes in seconds');136137$messages = array();138139foreach ($repositories as $repository) {140$repository_phid = $repository->getPHID();141142$leader_version = idx($leader_versions, $repository_phid);143if ($leader_version === null) {144$no_leader[] = $repository;145$messages[] = pht(146'Repository %s has an ambiguous leader.',147$viewer->renderHandle($repository_phid)->render());148continue;149}150151$versions = idx($repository_versions, $repository_phid, array());152153// Filter out any versions for devices which are no longer active.154foreach ($versions as $key => $version) {155$version_device_phid = $version->getDevicePHID();156if (empty($active_devices[$version_device_phid])) {157unset($versions[$key]);158}159}160161$leaders = 0;162foreach ($versions as $version) {163if ($version->getRepositoryVersion() == $leader_version) {164$leaders++;165}166}167168if ($leaders == count($active_devices)) {169$full_sync[] = $repository;170} else {171$push_epoch = idx($push_times, $repository_phid);172if ($push_epoch) {173$duration = (PhabricatorTime::getNow() - $push_epoch);174$lag[] = $duration;175} else {176$duration = null;177}178179if ($leaders >= 2 || ($duration && ($duration < $threshold))) {180$partial_sync[] = $repository;181} else {182$no_sync[] = $repository;183if ($push_epoch) {184$messages[] = pht(185'Repository %s has unreplicated changes (for %s).',186$viewer->renderHandle($repository_phid)->render(),187phutil_format_relative_time($duration));188} else {189$messages[] = pht(190'Repository %s has unreplicated changes.',191$viewer->renderHandle($repository_phid)->render());192}193}194195}196}197198$with_lag = false;199200if ($no_leader) {201$replication_icon = 'fa-times red';202$replication_label = pht('Ambiguous Leader');203} else if ($no_sync) {204$replication_icon = 'fa-refresh yellow';205$replication_label = pht('Unsynchronized');206$with_lag = true;207} else if ($partial_sync) {208$replication_icon = 'fa-refresh green';209$replication_label = pht('Partial');210$with_lag = true;211} else if ($full_sync) {212$replication_icon = 'fa-check green';213$replication_label = pht('Synchronized');214} else {215$replication_icon = 'fa-times grey';216$replication_label = pht('No Repositories');217}218219if ($with_lag && $lag) {220$lag_status = phutil_format_relative_time(max($lag));221$lag_status = pht(' (%s)', $lag_status);222} else {223$lag_status = null;224}225226$replication_status = array(227id(new PHUIIconView())->setIcon($replication_icon),228' ',229$replication_label,230$lag_status,231);232233$messages = phutil_implode_html(phutil_tag('br'), $messages);234235$rows[] = array(236$status_icon,237$viewer->renderHandle($service->getPHID()),238$device_status,239$repository_status,240$replication_status,241$messages,242);243}244245return id(new AphrontTableView($rows))246->setNoDataString(247pht('No repository cluster services are configured.'))248->setHeaders(249array(250null,251pht('Service'),252pht('Devices'),253pht('Repos'),254pht('Sync'),255pht('Messages'),256))257->setColumnClasses(258array(259null,260'pri',261null,262null,263null,264'wide',265));266}267268private function getDevices(269array $all_services,270$only_active) {271272$devices = array();273foreach ($all_services as $service) {274$map = array();275foreach ($service->getBindings() as $binding) {276if ($only_active && $binding->getIsDisabled()) {277continue;278}279280$device = $binding->getDevice();281$device_phid = $device->getPHID();282283$map[$device_phid] = $device;284}285$devices[$service->getPHID()] = $map;286}287288return $devices;289}290291private function getLeaderVersionsByRepository(292array $all_repositories,293array $all_versions,294array $active_devices) {295296$version_map = mgroup($all_versions, 'getRepositoryPHID');297298$result = array();299foreach ($all_repositories as $repository_phid => $repository) {300$service_phid = $repository->getAlmanacServicePHID();301if (!$service_phid) {302continue;303}304305$devices = idx($active_devices, $service_phid);306if (!$devices) {307continue;308}309310$versions = idx($version_map, $repository_phid, array());311$versions = mpull($versions, null, 'getDevicePHID');312$versions = array_select_keys($versions, array_keys($devices));313if (!$versions) {314continue;315}316317$leader = (int)max(mpull($versions, 'getRepositoryVersion'));318$result[$repository_phid] = $leader;319}320321return $result;322}323324private function loadLeaderPushTimes(array $leader_versions) {325$viewer = $this->getViewer();326327if (!$leader_versions) {328return array();329}330331$events = id(new PhabricatorRepositoryPushEventQuery())332->setViewer($viewer)333->withIDs($leader_versions)334->execute();335$events = mpull($events, null, 'getID');336337$result = array();338foreach ($leader_versions as $key => $version) {339$event = idx($events, $version);340if (!$event) {341continue;342}343344$result[$key] = $event->getEpoch();345}346347return $result;348}349350351private function buildClusterRepositoryErrors() {352$viewer = $this->getViewer();353354$messages = id(new PhabricatorRepositoryStatusMessage())->loadAllWhere(355'statusCode IN (%Ls)',356array(357PhabricatorRepositoryStatusMessage::CODE_ERROR,358));359360$repository_ids = mpull($messages, 'getRepositoryID');361if ($repository_ids) {362// NOTE: We're bypassing policies when loading repositories because we363// want to show errors exist even if the viewer can't see the repository.364// We use handles to describe the repository below, so the viewer won't365// actually be able to see any particulars if they can't see the366// repository.367$repositories = id(new PhabricatorRepositoryQuery())368->setViewer(PhabricatorUser::getOmnipotentUser())369->withIDs($repository_ids)370->execute();371$repositories = mpull($repositories, null, 'getID');372}373374$rows = array();375foreach ($messages as $message) {376$repository = idx($repositories, $message->getRepositoryID());377if (!$repository) {378continue;379}380381if (!$repository->isTracked()) {382continue;383}384385$icon = id(new PHUIIconView())386->setIcon('fa-exclamation-triangle red');387388$rows[] = array(389$icon,390$viewer->renderHandle($repository->getPHID()),391phutil_tag(392'a',393array(394'href' => $repository->getPathURI('manage/status/'),395),396$message->getStatusTypeName()),397);398}399400return id(new AphrontTableView($rows))401->setNoDataString(402pht('No active repositories have outstanding errors.'))403->setHeaders(404array(405null,406pht('Repository'),407pht('Error'),408))409->setColumnClasses(410array(411null,412'pri',413'wide',414));415}416417}418419420