1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-28 01:32:42 +01:00

(stable) Promote 2016 Week 34

This commit is contained in:
epriestley 2016-08-20 16:01:39 -07:00
commit b88348a55e
51 changed files with 1451 additions and 432 deletions

View file

@ -7,7 +7,7 @@
*/
return array(
'names' => array(
'core.pkg.css' => '340c5b75',
'core.pkg.css' => 'ed7ae7bb',
'core.pkg.js' => 'b562c3db',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '3fb7f532',
@ -70,6 +70,7 @@ return array(
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2',
'rsrc/css/application/flag/flag.css' => '5337623f',
'rsrc/css/application/guides/guides.css' => '1d5414e5',
'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4',
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
'rsrc/css/application/herald/herald.css' => 'dc31f6e9',
@ -112,7 +113,7 @@ return array(
'rsrc/css/font/font-aleo.css' => '8bdb2835',
'rsrc/css/font/font-awesome.css' => '2b7ebbcc',
'rsrc/css/font/font-lato.css' => 'c7ccd872',
'rsrc/css/font/phui-font-icon-base.css' => '6449bce8',
'rsrc/css/font/phui-font-icon-base.css' => '4e8274c4',
'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82',
'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983',
'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893',
@ -127,6 +128,7 @@ return array(
'rsrc/css/phui/phui-box.css' => '5c8387cf',
'rsrc/css/phui/phui-button.css' => '4a5fbe3d',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-cms.css' => 'be43c8a8',
'rsrc/css/phui/phui-crumbs-view.css' => '9dac418c',
'rsrc/css/phui/phui-curtain-view.css' => '7148ae25',
'rsrc/css/phui/phui-document-pro.css' => 'dc3d46ed',
@ -134,19 +136,19 @@ return array(
'rsrc/css/phui/phui-document.css' => 'c32e8dec',
'rsrc/css/phui/phui-feed-story.css' => 'aa49845d',
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
'rsrc/css/phui/phui-form-view.css' => 'fab0a10f',
'rsrc/css/phui/phui-form-view.css' => '76b4a46c',
'rsrc/css/phui/phui-form.css' => 'aac1d51d',
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
'rsrc/css/phui/phui-header-view.css' => '06385974',
'rsrc/css/phui/phui-hovercard.css' => 'de1a2119',
'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad',
'rsrc/css/phui/phui-icon.css' => 'd0534b71',
'rsrc/css/phui/phui-icon.css' => 'b1dbd620',
'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c',
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
'rsrc/css/phui/phui-info-view.css' => '28efab79',
'rsrc/css/phui/phui-list.css' => '9da2aa00',
'rsrc/css/phui/phui-object-box.css' => '6b487c57',
'rsrc/css/phui/phui-object-item-list-view.css' => '8d99e42b',
'rsrc/css/phui/phui-object-item-list-view.css' => '40010767',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-profile-menu.css' => '8a3fc181',
@ -422,7 +424,7 @@ return array(
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
'rsrc/js/application/projects/WorkboardBoard.js' => '52291776',
'rsrc/js/application/projects/WorkboardBoard.js' => 'fe7cb52a',
'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f',
'rsrc/js/application/projects/WorkboardColumn.js' => 'bae58312',
'rsrc/js/application/projects/WorkboardController.js' => '55baf5ed',
@ -572,6 +574,7 @@ return array(
'font-fontawesome' => '2b7ebbcc',
'font-lato' => 'c7ccd872',
'global-drag-and-drop-css' => '5c1b47c2',
'guides-app-css' => '1d5414e5',
'harbormaster-css' => 'f491c9f4',
'herald-css' => 'dc31f6e9',
'herald-rule-editor' => 'd6a7e717',
@ -750,7 +753,7 @@ return array(
'javelin-view-renderer' => '6c2b09a2',
'javelin-view-visitor' => 'efe49472',
'javelin-websocket' => 'e292eaf4',
'javelin-workboard-board' => '52291776',
'javelin-workboard-board' => 'fe7cb52a',
'javelin-workboard-card' => 'c587b80f',
'javelin-workboard-column' => 'bae58312',
'javelin-workboard-controller' => '55baf5ed',
@ -832,29 +835,30 @@ return array(
'phui-calendar-list-css' => 'fcc9fb41',
'phui-calendar-month-css' => '8e10e92c',
'phui-chart-css' => '6bf6f78e',
'phui-cms-css' => 'be43c8a8',
'phui-crumbs-view-css' => '9dac418c',
'phui-curtain-view-css' => '7148ae25',
'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => 'c32e8dec',
'phui-document-view-pro-css' => 'dc3d46ed',
'phui-feed-story-css' => 'aa49845d',
'phui-font-icon-base-css' => '6449bce8',
'phui-font-icon-base-css' => '4e8274c4',
'phui-fontkit-css' => '9cda225e',
'phui-form-css' => 'aac1d51d',
'phui-form-view-css' => 'fab0a10f',
'phui-form-view-css' => '76b4a46c',
'phui-head-thing-view-css' => 'fd311e5f',
'phui-header-view-css' => '06385974',
'phui-hovercard' => '1bd28176',
'phui-hovercard-view-css' => 'de1a2119',
'phui-icon-set-selector-css' => '1ab67aad',
'phui-icon-view-css' => 'd0534b71',
'phui-icon-view-css' => 'b1dbd620',
'phui-image-mask-css' => 'a8498f9c',
'phui-info-panel-css' => '27ea50a1',
'phui-info-view-css' => '28efab79',
'phui-inline-comment-view-css' => '5953c28e',
'phui-list-view-css' => '9da2aa00',
'phui-object-box-css' => '6b487c57',
'phui-object-item-list-view-css' => '8d99e42b',
'phui-object-item-list-view-css' => '40010767',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
'phui-profile-menu-css' => '8a3fc181',
@ -1297,15 +1301,6 @@ return array(
'javelin-vector',
'javelin-typeahead-static-source',
),
52291776 => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
),
'5359e785' => array(
'javelin-install',
'javelin-util',
@ -2219,6 +2214,15 @@ return array(
'javelin-view-visitor',
'javelin-util',
),
'fe7cb52a' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
),
'fea0eb47' => array(
'javelin-install',
),

View file

@ -132,7 +132,6 @@ phutil_register_library_map(array(
'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php',
'AphrontBarView' => 'view/widget/bars/AphrontBarView.php',
'AphrontBoolHTTPParameterType' => 'aphront/httpparametertype/AphrontBoolHTTPParameterType.php',
'AphrontCSRFException' => 'aphront/exception/AphrontCSRFException.php',
'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php',
'AphrontController' => 'aphront/AphrontController.php',
'AphrontCursorPagerView' => 'view/control/AphrontCursorPagerView.php',
@ -1598,6 +1597,7 @@ phutil_register_library_map(array(
'PHUIButtonBarView' => 'view/phui/PHUIButtonBarView.php',
'PHUIButtonExample' => 'applications/uiexample/examples/PHUIButtonExample.php',
'PHUIButtonView' => 'view/phui/PHUIButtonView.php',
'PHUICMSView' => 'view/phui/PHUICMSView.php',
'PHUICalendarDayView' => 'view/phui/calendar/PHUICalendarDayView.php',
'PHUICalendarListView' => 'view/phui/calendar/PHUICalendarListView.php',
'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php',
@ -2630,6 +2630,13 @@ phutil_register_library_map(array(
'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php',
'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php',
'PhabricatorGoogleAuthProvider' => 'applications/auth/provider/PhabricatorGoogleAuthProvider.php',
'PhabricatorGuideApplication' => 'applications/guides/application/PhabricatorGuideApplication.php',
'PhabricatorGuideController' => 'applications/guides/controller/PhabricatorGuideController.php',
'PhabricatorGuideInstallController' => 'applications/guides/controller/PhabricatorGuideInstallController.php',
'PhabricatorGuideItemView' => 'applications/guides/view/PhabricatorGuideItemView.php',
'PhabricatorGuideListView' => 'applications/guides/view/PhabricatorGuideListView.php',
'PhabricatorGuideQuickStartController' => 'applications/guides/controller/PhabricatorGuideQuickStartController.php',
'PhabricatorGuideWelcomeController' => 'applications/guides/controller/PhabricatorGuideWelcomeController.php',
'PhabricatorHTTPParameterTypeTableView' => 'applications/config/view/PhabricatorHTTPParameterTypeTableView.php',
'PhabricatorHandleList' => 'applications/phid/handle/pool/PhabricatorHandleList.php',
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php',
@ -4566,7 +4573,6 @@ phutil_register_library_map(array(
'AphrontApplicationConfiguration' => 'Phobject',
'AphrontBarView' => 'AphrontView',
'AphrontBoolHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontCSRFException' => 'AphrontException',
'AphrontCalendarEventView' => 'AphrontView',
'AphrontController' => 'Phobject',
'AphrontCursorPagerView' => 'AphrontView',
@ -6245,6 +6251,7 @@ phutil_register_library_map(array(
'PHUIButtonBarView' => 'AphrontTagView',
'PHUIButtonExample' => 'PhabricatorUIExample',
'PHUIButtonView' => 'AphrontTagView',
'PHUICMSView' => 'AphrontTagView',
'PHUICalendarDayView' => 'AphrontView',
'PHUICalendarListView' => 'AphrontTagView',
'PHUICalendarMonthView' => 'AphrontView',
@ -7431,6 +7438,13 @@ phutil_register_library_map(array(
'PhabricatorGlobalLock' => 'PhutilLock',
'PhabricatorGlobalUploadTargetView' => 'AphrontView',
'PhabricatorGoogleAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorGuideApplication' => 'PhabricatorApplication',
'PhabricatorGuideController' => 'PhabricatorController',
'PhabricatorGuideInstallController' => 'PhabricatorGuideController',
'PhabricatorGuideItemView' => 'Phobject',
'PhabricatorGuideListView' => 'AphrontView',
'PhabricatorGuideQuickStartController' => 'PhabricatorGuideController',
'PhabricatorGuideWelcomeController' => 'PhabricatorGuideController',
'PhabricatorHTTPParameterTypeTableView' => 'AphrontView',
'PhabricatorHandleList' => array(
'Phobject',

View file

@ -261,25 +261,30 @@ final class AphrontRequest extends Phobject {
// Add some diagnostic details so we can figure out if some CSRF issues
// are JS problems or people accessing Ajax URIs directly with their
// browsers.
$more_info = array();
$info = array();
$info[] = pht(
'You are trying to save some data to Phabricator, but the request '.
'your browser made included an incorrect token. Reload the page '.
'and try again. You may need to clear your cookies.');
if ($this->isAjax()) {
$more_info[] = pht('This was an Ajax request.');
$info[] = pht('This was an Ajax request.');
} else {
$more_info[] = pht('This was a Web request.');
$info[] = pht('This was a Web request.');
}
if ($token) {
$more_info[] = pht('This request had an invalid CSRF token.');
$info[] = pht('This request had an invalid CSRF token.');
} else {
$more_info[] = pht('This request had no CSRF token.');
$info[] = pht('This request had no CSRF token.');
}
// Give a more detailed explanation of how to avoid the exception
// in developer mode.
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
// TODO: Clean this up, see T1921.
$more_info[] = pht(
$info[] = pht(
"To avoid this error, use %s to construct forms. If you are already ".
"using %s, make sure the form 'action' uses a relative URI (i.e., ".
"begins with a '%s'). Forms using absolute URIs do not include CSRF ".
@ -299,16 +304,16 @@ final class AphrontRequest extends Phobject {
'setRenderAsForm(true)');
}
$message = implode("\n", $info);
// This should only be able to happen if you load a form, pull your
// internet for 6 hours, and then reconnect and immediately submit,
// but give the user some indication of what happened since the workflow
// is incredibly confusing otherwise.
throw new AphrontCSRFException(
pht(
'You are trying to save some data to Phabricator, but the request '.
'your browser made included an incorrect token. Reload the page '.
'and try again. You may need to clear your cookies.')."\n\n".
implode("\n", $more_info));
throw new AphrontMalformedRequestException(
pht('Invalid Request (CSRF)'),
$message,
true);
}
return true;
@ -480,7 +485,8 @@ final class AphrontRequest extends Phobject {
$configured_as = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$accessed_as = $this->getHost();
throw new Exception(
throw new AphrontMalformedRequestException(
pht('Bad Host Header'),
pht(
'This Phabricator install is configured as "%s", but you are '.
'using the domain name "%s" to access a page which is trying to '.
@ -488,7 +494,8 @@ final class AphrontRequest extends Phobject {
'domain or a configured alternate domain. Phabricator will not '.
'set cookies on other domains for security reasons.',
$configured_as,
$accessed_as));
$accessed_as),
true);
}
$base_domain = $base_domain_uri->getDomain();

View file

@ -1,3 +0,0 @@
<?php
final class AphrontCSRFException extends AphrontException {}

View file

@ -28,8 +28,18 @@ final class PhabricatorDefaultRequestExceptionHandler
$viewer = $this->getViewer($request);
// Always log the unhandled exception.
phlog($ex);
// Some types of uninteresting request exceptions don't get logged, usually
// because they are caused by the background radiation of bot traffic on
// the internet. These include requests with bad CSRF tokens and
// questionable "Host" headers.
$should_log = true;
if ($ex instanceof AphrontMalformedRequestException) {
$should_log = !$ex->getIsUnlogged();
}
if ($should_log) {
phlog($ex);
}
$class = get_class($ex);
$message = $ex->getMessage();

View file

@ -16,7 +16,7 @@ final class AlmanacConsoleController extends AlmanacController {
id(new PHUIObjectItemView())
->setHeader(pht('Devices'))
->setHref($this->getApplicationURI('device/'))
->setIcon('fa-server')
->setImageIcon('fa-server')
->addAttribute(
pht(
'Create an inventory of physical and virtual hosts and '.
@ -26,7 +26,7 @@ final class AlmanacConsoleController extends AlmanacController {
id(new PHUIObjectItemView())
->setHeader(pht('Services'))
->setHref($this->getApplicationURI('service/'))
->setIcon('fa-plug')
->setImageIcon('fa-plug')
->addAttribute(
pht(
'Create and update services, and map them to interfaces on '.
@ -36,7 +36,7 @@ final class AlmanacConsoleController extends AlmanacController {
id(new PHUIObjectItemView())
->setHeader(pht('Networks'))
->setHref($this->getApplicationURI('network/'))
->setIcon('fa-globe')
->setImageIcon('fa-globe')
->addAttribute(
pht(
'Manage public and private networks.')));
@ -45,7 +45,7 @@ final class AlmanacConsoleController extends AlmanacController {
id(new PHUIObjectItemView())
->setHeader(pht('Namespaces'))
->setHref($this->getApplicationURI('namespace/'))
->setIcon('fa-asterisk')
->setImageIcon('fa-asterisk')
->addAttribute(
pht('Control who can create new named services and devices.')));
@ -56,7 +56,7 @@ final class AlmanacConsoleController extends AlmanacController {
id(new PHUIObjectItemView())
->setHeader(pht('Documentation'))
->setHref($docs_uri)
->setIcon('fa-book')
->setImageIcon('fa-book')
->addAttribute(pht('Browse documentation for Almanac.')));
$crumbs = $this->buildApplicationCrumbs();

View file

@ -53,7 +53,7 @@ final class PhabricatorAuthListController
}
if ($config->getIsEnabled()) {
$item->setState(PHUIObjectItemView::STATE_SUCCESS);
$item->setStatusIcon('fa-check-circle green');
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
@ -61,8 +61,8 @@ final class PhabricatorAuthListController
->setDisabled(!$can_manage)
->addSigil('workflow'));
} else {
$item->setState(PHUIObjectItemView::STATE_FAIL);
$item->addIcon('fa-times grey', pht('Disabled'));
$item->setStatusIcon('fa-ban red');
$item->addIcon('fa-ban grey', pht('Disabled'));
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-plus')

View file

@ -464,12 +464,14 @@ abstract class PhabricatorAuthProvider extends Phobject {
public function getAuthCSRFCode(AphrontRequest $request) {
$phcid = $request->getCookie(PhabricatorCookies::COOKIE_CLIENTID);
if (!strlen($phcid)) {
throw new Exception(
throw new AphrontMalformedRequestException(
pht('Missing Client ID Cookie'),
pht(
'Your browser did not submit a "%s" cookie with client state '.
'information in the request. Check that cookies are enabled. '.
'If this problem persists, you may need to clear your cookies.',
PhabricatorCookies::COOKIE_CLIENTID));
PhabricatorCookies::COOKIE_CLIENTID),
true);
}
return PhabricatorHash::digest($phcid);

View file

@ -3,6 +3,17 @@
abstract class ConduitListParameterType
extends ConduitParameterType {
private $allowEmptyList = true;
public function setAllowEmptyList($allow_empty_list) {
$this->allowEmptyList = $allow_empty_list;
return $this;
}
public function getAllowEmptyList() {
return $this->allowEmptyList;
}
protected function getParameterValue(array $request, $key) {
$value = parent::getParameterValue($request, $key);
@ -27,6 +38,13 @@ abstract class ConduitListParameterType
pht('Expected a list, but value is an object.'));
}
if (!$value && !$this->getAllowEmptyList()) {
$this->raiseValidationException(
$request,
$key,
pht('Expected a nonempty list, but value is an empty list.'));
}
return $value;
}

View file

@ -61,7 +61,11 @@ abstract class ConduitParameterType extends Phobject {
protected function raiseValidationException(array $request, $key, $message) {
// TODO: Specialize this so we can give users more tailored messages from
// Conduit.
throw new Exception($message);
throw new Exception(
pht(
'Error while reading "%s": %s',
$key,
$message));
}

View file

@ -51,7 +51,7 @@ final class PhabricatorConfigListController
->setHeader($group->getName())
->setHref('/config/group/'.$group->getKey().'/')
->addAttribute($group->getDescription())
->setIcon($group->getIcon());
->setImageIcon($group->getIcon());
$list->addItem($item);
}
}

View file

@ -234,7 +234,7 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject {
if (isset($options[$key])) {
throw new Exception(
pht(
"Mulitple %s subclasses contain an option named '%s'!",
"Multiple %s subclasses contain an option named '%s'!",
__CLASS__,
$key));
}

View file

@ -1533,7 +1533,7 @@ final class DifferentialTransactionEditor
foreach ($packages as $key => $package) {
$package_phid = $package->getPHID();
if ($authority[$package_phid]) {
if (isset($authority[$package_phid])) {
unset($packages[$key]);
continue;
}

View file

@ -7,7 +7,6 @@ final class HeraldCommitAdapter
protected $diff;
protected $revision;
protected $repository;
protected $commit;
protected $commitData;
private $commitDiff;
@ -42,6 +41,7 @@ final class HeraldCommitAdapter
public function setObject($object) {
$this->commit = $object;
return $this;
}
@ -86,41 +86,26 @@ final class HeraldCommitAdapter
}
public function getTriggerObjectPHIDs() {
$project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
return array_merge(
array(
$this->repository->getPHID(),
$this->getRepository()->getPHID(),
$this->getPHID(),
),
$this->repository->getProjectPHIDs());
$this->loadEdgePHIDs($project_type));
}
public function explainValidTriggerObjects() {
return pht('This rule can trigger for **repositories** and **projects**.');
}
public static function newLegacyAdapter(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $commit_data) {
$object = new HeraldCommitAdapter();
$commit->attachRepository($repository);
$object->repository = $repository;
$object->commit = $commit;
$object->commitData = $commit_data;
return $object;
}
public function setCommit(PhabricatorRepositoryCommit $commit) {
$viewer = PhabricatorUser::getOmnipotentUser();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withIDs(array($commit->getRepositoryID()))
->needProjectPHIDs(true)
->executeOne();
if (!$repository) {
throw new Exception(pht('Unable to load repository!'));
@ -137,7 +122,6 @@ final class HeraldCommitAdapter
$this->commit->attachRepository($repository);
$this->commit->attachCommitData($data);
$this->repository = $repository;
$this->commitData = $data;
return $this;
@ -150,7 +134,7 @@ final class HeraldCommitAdapter
public function loadAffectedPaths() {
if ($this->affectedPaths === null) {
$result = PhabricatorOwnerPathQuery::loadAffectedPaths(
$this->repository,
$this->getRepository(),
$this->commit,
PhabricatorUser::getOmnipotentUser());
$this->affectedPaths = $result;
@ -161,7 +145,7 @@ final class HeraldCommitAdapter
public function loadAffectedPackages() {
if ($this->affectedPackages === null) {
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
$this->repository,
$this->getRepository(),
$this->loadAffectedPaths());
$this->affectedPackages = $packages;
}
@ -314,7 +298,7 @@ final class HeraldCommitAdapter
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $viewer,
'repository' => $this->repository,
'repository' => $this->getRepository(),
'commit' => $this->commit->getCommitIdentifier(),
));
@ -325,6 +309,10 @@ final class HeraldCommitAdapter
$params);
}
private function getRepository() {
return $this->getObject()->getRepository();
}
/* -( HarbormasterBuildableAdapterInterface )------------------------------ */

View file

@ -31,7 +31,7 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Blueprints'))
->setIcon('fa-map-o')
->setImageIcon('fa-map-o')
->setHref($this->getApplicationURI('blueprint/'))
->addAttribute(
pht(
@ -41,7 +41,7 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Resources'))
->setIcon('fa-map')
->setImageIcon('fa-map')
->setHref($this->getApplicationURI('resource/'))
->addAttribute(
pht('View and manage resources Drydock has built, like hosts.')));
@ -49,14 +49,14 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Leases'))
->setIcon('fa-link')
->setImageIcon('fa-link')
->setHref($this->getApplicationURI('lease/'))
->addAttribute(pht('Manage leases on resources.')));
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Repository Operations'))
->setIcon('fa-fighter-jet')
->setImageIcon('fa-fighter-jet')
->setHref($this->getApplicationURI('operation/'))
->addAttribute(pht('Review the repository operation queue.')));

View file

@ -0,0 +1,41 @@
<?php
final class PhabricatorGuideApplication extends PhabricatorApplication {
public function getBaseURI() {
return '/guides/';
}
public function getName() {
return pht('Guides');
}
public function getShortDescription() {
return pht('Short Tutorials');
}
public function getIcon() {
return 'fa-map-o';
}
public function isPrototype() {
return true;
}
public function getApplicationGroup() {
return self::GROUP_UTILITIES;
}
public function getRoutes() {
return array(
'/guides/' => array(
'' => 'PhabricatorGuideWelcomeController',
'install/'
=> 'PhabricatorGuideInstallController',
'quickstart/'
=> 'PhabricatorGuideQuickStartController',
),
);
}
}

View file

@ -0,0 +1,21 @@
<?php
abstract class PhabricatorGuideController extends PhabricatorController {
public function buildSideNavView($filter = null, $for_app = false) {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$nav->addLabel(pht('Guides'));
$nav->addFilter('/', pht('Welcome'));
$nav->addFilter('install/', pht('Installation Guide'));
$nav->addFilter('quickstart/', pht('Quick Start Guide'));
return $nav;
}
public function buildApplicationMenu() {
return $this->buildSideNavView(null, true)->getMenu();
}
}

View file

@ -0,0 +1,196 @@
<?php
final class PhabricatorGuideInstallController
extends PhabricatorGuideController {
public function shouldAllowPublic() {
return false;
}
public function handleRequest(AphrontRequest $request) {
require_celerity_resource('guides-app-css');
$viewer = $request->getViewer();
$title = pht('Installation Guide');
$nav = $this->buildSideNavView();
$nav->selectFilter('install/');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setProfileHeader(true);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Installation'));
$content = $this->getGuideContent($viewer);
$view = id(new PHUICMSView())
->setCrumbs($crumbs)
->setNavigation($nav)
->setHeader($header)
->setContent($content);
return $this->newPage()
->setTitle($title)
->addClass('phui-cms-body')
->appendChild($view);
}
private function getGuideContent($viewer) {
$guide_items = new PhabricatorGuideListView();
$title = pht('Resolve Setup Issues');
$issues_resolved = !PhabricatorSetupCheck::getOpenSetupIssueKeys();
$href = PhabricatorEnv::getURI('/config/issue/');
if ($issues_resolved) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've resolved (or ignored) all outstanding setup issues.");
} else {
$icon = 'fa-warning';
$icon_bg = 'bg-red';
$skip = '#';
$description =
pht('You have some unresolved setup issues to take care of.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->execute();
$title = pht('Login and Registration');
$href = PhabricatorEnv::getURI('/auth/');
$have_auth = (bool)$configs;
if ($have_auth) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've configured at least one authentication provider.");
} else {
$icon = 'fa-key';
$icon_bg = 'bg-sky';
$skip = '#';
$description = pht(
'Authentication providers allow users to register accounts and '.
'log in to Phabricator.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Configure Phabricator');
$href = PhabricatorEnv::getURI('/config/');
// Just load any config value at all; if one exists the install has figured
// out how to configure things.
$have_config = (bool)id(new PhabricatorConfigEntry())->loadAllWhere(
'1 = 1 LIMIT 1');
if ($have_config) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've configured at least one setting from the web interface.");
} else {
$icon = 'fa-sliders';
$icon_bg = 'bg-sky';
$skip = '#';
$description = pht(
'Learn how to configure mail and other options in Phabricator.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('User Account Settings');
$href = PhabricatorEnv::getURI('/settings/');
$preferences = id(new PhabricatorUserPreferencesQuery())
->setViewer($viewer)
->withUsers(array($viewer))
->executeOne();
$have_settings = ($preferences && $preferences->getPreferences());
if ($have_settings) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've adjusted at least one setting on your account.");
} else {
$icon = 'fa-wrench';
$icon_bg = 'bg-sky';
$skip = '#';
$description = pht(
'Configure account settings for all users, or just yourself');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Notification Server');
$href = PhabricatorEnv::getURI('/config/notifications/');
// TODO: Wire up a notifications check
$have_notifications = false;
if ($have_notifications) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've set up a real-time notification server.");
} else {
$icon = 'fa-bell';
$icon_bg = 'bg-sky';
$skip = '#';
$description = pht(
'Phabricator can deliver notifications in real-time with WebSockets.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
return $guide_items;
}
}

View file

@ -0,0 +1,210 @@
<?php
final class PhabricatorGuideQuickStartController
extends PhabricatorGuideController {
public function shouldAllowPublic() {
return false;
}
public function handleRequest(AphrontRequest $request) {
require_celerity_resource('guides-app-css');
$viewer = $request->getViewer();
$title = pht('Quick Start Guide');
$nav = $this->buildSideNavView();
$nav->selectFilter('quickstart/');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setProfileHeader(true);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Quick Start'));
$content = $this->getGuideContent($viewer);
$view = id(new PHUICMSView())
->setCrumbs($crumbs)
->setNavigation($nav)
->setHeader($header)
->setContent($content);
return $this->newPage()
->setTitle($title)
->addClass('phui-cms-body')
->appendChild($view);
}
private function getGuideContent($viewer) {
$guide_items = new PhabricatorGuideListView();
$title = pht('Configure Applications');
$apps_check = true;
$href = PhabricatorEnv::getURI('/applications/');
if ($apps_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've uninstalled any unneeded applications for now.");
} else {
$icon = 'fa-globe';
$icon_bg = 'bg-sky';
$skip = '#';
$description =
pht('Use all our applications, or uninstall the ones you don\'t want.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Invite Collaborators');
$people_check = true;
$href = PhabricatorEnv::getURI('/people/invite/');
if ($people_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
'You will not be alone on this journey.');
} else {
$icon = 'fa-group';
$icon_bg = 'bg-sky';
$skip = '#';
$description =
pht('Invite the rest of your team to get started on Phabricator.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Create a Repository');
$repository_check = true;
$href = PhabricatorEnv::getURI('/diffusion/');
if ($repository_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've created at least one repository.");
} else {
$icon = 'fa-code';
$icon_bg = 'bg-sky';
$skip = '#';
$description =
pht('If you are here for code review, let\'s set up your first '.
'repository.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Create a Project');
$project_check = true;
$href = PhabricatorEnv::getURI('/project/');
if ($project_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've created at least one project.");
} else {
$icon = 'fa-briefcase';
$icon_bg = 'bg-sky';
$skip = '#';
$description =
pht('Project tags define everything. Create them for teams, tags, '.
'or actual projects.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Build a Dashboard');
$dashboard_check = true;
$href = PhabricatorEnv::getURI('/dashboard/');
if ($dashboard_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
"You've created at least one dashboard.");
} else {
$icon = 'fa-dashboard';
$icon_bg = 'bg-sky';
$skip = '#';
$description =
pht('Customize the default homepage layout and items.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Personalize your Install');
$ui_check = true;
$href = PhabricatorEnv::getURI('/config/group/ui/');
if ($dashboard_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$skip = null;
$description = pht(
'It looks amazing, good work. Home Sweet Home.');
} else {
$icon = 'fa-home';
$icon_bg = 'bg-sky';
$skip = '#';
$description =
pht('Change the name and add your company logo, just to give it a '.
'little extra polish.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setSkipHref($skip)
->setDescription($description);
$guide_items->addItem($item);
return $guide_items;
}
}

View file

@ -0,0 +1,53 @@
<?php
final class PhabricatorGuideWelcomeController
extends PhabricatorGuideController {
public function shouldAllowPublic() {
return false;
}
public function handleRequest(AphrontRequest $request) {
require_celerity_resource('guides-app-css');
$viewer = $request->getViewer();
$title = pht('Welcome to Phabricator');
$nav = $this->buildSideNavView();
$nav->selectFilter('/');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setProfileHeader(true);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Welcome'));
$content = id(new PHUIDocumentViewPro())
->appendChild($this->getGuideContent($viewer));
$view = id(new PHUICMSView())
->setCrumbs($crumbs)
->setNavigation($nav)
->setHeader($header)
->setContent($content);
return $this->newPage()
->setTitle($title)
->addClass('phui-cms-body')
->appendChild($view);
}
private function getGuideContent($viewer) {
$content = pht(
'You have successfully installed Phabricator. These next guides will '.
'take you through configuration and new user orientation. '.
'These steps are optional, and you can go through them in any order. '.
'If you want to get back to this guide later on, you can find it in '.
'the **Config** application under **Welcome Guide**.');
return new PHUIRemarkupView($viewer, $content);
}
}

View file

@ -0,0 +1,67 @@
<?php
final class PhabricatorGuideItemView extends Phobject {
private $title;
private $href;
private $description;
private $icon;
private $iconBackground;
private $skipHref;
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function setDescription($description) {
$this->description = $description;
return $this;
}
public function setHref($href) {
$this->href = $href;
return $this;
}
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function setIconBackground($background) {
$this->iconBackground = $background;
return $this;
}
public function setSkipHref($href) {
$this->skipHref = $href;
return $this;
}
public function getTitle() {
return $this->title;
}
public function getDescription() {
return $this->description;
}
public function getHref() {
return $this->href;
}
public function getIcon() {
return $this->icon;
}
public function getIconBackground() {
return $this->iconBackground;
}
public function getSkipHref() {
return $this->skipHref;
}
}

View file

@ -0,0 +1,45 @@
<?php
final class PhabricatorGuideListView extends AphrontView {
private $items = array();
public function addItem(PhabricatorGuideItemView $item) {
$this->items[] = $item;
return $this;
}
public function render() {
require_celerity_resource('guides-app-css');
$list = id(new PHUIObjectItemListView())
->addClass('guides-app');
foreach ($this->items as $item) {
$icon = id(new PHUIIconView())
->setIcon($item->getIcon())
->setBackground($item->getIconBackground());
$list_item = id(new PHUIObjectItemView())
->setHeader($item->getTitle())
->setHref($item->getHref())
->setImageIcon($icon)
->addAttribute($item->getDescription());
$skip_href = $item->getSkipHref();
if ($skip_href) {
$skip = id(new PHUIButtonView())
->setText(pht('Skip'))
->setTag('a')
->setHref($skip_href)
->setColor(PHUIButtonView::GREY);
$list_item->setLaunchButton($skip);
}
$list->addItem($list_item);
}
return $list;
}
}

View file

@ -61,6 +61,10 @@ final class HarbormasterBuildSearchEngine
$query->withBuildPlanPHIDs($map['plans']);
}
if ($map['buildables']) {
$query->withBuildablePHIDs($map['buildables']);
}
if ($map['statuses']) {
$query->withBuildStatuses($map['statuses']);
}

View file

@ -214,18 +214,6 @@ final class PhabricatorAppSearchEngine
$icon = 'application';
}
// TODO: This sheet doesn't work the same way other sheets do so it
// ends up with the wrong classes if we try to use PHUIIconView. This
// is probably all changing in the redesign anyway.
$icon_view = javelin_tag(
'span',
array(
'class' => 'phui-icon-view phui-font-fa '.$icon,
'aural' => false,
),
'');
$description = $application->getShortDescription();
$configure = id(new PHUIButtonView())
@ -241,7 +229,7 @@ final class PhabricatorAppSearchEngine
$item = id(new PHUIObjectItemView())
->setHeader($name)
->setImageIcon($icon_view)
->setImageIcon($icon)
->setSubhead($description)
->setLaunchButton($configure);

View file

@ -61,15 +61,21 @@ final class PHUIHandleTagListView extends AphrontTagView {
}
}
if ($this->limit && (count($handles) > $this->limit)) {
if (!is_array($handles)) {
$handles = iterator_to_array($handles);
}
$handles = array_slice($handles, 0, $this->limit);
// We may be passed a PhabricatorHandleList; if we are, convert it into
// a normal array.
if (!is_array($handles)) {
$handles = iterator_to_array($handles);
}
$over_limit = $this->limit && (count($handles) > $this->limit);
if ($over_limit) {
$visible = array_slice($handles, 0, $this->limit);
} else {
$visible = $handles;
}
$list = array();
foreach ($handles as $handle) {
foreach ($visible as $handle) {
$tag = $handle->renderTag();
if ($this->showHovercards) {
$tag->setPHID($handle->getPHID());
@ -84,21 +90,21 @@ final class PHUIHandleTagListView extends AphrontTagView {
));
}
if ($this->limit) {
if (count($this->handles) > $this->limit) {
$tip_text = implode(', ', mpull($this->handles, 'getName'));
if ($over_limit) {
$tip_text = implode(', ', mpull($handles, 'getName'));
$more = $this->newPlaceholderTag()
->setName("\xE2\x80\xA6")
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $tip_text,
'size' => 200,
));
Javelin::initBehavior('phabricator-tooltips');
$list[] = $this->newItem($more);
}
$more = $this->newPlaceholderTag()
->setName("\xE2\x80\xA6")
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $tip_text,
'size' => 200,
));
$list[] = $this->newItem($more);
}
return $list;

View file

@ -19,6 +19,10 @@ final class PhabricatorRepositoryManagementMovePathsWorkflow
'param' => 'prefix',
'help' => pht('Replace matching prefixes with this string.'),
),
array(
'name' => 'force',
'help' => pht('Apply changes without prompting.'),
),
));
}
@ -47,6 +51,8 @@ final class PhabricatorRepositoryManagementMovePathsWorkflow
'You must specify a path prefix to move to with --to.'));
}
$is_force = $args->getArg('force');
$rows = array();
$any_changes = false;
@ -118,7 +124,7 @@ final class PhabricatorRepositoryManagementMovePathsWorkflow
}
$prompt = pht('Apply these changes?');
if (!phutil_console_confirm($prompt)) {
if (!$is_force && !phutil_console_confirm($prompt)) {
throw new Exception(pht('Declining to apply changes.'));
}

View file

@ -24,7 +24,8 @@ final class PhabricatorIDsSearchField
}
protected function newConduitParameterType() {
return new ConduitIntListParameterType();
return id(new ConduitIntListParameterType())
->setAllowEmptyList(false);
}
}

View file

@ -24,7 +24,8 @@ final class PhabricatorPHIDsSearchField
}
protected function newConduitParameterType() {
return new ConduitPHIDListParameterType();
return id(new ConduitPHIDListParameterType())
->setAllowEmptyList(false);
}
}

View file

@ -22,7 +22,8 @@ final class PhabricatorSearchDatasourceField
protected function newConduitParameterType() {
if (!$this->conduitParameterType) {
return new ConduitStringListParameterType();
return id(new ConduitStringListParameterType())
->setAllowEmptyList(false);
}
return $this->conduitParameterType;

View file

@ -62,11 +62,14 @@ final class PhabricatorUserPreferencesSearchEngine
->setViewer($viewer);
foreach ($settings as $setting) {
$icon = id(new PHUIIconView())
->setIcon('fa-globe')
->setBackground('bg-sky');
$item = id(new PHUIObjectItemView())
->setHeader($setting->getDisplayName())
->setHref($setting->getEditURI())
->setImageURI(PhabricatorUser::getDefaultProfileImageURI())
->setIcon('fa-globe')
->setImageIcon($icon)
->addAttribute(pht('Edit global default settings for all users.'));
$list->addItem($item);

View file

@ -9,7 +9,7 @@ final class PhabricatorEditorMultipleSetting
const VALUE_SINGLE = 'disable';
public function getSettingName() {
return pht('Edit Mulitple Files');
return pht('Edit Multiple Files');
}
public function getSettingPanelKey() {

View file

@ -130,6 +130,17 @@ final class PHUIIconExample extends PhabricatorUIExample {
->addClass('mmr');
}
$squares = array('fa-briefcase', 'fa-code', 'fa-globe', 'fa-home');
$squareview = array();
foreach ($squares as $icon) {
$squareview[] =
id(new PHUIIconView())
->setIcon($icon)
->setBackground('bg-blue')
->setHref('#')
->addClass('mmr');
}
$layout_cicons = id(new PHUIBoxView())
->appendChild($cicons)
->addMargin(PHUI::MARGIN_LARGE);
@ -155,6 +166,10 @@ final class PHUIIconExample extends PhabricatorUIExample {
->addMargin(PHUI::MARGIN_MEDIUM);
$layout5 = id(new PHUIBoxView())
->appendChild($squareview)
->addMargin(PHUI::MARGIN_MEDIUM);
$layout6 = id(new PHUIBoxView())
->appendChild($loginview)
->addMargin(PHUI::MARGIN_MEDIUM);
@ -187,9 +202,13 @@ final class PHUIIconExample extends PhabricatorUIExample {
->appendChild($layout4);
$wrap5 = id(new PHUIObjectBoxView())
->setHeaderText(pht('Authentication'))
->setHeaderText(pht('Squares'))
->appendChild($layout5);
$wrap6 = id(new PHUIObjectBoxView())
->setHeaderText(pht('Authentication'))
->appendChild($layout6);
return phutil_tag(
'div',
array(
@ -202,6 +221,7 @@ final class PHUIIconExample extends PhabricatorUIExample {
$wrap3,
$wrap4,
$wrap5,
$wrap6,
));
}
}

View file

@ -340,55 +340,6 @@ final class PHUIObjectItemListExample extends PhabricatorUIExample {
$out[] = $box;
$list = id(new PHUIObjectItemListView())
->setStates(true);
$list->addItem(
id(new PHUIObjectItemView())
->setObjectName('X1200')
->setHeader(pht('Action Passed'))
->addAttribute(pht('That went swimmingly, go you'))
->setHref('#')
->setState(PHUIObjectItemView::STATE_SUCCESS));
$list->addItem(
id(new PHUIObjectItemView())
->setObjectName('X1201')
->setHeader(pht('Action Failed'))
->addAttribute(pht('Whoopsies, might want to fix that'))
->setHref('#')
->setState(PHUIObjectItemView::STATE_FAIL));
$list->addItem(
id(new PHUIObjectItemView())
->setObjectName('X1202')
->setHeader(pht('Action Warning'))
->addAttribute(pht('We need to talk about things'))
->setHref('#')
->setState(PHUIObjectItemView::STATE_WARN));
$list->addItem(
id(new PHUIObjectItemView())
->setObjectName('X1203')
->setHeader(pht('Action Noted'))
->addAttribute(pht('The weather seems nice today'))
->setHref('#')
->setState(PHUIObjectItemView::STATE_NOTE));
$list->addItem(
id(new PHUIObjectItemView())
->setObjectName('X1203')
->setHeader(pht('Action In Progress'))
->addAttribute(pht('Outlook fuzzy, try again later'))
->setHref('#')
->setState(PHUIObjectItemView::STATE_BUILD));
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('State Icons'))
->setObjectList($list);
$out[] = array($box);
return $out;
}
}

View file

@ -367,7 +367,7 @@ produce a consistent message about a common error state in a convenient way.
There are a handful of error strings in the codebase which may be used before
the translation framework is loaded, or may be used during handling other
errors, possibly rasised from within the translation framework. This handful
errors, possibly raised from within the translation framework. This handful
of special cases are left untranslated to prevent fatals and cycles in the
error handler.

View file

@ -4,7 +4,8 @@
Adjust environmental settings (SSL, remote IP, rate limiting) using a preamble
script.
= Overview =
Overview
========
If Phabricator is deployed in an environment where HTTP headers behave oddly
(usually, because it is behind a load balancer), it may not be able to detect
@ -37,27 +38,52 @@ If present, this script will be executed at the very beginning of each web
request, allowing you to adjust the environment. For common adjustments and
examples, see the next sections.
= Adjusting Client IPs =
Adjusting Client IPs
====================
If your install is behind a load balancer, Phabricator may incorrectly detect
all requests as originating from the load balancer, rather than from the correct
client IPs. If this is the case and some other header (like `X-Forwarded-For`)
is known to be trustworthy, you can overwrite the `REMOTE_ADDR` setting so
Phabricator can figure out the client IP correctly:
all requests as originating from the load balancer, rather than from the
correct client IPs.
If this is the case and some other header (like `X-Forwarded-For`) is known to
be trustworthy, you can read the header and overwrite the `REMOTE_ADDR` value
so Phabricator can figure out the client IP correctly.
You should do this //only// if the `X-Forwarded-For` header is known to be
trustworthy. In particular, if users can make requests to the web server
directly, they can provide an arbitrary `X-Forwarded-For` header, and thereby
spoof an arbitrary client IP.
The `X-Forwarded-For` header may also contain a list of addresses if a request
has been forwarded through multiple loadbalancers. Using a snippet like this
will usually handle most situations correctly:
```
name=Overwrite REMOTE_ADDR with X-Forwarded-For
<?php
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
// Overwrite REMOTE_ADDR with the value in the "X-Forwarded-For" HTTP header.
// Only do this if you're certain the request is coming from a loadbalancer!
// If the request came directly from a client, doing this will allow them to
// them spoof any remote address.
// The header may contain a list of IPs, like "1.2.3.4, 4.5.6.7", if the
// request the load balancer received also had this header.
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$forwarded_for = $_SERVER['HTTP_X_FORWARDED_FOR'];
if ($forwarded_for) {
$forwarded_for = explode(',', $forwarded_for);
$forwarded_for = end($forwarded_for);
$forwarded_for = trim($forwarded_for);
$_SERVER['REMOTE_ADDR'] = $forwarded_for;
}
}
```
You should do this //only// if the `X-Forwarded-For` header is always
trustworthy. In particular, if users can make requests to the web server
directly, they can provide an arbitrary `X-Forwarded-For` header, and thereby
spoof an arbitrary client IP.
= Adjusting SSL =
Adjusting SSL
=============
If your install is behind an SSL terminating load balancer, Phabricator may
detect requests as HTTP when the client sees them as HTTPS. This can cause
@ -76,38 +102,9 @@ $_SERVER['HTTPS'] = true;
You can also set this value to `false` to explicitly tell Phabricator that a
request is not an SSL request.
= Adjusting Rate Limiting =
Phabricator performs coarse, IP-based rate limiting by default. In most
situations the default settings should be reasonable: they are set fairly high,
and intended to prevent only significantly abusive behavior.
However, if legitimate traffic is being rate limited (or you want to make the
limits more strict) you can adjust the limits in the preamble script.
```
name=Adjust Rate Limiting Behavior
<?php
// The default is 1000, so a value of 2000 increases the limit by a factor
// of 2: users will be able to make twice as many requests before being
// rate limited.
// You can set the limit to 0 to disable rate limiting.
PhabricatorStartup::setMaximumRate(2000);
```
By examining `$_SERVER['REMOTE_ADDR']` or similar parameters, you could also
adjust the rate limit dynamically: for example, remove it for requests from an
internal network, but impose a strict limit for external requests.
Rate limiting needs to be configured in this way in order to make it as cheap as
possible to activate after a client is rate limited. The limiting checks execute
before any libraries or configuration are loaded, and can emit a response within
a few milliseconds.
= Next Steps =
Next Steps
==========
Continue by:

View file

@ -59,6 +59,24 @@ not necessarily that the repository is still importing.
- //Field Not Present// This commit was processed before we implemented
this diagnostic feature, and no information is available.
Manually Closing Revisions
==========================
If autoclose didn't activate for some reason and you want to manually close
revisions, you can do so in several ways:
**Close Revision**: The revision author can use the "Close Revision" action
from the web UI, located in the action dropdown on the revision page (where
reviewers would "Accept Revision" or "Request Changes").
`differential.always-allow-close`: If you set this option in {nav Config},
any user can take the "Close Revision" action in the web UI.
`arc close-revision`: You can close revisions from the command line by using
`arc close-revision`.
Next Steps
==========

View file

@ -104,6 +104,8 @@ final class PhabricatorWorkerArchiveTaskQuery
if ($this->dateCreatedBefore) {
return qsprintf($conn_r, 'ORDER BY dateCreated DESC, id DESC');
} else if ($this->dateModifiedSince) {
return qsprintf($conn_r, 'ORDER BY dateModified DESC, id DESC');
} else {
return qsprintf($conn_r, 'ORDER BY id DESC');
}

View file

@ -31,6 +31,9 @@ final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask {
'leaseOwner' => array(
'columns' => array('leaseOwner', 'priority', 'id'),
),
'key_modified' => array(
'columns' => array('dateModified'),
),
) + $parent[self::CONFIG_KEY_SCHEMA];
return $config;

View file

@ -16,6 +16,26 @@ final class PhabricatorStorageManagementDumpWorkflow
'Add __--master-data__ to the __mysqldump__ command, '.
'generating a CHANGE MASTER statement in the output.'),
),
array(
'name' => 'output',
'param' => 'file',
'help' => pht(
'Write output directly to disk. This handles errors better '.
'than using pipes. Use with __--compress__ to gzip the '.
'output.'),
),
array(
'name' => 'compress',
'help' => pht(
'With __--output__, write a compressed file to disk instead '.
'of a plaintext file.'),
),
array(
'name' => 'overwrite',
'help' => pht(
'With __--output__, overwrite the output file if it already '.
'exists.'),
),
));
}
@ -55,6 +75,38 @@ final class PhabricatorStorageManagementDumpWorkflow
}
}
$output_file = $args->getArg('output');
$is_compress = $args->getArg('compress');
$is_overwrite = $args->getArg('overwrite');
if ($is_compress) {
if ($output_file === null) {
throw new PhutilArgumentUsageException(
pht(
'The "--compress" flag can only be used alongside "--output".'));
}
}
if ($is_overwrite) {
if ($output_file === null) {
throw new PhutilArgumentUsageException(
pht(
'The "--overwrite" flag can only be used alongside "--output".'));
}
}
if ($output_file !== null) {
if (Filesystem::pathExists($output_file)) {
if (!$is_overwrite) {
throw new PhutilArgumentUsageException(
pht(
'Output file "%s" already exists. Use "--overwrite" '.
'to overwrite.',
$output_file));
}
}
}
$argv = array();
$argv[] = '--hex-blob';
$argv[] = '--single-transaction';
@ -79,13 +131,83 @@ final class PhabricatorStorageManagementDumpWorkflow
$argv[] = $database;
}
if ($has_password) {
$err = phutil_passthru('mysqldump -p%P %Ls', $password, $argv);
$command = csprintf('mysqldump -p%P %Ls', $password, $argv);
} else {
$err = phutil_passthru('mysqldump %Ls', $argv);
$command = csprintf('mysqldump %Ls', $argv);
}
return $err;
// If we aren't writing to a file, just passthru the command.
if ($output_file === null) {
return phutil_passthru('%C', $command);
}
// If we are writing to a file, stream the command output to disk. This
// mode makes sure the whole command fails if there's an error (commonly,
// a full disk). See T6996 for discussion.
if ($is_compress) {
$file = gzopen($output_file, 'wb');
} else {
$file = fopen($output_file, 'wb');
}
if (!$file) {
throw new Exception(
pht(
'Failed to open file "%s" for writing.',
$file));
}
$future = new ExecFuture('%C', $command);
$lines = new LinesOfALargeExecFuture($future);
try {
foreach ($lines as $line) {
$line = $line."\n";
if ($is_compress) {
$ok = gzwrite($file, $line);
} else {
$ok = fwrite($file, $line);
}
if ($ok !== strlen($line)) {
throw new Exception(
pht(
'Failed to write %d byte(s) to file "%s".',
new PhutilNumber(strlen($line)),
$output_file));
}
}
if ($is_compress) {
$ok = gzclose($file);
} else {
$ok = fclose($file);
}
if ($ok !== true) {
throw new Exception(
pht(
'Failed to close file "%s".',
$output_file));
}
} catch (Exception $ex) {
// If we might have written a partial file to disk, try to remove it so
// we don't leave any confusing artifacts laying around.
try {
Filesystem::remove($output_file);
} catch (Exception $ex) {
// Ignore any errors we hit.
}
throw $ex;
}
return 0;
}
}

View file

@ -8,15 +8,20 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
->setName('renamespace')
->setExamples(
'**renamespace** [__options__] '.
'--in __dump.sql__ --from __old__ --to __new__ > __out.sql__')
'--input __dump.sql__ --from __old__ --to __new__ > __out.sql__')
->setSynopsis(pht('Change the database namespace of a .sql dump file.'))
->setArguments(
array(
array(
'name' => 'in',
'name' => 'input',
'param' => 'file',
'help' => pht('SQL dumpfile to process.'),
),
array(
'name' => 'live',
'help' => pht(
'Generate a live dump instead of processing a file on disk.'),
),
array(
'name' => 'from',
'param' => 'namespace',
@ -27,6 +32,21 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
'param' => 'namespace',
'help' => pht('Desired database namespace for output.'),
),
array(
'name' => 'output',
'param' => 'file',
'help' => pht('Write output directly to a file on disk.'),
),
array(
'name' => 'compress',
'help' => pht('Emit gzipped output instead of plain text.'),
),
array(
'name' => 'overwrite',
'help' => pht(
'With __--output__, write to disk even if the file already '.
'exists.'),
),
));
}
@ -37,12 +57,13 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
public function didExecute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$in = $args->getArg('in');
if (!strlen($in)) {
$input = $args->getArg('input');
$is_live = $args->getArg('live');
if (!strlen($input) && !$is_live) {
throw new PhutilArgumentUsageException(
pht(
'Specify the dumpfile to read with %s.',
'--in'));
'Specify the dumpfile to read with "--in", or use "--live" to '.
'generate one automatically.'));
}
$from = $args->getArg('from');
@ -61,6 +82,62 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
'--to'));
}
$output_file = $args->getArg('output');
$is_overwrite = $args->getArg('overwrite');
$is_compress = $args->getArg('compress');
if ($is_overwrite) {
if ($output_file === null) {
throw new PhutilArgumentUsageException(
pht(
'The "--overwrite" flag can only be used alongside "--output".'));
}
}
if ($output_file !== null) {
if (Filesystem::pathExists($output_file)) {
if (!$is_overwrite) {
throw new PhutilArgumentUsageException(
pht(
'Output file "%s" already exists. Use "--overwrite" '.
'to overwrite.',
$output_file));
}
}
}
if ($is_live) {
$root = dirname(phutil_get_library_root('phabricator'));
$future = new ExecFuture(
'%R dump',
$root.'/bin/storage');
$lines = new LinesOfALargeExecFuture($future);
} else {
$lines = new LinesOfALargeFile($input);
}
if ($output_file === null) {
$file = fopen('php://stdout', 'wb');
$output_name = pht('stdout');
} else {
if ($is_compress) {
$file = gzopen($output_file, 'wb');
} else {
$file = fopen($output_file, 'wb');
}
$output_name = $output_file;
}
if (!$file) {
throw new Exception(
pht(
'Failed to open output file "%s" for writing.',
$output_name));
}
$patterns = array(
'use' => '@^(USE `)([^_]+)(_.*)$@',
'create' => '@^(CREATE DATABASE /\*.*?\*/ `)([^_]+)(_.*)$@',
@ -68,27 +145,66 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
$found = array_fill_keys(array_keys($patterns), 0);
$matches = null;
foreach (new LinesOfALargeFile($in) as $line) {
try {
$matches = null;
foreach ($lines as $line) {
foreach ($patterns as $key => $pattern) {
if (preg_match($pattern, $line, $matches)) {
$namespace = $matches[2];
if ($namespace != $from) {
throw new Exception(
pht(
'Expected namespace "%s", found "%s": %s.',
$from,
$namespace,
$line));
foreach ($patterns as $key => $pattern) {
if (preg_match($pattern, $line, $matches)) {
$namespace = $matches[2];
if ($namespace != $from) {
throw new Exception(
pht(
'Expected namespace "%s", found "%s": %s.',
$from,
$namespace,
$line));
}
$line = $matches[1].$to.$matches[3];
$found[$key]++;
}
}
$line = $matches[1].$to.$matches[3];
$found[$key]++;
$data = $line."\n";
if ($is_compress) {
$bytes = gzwrite($file, $data);
} else {
$bytes = fwrite($file, $data);
}
if ($bytes !== strlen($data)) {
throw new Exception(
pht(
'Failed to write %d byte(s) to "%s".',
new PhutilNumber(strlen($data)),
$output_name));
}
}
echo $line."\n";
if ($is_compress) {
$ok = gzclose($file);
} else {
$ok = fclose($file);
}
if ($ok !== true) {
throw new Exception(
pht(
'Failed to close file "%s".',
$output_file));
}
} catch (Exception $ex) {
try {
if ($output_file !== null) {
Filesystem::remove($output_file);
}
} catch (Exception $ex) {
// Ignore any exception.
}
throw $ex;
}
// Give the user a chance to catch things if the results are crazy.

View file

@ -0,0 +1,118 @@
<?php
final class PHUICMSView extends AphrontTagView {
private $header;
private $nav;
private $crumbs;
private $content;
private $toc;
private $comments;
public function setHeader(PHUIHeaderView $header) {
$this->header = $header;
return $this;
}
public function setNavigation(AphrontSideNavFilterView $nav) {
$this->nav = $nav;
return $this;
}
public function setCrumbs(PHUICrumbsView $crumbs) {
$this->crumbs = $crumbs;
return $this;
}
public function setContent($content) {
$this->content = $content;
return $this;
}
public function setToc($toc) {
$this->toc = $toc;
return $this;
}
public function setComments($comments) {
$this->comments = $comments;
}
protected function getTagName() {
return 'div';
}
protected function getTagAttributes() {
require_celerity_resource('phui-cms-css');
$classes = array();
$classes[] = 'phui-cms-view';
if ($this->comments) {
$classes[] = 'phui-cms-has-comments';
}
return array(
'class' => implode(' ', $classes),
);
}
protected function getTagContent() {
$content = phutil_tag(
'div',
array(
'class' => 'phui-cms-page-content',
),
array(
$this->header,
$this->content,
));
$comments = null;
if ($this->comments) {
$comments = phutil_tag(
'div',
array(
'class' => 'phui-cms-comments',
),
array(
$this->comments,
));
}
$navigation = $this->nav;
$navigation->appendChild($content);
$navigation->appendChild($comments);
$page = phutil_tag(
'div',
array(
'class' => 'phui-cms-inner',
),
array(
$navigation,
));
$cms_view = phutil_tag(
'div',
array(
'class' => 'phui-cms-wrap',
),
array(
$this->crumbs,
$page,
));
$classes = array();
$classes[] = 'phui-cms-page';
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$cms_view);
}
}

View file

@ -17,6 +17,7 @@ final class PHUIIconView extends AphrontTagView {
private $spriteSheet;
private $iconFont;
private $iconColor;
private $iconBackground;
public function setHref($href) {
$this->href = $href;
@ -54,6 +55,11 @@ final class PHUIIconView extends AphrontTagView {
return $this;
}
public function setBackground($color) {
$this->iconBackground = $color;
return $this;
}
protected function getTagName() {
$tag = 'span';
if ($this->href) {
@ -79,6 +85,10 @@ final class PHUIIconView extends AphrontTagView {
if ($this->iconColor) {
$classes[] = $this->iconColor;
}
if ($this->iconBackground) {
$classes[] = 'phui-icon-square';
$classes[] = $this->iconBackground;
}
} else {
if ($this->headSize) {
$classes[] = $this->headSize;

View file

@ -9,7 +9,6 @@ final class PHUIObjectItemListView extends AphrontTagView {
private $flush;
private $simple;
private $allowEmptyList;
private $states;
private $itemClass = 'phui-object-item-standard';
public function setAllowEmptyList($allow_empty_list) {
@ -51,11 +50,6 @@ final class PHUIObjectItemListView extends AphrontTagView {
return $this;
}
public function setStates($states) {
$this->states = $states;
return $this;
}
public function setItemClass($item_class) {
$this->itemClass = $item_class;
return $this;
@ -69,9 +63,6 @@ final class PHUIObjectItemListView extends AphrontTagView {
$classes = array();
$classes[] = 'phui-object-item-list-view';
if ($this->states) {
$classes[] = 'phui-object-list-states';
}
if ($this->flush) {
$classes[] = 'phui-object-list-flush';
}

View file

@ -19,8 +19,6 @@ final class PHUIObjectItemView extends AphrontTagView {
private $headIcons = array();
private $disabled;
private $imageURI;
private $state;
private $fontIcon;
private $imageIcon;
private $titleText;
private $badge;
@ -29,16 +27,6 @@ final class PHUIObjectItemView extends AphrontTagView {
private $launchButton;
private $coverImage;
const AGE_FRESH = 'fresh';
const AGE_STALE = 'stale';
const AGE_OLD = 'old';
const STATE_SUCCESS = 'green';
const STATE_FAIL = 'red';
const STATE_WARN = 'yellow';
const STATE_NOTE = 'blue';
const STATE_BUILD = 'sky';
public function setDisabled($disabled) {
$this->disabled = $disabled;
return $this;
@ -143,6 +131,10 @@ final class PHUIObjectItemView extends AphrontTagView {
}
public function setImageIcon($image_icon) {
if (!$image_icon instanceof PHUIIconView) {
$image_icon = id(new PHUIIconView())
->setIcon($image_icon);
}
$this->imageIcon = $image_icon;
return $this;
}
@ -156,63 +148,9 @@ final class PHUIObjectItemView extends AphrontTagView {
return $this;
}
public function setState($state) {
$this->state = $state;
switch ($state) {
case self::STATE_SUCCESS:
$fi = 'fa-check-circle green';
break;
case self::STATE_FAIL:
$fi = 'fa-times-circle red';
break;
case self::STATE_WARN:
$fi = 'fa-exclamation-circle yellow';
break;
case self::STATE_NOTE:
$fi = 'fa-info-circle blue';
break;
case self::STATE_BUILD:
$fi = 'fa-refresh ph-spin sky';
break;
}
$this->setIcon($fi);
return $this;
}
public function setIcon($icon) {
$this->fontIcon = id(new PHUIIconView())
->setIcon($icon);
return $this;
}
public function setEpoch($epoch, $age = self::AGE_FRESH) {
public function setEpoch($epoch) {
$date = phabricator_datetime($epoch, $this->getUser());
$days = floor((time() - $epoch) / 60 / 60 / 24);
switch ($age) {
case self::AGE_FRESH:
$this->addIcon('none', $date);
break;
case self::AGE_STALE:
$attr = array(
'tip' => pht('Stale (%s day(s))', new PhutilNumber($days)),
'class' => 'icon-age-stale',
);
$this->addIcon('fa-clock-o yellow', $date, $attr);
break;
case self::AGE_OLD:
$attr = array(
'tip' => pht('Old (%s day(s))', new PhutilNumber($days)),
'class' => 'icon-age-old',
);
$this->addIcon('fa-clock-o red', $date, $attr);
break;
default:
throw new Exception(pht("Unknown age '%s'!", $age));
}
$this->addIcon('none', $date);
return $this;
}
@ -309,10 +247,6 @@ final class PHUIObjectItemView extends AphrontTagView {
$item_classes[] = 'phui-object-item-disabled';
}
if ($this->state) {
$item_classes[] = 'phui-object-item-state-'.$this->state;
}
switch ($this->effect) {
case 'highlighted':
$item_classes[] = 'phui-object-item-highlighted';
@ -338,10 +272,6 @@ final class PHUIObjectItemView extends AphrontTagView {
$item_classes[] = 'phui-object-item-with-image-icon';
}
if ($this->fontIcon) {
$item_classes[] = 'phui-object-item-with-ficon';
}
return array(
'class' => $item_classes,
);
@ -596,16 +526,6 @@ final class PHUIObjectItemView extends AphrontTagView {
$this->getImageIcon());
}
$ficon = null;
if ($this->fontIcon) {
$image = phutil_tag(
'div',
array(
'class' => 'phui-object-item-ficon',
),
$this->fontIcon);
}
if ($image && $this->href) {
$image = phutil_tag(
'a',

View file

@ -0,0 +1,34 @@
/**
* @provides guides-app-css
*/
.guides-app ul.phui-object-item-list-view {
margin: 0;
padding: 20px;
}
.guides-app .phui-object-item-no-bar .phui-object-item-frame {
border: 0;
}
.guides-app .phui-object-item-image-icon {
margin: 8px 2px 12px;
}
.guides-app a.phui-object-item-link {
color: #000;
font-size: {$biggestfontsize};
}
.guides-app .phui-object-item-name {
padding-top: 6px;
}
.guides-app .phui-object-item-launch-button a.button {
font-size: {$normalfontsize};
padding: 3px 12px 4px;
}
.device-desktop .guides-app .phui-object-item {
margin-bottom: 8px;
}

View file

@ -151,6 +151,45 @@
color: rgba({$alphagrey},0.3);
}
/* Backgrounds */
.phui-icon-view.bg-dark {
background-color: {$darkgreytext};
}
.phui-icon-view.bg-bluegrey {
background-color: {$bluetext};
}
.phui-icon-view.bg-red {
background-color: {$red};
}
.phui-icon-view.bg-orange {
background-color: {$orange};
}
.phui-icon-view.bg-yellow {
background-color: {$yellow};
}
.phui-icon-view.bg-green {
background-color: {$green}
}
.phui-icon-view.bg-blue {
background-color: {$blue};
}
.phui-icon-view.bg-sky {
background-color: {$sky};
}
.phui-icon-view.bg-indigo {
background-color: {$indigo};
}
.phui-icon-view.bg-pink {
background-color: {$pink};
}
.phui-icon-view.bg-fire {
background-color: {$fire};
}
.phui-icon-view.bg-violet {
background-color: {$violet};
}
/* Hovers */
.device-desktop a.phui-icon-view.lightgreytext:hover,

View file

@ -0,0 +1,48 @@
/**
* @provides phui-cms-css
*/
.phui-cms-body {
background-color: #f0f0f2;
}
.phui-cms-view .phui-crumbs-view {
border-bottom: 1px solid {$thinblueborder};
}
.phui-cms-page {
max-width: 1140px;
margin: 0 auto;
background-color: #fff;
border-left: 1px solid {$lightblueborder};
border-right: 1px solid {$lightblueborder};
border-bottom: 1px solid {$lightblueborder};
margin-bottom: 20px;
}
.phui-cms-view .phui-basic-nav.phui-navigation-shell .phabricator-nav-local {
background-color: {$page.background};
width: 240px;
border-right: 1px solid {$thinblueborder};
}
.phui-cms-view .phabricator-nav-content {
padding: 0;
}
.phui-cms-view .phui-document-container {
border: none;
}
.phui-cms-view .phui-cms-page-content .phui-profile-header {
padding: 32px 20px;
border-bottom: 1px solid {$thinblueborder};
}
.phui-cms-view .phui-document-view.phui-document-view-pro {
width: auto;
max-width: inherit;
padding: 0 20px;
margin: 0;
}

View file

@ -109,7 +109,8 @@
-webkit-font-smoothing: antialiased;
}
.aphront-form-input input {
.aphront-form-input input[type="text"],
.aphront-form-input input[type="password"] {
width: 100%;
}

View file

@ -80,3 +80,20 @@ a.phui-icon-circle:hover {
a.phui-icon-circle:hover .phui-icon-view {
color: {$sky};
}
/* - Icon in a Square ------------------------------------------------------- */
.phui-icon-view.phui-icon-square {
height: 40px;
width: 40px;
color: #fff;
font-size: 26px;
text-align: center;
line-height: 38px;
border-radius: 3px;
}
a.phui-icon-view.phui-icon-square:hover {
text-decoration: none;
color: #fff;
}

View file

@ -487,46 +487,6 @@ ul.phui-object-item-list-view .phui-object-item-selected
}
/* - Foot Icons ----------------------------------------------------------------
Object counts shown in the footer.
*/
.phui-object-item-foot-icons {
margin-left: 10px;
bottom: 0;
position: absolute;
}
.phui-object-item-with-foot-icons .phui-object-item-content,
.device-phone .phui-object-item-with-foot-icons .phui-object-item-col2 {
padding-bottom: 24px;
}
.device-phone .phui-object-item-with-foot-icons .phui-object-item-content {
padding-bottom: 0;
}
.phui-object-item-foot-icon {
display: inline-block;
background: {$lightgreyborder};
color: #ffffff;
font-weight: bold;
margin-right: 3px;
padding: 3px 6px 0;
height: 17px;
vertical-align: middle;
position: relative;
font-size: 12px;
-webkit-font-smoothing: antialiased;
}
.phui-object-item-foot-icon .phui-icon-view {
margin-right: 4px;
}
/* - Handle Icons --------------------------------------------------------------
Shows owners, reviewers, etc., using profile picture icons.
@ -621,35 +581,6 @@ ul.phui-object-item-list-view .phui-object-item-selected
list-style: none;
}
/* - State ---------------------------------------------------------------------
Provides a list of object status or states, success or fail, etc
*/
.phui-object-item-ficon {
width: 48px;
height: 26px;
margin-top: 12px;
position: absolute;
text-align: center;
font-size: 24px;
}
.phui-object-item-with-ficon .phui-object-item-content-box {
margin-left: 38px;
}
.phui-object-box .phui-object-list-states {
padding: 0;
}
.phui-object-list-states .phui-info-view {
margin: 0;
border: none;
}
/* - Badges ---------------------------------------------------------------- */
.phui-object-item-col0.phui-object-item-badge {
@ -720,37 +651,19 @@ ul.phui-object-item-list-view .phui-object-item-selected
.phui-object-item-image-icon {
background: none;
width: 30px;
height: 30px;
margin: 4px 0;
width: 40px;
height: 40px;
margin: 8px 6px;
position: absolute;
}
.phui-object-item-image-icon .phui-icon-view {
position: absolute;
width: 24px;
height: 24px;
left: 6px;
top: 10px;
font-size: 24px;
width: 40px;
height: 40px;
font-size: 26px;
text-align: center;
vertical-align: bottom;
}
.phui-object-item-with-image-icon .phui-object-item-frame {
min-height: 48px;
}
.phui-object-item-with-image-icon .phui-object-item-content-box {
margin-left: 36px;
}
.device-desktop .phui-object-item-launcher-list .phui-object-item-content {
margin-right: 0;
}
.device-desktop .phui-object-item-launcher-list .phui-object-icon-pane {
width: auto;
line-height: 36px;
}
.phui-object-item-image {
@ -762,10 +675,12 @@ ul.phui-object-item-list-view .phui-object-item-selected
position: absolute;
}
.phui-object-item-with-image-icon .phui-object-item-frame,
.phui-object-item-with-image .phui-object-item-frame {
min-height: 52px;
}
.phui-object-item-with-image-icon .phui-object-item-content-box,
.phui-object-item-with-image .phui-object-item-content-box {
margin-left: 46px;
}

View file

@ -216,8 +216,18 @@ JX.install('WorkboardBoard', {
}
var column_maps = response.columnMaps;
var natural_column;
for (var natural_phid in column_maps) {
this.getColumn(natural_phid).setNaturalOrder(column_maps[natural_phid]);
natural_column = this.getColumn(natural_phid);
if (!natural_column) {
// Our view of the board may be out of date, so we might get back
// information about columns that aren't visible. Just ignore the
// position information for any columns we aren't displaying on the
// client.
continue;
}
natural_column.setNaturalOrder(column_maps[natural_phid]);
}
var property_maps = response.propertyMaps;