diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..a5fe8e7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [erikdubbelboer] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4dc5d11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +config.inc.php +*.phar +vendor + +# IDEs metadata +/nbproject/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 5ad0d4b..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "predis"] - path = predis - url = git://github.com/nrk/predis.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..610dbda --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM composer:2.2 + +RUN apk add --no-cache tini tzdata + +WORKDIR /src/app + +COPY . . + +RUN set -xe; \ + composer install; \ + cp includes/config.environment.inc.php includes/config.inc.php + +ENV PORT 80 +EXPOSE 80 +ENTRYPOINT [ "sh", "-c", "tini -- php -S 0.0.0.0:$PORT" ] diff --git a/README.markdown b/README.markdown index 4241b4d..0938c08 100644 --- a/README.markdown +++ b/README.markdown @@ -1,35 +1,71 @@ phpRedisAdmin ============= -phpRedisAdmin is a simple web interface to manage [Redis](http://redis.io/) databases. It is released under the [Creative Commons Attribution 3.0 license](http://creativecommons.org/licenses/by/3.0/). This code is being developed and maintained by [Erik Dubbelboer](https://github.com/ErikDubbelboer/). +phpRedisAdmin is a simple web interface to manage [Redis](http://redis.io/) +databases. It is released under the +[Creative Commons Attribution 3.0 license](http://creativecommons.org/licenses/by/3.0/). +This code is being developed and maintained by [Erik Dubbelboer](https://github.com/ErikDubbelboer/). -You can send comments, patches, questions [here on github](https://github.com/ErikDubbelboer/phpRedisAdmin/issues) or to erik@dubbelboer.com. +You can send comments, patches, questions +[here on github](https://github.com/ErikDubbelboer/phpRedisAdmin/issues) +or to erik@dubbelboer.com. Example ======= -You can find an example database at [http://dubbelboer.com/phpRedisAdmin/](http://dubbelboer.com/phpRedisAdmin/) +You can find an example database at +[http://dubbelboer.com/phpRedisAdmin/](http://dubbelboer.com/phpRedisAdmin/) Installing/Configuring ====================== -To install phpRedisAdmin in the current directory you need to execute the following commands: +To install [phpRedisAdmin](https://packagist.org/packages/erik-dubbelboer/php-redis-admin) through [composer](http://getcomposer.org/) you need to execute the following commands: + +``` +curl -s http://getcomposer.org/installer | php +php composer.phar create-project -s dev erik-dubbelboer/php-redis-admin path/to/install +``` + +You may also want to copy includes/config.sample.inc.php to includes/config.inc.php +and edit it with your specific redis configuration. + +Instead of using composer, you can also do a manual install using: ``` git clone https://github.com/ErikDubbelboer/phpRedisAdmin.git cd phpRedisAdmin -git submodule init -git submodule update +git clone https://github.com/nrk/predis.git vendor ``` -You will also need to edit config.inc.php with your redis information. +Docker Image +============ +A public [phpRedisAdmin Docker image](https://hub.docker.com/r/erikdubbelboer/phpredisadmin/) is available on Docker Hub built from the latest tag. +The file ```includes/config.environment.inc.php``` is used as the configuration file to allow environment variables to be used as configuration values. +Example: +``` +docker run --rm -it -e REDIS_1_HOST=myredis.host -e REDIS_1_NAME=MyRedis -p 80:80 erikdubbelboer/phpredisadmin +``` +Also, a Docker Compose manifest with a stack for testing and development is provided. Just issue ```docker-compose up --build``` to start it and browse to http://localhost. See ```docker-compose.yml``` file for configuration details. +Environment variables summary +==== + +* ``REDIS_1_HOST`` - define host of the Redis server +* ``REDIS_1_NAME`` - define name of the Redis server +* ``REDIS_1_PORT`` - define port of the Redis server +* ``REDIS_1_SCHEME`` - define scheme of the Redis server (tcp or tls) +* ``REDIS_1_AUTH`` - define password of the Redis server +* ``REDIS_1_AUTH_FILE`` - define file containing the password of the Redis server +* ``REDIS_1_DATABASES`` - You can modify you config to prevent phpRedisAdmin from using CONFIG command +* ``ADMIN_USER`` - define username for user-facing Basic Auth +* ``ADMIN_PASS`` - define password for user-facing Basic Auth TODO ==== +* Encoding support for editing * Javascript sorting of tables * Better error handling * Move or Copy key to different server @@ -43,4 +79,3 @@ Credits Icons by [http://p.yusukekamiyamane.com/](http://p.yusukekamiyamane.com/) ([https://github.com/yusukekamiyamane/fugue-icons/tree/master/icons-shadowless](https://github.com/yusukekamiyamane/fugue-icons/tree/master/icons-shadowless)) Favicon from [https://github.com/antirez/redis-io/blob/master/public/images/favicon.png](https://github.com/antirez/redis-io/blob/master/public/images/favicon.png) - diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1627ff2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.13.x | :white_check_mark: | +| 1.12.x | :white_check_mark: | +| < 1.12 | :x: | + +## Reporting a Vulnerability + +Vulnerabilities can be emailed to erik@dubbelboer.com diff --git a/common.inc.php b/common.inc.php deleted file mode 100644 index ac0303a..0000000 --- a/common.inc.php +++ /dev/null @@ -1,90 +0,0 @@ - $v) { - unset($process[$key][$k]); - - if (is_array($v)) { - $process[$key][stripslashes($k)] = $v; - $process[] = &$process[$key][stripslashes($k)]; - } else { - $process[$key][stripslashes($k)] = stripslashes($v); - } - } - } - - unset($process); -} - - - - -// These includes are needed by each script. -require_once 'config.inc.php'; -require_once 'functions.inc.php'; -require_once 'page.inc.php'; -require_once 'predis/autoload.php'; - - -if (isset($config['login'])) { - require_once 'login.inc.php'; -} - - - - -if (isset($login['servers'])) { - $i = current($login['servers']); -} else { - $i = 0; -} - - -if (isset($_GET['s']) && is_numeric($_GET['s']) && ($_GET['s'] < count($config['servers']))) { - $i = $_GET['s']; -} - -$server = $config['servers'][$i]; -$server['id'] = $i; - - -if (isset($login, $login['servers'])) { - if (array_search($i, $login['servers']) === false) { - die('You are not allowed to access this database.'); - } - - foreach ($config['servers'] as $key => $ignore) { - if (array_search($key, $login['servers']) === false) { - unset($config['servers'][$key]); - } - } -} - - -if (!isset($server['db'])) { - $server['db'] = 0; -} - - -// Setup a connection to Redis. -$redis = new Predis\Client('tcp://'.$server['host'].':'.$server['port']); - -if (isset($server['auth'])) { - if (!$redis->auth($server['auth'])) { - die('ERROR: Authentication failed ('.$server['host'].':'.$server['port'].')'); - } -} - - -if ($server['db'] != 0) { - if (!$redis->select($server['db'])) { - die('ERROR: Selecting database failed ('.$server['host'].':'.$server['port'].','.$server['db'].')'); - } -} - -?> diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3b3ab81 --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "erik-dubbelboer/php-redis-admin", + "description": "Simple web interface to manage Redis databases.", + "version": "1.24.0", + "license": "CC-BY-3.0", + "homepage": "https://github.com/ErikDubbelboer/phpRedisAdmin", + "authors": [ + { + "name": "Erik Dubbelboer", + "email": "erik@dubbelboer.com", + "homepage": "http://blog.dubbelboer.com/", + "role": "Developer" + } + ], + "require": { + "ext-mbstring": "*", + "ext-json": "*", + "predis/predis": "v2.3.0", + "paragonie/random_compat": ">=2" + }, + "minimum-stability": "stable", + "target-dir": "ErikDubbelboer/phpRedisAdmin" +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f724f31 --- /dev/null +++ b/composer.lock @@ -0,0 +1,133 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "401ff61cebe5223d003a47192d7c3d6a", + "packages": [ + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com/" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "predis/predis", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/predis/predis.git", + "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/predis/predis/zipball/bac46bfdb78cd6e9c7926c697012aae740cb9ec9", + "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^8.0 || ^9.4" + }, + "suggest": { + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Till Krüss", + "homepage": "https://till.im/", + "role": "Maintainer" + } + ], + "description": "A flexible and feature-complete Redis client for PHP.", + "homepage": "http://github.com/predis/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "support": { + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2024-11-21T20:00:02+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "ext-mbstring": "*", + "ext-json": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/config.inc.php b/config.inc.php deleted file mode 100644 index a0fdf4d..0000000 --- a/config.inc.php +++ /dev/null @@ -1,56 +0,0 @@ - array( - 0 => array( - 'name' => 'local server', // Optional name. - 'host' => '127.0.0.1', - 'port' => 6379, - - // Optional Redis authentication. - //'auth' => 'redispasswordhere' // Warning: The password is sent in plain-text to the Redis server. - ), - - /*1 => array( - 'host' => 'localhost', - 'port' => 6380 - ),*/ - - /*2 => array( - 'name' => 'local db 2', - 'host' => 'localhost', - 'port' => 6379, - 'db' => 1 // Optional database number, see http://redis.io/commands/select - )*/ - ), - - - 'seperator' => ':', - - - // Uncomment to show less information and make phpRedisAdmin fire less commands to the Redis server. Recommended for a really busy Redis server. - //'faster' => true, - - - // Uncomment to enable HTTP authentication - /*'login' => array( - // Username => Password - // Multiple combinations can be used - 'admin' => array( - 'password' => 'adminpassword', - ), - 'guest' => array( - 'password' => '', - 'servers' => array(1) // Optional list of servers this user can access. - ) - ),*/ - - - - - // You can ignore settings below this point. - - 'maxkeylen' => 100 -); - -?> diff --git a/css/common.css b/css/common.css index 942c49a..9dc6276 100644 --- a/css/common.css +++ b/css/common.css @@ -1,4 +1,3 @@ - html { font-size: x-small; /* Wikipedia font-size scaling method */ } @@ -9,8 +8,8 @@ color: #000; margin: 0; padding: 0; border: 0; -height: 100%; -max-height: 100%; +height: 100%; +max-height: 100%; background-color: #fff; } @@ -51,3 +50,8 @@ padding: 3px 0 1px 20px; background: url(http://github.com/images/add.png) left center no-repeat; } + +.data { +white-space: pre-wrap; +} + diff --git a/css/frame.css b/css/frame.css index c467b85..7cf935d 100644 --- a/css/frame.css +++ b/css/frame.css @@ -1,4 +1,3 @@ - form { margin: 0; } @@ -15,7 +14,6 @@ margin-left: -8em; } form .button { -margin-left: -7em; } @@ -54,3 +52,13 @@ float: left; margin: 1em; } + +.exception { +border: 1px solid #ff0000; +background-color: rgba(255, 0, 0, 0.2); +color: #880000; +padding: 10px; +border-radius: 5px; +margin: 20px; +} + diff --git a/css/index.css b/css/index.css index b562ccf..4a56aa9 100644 --- a/css/index.css +++ b/css/index.css @@ -1,14 +1,16 @@ - #sidebar { position: absolute; top: 0; -bottom: 0; +bottom: 0; left: 0; -width: 24em; +display: flex; +flex-direction: column; +width: 290px; height: 100%; -padding-left: 1em; -border-right: 1px solid #000; -overflow: hidden; +} + +#header { +padding-left: 10px; } #sidebar a, #sidebar a:visited { @@ -25,11 +27,10 @@ text-decoration: underline; #keys { -position: fixed; -top: 15.5em; -bottom: 0; -width: 24em; -padding-bottom: 1em; +flex-grow: 1; +width: 290px; +margin-top: 10px; +padding-left: 10px; overflow: auto; } @@ -41,11 +42,17 @@ padding: 0; #keys li { font-weight: normal; +white-space: nowrap; } #keys li.folder { font-weight: bold; margin-top: .05em; +cursor: pointer; +} + +#keys li.empty a { +color: #888; } #keys li.current a { @@ -105,18 +112,41 @@ display: none; display: inline; } +#resize { +position: fixed; +top: 0; +left: 300px; +bottom: 0; +width: 5px; +background-color: #aaa; +cursor: col-resize; +padding: 0; +margin: 0; +border-left: 1px solid #000; +} + +#resize-layover { +position: fixed; +top: 0; +left: 305px; +right: 0; +bottom: 0; +width: 100%; +z-index: 1000; +} #frame { position: fixed; -top: 0; -left: 25em; +top: 0; +left: 305px; right: 0; bottom: 0; -padding-left: 1em; } #frame iframe { -width: 100%; +width: calc(100% - 3em); height: 100%; +margin-left: 1.5em; +margin-right: 1.5em; } diff --git a/css/login.css b/css/login.css new file mode 100644 index 0000000..f439934 --- /dev/null +++ b/css/login.css @@ -0,0 +1,116 @@ +/* Styles borrowed from http://getbootstrap.com/examples/signin/ */ +h1.logo { + text-align: center; +} +h2 { + font-size: 30px; +} +h2 { + margin-top: 20px; + margin-bottom: 10px; +} +h2 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +.form-signin { + max-width: 330px; + padding: 15px; + margin: 0 auto; +} +.form-signin .form-signin-heading, +.form-signin .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; +} +.form-signin .form-control:focus { + z-index: 2; +} +.form-signin input[type="text"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); + -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: 400; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-lg { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.invalid-credentials { + border: 1px solid #ff0000; + background-color: rgba(255, 0, 0, 0.2); + color: #880000; + padding: 10px; + border-radius: 5px; + margin: 20px; +} diff --git a/delete.php b/delete.php index f37f306..c329919 100644 --- a/delete.php +++ b/delete.php @@ -1,13 +1,13 @@ del($key); } - - die('?&s='.$server['id']); + + die('?view&s='.$server['id'].'&d='.$server['db']); +} + +if (isset($_GET['batch_del'])) { + if (empty($_POST['selected_keys'])) { + die('No keys to delete'); + } + $keys = json_decode($_POST['selected_keys']); + + foreach ($keys as $key) { + $redis->del($key); + } + + die('?view&s=' . $server['id'] . '&d=' . $server['db'] . '&key=' . urlencode($keys[0])); } -?> diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5ef83df --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +services: + phpredisadmin: + build: . + environment: + - ADMIN_USER=admin + - ADMIN_PASS=admin + - REDIS_1_HOST=redis + - REDIS_1_PORT=6379 + links: + - redis + ports: + - "80:80" + + redis: + image: redis + command: --loglevel verbose diff --git a/edit.php b/edit.php index ba9debf..998012c 100644 --- a/edit.php +++ b/edit.php @@ -1,9 +1,8 @@ set($_POST['key'], $_POST['value']); + $key = input_convert($_POST['key']); + $value = input_convert($_POST['value']); + $value = encodeOrDecode('save', $key, $value); + + if ($value === false || is_null($value)) { + die('ERROR: could not encode value'); } - // Hash - else if (($_POST['type'] == 'hash') && isset($_POST['hkey'])) { - if (strlen($_POST['hkey']) > $config['maxkeylen']) { - die('ERROR: Your hash key is to long (max length is '.$config['maxkeylen'].')'); + try { + // String + if ($_POST['type'] == 'string') { + $redis->set($key, $value); } - if ($edit && !$redis->hExists($_POST['key'], $_POST['hkey'])) { - $redis->hDel($_POST['key'], $_GET['hkey']); - } + // Hash + else if (($_POST['type'] == 'hash') && isset($_POST['hkey'])) { + if (strlen($_POST['hkey']) > $config['maxkeylen']) { + die('ERROR: Your hash key is to long (max length is '.$config['maxkeylen'].')'); + } - $redis->hSet($_POST['key'], $_POST['hkey'], $_POST['value']); - } + if ($edit && !$redis->hExists($key, input_convert($_POST['hkey']))) { + $redis->hDel($key, input_convert($_GET['hkey'])); + } - // List - else if (($_POST['type'] == 'list') && isset($_POST['index'])) { - $size = $redis->lLen($_POST['key']); - - if (($_POST['index'] == '') || - ($_POST['index'] == $size) || - ($_POST['index'] == -1)) { - // Push it at the end - $redis->rPush($_POST['key'], $_POST['value']); - } else if (($_POST['index'] >= 0) && - ($_POST['index'] < $size)) { - // Overwrite an index - $redis->lSet($_POST['key'], $_POST['index'], $_POST['value']); - } else { - die('ERROR: Out of bounds index'); + $redis->hSet($key, input_convert($_POST['hkey']), $value); } - } - // Set - else if ($_POST['type'] == 'set') { - if ($_POST['value'] != $_POST['oldvalue']) { - // The only way to edit a Set value is to add it and remove the old value. - $redis->sRem($_POST['key'], $_POST['oldvalue']); - $redis->sAdd($_POST['key'], $_POST['value']); + // List + else if (($_POST['type'] == 'list') && isset($_POST['index'])) { + $size = $redis->lLen($key); + + if (($_POST['index'] == '') || + ($_POST['index'] == $size)) { + // Push it at the end + $redis->rPush($key, $value); + } else if ($_POST['index'] == -1) { + // Push it at the start + $redis->lPush($key, $value); + } else if (($_POST['index'] >= 0) && + ($_POST['index'] < $size)) { + // Overwrite an index + $redis->lSet($key, input_convert($_POST['index']), $value); + } else { + die('ERROR: Out of bounds index'); + } } - } - // ZSet - else if (($_POST['type'] == 'zset') && isset($_POST['score'])) { - if ($_POST['value'] != $_POST['oldvalue']) { + // Set + else if ($_POST['type'] == 'set') { + if ($_POST['value'] != $_POST['oldvalue']) { + // The only way to edit a Set value is to add it and remove the old value. + $redis->sRem($key, encodeOrDecode('save', $key, input_convert($_POST['oldvalue']))); + $redis->sAdd($key, $value); + } + } + + // ZSet + else if (($_POST['type'] == 'zset') && isset($_POST['score']) && is_numeric($_POST['score'])) { // The only way to edit a ZSet value is to add it and remove the old value. - $redis->zRem($_POST['key'], $_POST['oldvalue']); - $redis->zAdd($_POST['key'], $_POST['score'], $_POST['value']); + $redis->zRem($key, encodeOrDecode('save', $key, input_convert($_POST['oldvalue']))); + $redis->zAdd($key, input_convert($_POST['score']), $value); } - } - // Refresh the top so the key tree is updated. - require 'header.inc.php'; + // Refresh the top so the key tree is updated. + require 'includes/header.inc.php'; - ?> - - + + +