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/Dockerfile b/Dockerfile
index ebca2d9..610dbda 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,12 +1,15 @@
-FROM composer/composer
+FROM composer:2.2
+
+RUN apk add --no-cache tini tzdata
-ADD . /src/app/
WORKDIR /src/app
-RUN \
- composer install && \
- cp includes/config.environment.inc.php includes/config.inc.php
+COPY . .
-EXPOSE 80
+RUN set -xe; \
+ composer install; \
+ cp includes/config.environment.inc.php includes/config.inc.php
-ENTRYPOINT [ "php", "-S", "0.0.0.0:80" ]
+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 aa24a86..0938c08 100644
--- a/README.markdown
+++ b/README.markdown
@@ -39,6 +39,29 @@ cd phpRedisAdmin
git clone https://github.com/nrk/predis.git vendor
```
+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
====
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/composer.json b/composer.json
index fa9bd8b..3b3ab81 100644
--- a/composer.json
+++ b/composer.json
@@ -1,8 +1,8 @@
{
"name": "erik-dubbelboer/php-redis-admin",
"description": "Simple web interface to manage Redis databases.",
- "version": "1.9.0",
- "license": "CC-BY-ND",
+ "version": "1.24.0",
+ "license": "CC-BY-3.0",
"homepage": "https://github.com/ErikDubbelboer/phpRedisAdmin",
"authors": [
{
@@ -13,8 +13,11 @@
}
],
"require": {
- "predis/predis": "1.0.3"
+ "ext-mbstring": "*",
+ "ext-json": "*",
+ "predis/predis": "v2.3.0",
+ "paragonie/random_compat": ">=2"
},
"minimum-stability": "stable",
- "target-dir": "ErikDubbelboer/phpRedisAdmin"
+ "target-dir": "ErikDubbelboer/phpRedisAdmin"
}
diff --git a/composer.lock b/composer.lock
index b9c3078..f724f31 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1,34 +1,85 @@
{
"_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#composer-lock-the-lock-file",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "c4f147b8be07b8af9b1dfaffaa9a7616",
+ "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": "v1.0.3",
+ "version": "v2.3.0",
"source": {
"type": "git",
- "url": "https://github.com/nrk/predis.git",
- "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04"
+ "url": "https://github.com/predis/predis.git",
+ "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nrk/predis/zipball/84060b9034d756b4d79641667d7f9efe1aeb8e04",
- "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04",
+ "url": "https://api.github.com/repos/predis/predis/zipball/bac46bfdb78cd6e9c7926c697012aae740cb9ec9",
+ "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9",
"shasum": ""
},
"require": {
- "php": ">=5.3.2"
+ "php": "^7.2 || ^8.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.0"
+ "friendsofphp/php-cs-fixer": "^3.3",
+ "phpstan/phpstan": "^1.9",
+ "phpunit/phpunit": "^8.0 || ^9.4"
},
"suggest": {
- "ext-curl": "Allows access to Webdis when paired with phpiredis",
- "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
+ "ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
},
"type": "library",
"autoload": {
@@ -42,27 +93,41 @@
],
"authors": [
{
- "name": "Daniele Alessandri",
- "email": "suppakilla@gmail.com",
- "homepage": "http://clorophilla.net/"
+ "name": "Till Krüss",
+ "homepage": "https://till.im/",
+ "role": "Maintainer"
}
],
- "description": "Flexible and feature-complete PHP client library for Redis",
- "homepage": "http://github.com/nrk/predis",
+ "description": "A flexible and feature-complete Redis client for PHP.",
+ "homepage": "http://github.com/predis/predis",
"keywords": [
"nosql",
"predis",
"redis"
],
- "time": "2015-07-30T18:34:15+00:00"
+ "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": [],
+ "stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
- "platform": [],
- "platform-dev": []
+ "platform": {
+ "ext-mbstring": "*",
+ "ext-json": "*"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
}
diff --git a/css/common.css b/css/common.css
index b2a8431..9dc6276 100644
--- a/css/common.css
+++ b/css/common.css
@@ -52,6 +52,6 @@ background: url(http://github.com/images/add.png) left center no-repeat;
.data {
-white-space: pre;
+white-space: pre-wrap;
}
diff --git a/css/frame.css b/css/frame.css
index e54ca9e..7cf935d 100644
--- a/css/frame.css
+++ b/css/frame.css
@@ -14,7 +14,6 @@ margin-left: -8em;
}
form .button {
-margin-left: -7em;
}
@@ -53,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 80ceade..4a56aa9 100644
--- a/css/index.css
+++ b/css/index.css
@@ -3,10 +3,14 @@ position: absolute;
top: 0;
bottom: 0;
left: 0;
+display: flex;
+flex-direction: column;
width: 290px;
height: 100%;
+}
+
+#header {
padding-left: 10px;
-border-right: 1px solid #000;
}
#sidebar a, #sidebar a:visited {
@@ -23,7 +27,9 @@ text-decoration: underline;
#keys {
+flex-grow: 1;
width: 290px;
+margin-top: 10px;
padding-left: 10px;
overflow: auto;
}
@@ -36,6 +42,7 @@ padding: 0;
#keys li {
font-weight: normal;
+white-space: nowrap;
}
#keys li.folder {
@@ -44,6 +51,10 @@ margin-top: .05em;
cursor: pointer;
}
+#keys li.empty a {
+color: #888;
+}
+
#keys li.current a {
background-color: #eee;
}
@@ -111,6 +122,7 @@ background-color: #aaa;
cursor: col-resize;
padding: 0;
margin: 0;
+border-left: 1px solid #000;
}
#resize-layover {
@@ -129,11 +141,12 @@ top: 0;
left: 305px;
right: 0;
bottom: 0;
-padding-left: 2em;
}
#frame iframe {
-width: 100%;
+width: calc(100% - 3em);
height: 100%;
+margin-left: 1.5em;
+margin-right: 1.5em;
}
diff --git a/delete.php b/delete.php
index df51bee..c329919 100644
--- a/delete.php
+++ b/delete.php
@@ -1,13 +1,13 @@
+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 e50da25..998012c 100644
--- a/edit.php
+++ b/edit.php
@@ -2,8 +2,7 @@
require_once 'includes/common.inc.php';
-
-
+global $redis, $config, $csrfToken, $server;
// Are we editing or creating a new key?
$edit = false;
@@ -35,70 +34,83 @@
die('ERROR: could not encode value');
}
- // String
- if ($_POST['type'] == 'string') {
- $redis->set($key, $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($key, input_convert($_POST['hkey']))) {
- $redis->hDel($key, input_convert($_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'].')');
+ }
+
+ if ($edit && !$redis->hExists($key, input_convert($_POST['hkey']))) {
+ $redis->hDel($key, input_convert($_GET['hkey']));
+ }
+
+ $redis->hSet($key, input_convert($_POST['hkey']), $value);
}
- $redis->hSet($key, input_convert($_POST['hkey']), $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');
+ }
+ }
- // List
- else if (($_POST['type'] == 'list') && isset($_POST['index'])) {
- $size = $redis->lLen($key);
-
- if (($_POST['index'] == '') ||
- ($_POST['index'] == $size) ||
- ($_POST['index'] == -1)) {
- // Push it at the end
- $redis->rPush($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');
+ // 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);
+ }
}
- }
- // 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($key, encodeOrDecode('save', $key, input_convert($_POST['oldvalue'])));
+ $redis->zAdd($key, input_convert($_POST['score']), $value);
}
- }
- // ZSet
- else if (($_POST['type'] == 'zset') && isset($_POST['score'])) {
- // The only way to edit a ZSet value is to add it and remove the old 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 'includes/header.inc.php';
- // Refresh the top so the key tree is updated.
- require 'includes/header.inc.php';
+ ?>
+
+
-
-
+
+
getMessage() ?>
+
+
-
+?>
\ No newline at end of file
diff --git a/export.php b/export.php
index ae1f0c3..d17839e 100644
--- a/export.php
+++ b/export.php
@@ -2,6 +2,7 @@
require_once 'includes/common.inc.php';
+global $redis, $config, $csrfToken, $server;
// Export to redis-cli commands
function export_redis($key, $filter = false, $transform = false) {
@@ -15,7 +16,7 @@ function export_redis($key, $filter = false, $transform = false) {
$outputKey = str_replace($filter, $transform, $key);
else
$outputKey = $key;
-
+
// String
if ($type == 'string') {
echo 'SET "',addslashes($outputKey),'" "',addslashes($redis->get($key)),'"',PHP_EOL;
@@ -124,7 +125,7 @@ function export_json($key) {
// JSON
if ($_POST['type'] == 'json') {
-
+
// Single key
if (isset($_GET['key'])) {
echo json_encode(export_json($_GET['key']));
@@ -137,13 +138,13 @@ function export_json($key) {
// if we have a filter and no match, nothing to do
if($filter !== false && stripos($key, $filter) === false)
continue;
-
+
// we rename the keys as necessary
if($filter !== false && $transform !== false)
$outputKey = str_replace($filter, $transform, $key);
else
$outputKey = $key;
-
+
$vals[$outputKey] = export_json($key);
}
@@ -165,7 +166,7 @@ function export_json($key) {
// if we have a filter and no match, we skip
if($filter !== false && stripos($key, $filter) === false)
continue;
-
+
export_redis($key, $filter, $transform);
}
}
@@ -186,7 +187,8 @@ function export_json($key) {
?>
Export
-
flushdb();
diff --git a/import.php b/import.php
index 9dbca76..5e5a89e 100644
--- a/import.php
+++ b/import.php
@@ -1,9 +1,7 @@
Import
-
$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);
- }
- }
+ if (isset($_SESSION['phpredisadmin_csrf'])) {
+ $csrfToken = $_SESSION['phpredisadmin_csrf'];
+ } else {
+ $csrfToken = bin2hex(random_bytes(16));
+ $_SESSION['phpredisadmin_csrf'] = $csrfToken;
}
-
- unset($process);
+} else {
+ $csrfToken = 'nosession';
}
-
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ if ($_POST['csrf'] !== $csrfToken) {
+ die('bad csrf token');
+ }
+}
// These includes are needed by each script.
@@ -46,7 +43,6 @@
$i = 0;
}
-
if (isset($_GET['s']) && is_numeric($_GET['s']) && ($_GET['s'] < count($config['servers']))) {
$i = $_GET['s'];
}
@@ -55,10 +51,8 @@
$server['id'] = $i;
$server['charset'] = isset($server['charset']) && $server['charset'] ? $server['charset'] : false;
-
mb_internal_encoding('utf-8');
-
if (isset($login, $login['servers'])) {
if (array_search($i, $login['servers']) === false) {
die('You are not allowed to access this database.');
@@ -102,7 +96,19 @@
}
if (!isset($server['scansize'])) {
- $server['scansize'] = $config['scansize'];
+ if (isset($config['scansize'])) {
+ $server['scansize'] = $config['scansize'];
+ } else {
+ $server['scansize'] = 1000;
+ }
+}
+
+if (!isset($server['scanmax'])) {
+ if (isset($config['scanmax'])) {
+ $server['scanmax'] = $config['scanmax'];
+ } else {
+ $server['scanmax'] = 0;
+ }
}
if (!isset($server['serialization'])) {
@@ -111,11 +117,23 @@
}
}
+if (!isset($config['hideEmptyDBs'])) {
+ $config['hideEmptyDBs'] = false;
+}
+
+if (!isset($config['showEmptyNamespaceAsKey'])) {
+ $config['showEmptyNamespaceAsKey'] = false;
+}
+
+if (!isset($server['scheme']) || empty($server['scheme'])) {
+ $server['scheme'] = 'tcp';
+}
+
// Setup a connection to Redis.
-if(isset($server['scheme']) && $server['scheme'] === 'unix' && $server['path']) {
+if ($server['scheme'] === 'unix' && $server['path']) {
$redis = new Predis\Client(array('scheme' => 'unix', 'path' => $server['path']));
} else {
- $redis = !$server['port'] ? new Predis\Client($server['host']) : new Predis\Client('tcp://'.$server['host'].':'.$server['port']);
+ $redis = !$server['port'] ? new Predis\Client($server['host']) : new Predis\Client($server['scheme'].'://'.$server['host'].':'.$server['port']);
}
try {
@@ -130,11 +148,12 @@
}
}
+if (!isset($config['login']) && !empty($config['login_as_acl_auth'])) {
+ require_once PHPREDIS_ADMIN_PATH . '/includes/login_acl.inc.php';
+}
if ($server['db'] != 0) {
if (!$redis->select($server['db'])) {
die('ERROR: Selecting database failed ('.$server['host'].':'.$server['port'].','.$server['db'].')');
}
}
-
-?>
diff --git a/includes/config.environment.inc.php b/includes/config.environment.inc.php
index bb7c76c..06a06fe 100644
--- a/includes/config.environment.inc.php
+++ b/includes/config.environment.inc.php
@@ -2,6 +2,19 @@
include 'config.sample.inc.php';
+// get configs from environment variables
+$config['cookie_auth'] = getenv('COOKIE_AUTH') ?: false;
+$config['count_elements_page'] = getenv('COUNT_ELEMENTS_PAGE') ?: 100;
+$config['faster'] = getenv('FASTER') ?: true;
+$config['filter'] = getenv('FILTER') ?: '*';
+$config['hideEmptyDBs'] = getenv('HIDE_EMPTY_DBS') ?: false;
+$config['keys'] = getenv('KEYS') ?: false;
+$config['maxkeylen'] = getenv('MAX_KEY_LEN') ?: 100;
+$config['scansize'] = getenv('SCAN_SIZE') ?: 1000;
+$config['scanmax'] = getenv('SCAN_MAX') ?: 1000;
+$config['seperator'] = getenv('SEPERATOR') ?: ':';
+$config['showEmptyNamespaceAsKey'] = getenv('SHOW_EMPTY_NAMESPACE_AS_KEY') ?: false;
+
$admin_user = getenv('ADMIN_USER');
$admin_pass = getenv('ADMIN_PASS');
@@ -14,14 +27,22 @@
}
$i=1;
+$config['servers'] = array();
-while (TRUE) {
+while (true) {
$prefix = 'REDIS_' . $i . '_';
$server_name = getenv($prefix . 'NAME');
$server_host = getenv($prefix . 'HOST');
$server_port = getenv($prefix . 'PORT');
+ $server_scheme = getenv($prefix . 'SCHEME');
+ if (getenv($prefix . 'AUTH_FILE') !== false) {
+ $server_auth = file_get_contents(getenv($prefix . 'AUTH_FILE'));
+ } else {
+ $server_auth = getenv($prefix . 'AUTH');
+ }
+ $server_databases = getenv($prefix . 'DATABASES');
if (empty($server_host)) {
break;
@@ -31,16 +52,35 @@
$server_name = $server_host;
}
- if (empty($server_port)) {
+ if (empty($server_auth)) {
+ $server_auth = "";
+ }
+
+ if (empty($server_port) && strpos($server_host, ':') === false) {
$server_port = 6379;
}
+ if (empty($server_scheme)) {
+ $server_scheme = 'tcp';
+ }
+
$config['servers'][] = array(
- 'name' => $server_name,
- 'host' => $server_host,
- 'port' => $server_port,
- 'filter' => '*',
+ 'name' => $server_name,
+ 'host' => $server_host,
+ 'port' => $server_port,
+ 'filter' => $config['filter'],
+ 'scansize' => $config['scansize'],
+ 'scanmax' => $config['scanmax'],
+ 'scheme' => $server_scheme,
);
+ if (!empty($server_auth)) {
+ $config['servers'][$i-1]['auth'] = $server_auth;
+ }
+
+ if (!empty($server_databases)) {
+ $config['servers'][$i-1]['databases'] = $server_databases;
+ }
+
$i++;
}
diff --git a/includes/config.sample.inc.php b/includes/config.sample.inc.php
index c11b25b..923a60c 100644
--- a/includes/config.sample.inc.php
+++ b/includes/config.sample.inc.php
@@ -9,7 +9,8 @@
'port' => 6379,
'filter' => '*',
'scheme' => 'tcp', // Optional. Connection scheme. 'tcp' - for TCP connection, 'unix' - for connection by unix domain socket
- 'path' => '' // Optional. Path to unix domain socket. Uses only if 'scheme' => 'unix'. Example: '/var/run/redis/redis.sock'
+ 'path' => '', // Optional. Path to unix domain socket. Uses only if 'scheme' => 'unix'. Example: '/var/run/redis/redis.sock'
+ 'hide' => false, // Optional. Override global setting. Hide empty databases in the database list.
// Optional Redis authentication.
//'auth' => 'redispasswordhere' // Warning: The password is sent in plain-text to the Redis server.
@@ -31,13 +32,17 @@
'flush' => false, // Set to true to enable the flushdb button for this instance.
'charset' => 'cp1251', // Keys and values are stored in redis using this encoding (default utf-8).
'keys' => false, // Use the old KEYS command instead of SCAN to fetch all keys for this server (default uses config default).
- 'scansize' => 1000 // How many entries to fetch using each SCAN command for this server (default uses config default).
+ 'scansize' => 1000, // How many entries to fetch using each SCAN command for this server (default uses config default).
+ 'scanmax' => 1000, // In each query, SCAN command may be executed several times. To shorten the duration, it is recommended to limit the total number of entries to fetch (default uses config default).
),*/
),
'seperator' => ':',
+ 'showEmptyNamespaceAsKey' => false,
+ // Hide empty databases in the database list (global, valid for all servers unless set at server level)
+ 'hideEmptyDBs' => false,
// Uncomment to show less information and make phpRedisAdmin fire less commands to the Redis server. Recommended for a really busy Redis server.
//'faster' => true,
@@ -56,6 +61,12 @@
)
),*/
+ // Uncomment to enable login as ACL authentication (won't work if 'login' or 'auth' is also used)
+ // Only support using one server at this moment.
+ // If you set the default user off, browsers will be redirected to login page.
+ // The user and password will be stored in browser as plaintext so using HTTPS is strongly recommended.
+ // 'login_as_acl_auth' => true,
+
// Use HTML form/cookie-based auth instead of HTTP Basic/Digest auth
'cookie_auth' => false,
@@ -79,7 +90,8 @@
'keys' => false,
// How many entries to fetch using each SCAN command.
- 'scansize' => 1000
-);
+ 'scansize' => 1000,
-?>
+ // The total number of entries to fetch. Set to 0 or -1 for no limit.
+ 'scanmax' => 0
+);
diff --git a/includes/functions.inc.php b/includes/functions.inc.php
index f884260..589d324 100644
--- a/includes/functions.inc.php
+++ b/includes/functions.inc.php
@@ -26,39 +26,30 @@ function input_convert($str) {
}
-function format_ago($time, $ago = false) {
+function format_time($time) {
$minute = 60;
$hour = $minute * 60;
$day = $hour * 24;
$when = $time;
- if ($when >= 0)
- $suffix = 'ago';
- else {
- $when = -$when;
- $suffix = 'in the future';
- }
-
if ($when > $day) {
- $when = round($when / $day);
- $what = 'day';
+ $tmpday = floor($when / $day);
+ $tmphour = floor(($when / $hour) - (24*$tmpday));
+ $tmpminute = floor(($when / $minute) - (24*60*$tmpday) - ($tmphour * 60));
+ $tmpsec = floor($when - (24*60*60*$tmpday) - ($tmphour * 60 * 60) - ($tmpminute * 60));
+ return sprintf("%d day%s %d hour%s %d min%s %d sec%s",$tmpday,($tmpday != 1) ? 's' : '',$tmphour,($tmphour != 1) ? 's' : '',$tmpminute,($tmpminute != 1) ? 's' : '',$tmpsec,($tmpsec != 1) ? 's' : '');
} else if ($when > $hour) {
- $when = round($when / $hour);
- $what = 'hour';
+ $tmphour = floor($when / $hour);
+ $tmpminute = floor(($when / $minute) - ($tmphour * 60));
+ $tmpsec = floor($when - ($tmphour * 60 * 60) - ($tmpminute * 60));
+ return sprintf("%d hour%s %d min%s %d sec%s",$tmphour,($tmphour != 1) ? 's' : '',$tmpminute,($tmpminute != 1) ? 's' : '',$tmpsec,($tmpsec != 1) ? 's' : '');
} else if ($when > $minute) {
- $when = round($when / $minute);
- $what = 'minute';
+ $tmpminute = floor($when / $minute);
+ $tmpsec = floor($when - ($tmpminute * 60));
+ return sprintf("%d min%s %d sec%s",$tmpminute,($tmpminute != 1) ? 's' : '',$tmpsec,($tmpsec != 1) ? 's' : '');
} else {
- $what = 'second';
- }
-
- if ($when != 1) $what .= 's';
-
- if ($ago) {
- return "$when $what $suffix";
- } else {
- return "$when $what";
+ return sprintf("%d sec%s",$when,($when != 1) ? 's' : '');
}
}
@@ -76,7 +67,7 @@ function format_size($size) {
function format_ttl($seconds) {
if ($seconds > 60) {
- return sprintf('%d (%s)', $seconds, format_ago($seconds));
+ return sprintf('%d (%s)', $seconds, format_time($seconds));
} else {
return $seconds;
}
@@ -110,3 +101,6 @@ function encodeOrDecode($action, $key, $data) {
return $data;
}
+function getRelativePath($base) {
+ return substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $base));
+}
diff --git a/includes/header.inc.php b/includes/header.inc.php
index aa618f1..7679bd6 100644
--- a/includes/header.inc.php
+++ b/includes/header.inc.php
@@ -4,6 +4,7 @@
header('Content-Type: text/html; charset=utf-8');
header('Cache-Control: private');
+header('X-Frame-Options: sameorigin');
?>
@@ -35,5 +36,9 @@
+
+
diff --git a/includes/login.inc.php b/includes/login.inc.php
index f1abce6..8cfb26d 100644
--- a/includes/login.inc.php
+++ b/includes/login.inc.php
@@ -56,7 +56,7 @@ function authHttpDigest()
$response = md5($password.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']));
- if ($data['response'] != $response) {
+ if ($data['response'] !== $response) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.'",qop="auth",nonce="'.uniqid().'",opaque="'.$opaque.'"');
die('Invalid username and/or password combination.');
@@ -80,12 +80,12 @@ function authCookie()
// Using SHA512 because MD5, SHA1 are both now considered broken
return hash(
'sha512',
- implode(':', [
+ implode(':', array(
$username,
$_SERVER['HTTP_USER_AGENT'],
$_SERVER['REMOTE_ADDR'],
$config['login'][$username]['password'],
- ])
+ ))
);
};
diff --git a/includes/login_acl.inc.php b/includes/login_acl.inc.php
new file mode 100644
index 0000000..e8b7d72
--- /dev/null
+++ b/includes/login_acl.inc.php
@@ -0,0 +1,100 @@
+auth($login['username'], $login['password']);
+ } catch (Predis\Response\ServerException $exception) {
+ return false;
+ }
+ return true;
+}
+
+// This fill will perform HTTP basic authentication. The authentication data will be sent and stored as plaintext
+// Please make sure to use HTTPS proxy such as Apache or Nginx to prevent traffic eavesdropping.
+function authHttpBasic()
+{
+ $realm = 'phpRedisAdmin';
+
+ if (!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
+ try {
+ global $redis;
+ $redis->ping();
+ } catch (Predis\Response\ServerException $exception) {
+ header('HTTP/1.1 401 Unauthorized');
+ header('WWW-Authenticate: Basic realm="' . $realm . '"');
+ die('NOAUTH -- Authentication required');
+ }
+ }
+
+ $login = [
+ 'username' => $_SERVER['PHP_AUTH_USER'],
+ 'password' => $_SERVER['PHP_AUTH_PW'],
+ ];
+
+ if (!authCheck($login)) {
+ header('HTTP/1.1 401 Unauthorized');
+ header('WWW-Authenticate: Basic realm="' . $realm . '"');
+ die('NOAUTH -- Authentication required');
+ }
+
+ return $login;
+}
+
+// Perform auth using a standard HTML