@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
at recaptime-dev/main 862 lines 22 kB view raw
1<?php 2 3final class PhabricatorConfigDatabaseStatusController 4 extends PhabricatorConfigDatabaseController { 5 6 private $database; 7 private $table; 8 private $column; 9 private $key; 10 private $ref; 11 12 public function handleRequest(AphrontRequest $request) { 13 $viewer = $request->getViewer(); 14 $this->database = $request->getURIData('database'); 15 $this->table = $request->getURIData('table'); 16 $this->column = $request->getURIData('column'); 17 $this->key = $request->getURIData('key'); 18 $this->ref = $request->getURIData('ref'); 19 20 $query = new PhabricatorConfigSchemaQuery(); 21 22 $actual = $query->loadActualSchemata(); 23 $expect = $query->loadExpectedSchemata(); 24 $comp = $query->buildComparisonSchemata($expect, $actual); 25 26 if ($this->ref !== null) { 27 $server_actual = idx($actual, $this->ref); 28 if (!$server_actual) { 29 return new Aphront404Response(); 30 } 31 32 $server_comparison = $comp[$this->ref]; 33 $server_expect = $expect[$this->ref]; 34 35 if ($this->column) { 36 return $this->renderColumn( 37 $server_comparison, 38 $server_expect, 39 $server_actual, 40 $this->database, 41 $this->table, 42 $this->column); 43 } else if ($this->key) { 44 return $this->renderKey( 45 $server_comparison, 46 $server_expect, 47 $server_actual, 48 $this->database, 49 $this->table, 50 $this->key); 51 } else if ($this->table) { 52 return $this->renderTable( 53 $server_comparison, 54 $server_expect, 55 $server_actual, 56 $this->database, 57 $this->table); 58 } else if ($this->database) { 59 return $this->renderDatabase( 60 $server_comparison, 61 $server_expect, 62 $server_actual, 63 $this->database); 64 } 65 } 66 67 return $this->renderServers( 68 $comp, 69 $expect, 70 $actual); 71 } 72 73 private function buildResponse($title, $body) { 74 $nav = $this->newNavigation('schemata'); 75 76 if (!$title) { 77 $title = pht('Database Status'); 78 } 79 80 $ref = $this->ref; 81 $database = $this->database; 82 $table = $this->table; 83 $column = $this->column; 84 $key = $this->key; 85 86 $links = array(); 87 $links[] = array( 88 pht('Database Status'), 89 'database/', 90 ); 91 92 if ($database) { 93 $links[] = array( 94 $database, 95 "database/{$ref}/{$database}/", 96 ); 97 } 98 99 if ($table) { 100 $links[] = array( 101 $table, 102 "database/{$ref}/{$database}/{$table}/", 103 ); 104 } 105 106 if ($column) { 107 $links[] = array( 108 $column, 109 "database/{$ref}/{$database}/{$table}/col/{$column}/", 110 ); 111 } 112 113 if ($key) { 114 $links[] = array( 115 $key, 116 "database/{$ref}/{$database}/{$table}/key/{$key}/", 117 ); 118 } 119 120 $crumbs = $this->newCrumbs(); 121 122 $last_key = last_key($links); 123 foreach ($links as $link_key => $link) { 124 list($name, $href) = $link; 125 if ($link_key == $last_key) { 126 $crumbs->addTextCrumb($name); 127 } else { 128 $crumbs->addTextCrumb($name, $this->getApplicationURI($href)); 129 } 130 } 131 132 $doc_link = PhabricatorEnv::getDoclink('Managing Storage Adjustments'); 133 $button = id(new PHUIButtonView()) 134 ->setTag('a') 135 ->setIcon('fa-book') 136 ->setHref($doc_link) 137 ->setText(pht('Documentation')); 138 139 $header = $this->buildHeaderView($title, $button); 140 141 $content = id(new PHUITwoColumnView()) 142 ->setHeader($header) 143 ->setFooter($body); 144 145 return $this->newPage() 146 ->setTitle($title) 147 ->setCrumbs($crumbs) 148 ->setNavigation($nav) 149 ->appendChild($content); 150 } 151 152 153 private function renderServers( 154 array $comp_servers, 155 array $expect_servers, 156 array $actual_servers) { 157 158 $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; 159 $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 160 161 $rows = array(); 162 foreach ($comp_servers as $ref_key => $comp) { 163 $actual = $actual_servers[$ref_key]; 164 $expect = $expect_servers[$ref_key]; 165 foreach ($comp->getDatabases() as $database_name => $database) { 166 $actual_database = $actual->getDatabase($database_name); 167 if ($actual_database) { 168 $charset = $actual_database->getCharacterSet(); 169 $collation = $actual_database->getCollation(); 170 } else { 171 $charset = null; 172 $collation = null; 173 } 174 175 $status = $database->getStatus(); 176 $issues = $database->getIssues(); 177 178 $uri = $this->getURI( 179 array( 180 'ref' => $ref_key, 181 'database' => $database_name, 182 )); 183 184 $rows[] = array( 185 $this->renderIcon($status), 186 $ref_key, 187 phutil_tag( 188 'a', 189 array( 190 'href' => $uri, 191 ), 192 $database_name), 193 $this->renderAttr($charset, $database->hasIssue($charset_issue)), 194 $this->renderAttr($collation, $database->hasIssue($collation_issue)), 195 ); 196 } 197 } 198 199 $table = id(new AphrontTableView($rows)) 200 ->setHeaders( 201 array( 202 null, 203 pht('Server'), 204 pht('Database'), 205 pht('Charset'), 206 pht('Collation'), 207 )) 208 ->setColumnClasses( 209 array( 210 null, 211 null, 212 'wide pri', 213 null, 214 null, 215 )); 216 217 $title = pht('Database Status'); 218 $properties = $this->buildProperties( 219 array( 220 ), 221 $comp->getIssues()); 222 $properties = $this->buildConfigBoxView(pht('Properties'), $properties); 223 $table = $this->buildConfigBoxView(pht('Database'), $table); 224 225 return $this->buildResponse($title, array($properties, $table)); 226 } 227 228 private function renderDatabase( 229 PhabricatorConfigServerSchema $comp, 230 PhabricatorConfigServerSchema $expect, 231 PhabricatorConfigServerSchema $actual, 232 $database_name) { 233 234 $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 235 236 $database = $comp->getDatabase($database_name); 237 if (!$database) { 238 return new Aphront404Response(); 239 } 240 241 $rows = array(); 242 foreach ($database->getTables() as $table_name => $table) { 243 $status = $table->getStatus(); 244 245 $uri = $this->getURI( 246 array( 247 'table' => $table_name, 248 )); 249 250 $rows[] = array( 251 $this->renderIcon($status), 252 phutil_tag( 253 'a', 254 array( 255 'href' => $uri, 256 ), 257 $table_name), 258 $this->renderAttr( 259 $table->getCollation(), 260 $table->hasIssue($collation_issue)), 261 $table->getPersistenceTypeDisplayName(), 262 ); 263 } 264 265 $table = id(new AphrontTableView($rows)) 266 ->setHeaders( 267 array( 268 null, 269 pht('Table'), 270 pht('Collation'), 271 pht('Persistence'), 272 )) 273 ->setColumnClasses( 274 array( 275 null, 276 'wide pri', 277 null, 278 null, 279 )); 280 281 $title = $database_name; 282 283 $actual_database = $actual->getDatabase($database_name); 284 if ($actual_database) { 285 $actual_charset = $actual_database->getCharacterSet(); 286 $actual_collation = $actual_database->getCollation(); 287 } else { 288 $actual_charset = null; 289 $actual_collation = null; 290 } 291 292 $expect_database = $expect->getDatabase($database_name); 293 if ($expect_database) { 294 $expect_charset = $expect_database->getCharacterSet(); 295 $expect_collation = $expect_database->getCollation(); 296 } else { 297 $expect_charset = null; 298 $expect_collation = null; 299 } 300 301 $properties = $this->buildProperties( 302 array( 303 array( 304 pht('Server'), 305 $this->ref, 306 ), 307 array( 308 pht('Character Set'), 309 $actual_charset, 310 ), 311 array( 312 pht('Expected Character Set'), 313 $expect_charset, 314 ), 315 array( 316 pht('Collation'), 317 $actual_collation, 318 ), 319 array( 320 pht('Expected Collation'), 321 $expect_collation, 322 ), 323 ), 324 $database->getIssues()); 325 326 $properties = $this->buildConfigBoxView(pht('Properties'), $properties); 327 $table = $this->buildConfigBoxView(pht('Database'), $table); 328 329 return $this->buildResponse($title, array($properties, $table)); 330 } 331 332 private function renderTable( 333 PhabricatorConfigServerSchema $comp, 334 PhabricatorConfigServerSchema $expect, 335 PhabricatorConfigServerSchema $actual, 336 $database_name, 337 $table_name) { 338 339 $type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE; 340 $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; 341 $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 342 $nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE; 343 $unique_issue = PhabricatorConfigStorageSchema::ISSUE_UNIQUE; 344 $columns_issue = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS; 345 $longkey_issue = PhabricatorConfigStorageSchema::ISSUE_LONGKEY; 346 $auto_issue = PhabricatorConfigStorageSchema::ISSUE_AUTOINCREMENT; 347 348 $database = $comp->getDatabase($database_name); 349 if (!$database) { 350 return new Aphront404Response(); 351 } 352 353 $table = $database->getTable($table_name); 354 if (!$table) { 355 return new Aphront404Response(); 356 } 357 358 $actual_database = $actual->getDatabase($database_name); 359 $actual_table = null; 360 if ($actual_database) { 361 $actual_table = $actual_database->getTable($table_name); 362 } 363 364 $expect_database = $expect->getDatabase($database_name); 365 $expect_table = null; 366 if ($expect_database) { 367 $expect_table = $expect_database->getTable($table_name); 368 } 369 370 $rows = array(); 371 foreach ($table->getColumns() as $column_name => $column) { 372 $expect_column = null; 373 if ($expect_table) { 374 $expect_column = $expect_table->getColumn($column_name); 375 } 376 377 $status = $column->getStatus(); 378 379 $data_type = null; 380 if ($expect_column) { 381 $data_type = $expect_column->getDataType(); 382 } 383 384 $uri = $this->getURI( 385 array( 386 'column' => $column_name, 387 )); 388 389 $rows[] = array( 390 $this->renderIcon($status), 391 phutil_tag( 392 'a', 393 array( 394 'href' => $uri, 395 ), 396 $column_name), 397 $data_type, 398 $this->renderAttr( 399 $column->getColumnType(), 400 $column->hasIssue($type_issue)), 401 $this->renderAttr( 402 $this->renderBoolean($column->getNullable()), 403 $column->hasIssue($nullable_issue)), 404 $this->renderAttr( 405 $this->renderBoolean($column->getAutoIncrement()), 406 $column->hasIssue($auto_issue)), 407 $this->renderAttr( 408 $column->getCharacterSet(), 409 $column->hasIssue($charset_issue)), 410 $this->renderAttr( 411 $column->getCollation(), 412 $column->hasIssue($collation_issue)), 413 ); 414 } 415 416 $table_view = id(new AphrontTableView($rows)) 417 ->setHeaders( 418 array( 419 null, 420 pht('Column'), 421 pht('Data Type'), 422 pht('Column Type'), 423 pht('Nullable'), 424 pht('Autoincrement'), 425 pht('Character Set'), 426 pht('Collation'), 427 )) 428 ->setColumnClasses( 429 array( 430 null, 431 'wide pri', 432 null, 433 null, 434 null, 435 null, 436 null, 437 )); 438 439 $key_rows = array(); 440 foreach ($table->getKeys() as $key_name => $key) { 441 $expect_key = null; 442 if ($expect_table) { 443 $expect_key = $expect_table->getKey($key_name); 444 } 445 446 $status = $key->getStatus(); 447 448 $size = 0; 449 foreach ($key->getColumnNames() as $column_spec) { 450 list($column_name, $prefix) = $key->getKeyColumnAndPrefix($column_spec); 451 $column = $table->getColumn($column_name); 452 if (!$column) { 453 $size = 0; 454 break; 455 } 456 $size += $column->getKeyByteLength($prefix); 457 } 458 459 $size_formatted = null; 460 if ($size) { 461 $size_formatted = $this->renderAttr( 462 $size, 463 $key->hasIssue($longkey_issue)); 464 } 465 466 $uri = $this->getURI( 467 array( 468 'key' => $key_name, 469 )); 470 471 $key_rows[] = array( 472 $this->renderIcon($status), 473 phutil_tag( 474 'a', 475 array( 476 'href' => $uri, 477 ), 478 $key_name), 479 $this->renderAttr( 480 implode(', ', $key->getColumnNames()), 481 $key->hasIssue($columns_issue)), 482 $this->renderAttr( 483 $this->renderBoolean($key->getUnique()), 484 $key->hasIssue($unique_issue)), 485 $size_formatted, 486 ); 487 } 488 489 $keys_view = id(new AphrontTableView($key_rows)) 490 ->setHeaders( 491 array( 492 null, 493 pht('Key'), 494 pht('Columns'), 495 pht('Unique'), 496 pht('Size'), 497 )) 498 ->setColumnClasses( 499 array( 500 null, 501 'wide pri', 502 null, 503 null, 504 null, 505 )); 506 507 $title = pht('%s.%s', $database_name, $table_name); 508 509 if ($actual_table) { 510 $actual_collation = $actual_table->getCollation(); 511 } else { 512 $actual_collation = null; 513 } 514 515 if ($expect_table) { 516 $expect_collation = $expect_table->getCollation(); 517 } else { 518 $expect_collation = null; 519 } 520 521 $properties = $this->buildProperties( 522 array( 523 array( 524 pht('Server'), 525 $this->ref, 526 ), 527 array( 528 pht('Collation'), 529 $actual_collation, 530 ), 531 array( 532 pht('Expected Collation'), 533 $expect_collation, 534 ), 535 ), 536 $table->getIssues()); 537 538 $box_header = pht('%s.%s', $database_name, $table_name); 539 540 $properties = $this->buildConfigBoxView(pht('Properties'), $properties); 541 $table = $this->buildConfigBoxView(pht('Database'), $table_view); 542 $keys = $this->buildConfigBoxView(pht('Keys'), $keys_view); 543 544 return $this->buildResponse( 545 $title, array($properties, $table, $keys)); 546 } 547 548 private function renderColumn( 549 PhabricatorConfigServerSchema $comp, 550 PhabricatorConfigServerSchema $expect, 551 PhabricatorConfigServerSchema $actual, 552 $database_name, 553 $table_name, 554 $column_name) { 555 556 $database = $comp->getDatabase($database_name); 557 if (!$database) { 558 return new Aphront404Response(); 559 } 560 561 $table = $database->getTable($table_name); 562 if (!$table) { 563 return new Aphront404Response(); 564 } 565 566 $column = $table->getColumn($column_name); 567 if (!$column) { 568 return new Aphront404Response(); 569 } 570 571 $actual_database = $actual->getDatabase($database_name); 572 $actual_table = null; 573 $actual_column = null; 574 if ($actual_database) { 575 $actual_table = $actual_database->getTable($table_name); 576 if ($actual_table) { 577 $actual_column = $actual_table->getColumn($column_name); 578 } 579 } 580 581 $expect_database = $expect->getDatabase($database_name); 582 $expect_table = null; 583 $expect_column = null; 584 if ($expect_database) { 585 $expect_table = $expect_database->getTable($table_name); 586 if ($expect_table) { 587 $expect_column = $expect_table->getColumn($column_name); 588 } 589 } 590 591 if ($actual_column) { 592 $actual_coltype = $actual_column->getColumnType(); 593 $actual_charset = $actual_column->getCharacterSet(); 594 $actual_collation = $actual_column->getCollation(); 595 $actual_nullable = $actual_column->getNullable(); 596 $actual_auto = $actual_column->getAutoIncrement(); 597 } else { 598 $actual_coltype = null; 599 $actual_charset = null; 600 $actual_collation = null; 601 $actual_nullable = null; 602 $actual_auto = null; 603 } 604 605 if ($expect_column) { 606 $data_type = $expect_column->getDataType(); 607 $expect_coltype = $expect_column->getColumnType(); 608 $expect_charset = $expect_column->getCharacterSet(); 609 $expect_collation = $expect_column->getCollation(); 610 $expect_nullable = $expect_column->getNullable(); 611 $expect_auto = $expect_column->getAutoIncrement(); 612 } else { 613 $data_type = null; 614 $expect_coltype = null; 615 $expect_charset = null; 616 $expect_collation = null; 617 $expect_nullable = null; 618 $expect_auto = null; 619 } 620 621 622 $title = pht( 623 '%s.%s.%s', 624 $database_name, 625 $table_name, 626 $column_name); 627 628 $properties = $this->buildProperties( 629 array( 630 array( 631 pht('Server'), 632 $this->ref, 633 ), 634 array( 635 pht('Data Type'), 636 $data_type, 637 ), 638 array( 639 pht('Column Type'), 640 $actual_coltype, 641 ), 642 array( 643 pht('Expected Column Type'), 644 $expect_coltype, 645 ), 646 array( 647 pht('Character Set'), 648 $actual_charset, 649 ), 650 array( 651 pht('Expected Character Set'), 652 $expect_charset, 653 ), 654 array( 655 pht('Collation'), 656 $actual_collation, 657 ), 658 array( 659 pht('Expected Collation'), 660 $expect_collation, 661 ), 662 array( 663 pht('Nullable'), 664 $this->renderBoolean($actual_nullable), 665 ), 666 array( 667 pht('Expected Nullable'), 668 $this->renderBoolean($expect_nullable), 669 ), 670 array( 671 pht('Autoincrement'), 672 $this->renderBoolean($actual_auto), 673 ), 674 array( 675 pht('Expected Autoincrement'), 676 $this->renderBoolean($expect_auto), 677 ), 678 ), 679 $column->getIssues()); 680 681 $properties = $this->buildConfigBoxView(pht('Properties'), $properties); 682 683 return $this->buildResponse($title, $properties); 684 } 685 686 private function renderKey( 687 PhabricatorConfigServerSchema $comp, 688 PhabricatorConfigServerSchema $expect, 689 PhabricatorConfigServerSchema $actual, 690 $database_name, 691 $table_name, 692 $key_name) { 693 694 $database = $comp->getDatabase($database_name); 695 if (!$database) { 696 return new Aphront404Response(); 697 } 698 699 $table = $database->getTable($table_name); 700 if (!$table) { 701 return new Aphront404Response(); 702 } 703 704 $key = $table->getKey($key_name); 705 if (!$key) { 706 return new Aphront404Response(); 707 } 708 709 $actual_database = $actual->getDatabase($database_name); 710 $actual_table = null; 711 $actual_key = null; 712 if ($actual_database) { 713 $actual_table = $actual_database->getTable($table_name); 714 if ($actual_table) { 715 $actual_key = $actual_table->getKey($key_name); 716 } 717 } 718 719 $expect_database = $expect->getDatabase($database_name); 720 $expect_table = null; 721 $expect_key = null; 722 if ($expect_database) { 723 $expect_table = $expect_database->getTable($table_name); 724 if ($expect_table) { 725 $expect_key = $expect_table->getKey($key_name); 726 } 727 } 728 729 if ($actual_key) { 730 $actual_columns = $actual_key->getColumnNames(); 731 $actual_unique = $actual_key->getUnique(); 732 } else { 733 $actual_columns = array(); 734 $actual_unique = null; 735 } 736 737 if ($expect_key) { 738 $expect_columns = $expect_key->getColumnNames(); 739 $expect_unique = $expect_key->getUnique(); 740 } else { 741 $expect_columns = array(); 742 $expect_unique = null; 743 } 744 745 $title = pht( 746 '%s.%s (%s)', 747 $database_name, 748 $table_name, 749 $key_name); 750 751 $properties = $this->buildProperties( 752 array( 753 array( 754 pht('Server'), 755 $this->ref, 756 ), 757 array( 758 pht('Unique'), 759 $this->renderBoolean($actual_unique), 760 ), 761 array( 762 pht('Expected Unique'), 763 $this->renderBoolean($expect_unique), 764 ), 765 array( 766 pht('Columns'), 767 implode(', ', $actual_columns), 768 ), 769 array( 770 pht('Expected Columns'), 771 implode(', ', $expect_columns), 772 ), 773 ), 774 $key->getIssues()); 775 776 $properties = $this->buildConfigBoxView(pht('Properties'), $properties); 777 778 return $this->buildResponse($title, $properties); 779 } 780 781 private function buildProperties(array $properties, array $issues) { 782 $view = id(new PHUIPropertyListView()) 783 ->setUser($this->getRequest()->getUser()); 784 785 foreach ($properties as $property) { 786 list($key, $value) = $property; 787 $view->addProperty($key, $value); 788 } 789 790 $status_view = new PHUIStatusListView(); 791 if (!$issues) { 792 $status_view->addItem( 793 id(new PHUIStatusItemView()) 794 ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 795 ->setTarget(pht('No Schema Issues'))); 796 } else { 797 foreach ($issues as $issue) { 798 $note = PhabricatorConfigStorageSchema::getIssueDescription($issue); 799 800 $status = PhabricatorConfigStorageSchema::getIssueStatus($issue); 801 switch ($status) { 802 case PhabricatorConfigStorageSchema::STATUS_WARN: 803 $icon = PHUIStatusItemView::ICON_WARNING; 804 $color = 'yellow'; 805 break; 806 case PhabricatorConfigStorageSchema::STATUS_FAIL: 807 default: 808 $icon = PHUIStatusItemView::ICON_REJECT; 809 $color = 'red'; 810 break; 811 } 812 813 $item = id(new PHUIStatusItemView()) 814 ->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue)) 815 ->setIcon($icon, $color) 816 ->setNote($note); 817 818 $status_view->addItem($item); 819 } 820 } 821 $view->addProperty(pht('Schema Status'), $status_view); 822 823 return phutil_tag_div('config-page-property', $view); 824 } 825 826 private function getURI(array $properties) { 827 $defaults = array( 828 'ref' => $this->ref, 829 'database' => $this->database, 830 'table' => $this->table, 831 'column' => $this->column, 832 'key' => $this->key, 833 ); 834 835 $properties = $properties + $defaults; 836 $properties = array_select_keys($properties, array_keys($defaults)); 837 838 $parts = array(); 839 foreach ($properties as $key => $property) { 840 if (!phutil_nonempty_string($property)) { 841 continue; 842 } 843 844 if ($key == 'column') { 845 $parts[] = 'col'; 846 } else if ($key == 'key') { 847 $parts[] = 'key'; 848 } 849 850 $parts[] = $property; 851 } 852 853 if ($parts) { 854 $parts = implode('/', $parts).'/'; 855 } else { 856 $parts = null; 857 } 858 859 return $this->getApplicationURI('/database/'.$parts); 860 } 861 862}