@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Copy repository status to a management panel

Summary: Ref T10748. Pretty straightforward. I'd like to put a little "!" icon in the menu if there's a warning/error eventually, but can deal with that latre.

Test Plan: {F1223096}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10748

Differential Revision: https://secure.phabricator.com/D15741

+473
+2
src/__phutil_library_map__.php
··· 774 774 'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php', 775 775 'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php', 776 776 'DiffusionRepositorySearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositorySearchConduitAPIMethod.php', 777 + 'DiffusionRepositoryStatusManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php', 777 778 'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php', 778 779 'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php', 779 780 'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php', ··· 4972 4973 'DiffusionRepositoryRef' => 'Phobject', 4973 4974 'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule', 4974 4975 'DiffusionRepositorySearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 4976 + 'DiffusionRepositoryStatusManagementPanel' => 'DiffusionRepositoryManagementPanel', 4975 4977 'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController', 4976 4978 'DiffusionRepositoryTag' => 'Phobject', 4977 4979 'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
+13
src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php
··· 25 25 $edit_uri = $repository->getPathURI('manage/'); 26 26 $activate_uri = $repository->getPathURI('edit/activate/'); 27 27 $delete_uri = $repository->getPathURI('edit/delete/'); 28 + $encoding_uri = $repository->getPathURI('edit/encoding/'); 28 29 29 30 if ($repository->isTracked()) { 30 31 $activate_icon = 'fa-pause'; ··· 39 40 ->setIcon('fa-pencil') 40 41 ->setName(pht('Edit Basic Information')) 41 42 ->setHref($edit_uri) 43 + ->setDisabled(!$can_edit) 44 + ->setWorkflow(!$can_edit), 45 + id(new PhabricatorActionView()) 46 + ->setIcon('fa-text-width') 47 + ->setName(pht('Edit Text Encoding')) 48 + ->setHref($encoding_uri) 42 49 ->setDisabled(!$can_edit) 43 50 ->setWorkflow(!$can_edit), 44 51 id(new PhabricatorActionView()) ··· 96 103 $short_name = phutil_tag('em', array(), $short_name); 97 104 } 98 105 $view->addProperty(pht('Short Name'), $short_name); 106 + 107 + $encoding = $repository->getDetail('encoding'); 108 + if (!$encoding) { 109 + $encoding = phutil_tag('em', array(), pht('Use Default (UTF-8)')); 110 + } 111 + $view->addProperty(pht('Encoding'), $encoding); 99 112 100 113 return $view; 101 114 }
+1
src/applications/diffusion/management/DiffusionRepositoryClusterManagementPanel.php
··· 160 160 161 161 return id(new PHUIObjectBoxView()) 162 162 ->setHeader($header) 163 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 163 164 ->setTable($table); 164 165 } 165 166
+457
src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php
··· 1 + <?php 2 + 3 + final class DiffusionRepositoryStatusManagementPanel 4 + extends DiffusionRepositoryManagementPanel { 5 + 6 + const PANELKEY = 'status'; 7 + 8 + public function getManagementPanelLabel() { 9 + return pht('Status'); 10 + } 11 + 12 + public function getManagementPanelOrder() { 13 + return 200; 14 + } 15 + 16 + protected function buildManagementPanelActions() { 17 + $repository = $this->getRepository(); 18 + $viewer = $this->getViewer(); 19 + 20 + $can_edit = PhabricatorPolicyFilter::hasCapability( 21 + $viewer, 22 + $repository, 23 + PhabricatorPolicyCapability::CAN_EDIT); 24 + 25 + $update_uri = $repository->getPathURI('edit/update/'); 26 + 27 + return array( 28 + id(new PhabricatorActionView()) 29 + ->setIcon('fa-refresh') 30 + ->setName(pht('Update Now')) 31 + ->setWorkflow(true) 32 + ->setDisabled(!$can_edit) 33 + ->setHref($update_uri), 34 + ); 35 + } 36 + 37 + public function buildManagementPanelContent() { 38 + $repository = $this->getRepository(); 39 + $viewer = $this->getViewer(); 40 + 41 + $view = id(new PHUIPropertyListView()) 42 + ->setViewer($viewer) 43 + ->setActionList($this->newActions()); 44 + 45 + $view->addProperty( 46 + pht('Update Frequency'), 47 + $this->buildRepositoryUpdateInterval($repository)); 48 + 49 + 50 + list($status, $raw_error) = $this->buildRepositoryStatus($repository); 51 + 52 + $view->addProperty(pht('Status'), $status); 53 + if ($raw_error) { 54 + $view->addSectionHeader(pht('Raw Error')); 55 + $view->addTextContent($raw_error); 56 + } 57 + 58 + return $this->newBox(pht('Status'), $view); 59 + } 60 + 61 + private function buildRepositoryUpdateInterval( 62 + PhabricatorRepository $repository) { 63 + 64 + $smart_wait = $repository->loadUpdateInterval(); 65 + 66 + $doc_href = PhabricatorEnv::getDoclink( 67 + 'Diffusion User Guide: Repository Updates'); 68 + 69 + return array( 70 + phutil_format_relative_time_detailed($smart_wait), 71 + " \xC2\xB7 ", 72 + phutil_tag( 73 + 'a', 74 + array( 75 + 'href' => $doc_href, 76 + 'target' => '_blank', 77 + ), 78 + pht('Learn More')), 79 + ); 80 + } 81 + 82 + private function buildRepositoryStatus( 83 + PhabricatorRepository $repository) { 84 + 85 + $viewer = $this->getViewer(); 86 + $is_cluster = $repository->getAlmanacServicePHID(); 87 + 88 + $view = new PHUIStatusListView(); 89 + 90 + $messages = id(new PhabricatorRepositoryStatusMessage()) 91 + ->loadAllWhere('repositoryID = %d', $repository->getID()); 92 + $messages = mpull($messages, null, 'getStatusType'); 93 + 94 + if ($repository->isTracked()) { 95 + $view->addItem( 96 + id(new PHUIStatusItemView()) 97 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 98 + ->setTarget(pht('Repository Active'))); 99 + } else { 100 + $view->addItem( 101 + id(new PHUIStatusItemView()) 102 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey') 103 + ->setTarget(pht('Repository Inactive')) 104 + ->setNote( 105 + pht('Activate this repository to begin or resume import.'))); 106 + return $view; 107 + } 108 + 109 + $binaries = array(); 110 + $svnlook_check = false; 111 + switch ($repository->getVersionControlSystem()) { 112 + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 113 + $binaries[] = 'git'; 114 + break; 115 + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 116 + $binaries[] = 'svn'; 117 + break; 118 + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 119 + $binaries[] = 'hg'; 120 + break; 121 + } 122 + 123 + if ($repository->isHosted()) { 124 + if ($repository->getServeOverHTTP() != PhabricatorRepository::SERVE_OFF) { 125 + switch ($repository->getVersionControlSystem()) { 126 + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 127 + $binaries[] = 'git-http-backend'; 128 + break; 129 + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 130 + $binaries[] = 'svnserve'; 131 + $binaries[] = 'svnadmin'; 132 + $binaries[] = 'svnlook'; 133 + $svnlook_check = true; 134 + break; 135 + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 136 + $binaries[] = 'hg'; 137 + break; 138 + } 139 + } 140 + if ($repository->getServeOverSSH() != PhabricatorRepository::SERVE_OFF) { 141 + switch ($repository->getVersionControlSystem()) { 142 + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 143 + $binaries[] = 'git-receive-pack'; 144 + $binaries[] = 'git-upload-pack'; 145 + break; 146 + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 147 + $binaries[] = 'svnserve'; 148 + $binaries[] = 'svnadmin'; 149 + $binaries[] = 'svnlook'; 150 + $svnlook_check = true; 151 + break; 152 + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 153 + $binaries[] = 'hg'; 154 + break; 155 + } 156 + } 157 + } 158 + 159 + $binaries = array_unique($binaries); 160 + if (!$is_cluster) { 161 + // We're only checking for binaries if we aren't running with a cluster 162 + // configuration. In theory, we could check for binaries on the 163 + // repository host machine, but we'd need to make this more complicated 164 + // to do that. 165 + 166 + foreach ($binaries as $binary) { 167 + $where = Filesystem::resolveBinary($binary); 168 + if (!$where) { 169 + $view->addItem( 170 + id(new PHUIStatusItemView()) 171 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 172 + ->setTarget( 173 + pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) 174 + ->setNote(pht( 175 + "Unable to find this binary in the webserver's PATH. You may ". 176 + "need to configure %s.", 177 + $this->getEnvConfigLink()))); 178 + } else { 179 + $view->addItem( 180 + id(new PHUIStatusItemView()) 181 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 182 + ->setTarget( 183 + pht('Found Binary %s', phutil_tag('tt', array(), $binary))) 184 + ->setNote(phutil_tag('tt', array(), $where))); 185 + } 186 + } 187 + 188 + // This gets checked generically above. However, for svn commit hooks, we 189 + // need this to be in environment.append-paths because subversion strips 190 + // PATH. 191 + if ($svnlook_check) { 192 + $where = Filesystem::resolveBinary('svnlook'); 193 + if ($where) { 194 + $path = substr($where, 0, strlen($where) - strlen('svnlook')); 195 + $dirs = PhabricatorEnv::getEnvConfig('environment.append-paths'); 196 + $in_path = false; 197 + foreach ($dirs as $dir) { 198 + if (Filesystem::isDescendant($path, $dir)) { 199 + $in_path = true; 200 + break; 201 + } 202 + } 203 + if (!$in_path) { 204 + $view->addItem( 205 + id(new PHUIStatusItemView()) 206 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 207 + ->setTarget( 208 + pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) 209 + ->setNote(pht( 210 + 'Unable to find this binary in `%s`. '. 211 + 'You need to configure %s and include %s.', 212 + 'environment.append-paths', 213 + $this->getEnvConfigLink(), 214 + $path))); 215 + } 216 + } 217 + } 218 + } 219 + 220 + $doc_href = PhabricatorEnv::getDocLink('Managing Daemons with phd'); 221 + 222 + $daemon_instructions = pht( 223 + 'Use %s to start daemons. See %s.', 224 + phutil_tag('tt', array(), 'bin/phd start'), 225 + phutil_tag( 226 + 'a', 227 + array( 228 + 'href' => $doc_href, 229 + ), 230 + pht('Managing Daemons with phd'))); 231 + 232 + 233 + $pull_daemon = id(new PhabricatorDaemonLogQuery()) 234 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 235 + ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) 236 + ->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon')) 237 + ->setLimit(1) 238 + ->execute(); 239 + 240 + if ($pull_daemon) { 241 + 242 + // TODO: In a cluster environment, we need a daemon on this repository's 243 + // host, specifically, and we aren't checking for that right now. This 244 + // is a reasonable proxy for things being more-or-less correctly set up, 245 + // though. 246 + 247 + $view->addItem( 248 + id(new PHUIStatusItemView()) 249 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 250 + ->setTarget(pht('Pull Daemon Running'))); 251 + } else { 252 + $view->addItem( 253 + id(new PHUIStatusItemView()) 254 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 255 + ->setTarget(pht('Pull Daemon Not Running')) 256 + ->setNote($daemon_instructions)); 257 + } 258 + 259 + 260 + $task_daemon = id(new PhabricatorDaemonLogQuery()) 261 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 262 + ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) 263 + ->withDaemonClasses(array('PhabricatorTaskmasterDaemon')) 264 + ->setLimit(1) 265 + ->execute(); 266 + if ($task_daemon) { 267 + $view->addItem( 268 + id(new PHUIStatusItemView()) 269 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 270 + ->setTarget(pht('Task Daemon Running'))); 271 + } else { 272 + $view->addItem( 273 + id(new PHUIStatusItemView()) 274 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 275 + ->setTarget(pht('Task Daemon Not Running')) 276 + ->setNote($daemon_instructions)); 277 + } 278 + 279 + 280 + if ($is_cluster) { 281 + // Just omit this status check for now in cluster environments. We 282 + // could make a service call and pull it from the repository host 283 + // eventually. 284 + } else if ($repository->usesLocalWorkingCopy()) { 285 + $local_parent = dirname($repository->getLocalPath()); 286 + if (Filesystem::pathExists($local_parent)) { 287 + $view->addItem( 288 + id(new PHUIStatusItemView()) 289 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 290 + ->setTarget(pht('Storage Directory OK')) 291 + ->setNote(phutil_tag('tt', array(), $local_parent))); 292 + } else { 293 + $view->addItem( 294 + id(new PHUIStatusItemView()) 295 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 296 + ->setTarget(pht('No Storage Directory')) 297 + ->setNote( 298 + pht( 299 + 'Storage directory %s does not exist, or is not readable by '. 300 + 'the webserver. Create this directory or make it readable.', 301 + phutil_tag('tt', array(), $local_parent)))); 302 + return $view; 303 + } 304 + 305 + $local_path = $repository->getLocalPath(); 306 + $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT); 307 + if ($message) { 308 + switch ($message->getStatusCode()) { 309 + case PhabricatorRepositoryStatusMessage::CODE_ERROR: 310 + $view->addItem( 311 + id(new PHUIStatusItemView()) 312 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 313 + ->setTarget(pht('Initialization Error')) 314 + ->setNote($message->getParameter('message'))); 315 + return $view; 316 + case PhabricatorRepositoryStatusMessage::CODE_OKAY: 317 + if (Filesystem::pathExists($local_path)) { 318 + $view->addItem( 319 + id(new PHUIStatusItemView()) 320 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 321 + ->setTarget(pht('Working Copy OK')) 322 + ->setNote(phutil_tag('tt', array(), $local_path))); 323 + } else { 324 + $view->addItem( 325 + id(new PHUIStatusItemView()) 326 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 327 + ->setTarget(pht('Working Copy Error')) 328 + ->setNote( 329 + pht( 330 + 'Working copy %s has been deleted, or is not '. 331 + 'readable by the webserver. Make this directory '. 332 + 'readable. If it has been deleted, the daemons should '. 333 + 'restore it automatically.', 334 + phutil_tag('tt', array(), $local_path)))); 335 + return $view; 336 + } 337 + break; 338 + case PhabricatorRepositoryStatusMessage::CODE_WORKING: 339 + $view->addItem( 340 + id(new PHUIStatusItemView()) 341 + ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') 342 + ->setTarget(pht('Initializing Working Copy')) 343 + ->setNote(pht('Daemons are initializing the working copy.'))); 344 + return $view; 345 + default: 346 + $view->addItem( 347 + id(new PHUIStatusItemView()) 348 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 349 + ->setTarget(pht('Unknown Init Status')) 350 + ->setNote($message->getStatusCode())); 351 + return $view; 352 + } 353 + } else { 354 + $view->addItem( 355 + id(new PHUIStatusItemView()) 356 + ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') 357 + ->setTarget(pht('No Working Copy Yet')) 358 + ->setNote( 359 + pht('Waiting for daemons to build a working copy.'))); 360 + return $view; 361 + } 362 + } 363 + 364 + $raw_error = null; 365 + 366 + $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); 367 + if ($message) { 368 + switch ($message->getStatusCode()) { 369 + case PhabricatorRepositoryStatusMessage::CODE_ERROR: 370 + $message = $message->getParameter('message'); 371 + 372 + $suggestion = null; 373 + if (preg_match('/Permission denied \(publickey\)./', $message)) { 374 + $suggestion = pht( 375 + 'Public Key Error: This error usually indicates that the '. 376 + 'keypair you have configured does not have permission to '. 377 + 'access the repository.'); 378 + } 379 + 380 + $raw_error = $message; 381 + 382 + $view->addItem( 383 + id(new PHUIStatusItemView()) 384 + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') 385 + ->setTarget(pht('Update Error')) 386 + ->setNote($suggestion)); 387 + return $view; 388 + case PhabricatorRepositoryStatusMessage::CODE_OKAY: 389 + $ago = (PhabricatorTime::getNow() - $message->getEpoch()); 390 + $view->addItem( 391 + id(new PHUIStatusItemView()) 392 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 393 + ->setTarget(pht('Updates OK')) 394 + ->setNote( 395 + pht( 396 + 'Last updated %s (%s ago).', 397 + phabricator_datetime($message->getEpoch(), $viewer), 398 + phutil_format_relative_time_detailed($ago)))); 399 + break; 400 + } 401 + } else { 402 + $view->addItem( 403 + id(new PHUIStatusItemView()) 404 + ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') 405 + ->setTarget(pht('Waiting For Update')) 406 + ->setNote( 407 + pht('Waiting for daemons to read updates.'))); 408 + } 409 + 410 + if ($repository->isImporting()) { 411 + $ratio = $repository->loadImportProgress(); 412 + $percentage = sprintf('%.2f%%', 100 * $ratio); 413 + 414 + $view->addItem( 415 + id(new PHUIStatusItemView()) 416 + ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') 417 + ->setTarget(pht('Importing')) 418 + ->setNote( 419 + pht('%s Complete', $percentage))); 420 + } else { 421 + $view->addItem( 422 + id(new PHUIStatusItemView()) 423 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 424 + ->setTarget(pht('Fully Imported'))); 425 + } 426 + 427 + if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) { 428 + $view->addItem( 429 + id(new PHUIStatusItemView()) 430 + ->setIcon(PHUIStatusItemView::ICON_UP, 'indigo') 431 + ->setTarget(pht('Prioritized')) 432 + ->setNote(pht('This repository will be updated soon!'))); 433 + } 434 + 435 + $can_edit = PhabricatorPolicyFilter::hasCapability( 436 + $viewer, 437 + $repository, 438 + PhabricatorPolicyCapability::CAN_EDIT); 439 + 440 + if ($raw_error !== null) { 441 + if (!$can_edit) { 442 + $raw_message = pht( 443 + 'You must be able to edit a repository to see raw error messages '. 444 + 'because they sometimes disclose sensitive information.'); 445 + $raw_message = phutil_tag('em', array(), $raw_message); 446 + } else { 447 + $raw_message = phutil_escape_html_newlines($raw_error); 448 + } 449 + } else { 450 + $raw_message = null; 451 + } 452 + 453 + return array($view, $raw_message); 454 + } 455 + 456 + 457 + }