Skip to content

Commit a774620

Browse files
relrodepriestley
authored and
epriestley
committedDec 27, 2012
Start of a config web interface.
Summary: This is somewhat clowny, particularly in how it handles JSON encode/decode, but I've commented why I did things the way I did. The goal is to store minified JSON but show pretty-printed JSON where possible, to the user editing it. Test Plan: * Went to /config/ and saw a list of keys from the `default` config. * Clicked on one of them, submitted the default value successfully. * Changed the value to invalid JSON and got a decent error. * Changed the value to valid JSON and checked the DB to confirm it saved. * Confirmed the DB values were minified. * Confirmed the user-facing values were pretty-printed where they could be. * Confirmed that PHIDs were getting assigned properly and that isDeleted properly defaulted to false/0. Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T2246 Differential Revision: https://secure.phabricator.com/D4290
1 parent 1e2dfb5 commit a774620

11 files changed

+352
-0
lines changed
 
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CREATE TABLE {$NAMESPACE}_config.config_entry (
2+
`id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
3+
`phid` VARCHAR(64) NOT NULL COLLATE utf8_bin,
4+
`namespace` VARCHAR(64) BINARY NOT NULL COLLATE utf8_bin,
5+
`configKey` VARCHAR(64) BINARY NOT NULL COLLATE utf8_bin,
6+
`value` LONGTEXT NOT NULL,
7+
`isDeleted` BOOL NOT NULL,
8+
`dateCreated` INT UNSIGNED NOT NULL,
9+
`dateModified` INT UNSIGNED NOT NULL,
10+
UNIQUE KEY `key_phid` (`phid`),
11+
UNIQUE KEY `key_name` (`namespace`, `configKey`)
12+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

‎src/__phutil_library_map__.php

+12
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@
580580
'PhabricatorApplicationAuth' => 'applications/auth/application/PhabricatorApplicationAuth.php',
581581
'PhabricatorApplicationCalendar' => 'applications/calendar/application/PhabricatorApplicationCalendar.php',
582582
'PhabricatorApplicationConduit' => 'applications/conduit/application/PhabricatorApplicationConduit.php',
583+
'PhabricatorApplicationConfig' => 'applications/config/application/PhabricatorApplicationConfig.php',
583584
'PhabricatorApplicationCountdown' => 'applications/countdown/application/PhabricatorApplicationCountdown.php',
584585
'PhabricatorApplicationDaemons' => 'applications/daemon/application/PhabricatorApplicationDaemons.php',
585586
'PhabricatorApplicationDifferential' => 'applications/differential/application/PhabricatorApplicationDifferential.php',
@@ -679,8 +680,13 @@
679680
'PhabricatorConduitLogController' => 'applications/conduit/controller/PhabricatorConduitLogController.php',
680681
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
681682
'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php',
683+
'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php',
682684
'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php',
685+
'PhabricatorConfigEditController' => 'applications/config/controller/PhabricatorConfigEditController.php',
686+
'PhabricatorConfigEntry' => 'applications/config/storage/PhabricatorConfigEntry.php',
687+
'PhabricatorConfigEntryDAO' => 'applications/config/storage/PhabricatorConfigEntryDAO.php',
683688
'PhabricatorConfigFileSource' => 'infrastructure/env/PhabricatorConfigFileSource.php',
689+
'PhabricatorConfigListController' => 'applications/config/controller/PhabricatorConfigListController.php',
684690
'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
685691
'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php',
686692
'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php',
@@ -1873,6 +1879,7 @@
18731879
'PhabricatorApplicationAuth' => 'PhabricatorApplication',
18741880
'PhabricatorApplicationCalendar' => 'PhabricatorApplication',
18751881
'PhabricatorApplicationConduit' => 'PhabricatorApplication',
1882+
'PhabricatorApplicationConfig' => 'PhabricatorApplication',
18761883
'PhabricatorApplicationCountdown' => 'PhabricatorApplication',
18771884
'PhabricatorApplicationDaemons' => 'PhabricatorApplication',
18781885
'PhabricatorApplicationDifferential' => 'PhabricatorApplication',
@@ -1986,8 +1993,13 @@
19861993
'PhabricatorConduitLogController' => 'PhabricatorConduitController',
19871994
'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO',
19881995
'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
1996+
'PhabricatorConfigController' => 'PhabricatorController',
19891997
'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource',
1998+
'PhabricatorConfigEditController' => 'PhabricatorConfigController',
1999+
'PhabricatorConfigEntry' => 'PhabricatorConfigEntryDAO',
2000+
'PhabricatorConfigEntryDAO' => 'PhabricatorLiskDAO',
19902001
'PhabricatorConfigFileSource' => 'PhabricatorConfigProxySource',
2002+
'PhabricatorConfigListController' => 'PhabricatorConfigController',
19912003
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
19922004
'PhabricatorConfigStackSource' => 'PhabricatorConfigSource',
19932005
'PhabricatorContentSourceView' => 'AphrontView',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
final class PhabricatorApplicationConfig extends PhabricatorApplication {
4+
5+
public function getBaseURI() {
6+
return '/config/';
7+
}
8+
9+
public function getIconName() {
10+
return 'config';
11+
}
12+
13+
public function getTitleGlyph() {
14+
return "\xE2\x98\xBA";
15+
}
16+
17+
public function getApplicationGroup() {
18+
return self::GROUP_ADMIN;
19+
}
20+
21+
public function shouldAppearInLaunchView() {
22+
return false;
23+
}
24+
25+
public function getRoutes() {
26+
return array(
27+
'/config/' => array(
28+
'' => 'PhabricatorConfigListController',
29+
'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController',
30+
),
31+
);
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
abstract class PhabricatorConfigController extends PhabricatorController {
4+
5+
public function shouldRequireAdmin() {
6+
return true;
7+
}
8+
9+
public function buildSideNavView($filter = null, $for_app = false) {
10+
$user = $this->getRequest()->getUser();
11+
12+
$nav = new AphrontSideNavFilterView();
13+
$nav->setBaseURI(new PhutilURI($this->getApplicationURI('filter/')));
14+
15+
return $nav;
16+
}
17+
18+
public function buildApplicationMenu() {
19+
return $this->buildSideNavView(null, true)->getMenu();
20+
}
21+
22+
public function buildApplicationCrumbs() {
23+
$crumbs = parent::buildApplicationCrumbs();
24+
return $crumbs;
25+
}
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
final class PhabricatorConfigEditController
4+
extends PhabricatorConfigController {
5+
6+
private $key;
7+
8+
public function willProcessRequest(array $data) {
9+
$this->key = idx($data, 'key');
10+
}
11+
12+
public function processRequest() {
13+
$request = $this->getRequest();
14+
$user = $request->getUser();
15+
16+
$config = id(new PhabricatorConfigFileSource('default'))
17+
->getAllKeys();
18+
if (!$this->key || !array_key_exists($this->key, $config)) {
19+
return new Aphront404Response();
20+
}
21+
22+
// Check if the config key is already stored in the database.
23+
// Grab the value if it is.
24+
$value = null;
25+
$config_entry = id(new PhabricatorConfigEntry())
26+
->loadOneWhere(
27+
'configKey = %s AND namespace=%s',
28+
$this->key,
29+
'default');
30+
if ($config_entry) {
31+
$value = $config_entry->getValue();
32+
} else {
33+
$config_entry = id(new PhabricatorConfigEntry())
34+
->setConfigKey($this->key);
35+
}
36+
37+
$e_value = null;
38+
$errors = array();
39+
if ($request->isFormPost()) {
40+
$new_value = $request->getStr('value');
41+
if (strlen($new_value)) {
42+
$json = json_decode($new_value, true);
43+
if ($json === null && strtolower($value) != 'null') {
44+
$e_value = 'Invalid';
45+
$errors[] = 'The given value must be valid JSON. This means, among '.
46+
'other things, that you must wrap strings in double-quotes.';
47+
$value = $new_value;
48+
} else {
49+
$value = $json;
50+
}
51+
} else {
52+
// TODO: When we do Transactions, make this just set isDeleted = 1
53+
$config_entry->delete();
54+
}
55+
56+
$config_entry->setValue($value);
57+
$config_entry->setNamespace('default');
58+
59+
if (!$errors) {
60+
$config_entry->save();
61+
return id(new AphrontRedirectResponse())
62+
->setURI($config_entry->getURI());
63+
}
64+
}
65+
66+
$form = new AphrontFormView();
67+
$form->setFlexible(true);
68+
69+
$error_view = null;
70+
if ($errors) {
71+
$error_view = id(new AphrontErrorView())
72+
->setTitle('You broke everything!')
73+
->setErrors($errors);
74+
} else {
75+
// Check not only that it's an array, but that it's an "unnatural" array
76+
// meaning that the keys aren't 0 -> size_of_array.
77+
if (is_array($value) &&
78+
array_keys($value) != range(0, count($value) - 1)) {
79+
$value = id(new PhutilJSON())->encodeFormatted($value);
80+
} else {
81+
$value = json_encode($value);
82+
}
83+
}
84+
85+
$form
86+
->setUser($user)
87+
->appendChild(
88+
id(new AphrontFormTextAreaControl())
89+
->setLabel('JSON Value')
90+
->setError($e_value)
91+
->setValue($value)
92+
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
93+
->setCustomClass('PhabricatorMonospaced')
94+
->setName('value'))
95+
->appendChild(
96+
id(new AphrontFormSubmitControl())
97+
->addCancelButton($config_entry->getURI())
98+
->setValue(pht('Save Config Entry')));
99+
100+
$title = pht('Edit %s', $this->key);
101+
$short = pht('Edit');
102+
103+
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
104+
$crumbs->addCrumb(
105+
id(new PhabricatorCrumbView())
106+
->setName($this->key)
107+
->setHref('/config/edit/'.$this->key));
108+
$crumbs->addCrumb(
109+
id(new PhabricatorCrumbView())->setName($short));
110+
111+
return $this->buildApplicationPage(
112+
array(
113+
$crumbs,
114+
id(new PhabricatorHeaderView())->setHeader($title),
115+
$error_view,
116+
$form,
117+
),
118+
array(
119+
'title' => $title,
120+
'device' => true,
121+
));
122+
}
123+
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
final class PhabricatorConfigListController
4+
extends PhabricatorConfigController {
5+
6+
public function processRequest() {
7+
$request = $this->getRequest();
8+
$user = $request->getUser();
9+
10+
$nav = $this->buildSideNavView();
11+
12+
$pager = new AphrontCursorPagerView();
13+
$pager->readFromRequest($request);
14+
15+
$config = new PhabricatorConfigFileSource('default');
16+
$list = $this->buildConfigList(array_keys($config->getAllKeys()));
17+
$list->setPager($pager);
18+
$list->setNoDataString(
19+
'No data. Something probably went wrong in reading the default config.');
20+
21+
$header = id(new PhabricatorHeaderView())
22+
->setHeader(pht('Configuration'));
23+
24+
$nav->appendChild(
25+
array(
26+
$header,
27+
$list,
28+
));
29+
30+
$crumbs = $this
31+
->buildApplicationCrumbs($nav)
32+
->addCrumb(
33+
id(new PhabricatorCrumbView())
34+
->setName(pht('Configuration'))
35+
->setHref($this->getApplicationURI('filter/')));
36+
37+
$nav->setCrumbs($crumbs);
38+
39+
return $this->buildApplicationPage(
40+
$nav,
41+
array(
42+
'title' => pht('Configuration'),
43+
'device' => true,
44+
)
45+
);
46+
}
47+
48+
private function buildConfigList(array $keys) {
49+
$list = new PhabricatorObjectItemListView();
50+
51+
foreach ($keys as $key) {
52+
$item = id(new PhabricatorObjectItemView())
53+
->setHeader($key)
54+
->setHref('/config/edit/'.$key)
55+
->setObject($key);
56+
$list->addItem($item);
57+
}
58+
59+
return $list;
60+
}
61+
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
final class PhabricatorConfigEntry extends PhabricatorConfigEntryDAO {
4+
5+
protected $id;
6+
protected $phid;
7+
protected $namespace;
8+
protected $configKey;
9+
protected $value;
10+
11+
// TODO: Remove this default when implementing Transactions.
12+
protected $isDeleted = 0;
13+
14+
public function getURI() {
15+
return '/config/edit/'.$this->configKey;
16+
}
17+
18+
public function getConfiguration() {
19+
return array(
20+
self::CONFIG_AUX_PHID => true,
21+
self::CONFIG_SERIALIZATION => array(
22+
'value' => self::SERIALIZATION_JSON,
23+
),
24+
) + parent::getConfiguration();
25+
}
26+
27+
public function generatePHID() {
28+
return PhabricatorPHID::generateNewPHID(
29+
PhabricatorPHIDConstants::PHID_TYPE_CONF);
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
abstract class PhabricatorConfigEntryDAO extends PhabricatorLiskDAO {
4+
5+
public function getApplicationName() {
6+
return 'config';
7+
}
8+
9+
}

‎src/applications/phid/PhabricatorPHIDConstants.php

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ final class PhabricatorPHIDConstants {
3030
const PHID_TYPE_ANSW = 'ANSW';
3131
const PHID_TYPE_MOCK = 'MOCK';
3232
const PHID_TYPE_MCRO = 'MCRO';
33+
const PHID_TYPE_CONF = 'CONF';
3334

3435
const PHID_TYPE_XACT = 'XACT';
3536
const PHID_TYPE_XCMT = 'XCMT';

0 commit comments

Comments
 (0)
Failed to load comments.