summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristoph Schulthess2017-01-24 16:57:57 +0100
committerChristoph Schulthess2017-01-24 16:57:57 +0100
commit923105672ac4102bad6daeb1a086713b1e8231c4 (patch)
treef7c491a14fa03d1646bc1384bf61076360b0ae13
parentdeleted unused files (diff)
downloadslx-admin-923105672ac4102bad6daeb1a086713b1e8231c4.tar.gz
slx-admin-923105672ac4102bad6daeb1a086713b1e8231c4.tar.xz
slx-admin-923105672ac4102bad6daeb1a086713b1e8231c4.zip
debug config module
-rw-r--r--inc/property.inc.php4
-rw-r--r--modules-available/debugconfig/config.json2
-rw-r--r--modules-available/debugconfig/lang/de/messages.json2
-rw-r--r--modules-available/debugconfig/lang/en/module.json4
-rw-r--r--modules-available/debugconfig/lang/en/template-tags.json11
-rw-r--r--modules-available/debugconfig/page.inc.php337
-rw-r--r--modules-available/debugconfig/templates/_page.html138
-rw-r--r--modules-available/debugconfig/templates/debugconfig.html40
8 files changed, 94 insertions, 444 deletions
diff --git a/inc/property.inc.php b/inc/property.inc.php
index 0eb700e3..dc11d88c 100644
--- a/inc/property.inc.php
+++ b/inc/property.inc.php
@@ -193,11 +193,11 @@ class Property
public static function getRemoteDebugConfig()
{
- return self::get('remote-debug-config');
+ return json_decode(self::get('remote-debug-config'), true);
}
public static function setRemoteDebugConfig($value)
{
- return self::set('remote-debug-config', $value);
+ return self::set('remote-debug-config', json_encode($value));
}
}
diff --git a/modules-available/debugconfig/config.json b/modules-available/debugconfig/config.json
index e4d906e1..b8d03278 100644
--- a/modules-available/debugconfig/config.json
+++ b/modules-available/debugconfig/config.json
@@ -1,4 +1,4 @@
{
- "category": "main.settings-client",
+ "category": "main.settings-server",
"dependencies" : ["js_selectize", "bootstrap_multiselect"]
}
diff --git a/modules-available/debugconfig/lang/de/messages.json b/modules-available/debugconfig/lang/de/messages.json
index a0b8cdd4..bfa02332 100644
--- a/modules-available/debugconfig/lang/de/messages.json
+++ b/modules-available/debugconfig/lang/de/messages.json
@@ -2,4 +2,4 @@
"invalid-hook": "Ung\u00fcltiger Hook: {{0}}",
"no-module-hook": "Modul {{0}} hat keinen Hook",
"settings-updated": "Einstellungen wurden aktualisiert"
-} \ No newline at end of file
+}
diff --git a/modules-available/debugconfig/lang/en/module.json b/modules-available/debugconfig/lang/en/module.json
index 48591f88..74dc16c6 100644
--- a/modules-available/debugconfig/lang/en/module.json
+++ b/modules-available/debugconfig/lang/en/module.json
@@ -1,3 +1,3 @@
{
- "module_name": "Config variables"
-} \ No newline at end of file
+ "module_name": "Remote debugging"
+}
diff --git a/modules-available/debugconfig/lang/en/template-tags.json b/modules-available/debugconfig/lang/en/template-tags.json
index 8e75e5ac..351dc0bc 100644
--- a/modules-available/debugconfig/lang/en/template-tags.json
+++ b/modules-available/debugconfig/lang/en/template-tags.json
@@ -1,7 +1,6 @@
{
- "lang_basicConfiguration": "Basic Configuration",
- "lang_clientRelatedConfig": "The options on this page are related to the bwLehrpool client machines.",
- "lang_editOverrideNotice": "You're editing the settings of a sub-section",
- "lang_enableOverride": "Override",
- "lang_settingActive": "Setting active"
-} \ No newline at end of file
+ "lang_debugConfig": "Remote Debugging Configuration",
+ "lang_debugDescription": "Configure remote debugging options. If enabled, the satellite server will accept client requests and relay data to the debugging server.",
+ "lang_debuggingActive": "Remote debugging is enabled",
+ "lang_debuggingInactive": "Remote debugging is disabled"
+}
diff --git a/modules-available/debugconfig/page.inc.php b/modules-available/debugconfig/page.inc.php
index 5e99f2a0..ca6ba136 100644
--- a/modules-available/debugconfig/page.inc.php
+++ b/modules-available/debugconfig/page.inc.php
@@ -1,342 +1,45 @@
<?php
-class Page_BaseConfig extends Page
+class Page_debugConfig extends Page
{
- private $qry_extra = array();
- private $categories;
- /**
- * @var bool|string in case we're in module mode, set to the id of the module
- */
- private $targetModule = false;
+ const DEFAULT_CONFIG = array("enabled" => false, "debugServer" => "127.0.0.1");
protected function doPreprocess()
{
User::load();
-
- // Determine if we're setting global or module specific
- $this->getModuleSpecific();
-
- $newValues = Request::post('setting');
- if (is_array($newValues)) {
- if (!User::hasPermission('superadmin')) {
- Message::addError('main.no-permission');
- Util::redirect('?do=baseconfig');
- }
- // Build variables for specific sub-settings
- if ($this->targetModule === false) {
- // We're editing global settings - use the 'enabled' field
- $qry_insert = ', enabled';
- $qry_values = ', :enabled';
- $qry_update = ', enabled = :enabled';
- $params = array();
- } elseif (empty($this->qry_extra['field'])) {
- // Module specific, but module doesn't have an extra field
- $qry_insert = '';
- $qry_values = '';
- $qry_update = '';
- } else {
- // Module with extra field
- $qry_insert = ', ' . $this->qry_extra['field'];
- $qry_values = ', :field_value';
- $qry_update = '';
- $params = array('field_value' => $this->qry_extra['field_value']);
- $delExtra = " AND {$this->qry_extra['field']} = :field_value ";
- $delParams = array('field_value' => $this->qry_extra['field_value']);
- // Not editing global settings
- if ($this->getCurrentModuleName() === false) {
- Message::addError('main.value-invalid', $this->qry_extra['field'], $this->qry_extra['field_value']);
- Util::redirect('?do=BaseConfig');
- }
- }
- // Honor override/enabled checkbox
- $override = Request::post('override', array());
- // Load all existing config options to validate input
- $vars = BaseConfigUtil::getVariables();
- // First, handle shadowing so we don't create warnings for empty fields
- BaseConfigUtil::markShadowedVars($vars, $newValues);
- // Validate input
- foreach ($vars as $key => $var) {
- if (isset($var['shadowed']))
- continue;
- if ($this->targetModule === false) {
- // Global mode
- $params['enabled'] = (is_array($override) && isset($override[$key]) && $override[$key] === 'on') ? 1 : 0;
- } else {
- // Module mode
- if (is_array($override) && (!isset($override[$key]) || $override[$key] !== 'on')) {
- // override not set - delete
- $delParams['key'] = $key;
- Database::exec("DELETE FROM {$this->qry_extra['table']} WHERE setting = :key $delExtra", $delParams);
- continue;
- }
- }
- $validator = $var['validator'];
- $displayValue = (isset($newValues[$key]) ? $newValues[$key] : '');
- // Validate data first!
- $mangledValue = Validator::validate($validator, $displayValue);
- if ($mangledValue === false) {
- Message::addWarning('main.value-invalid', $key, $displayValue);
- continue;
- }
- // Now put into DB
- Database::exec("INSERT INTO {$this->qry_extra['table']} (setting, value, displayvalue $qry_insert)"
- . " VALUES (:key, :value, :displayvalue $qry_values)"
- . " ON DUPLICATE KEY UPDATE value = :value, displayvalue = :displayvalue $qry_update",
- array(
- 'key' => $key,
- 'value' => $mangledValue,
- 'displayvalue' => $displayValue
- ) + $params
- );
- }
- Message::addSuccess('settings-updated');
- if ($this->targetModule === false) {
- Util::redirect('?do=BaseConfig');
- } elseif (empty($this->qry_extra['field'])) {
- Util::redirect('?do=BaseConfig&module=' . $this->targetModule);
- } else {
- Util::redirect('?do=BaseConfig&module=' . $this->targetModule . '&' . $this->qry_extra['field'] . '=' . $this->qry_extra['field_value']);
- }
- }
- // Load categories so we can define them as sub menu items
- $this->categories = BaseConfigUtil::getCategories();
- asort($this->categories, SORT_DESC);
- foreach ($this->categories as $catid => $val) {
- Dashboard::addSubmenu(
- '#category_' . $catid,
- Dictionary::translateFileModule($this->categories[$catid]['module'], 'config-variable-categories', $catid, true)
- );
- }
- }
-
- protected function doRender()
- {
if (!User::hasPermission('superadmin')) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
- // Check if valid submodule mode, store name if any
- if ($this->targetModule !== false) {
- $this->qry_extra['subheading'] = $this->getCurrentModuleName();
- if ($this->qry_extra['subheading'] === false) {
- Message::addError('main.value-invalid', $this->qry_extra['field'], $this->qry_extra['field_value']);
- Util::redirect('?do=BaseConfig');
- }
- }
- // List config options
- $settings = array();
- $vars = BaseConfigUtil::getVariables();
- // Get stuff that's set in DB already
- if ($this->targetModule === false) {
- $fields = ', enabled';
- $where = '';
- $params = array();
- } elseif (isset($this->qry_extra['field'])) {
- $fields = '';
- $where = " WHERE {$this->qry_extra['field']} = :field_value";
- $params = array('field_value' => $this->qry_extra['field_value']);
- } else {
- $fields = '';
- $where = '';
- $params = array();
- }
- // Populate structure with existing config from db
- $res = Database::simpleQuery("SELECT setting, value, displayvalue $fields FROM {$this->qry_extra['table']} "
- . " {$where} ORDER BY setting ASC", $params);
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- if (!isset($vars[$row['setting']]) || !is_array($vars[$row['setting']])) {
- $unknown[] = $row['setting'];
- continue;
- }
- $row += $vars[$row['setting']];
- if (!isset($row['catid'])) {
- $row['catid'] = 'unknown';
- }
- $settings[$row['catid']]['settings'][$row['setting']] = $row;
- }
- // Add entries that weren't in the db (global), setup override checkbox (module specific)
- foreach ($vars as $key => $var) {
- if ($this->targetModule === false) {
- // Global settings - honor enabled field in db
- if (!isset($settings[$var['catid']]['settings'][$key]['enabled']) || $settings[$var['catid']]['settings'][$key]['enabled'] == 1) {
- $settings[$var['catid']]['settings'][$key]['checked'] = 'checked';
- }
- } elseif (isset($settings[$var['catid']]['settings'][$key])) {
- // Module specific - value is set in DB
- $settings[$var['catid']]['settings'][$key]['checked'] = 'checked';
- } else {
- // Module specific - value is not set in DB
- $settings[$var['catid']]['settings'][$key] = $var + array(
- 'setting' => $key
- );
- }
- if (!isset($settings[$var['catid']]['settings'][$key]['displayvalue'])) {
- $settings[$var['catid']]['settings'][$key]['displayvalue'] = $var['defaultvalue'];
- }
- if (!isset($settings[$var['catid']]['settings'][$key]['shadows'])) {
- $settings[$var['catid']]['settings'][$key]['shadows'] = null;
- }
- //echo "<pre>";
- //var_dump($settings[$var['catid']]['settings'][$key]);
- //echo "</pre>";
- $settings[$var['catid']]['settings'][$key] += array(
- 'item' => $this->makeInput(
- $var['validator'],
- $key,
- $settings[$var['catid']]['settings'][$key]['displayvalue'],
- $settings[$var['catid']]['settings'][$key]['shadows']
- ),
- 'description' => Util::markup(Dictionary::translateFileModule($var['module'], 'config-variables', $key)),
- 'setting' => $key,
- );
- }
- //die();
-
-
- // Sort categories
- $sortvals = array();
- foreach ($settings as $catid => &$setting) {
- $sortvals[] = isset($this->categories[$catid]) ? (int)$this->categories[$catid]['sortpos'] : 99999;
- $setting['category_id'] = $catid;
- $setting['category_name'] = Dictionary::translateFileModule($this->categories[$catid]['module'], 'config-variable-categories', $catid);
- if ($setting['category_name'] === false) {
- $setting['category_name'] = $catid;
- }
- ksort($setting['settings']);
- $setting['settings'] = array_values($setting['settings']);
+ if (!filter_var(Request::get('debugServer'), FILTER_VALIDATE_IP)) {
+ return;
}
- unset($setting);
- array_multisort($sortvals, SORT_ASC, SORT_NUMERIC, $settings);
- Render::addTemplate('_page', array(
- 'override' => $this->targetModule !== false,
- 'categories' => array_values($settings),
- 'target_module' => $this->targetModule,
- ) + $this->qry_extra);
- Module::isAvailable('bootstrap_switch');
}
- private function getCurrentModuleName()
+ protected function doRender()
{
- if (isset($this->qry_extra['tostring'])) {
- $method = explode('::', $this->qry_extra['tostring']);
- return call_user_func($method, $this->qry_extra['field_value']);
- }
- if (isset($this->qry_extra['field'])) {
- return $this->targetModule . ' // ' . $this->qry_extra['field'] . '=' . $this->qry_extra['field_value'];
- }
- return $this->targetModule;
+ $debug_config = $this->getConfig();
+ Render::addTemplate('debugconfig', array("debugEnabled" => $debug_config["enabled"], "debugServer" => $debug_config["debugServer"]));
}
-
- private function getModuleSpecific()
+
+ private function getConfig()
{
- $module = Request::any('module', '', 'string');
- if ($module === '') {
- $this->qry_extra = array(
- 'table' => 'setting_global',
- );
- return;
- }
- //\\//\\//\\
- if (!Module::isAvailable($module)) {
- Message::addError('main.no-such-module', $module);
- Util::redirect('?do=baseconfig');
+ if (Property::getRemoteDebugConfig() == Null) {
+ Property::setRemoteDebugConfig(self::DEFAULT_CONFIG);
}
- $file = 'modules/' . $module . '/baseconfig/hook.json';
- if (!file_exists($file)) {
- Message::addError('no-module-hook', $module);
- Util::redirect('?do=baseconfig');
- }
- $hook = json_decode(file_get_contents($file), true);
- if (empty($hook['table'])) {
- Message::addError('invalid-hook', $module);
- Util::redirect('?do=baseconfig');
- }
- if (isset($hook['field'])) {
- $hook['field_value'] = Request::any($hook['field'], '0', 'string');
- }
- $this->targetModule = $module;
- $this->qry_extra = $hook;
+ return Property::getRemoteDebugConfig();
}
- /**
- * Create html snippet for setting, based on given validator
- * @param type $validator
- * @return boolean
- */
- private function makeInput($validator, $setting, $current, $shadows)
+ private function setConfig()
{
- /* for the html snippet we need: */
- $args = array('class' => 'form-control', 'name' => "setting[$setting]", 'id' => $setting);
- if (!empty($shadows)) {
- $args['data-shadows'] = json_encode($shadows);
- }
- $inner = "";
- /* -- */
-
- $parts = explode(':', $validator, 2);
-
- if ($parts[0] === 'list') {
-
- $items = explode('|', $parts[1]);
- foreach ($items as $item) {
- if ($item === $current) {
- $inner .= "<option selected=\"selected\" value=\"$item\"> $item </option>";
- } else {
- $inner .= "<option value=\"$item\"> $item </option>";
- }
- }
-
- $tag = 'select';
- unset($args['type']);
- $current = '';
-
- } elseif ($parts[0] == 'multilist') {
-
- $items = explode('|', $parts[1]);
- $args['multiple'] = 'multiple';
- $args['class'] .= " multilist";
- $args['name'] .= '[]';
-
- $selected = explode(' ', $current);
-
- foreach ($items as $item) {
- if (in_array($item, $selected)) {
- $inner .= "<option selected=\"selected\" value=\"$item\"> $item </option>";
- } else {
- $inner .= "<option value=\"$item\"> $item </option>";
- }
- }
- $tag = 'select';
- unset($args['type']);
- $current = '';
+ error_log("here");
+ if (Request::get('toggleDebug' === 'enable')) {
+ $enabled = true;
} else {
- // Everything else is a text input for now
- $tag = 'input';
- $args['value'] = $current;
- $args['type'] = 'text';
- /* Password field guessing */
- if (stripos($validator, 'password') !== false) {
- $args['type'] = Property::getPasswordFieldType();
- }
- }
-
- /* multiinput: enter multiple free-form strings*/
- if ($validator === 'multiinput') {
- $args['class'] .= " multiinput";
+ $enabled = false;
}
-
- $output = "<$tag ";
- foreach ($args as $key => $val) {
- $output .= "$key=\"" . htmlspecialchars($val) . '" ';
- }
- if (empty($inner)) {
- $output .= '/>';
- } else {
- $output .= '>' . $inner . "</$tag>";
- }
-
- return $output;
- }
-
+ Property::setRemoteDebugConfig(array('enabled' => $enabled, 'debugServer' => Request::get('debugServer')));
+ }
}
+
diff --git a/modules-available/debugconfig/templates/_page.html b/modules-available/debugconfig/templates/_page.html
index 7f380495..2823d77c 100644
--- a/modules-available/debugconfig/templates/_page.html
+++ b/modules-available/debugconfig/templates/_page.html
@@ -1,122 +1,30 @@
-<h1>{{lang_basicConfiguration}}</h1>
-{{#override}}
-<h2>{{subheading}}</h2>
-<div class="alert alert-info">{{lang_editOverrideNotice}}</div>
-{{/override}}
-<p>{{lang_clientRelatedConfig}}</p>
-<form action="?do=BaseConfig" method="post">
+<form action="?do=DebugConfig" method="post">
<input type="hidden" name="token" value="{{token}}">
- {{#override}}
- <input name="module" type="hidden" value="{{target_module}}">
- <input name="{{field}}" type="hidden" value="{{field_value}}">
- {{/override}}
- <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="position:absolute;top:-2000px" tabindex="-1">
- <input type="password" name="password_fake" id="password_fake" value="" style="position:absolute;top:-2000px" tabindex="-1">
- {{#categories}}
+ <input type="hidden" name="action" value="debugconfig">
<div class="panel panel-default">
- <div class="panel-heading" role="tab" id="heading{{category_id}}">
- <a name="category_{{category_id}}"></a>
- {{category_name}}
- </div>
+ <div class="panel-heading">{{lang_debugConfig}}</div>
<div class="panel-body">
- <div class="list-group">
- {{#settings}}
- <div class="list-group-item">
- <div class="row">
- <div class="col-md-5 slx-cfg-toggle">
- <div>{{setting}}</div>
- {{^override}}
- <div class="slx-default">
- {{defaultvalue}}
- </div>
- <input class="bs-switch" name="override[{{setting}}]" id="CB_{{setting}}" type="checkbox" {{checked}}> <label for="CB_{{setting}}">{{lang_settingActive}}</label>
- {{/override}}
- {{#override}}
- <input class="bs-switch" name="override[{{setting}}]" id="CB_{{setting}}" type="checkbox" {{checked}}> <label for="CB_{{setting}}">{{lang_enableOverride}}</label>
- {{/override}}
- </div>
- <div class="col-md-5">
- {{{item}}}
- </div>
- <div class="col-md-2">
- <a class="btn btn-default" data-toggle="modal" data-target="#help-{{setting}}"><span class="glyphicon glyphicon-question-sign"></span></a>
- </div>
- </div>
- </div>
- <div class="modal fade" id="help-{{setting}}" tabindex="-1" role="dialog">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">{{setting}}</div>
- <div class="modal-body">{{{description}}}</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
- </div>
- </div>
- </div>
- {{/settings}}
+ <p>{{lang_debugDescription}}</p>
+ {{^debugEnabled}}
+ <p>{{lang_debuggingIsDisabled}}</p>
+ {{/debugEnabled}}
+ {{#debugEnabled}}
+ <div class="input-group" onclick="$('#doff').prop('checked', true);">
+ <span class="input-group-addon"><input id="doff" type="radio" name="mode" value="off"></span>
+ <span class="form-control">
+ {{lang_debugOff}}
+ </span>
+ </div>
+ {{/debugEnabled}}
+ <div class="input-group" onclick="$('#don').prop('checked', true);">
+ <span class="input-group-addon"><input id="don" type="radio" name="mode" value="random"></span>
+ <span class="form-control">
+ {{lang_debugOn}}
+ </span>
+ </div>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
</div>
</div>
</div>
- {{/categories}}
- <button class="btn btn-primary" type="submit">{{lang_save}}</button>
- <button class="btn btn-default" type="reset">{{lang_reset}}</button>
- {{^override}}
- <a class="btn btn-default" href="api.php?do=baseconfig&amp;user={{userid}}">Download</a>
- {{/override}}
- {{#override}}
- <a class="btn btn-default" href="api.php?do=baseconfig&amp;user={{userid}}&amp;module={{target_module}}&amp;value={{field_value}}&amp;force=1">Download</a>
- {{/override}}
</form>
-
-<script type="text/javascript">
-
-function updateShadows(e) {
- var rules = $(e).data('shadows');
- if (!rules) return;
- var currentValue = $(e).val();
- for (var triggerVal in rules) {
- var targets = rules[triggerVal];
- for (var i = 0; i < targets.length; ++i) {
- var target = targets[i];
- var inp = $('#' + target);
- var selitem = inp.data('selitem');
-
- if (currentValue === triggerVal) {
- inp.prop('disabled', true);
- if (selitem) selitem.disable();
- $('#' + target + '.multilist').multiselect('disable');
- } else {
- inp.prop('disabled', false);
- if (selitem) selitem.enable();
- $('#' + target + '.multilist').multiselect('enable');
- }
- }
- }
-}
-
-
-document.addEventListener("DOMContentLoaded", function () {
- /* apply selectize on all multiinput-selectize inputs */
- $("input.multiinput").each(function (idx, elem) {
- var e = $(elem);
- e.data('selitem', e.selectize({
- delimiter: ' ',
- create: true,
- plugins: ['restore_on_backspace', 'remove_button'],
- maxItems: 10000
-
- })[0].selectize);
- });
-
- var $multilists = $("select.multilist");
- $multilists.multiselect({
- includeSelectAllOption: true,
- buttonWidth: '100%',
- buttonClass: 'form-control'
- });
-
- /* data-shadowing bindings */
- $allShadowingFields = $('[data-shadows]');
- $allShadowingFields.on('change', function (event) { updateShadows(event.target); });
- $allShadowingFields.each(function (idx, elem) { updateShadows(elem); });
-});
-</script>
diff --git a/modules-available/debugconfig/templates/debugconfig.html b/modules-available/debugconfig/templates/debugconfig.html
new file mode 100644
index 00000000..4aa0b4e9
--- /dev/null
+++ b/modules-available/debugconfig/templates/debugconfig.html
@@ -0,0 +1,40 @@
+<form action="?do=DebugConfig" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="$this->setConfig()">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_debugConfig}}</div>
+ <div class="panel-body">
+ <p>{{lang_debugDescription}}</p>
+ <div class="col-md-5 slx-cfg-toggle">
+ <div>REMOTE_DBG_SERVER</div>
+ <div class="slx-default">master-ip</div>
+ {{#debugEnabled}}
+ <input class="bootstrap-switch" name="toggleDebug" value="enable" id="" type="checkbox" checked="true"><label style="margin-left: 10px;">{{lang_debuggingActive}}</label>
+ {{/debugEnabled}}
+ {{^debugEnabled}}
+ <input class="bootstrap-switch" name="toggleDebug" value="enable" id="" type="checkbox"><label style="margin-left: 10px;">{{lang_debuggingInactive}}</label>
+ {{/debugEnabled}}
+ </div>
+ <div class="col-md-5">
+ <input name="debugServer" id="DBG_SERVER_ADDR" class="form-control" value="{{debugServer}}" type="text"/>
+ </div>
+ <div class="col-md-2">
+ <a class="btn btn-default" data-toggle="modal" data-target="help-remoteDebugging">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ </a>
+ </div>
+ <div class="modal fade" id="help-remoteDebugging" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{setting}}</div>
+ <div class="modal-body">{{{description}}}</div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div>
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </div>
+</form>