├── public
├── js
│ ├── nvd3
│ │ ├── nv.d3.min.js
│ │ ├── src
│ │ │ ├── outro.js
│ │ │ ├── intro.js
│ │ │ ├── core.js
│ │ │ ├── utils.js
│ │ │ ├── tooltip.js
│ │ │ └── models
│ │ │ │ ├── distribution.js
│ │ │ │ ├── sparkline.js
│ │ │ │ ├── lineWithFisheye.js
│ │ │ │ └── legend.js
│ │ ├── .gitignore
│ │ └── lib
│ │ │ ├── fisheye.js
│ │ │ ├── hive.js
│ │ │ ├── cie.js
│ │ │ ├── horizon.js
│ │ │ └── crossfilter.min.js
│ ├── jquery-ui
│ │ └── css
│ │ │ └── images
│ │ │ ├── ui-icons_222222_256x240.png
│ │ │ ├── ui-icons_4b8e0b_256x240.png
│ │ │ ├── ui-icons_a83300_256x240.png
│ │ │ ├── ui-icons_cccccc_256x240.png
│ │ │ ├── ui-icons_ffffff_256x240.png
│ │ │ ├── ui-bg_flat_30_cccccc_40x100.png
│ │ │ ├── ui-bg_flat_50_5c5c5c_40x100.png
│ │ │ ├── ui-bg_glass_20_555555_1x400.png
│ │ │ ├── ui-bg_glass_40_0078a3_1x400.png
│ │ │ ├── ui-bg_glass_40_ffc73d_1x400.png
│ │ │ ├── ui-bg_gloss-wave_25_333333_500x100.png
│ │ │ ├── ui-bg_inset-soft_25_000000_1x100.png
│ │ │ ├── ui-bg_inset-soft_30_f58400_1x100.png
│ │ │ └── ui-bg_highlight-soft_80_eeeeee_1x100.png
│ └── redmin
│ │ ├── remlists.js
│ │ ├── strings.js
│ │ ├── hashes.js
│ │ ├── zsets.js
│ │ ├── sets.js
│ │ ├── lists.js
│ │ ├── modal.js
│ │ ├── main.js
│ │ ├── terminal.js
│ │ └── actions.js
├── .htaccess
├── img
│ └── ajax-loader.gif
├── font-awesome
│ ├── font
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.ttf
│ │ └── fontawesome-webfont.woff
│ └── .gitignore
├── bootstrap
│ └── img
│ │ ├── glyphicons-halflings.png
│ │ └── glyphicons-halflings-white.png
├── index.php
└── css
│ └── custom.css
├── views
├── 404.php
├── welcome
│ ├── config.php
│ ├── slowlog.php
│ ├── info.php
│ └── stats.php
├── strings
│ ├── add.php
│ └── view.php
├── sets
│ ├── add.php
│ ├── edit.php
│ └── view.php
├── hashes
│ ├── edit.php
│ ├── add.php
│ └── view.php
├── keys
│ ├── move.php
│ ├── rename.php
│ ├── ttl.php
│ └── search.php
├── zsets
│ ├── add.php
│ └── view.php
├── lists
│ ├── add.php
│ └── view.php
├── terminal
│ └── index.php
├── navigation.php
└── generalmodals.php
├── .dockerignore
├── .gitignore
├── docker
├── php.ini
├── crontab
├── default.conf
└── start.sh
├── libraries
├── drivers
│ ├── template
│ │ ├── json.php
│ │ └── php.php
│ ├── db
│ │ └── redis.php
│ └── log
│ │ ├── std.php
│ │ └── file.php
├── template.php
├── model.php
├── db.php
├── log.php
├── session.php
├── app.php
├── inputs.php
├── error.php
├── controller.php
└── router.php
├── models
├── info.php
└── stats.php
├── controllers
├── actions.php
├── strings.php
├── zsets.php
├── gearman.php
├── cron.php
├── welcome.php
├── hashes.php
├── lists.php
├── sets.php
├── terminal.php
├── stats.php
└── keys.php
├── Dockerfile
├── LICENSE
├── config.dist.php
└── helpers
└── redis.php
/public/js/nvd3/nv.d3.min.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/js/nvd3/src/outro.js:
--------------------------------------------------------------------------------
1 | })();
--------------------------------------------------------------------------------
/views/404.php:
--------------------------------------------------------------------------------
1 | Page not found
2 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | logs/
2 | README.md
3 |
--------------------------------------------------------------------------------
/public/js/nvd3/src/intro.js:
--------------------------------------------------------------------------------
1 | (function(){
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.swo
3 | *~
4 | logs/
5 | /nbproject/private/
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 | # Enable the short tags for PHP scripts
2 | php_value short_open_tag 1
--------------------------------------------------------------------------------
/docker/php.ini:
--------------------------------------------------------------------------------
1 | variables_order = "EGPCS"
2 | log_errors = On
3 | error_log = /dev/stderr
4 |
--------------------------------------------------------------------------------
/public/img/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/img/ajax-loader.gif
--------------------------------------------------------------------------------
/public/font-awesome/font/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/font-awesome/font/FontAwesome.otf
--------------------------------------------------------------------------------
/public/bootstrap/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/bootstrap/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/public/font-awesome/font/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/font-awesome/font/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/public/font-awesome/font/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/font-awesome/font/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/public/font-awesome/font/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/font-awesome/font/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/public/bootstrap/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/bootstrap/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-icons_222222_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-icons_222222_256x240.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-icons_4b8e0b_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-icons_4b8e0b_256x240.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-icons_a83300_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-icons_a83300_256x240.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-icons_cccccc_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-icons_cccccc_256x240.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_flat_30_cccccc_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_flat_30_cccccc_40x100.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_flat_50_5c5c5c_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_flat_50_5c5c5c_40x100.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_glass_20_555555_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_glass_20_555555_1x400.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_glass_40_0078a3_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_glass_40_0078a3_1x400.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_glass_40_ffc73d_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_glass_40_ffc73d_1x400.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_gloss-wave_25_333333_500x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_gloss-wave_25_333333_500x100.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_inset-soft_25_000000_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_inset-soft_25_000000_1x100.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_inset-soft_30_f58400_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_inset-soft_30_f58400_1x100.png
--------------------------------------------------------------------------------
/public/js/jquery-ui/css/images/ui-bg_highlight-soft_80_eeeeee_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasanrose/phpredmin/HEAD/public/js/jquery-ui/css/images/ui-bg_highlight-soft_80_eeeeee_1x100.png
--------------------------------------------------------------------------------
/docker/crontab:
--------------------------------------------------------------------------------
1 | # Cron tab for phpredmin to collect redis statistics
2 | * * * * * root . /root/.profile; cd /var/www/html/phpredmin/public && /usr/local/bin/php index.php cron/index > /dev/stdout 2>&1
3 |
--------------------------------------------------------------------------------
/docker/default.conf:
--------------------------------------------------------------------------------
1 |
2 | ServerAdmin webmaster@localhost
3 | DocumentRoot /var/www/html/phpredmin/public
4 |
5 | ErrorLog ${APACHE_LOG_DIR}/error.log
6 | CustomLog ${APACHE_LOG_DIR}/access.log combined
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/js/nvd3/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Jekyll Files #
3 | ################
4 | _site
5 |
6 |
7 | # Random Files #
8 | ################
9 | *.swp
10 | *~
11 | *.log
12 |
13 |
14 | # Private Test Data #
15 | #####################
16 | *REALDATA*
17 |
18 |
19 | # OS generated files #
20 | ######################
21 | .DS_Store*
22 | ehthumbs.db
23 | Icon?
24 | Thumbs.db
25 |
--------------------------------------------------------------------------------
/libraries/drivers/template/json.php:
--------------------------------------------------------------------------------
1 | > $HOME/.profile
5 |
6 | # Run gearman job server
7 | gearmand -d
8 | # Run cron daemon
9 | cron
10 |
11 | # Trap sigkill
12 | trap "pkill gearmand && pkill -WINCH apache2" TERM
13 |
14 | # Run gearman work & Start web server
15 | php index.php gearman/index & apache2-foreground
16 |
--------------------------------------------------------------------------------
/public/font-awesome/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.egg-info
3 | *.db
4 | *.db.old
5 | *.swp
6 | *.db-journal
7 |
8 | .coverage
9 | .DS_Store
10 | .installed.cfg
11 |
12 | .idea/*
13 | .svn/*
14 | src/website/static/*
15 | src/website/media/*
16 |
17 | bin
18 | build
19 | cfcache
20 | develop-eggs
21 | dist
22 | downloads
23 | eggs
24 | parts
25 | tmp
26 | .sass-cache
27 |
28 | src/website/settingslocal.py
29 | stunnel.log
--------------------------------------------------------------------------------
/models/info.php:
--------------------------------------------------------------------------------
1 | ').appendTo($('#list_remove_type'));
8 | }
9 | } else {
10 | $('#list_remove_type').empty();
11 | }
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/libraries/drivers/db/redis.php:
--------------------------------------------------------------------------------
1 | connect($config['host'], $config['port']);
7 |
8 | if (isset($config['password'])) {
9 | $this->auth($config['password']);
10 | }
11 |
12 | $this->select($config['database']);
13 | }
14 |
15 | public function changeDB($db)
16 | {
17 | $this->select($db);
18 |
19 | return $this;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/views/welcome/config.php:
--------------------------------------------------------------------------------
1 |
2 |
Redis Config
3 |
4 | config as $key => $value): ?>
5 |
6 |
7 | =$key?>
8 |
9 |
10 | =is_numeric($value) ? number_format($value) : $value?>
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/views/strings/add.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
13 |
--------------------------------------------------------------------------------
/libraries/template.php:
--------------------------------------------------------------------------------
1 | drivers.'template/'.(strtolower($driver)).'.php');
12 |
13 | $class = ucwords(strtolower($driver)).'Template';
14 | self::$_instances[$driver] = new $class;
15 | }
16 |
17 | return self::$_instances[$driver];
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libraries/model.php:
--------------------------------------------------------------------------------
1 | _objects['app'] = App::instance();
9 | $this->_objects['router'] = Router::instance();
10 | $this->_objects['session'] = Session::instance();
11 | $this->_objects['db'] = Db::factory($config);
12 | $this->_objects['log'] = Log::factory();
13 | }
14 |
15 | public function __get($object)
16 | {
17 | return isset($this->_objects[$object]) ? $this->_objects[$object] : null;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libraries/db.php:
--------------------------------------------------------------------------------
1 | config['database']['driver'];
9 |
10 | $instanceName = $driver . ':' . $config['host'] . ':' . $config['port'];
11 |
12 | if (!isset(self::$_instances[$instanceName])) {
13 | include_once(App::instance()->drivers.'db/'.(strtolower($driver)).'.php');
14 |
15 | $class = ucwords(strtolower($driver)).'Db';
16 | self::$_instances[$instanceName] = new $class($config);
17 | }
18 |
19 | return self::$_instances[$instanceName];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/libraries/drivers/log/std.php:
--------------------------------------------------------------------------------
1 | config['log']['threshold'] < Log::instance()->$type) {
7 | return;
8 | }
9 |
10 | $ip = isset($_SERVER['REMOTE_ADDR']) ? "[{$_SERVER['REMOTE_ADDR']}]" : '[Unknown Address]';
11 | $namespace = isset($namespace) ? '['.ucwords(strtolower($namespace)).']' : '';
12 |
13 | if (in_array($type, [Log::ERROR, Log::NOTICE, Log::WARNING])) {
14 | $stream = fopen('php://stderr', 'w');
15 | } else {
16 | $stream = fopen('php://stdout', 'w');
17 | }
18 |
19 | fwrite($stream, "PHPREDMIN: {$ip} {$namespace} [{$type}]: {$msg}\n");
20 | fclose($stream);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/controllers/actions.php:
--------------------------------------------------------------------------------
1 | statsModel = new Stats_Model($this->app->current);
10 | $this->template = Template::factory('json');
11 | }
12 |
13 | public function resetAction()
14 | {
15 | $this->statsModel->resetStats();
16 |
17 | $this->template->render(true);
18 | }
19 |
20 | public function fallAction()
21 | {
22 | $this->db->flushAll();
23 |
24 | $this->template->render(true);
25 | }
26 |
27 | public function fdbAction()
28 | {
29 | $this->db->flushDB();
30 |
31 | $this->template->render(true);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/views/strings/view.php:
--------------------------------------------------------------------------------
1 |
2 |
Edit Value
3 | edited) && $this->edited): ?>
4 |
5 |
×
6 | Key edited successfuly
7 |
8 | edited)): ?>
9 |
10 |
×
11 | There was a problem editing the key
12 |
13 |
14 |
15 | =$this->key?>
16 |
17 | =$this->value?>
18 |
19 |
20 | Edit
21 |
22 |
23 |
--------------------------------------------------------------------------------
/views/sets/add.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 | = isset($this->oldkey) ? "" : "Add Set"; ?>
4 |
5 |
6 | oldkey)): ?>
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Add
17 | oldkey)): ?>
18 | Add & Edit
19 |
20 |
21 |
--------------------------------------------------------------------------------
/public/js/redmin/strings.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#add_string').click(function(e) {
3 | e.preventDefault();
4 |
5 | var form = $(e.target).parents('form');
6 | var key = form.find('input[name="key"]').val().trim();
7 | var str = form.find('textarea[name="value"]').val().trim();
8 |
9 | if (key != '' && str != '') {
10 | $.ajax({
11 | url: baseurl+'/strings/add/' + currentServerDb,
12 | dataType: 'json',
13 | type: 'POST',
14 | data: 'key='+key+'&value='+str,
15 | success: function(data) {
16 | form.find('input').val('');
17 | form.find('textarea').val('');
18 |
19 | if (data)
20 | modalPopup.alert('saved');
21 | else
22 | modalPopup.alert('error');
23 | }
24 | });
25 | } else {
26 | modalPopup.alert('invalid')
27 | }
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/views/hashes/edit.php:
--------------------------------------------------------------------------------
1 |
2 |
Edit hash key
3 | edited) && $this->edited): ?>
4 |
5 |
×
6 | Hash key edited successfuly
7 |
8 | edited)): ?>
9 |
10 |
×
11 | There was a problem editing the hash key
12 |
13 |
14 |
15 | =$this->key?> / =$this->member?>
16 |
17 | =$this->value?>
18 |
19 |
20 |
21 | Edit
22 |
23 |
24 |
--------------------------------------------------------------------------------
/views/keys/move.php:
--------------------------------------------------------------------------------
1 |
2 |
Move key
3 | moved) && $this->moved): ?>
4 |
5 |
×
6 | Key moved successfuly
7 |
8 | moved)): ?>
9 |
10 |
×
11 | There was a problem moving the key
12 |
13 |
14 | moved) || (isset($this->moved) && !$this->moved)): ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 | Move
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/views/keys/rename.php:
--------------------------------------------------------------------------------
1 |
2 |
Rename key
3 | renamed) && $this->renamed): ?>
4 |
5 |
×
6 | Key renamed successfuly
7 |
8 | renamed)): ?>
9 |
10 |
×
11 | There was a problem renaming the key
12 |
13 |
14 | renamed) || (isset($this->renamed) && !$this->renamed)): ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 | Rename
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:5.6-apache
2 |
3 | MAINTAINER sasan.rose@gmail.com
4 |
5 | RUN apt-get update && apt-get install -y \
6 | cron \
7 | gearman-job-server \
8 | git-core \
9 | libgearman-dev \
10 | redis-tools \
11 | && pecl install gearman \
12 | && docker-php-ext-enable gearman
13 |
14 |
15 | EXPOSE 80
16 |
17 | COPY docker/default.conf /etc/apache2/sites-available/000-default.conf
18 | COPY docker/php.ini /usr/local/etc/php/
19 | COPY docker/start.sh /usr/src/start.sh
20 |
21 | WORKDIR /etc/cron.d
22 | COPY docker/crontab phpredmin
23 | RUN chmod 0644 phpredmin
24 |
25 | WORKDIR /usr/src
26 |
27 | RUN git clone https://github.com/phpredis/phpredis.git
28 | WORKDIR /usr/src/phpredis
29 | # Version 3 has a bug with zAdd so checkout to 2.2.8
30 | RUN git checkout tags/2.2.8 \
31 | && phpize \
32 | && ./configure \
33 | && make \
34 | && make install \
35 | && docker-php-ext-enable redis
36 |
37 | WORKDIR /var/www/html
38 | COPY . phpredmin/
39 |
40 | # Clean up
41 | RUN rm -rf /usr/src/phpredis
42 |
43 | RUN apt-get --purge remove -y git-core \
44 | && apt-get clean
45 |
46 | ENV PHPREDMIN_LOG_DRIVER="std"
47 | ENV PHPREDMIN_LOG_THRESHOLD="4"
48 |
49 | WORKDIR /var/www/html/phpredmin/public
50 |
51 | RUN chmod u+x /usr/src/start.sh
52 | CMD ["/usr/src/start.sh"]
53 |
--------------------------------------------------------------------------------
/views/hashes/add.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 | oldkey)) {
4 | echo "";
5 | } else {
6 | echo "Add Hash";
7 | } ?>
8 |
9 |
10 | oldkey)): ?>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Add
25 | oldkey)): ?>
26 | Add & Edit
27 |
28 |
29 |
--------------------------------------------------------------------------------
/views/zsets/add.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 | oldkey)) {
4 | echo "";
5 | } else {
6 | echo "Add Sorted Set";
7 | } ?>
8 |
9 |
10 | oldkey)): ?>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Add
25 | oldkey)): ?>
26 | Add & Edit
27 |
28 |
29 |
--------------------------------------------------------------------------------
/views/sets/edit.php:
--------------------------------------------------------------------------------
1 |
2 | edited) && $this->edited): ?>
3 |
4 |
×
5 | Set Key member edited successfully
6 |
7 | edited)): ?>
8 |
9 |
×
10 | There was a problem editing the Set Key member
11 |
12 |
13 | edited) || (isset($this->edited) && !$this->edited)): ?>
14 |
15 | Edit Set key
16 | =$this->key?> / =$this->member?>
17 |
18 | =$this->member?>
19 |
20 |
21 |
22 | Edit
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/controllers/strings.php:
--------------------------------------------------------------------------------
1 | router->method == Router::POST) {
10 | $newvalue = $this->inputs->post('newvalue', null);
11 | $key = $this->inputs->post('key', null);
12 |
13 | if (!isset($newvalue) || trim($newvalue) == '' || !isset($key) || trim($key) == '') {
14 | $edited = false;
15 | } else {
16 | $edited = $this->db->set($key, $newvalue);
17 | }
18 | }
19 |
20 | $value = $this->db->get(urldecode($key));
21 |
22 | Template::factory()->render('strings/view', array('edited' => $edited, 'key' => urldecode($key), 'value' => $value));
23 | }
24 |
25 | public function addAction()
26 | {
27 | $added = false;
28 |
29 | if ($this->router->method == Router::POST) {
30 | $value = $this->inputs->post('value', null);
31 | $key = $this->inputs->post('key', null);
32 |
33 | if (isset($value) && trim($value) != '' && isset($key) && trim($key) != '') {
34 | $added = $this->db->set($key, $value);
35 | }
36 | }
37 |
38 | Template::factory('json')->render($added);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/libraries/log.php:
--------------------------------------------------------------------------------
1 | 1,
10 | 'notice' => 2,
11 | 'info' => 3,
12 | 'warning' => 4,
13 | 'debug' => 5
14 | );
15 |
16 | const INFO = 'info';
17 | const DEBUG = 'debug';
18 | const ERROR = 'error';
19 | const WARNING = 'warning';
20 | const NOTICE = 'notice';
21 |
22 | public static function instance()
23 | {
24 | if (!isset(self::$_instance)) {
25 | self::$_instance = new self;
26 | }
27 |
28 | return self::$_instance;
29 | }
30 |
31 | public static function factory($driver = null)
32 | {
33 | $driver = isset($driver) ? $driver : App::instance()->config['log']['driver'];
34 |
35 | if (!isset(self::$_instances[$driver])) {
36 | include_once(App::instance()->drivers.'log/'.(strtolower($driver)).'.php');
37 |
38 | $class = ucwords(strtolower($driver)).'Log';
39 | self::$_instances[$driver] = new $class;
40 | }
41 |
42 | return self::$_instances[$driver];
43 | }
44 |
45 | public function __get($key)
46 | {
47 | return isset($this->_levels[$key]) ? $this->_levels[$key] : 0;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/views/lists/add.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 | = isset($this->oldkey) ? "" : "Add List" ?>
4 |
5 |
6 | oldkey)): ?>
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Prepend
19 | Append
20 | After
21 | Before
22 |
23 |
24 |
25 |
26 | Add
27 | oldkey)): ?>
28 | Add & Edit
29 |
30 |
31 |
--------------------------------------------------------------------------------
/views/terminal/index.php:
--------------------------------------------------------------------------------
1 | config['terminal']['enable']): ?>
2 |
3 |
4 | addHeader(""); ?>
5 |
6 | redis = $this->app->current['host'] ?>:= $this->app->current['port'] ?>>
7 |
8 |
9 |
15 |
16 |
17 |
18 |
×
19 | This functionaliy takes advantage of
PHP's exec function . Although, all the commands are escaped for security, you can disable terminal from configuration file.
20 |
21 |
22 |
23 |
24 | Terminal is not enabled.
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright © 2013, Sasan Rose
4 |
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
8 |
9 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
10 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
11 | Neither the name of the PHPRedMin nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 |
--------------------------------------------------------------------------------
/libraries/drivers/log/file.php:
--------------------------------------------------------------------------------
1 | config['log']['file']['directory'];
10 |
11 | $this->threshold = App::instance()->config['log']['threshold'];
12 |
13 | if ($this->threshold > 0) {
14 | if (!$config_dir) {
15 | die('Please provide a log directory in your config file');
16 | } else {
17 | $this->_dir = dirname(__FILE__).'/../../../'.$config_dir.'/'.PHP_SAPI.'/';
18 |
19 | if (!is_writable($this->_dir)) {
20 | if (!mkdir($this->_dir, 0755, true)) {
21 | die("{$this->_dir} does not exist or is not writable");
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
28 | public function write($type, $msg, $namespace = null)
29 | {
30 | if ($this->threshold < Log::instance()->$type) {
31 | return;
32 | }
33 |
34 | $logfile = $this->_dir.date('Y-m-d').'.log';
35 |
36 | if (($file = fopen($logfile, 'a+')) === false) {
37 | die('Can not open file: '.$logfile);
38 | }
39 |
40 | $ip = isset($_SERVER['REMOTE_ADDR']) ? "[{$_SERVER['REMOTE_ADDR']}]" : '';
41 | $namespace = isset($namespace) ? '['.ucwords(strtolower($namespace)).']' : '';
42 | $date = '['.date('Y-m-d H:i:s').']';
43 |
44 | fwrite($file, "{$date} {$ip} {$namespace} [{$type}]: {$msg}\n");
45 | fclose($file);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/libraries/session.php:
--------------------------------------------------------------------------------
1 | config['session']['gc_probability']);
10 | ini_set('session.gc_divisor', 100);
11 | ini_set('session.gc_maxlifetime', App::instance()->config['session']['lifetime']);
12 |
13 | session_name(App::instance()->config['session']['name']);
14 | session_start();
15 | }
16 |
17 | public static function instance()
18 | {
19 | if (!isset(self::$_instance)) {
20 | self::$_instance = new self;
21 | }
22 |
23 | return self::$_instance;
24 | }
25 |
26 | public function set($key, $value)
27 | {
28 | $_SESSION[$key] = $value;
29 |
30 | session_write_close();
31 | }
32 |
33 | public function get($key, $default = null)
34 | {
35 | return isset($_SESSION[$key]) ? $_SESSION[$key] : $default;
36 | }
37 |
38 | public function get_once($key, $default = null)
39 | {
40 | $result = $this->get($key, $default);
41 |
42 | unset($_SESSION[$key]);
43 |
44 | return $result;
45 | }
46 |
47 | public function del($key)
48 | {
49 | unset($_SESSION[$key]);
50 | }
51 |
52 | public function has($key)
53 | {
54 | return isset($_SESSION[$key]);
55 | }
56 |
57 | public function __set($key, $value)
58 | {
59 | $this->set($key, $value);
60 | }
61 |
62 | public function __get($key)
63 | {
64 | return $this->get($key);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/libraries/app.php:
--------------------------------------------------------------------------------
1 | _data['config'] = include_once(file_exists('../config.php') ? '../config.php' : '../config.dist.php');
11 | $this->_data['drivers'] = 'drivers/';
12 |
13 | $this->readEnvConfig();
14 | }
15 |
16 | public static function instance()
17 | {
18 | if (!self::$_instance) {
19 | self::$_instance = new self;
20 | }
21 |
22 | return self::$_instance;
23 | }
24 |
25 | public function __get($key)
26 | {
27 | return isset($this->_data[$key]) ? $this->_data[$key] : null;
28 | }
29 |
30 | public function __set($key, $value)
31 | {
32 | $this->_data[$key] = $value;
33 | }
34 |
35 | protected function readEnvConfig() {
36 | $envConf = preg_grep('/^PHPREDMIN_/', array_keys($_ENV));
37 |
38 | if (!empty($envConf)) {
39 | foreach ($envConf as $conf) {
40 | $keys = explode('_', $conf);
41 |
42 | if (!empty($keys)) {
43 | array_shift($keys);
44 |
45 | self::setConfig($this->_data['config'], $keys, $_ENV[$conf]);
46 | }
47 | }
48 | }
49 | }
50 |
51 | protected static function setConfig(&$config, $keys, $value) {
52 | $key = array_shift($keys);
53 |
54 | $key = strtolower($key);
55 |
56 | if (isset($config[$key])) {
57 | if (is_array($config[$key])) {
58 | return self::setConfig($config[$key], $keys, $value);
59 | } else {
60 | $config[$key] = $value;
61 | return True;
62 | }
63 | }
64 |
65 | return False;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/views/keys/ttl.php:
--------------------------------------------------------------------------------
1 |
2 |
Key Expiration
3 |
4 |
×
5 | 0 means no ttl (Values lower than 0, make the key persistant)
6 |
7 | updated) && $this->updated): ?>
8 |
9 |
×
10 | Key updated successfuly
11 |
12 | updated)): ?>
13 |
14 |
×
15 | There was a problem updating the key
16 |
17 |
18 | updated) || (isset($this->updated) && !$this->updated)): ?>
19 |
20 | ttl !== false && $this->ttl > 0): ?>
21 |
22 | Time in seconds
23 |
24 |
25 |
26 |
27 | " name="ttl">
28 |
29 |
30 | Update
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/libraries/drivers/template/php.php:
--------------------------------------------------------------------------------
1 | _dir = '../views/';
17 | $this->app = App::instance();
18 | $this->router = Router::instance();
19 | }
20 |
21 | public function render($__view, $data = array())
22 | {
23 | echo $this->renderPartial(App::instance()->config['default_layout'], array('content' => $this->renderPartial($__view, $data)));
24 | }
25 |
26 | public function renderPartial($__view, $__data = array())
27 | {
28 | $this->_data = array_merge($__data, $this->_data);
29 |
30 | ob_start();
31 |
32 | include($this->_dir.$__view.'.php');
33 |
34 | $content = ob_get_contents();
35 |
36 | ob_end_clean();
37 |
38 | return $content;
39 | }
40 |
41 | public function addHeader($header)
42 | {
43 | if (!in_array($header, $this->_headers)) {
44 | $this->_headers[] = $header;
45 | }
46 | }
47 |
48 | public function getHeaders()
49 | {
50 | return $this->_headers;
51 | }
52 |
53 | public function __set($key, $value)
54 | {
55 | $this->_data[$key] = $value;
56 | }
57 |
58 | public function __get($key)
59 | {
60 | return isset($this->_data[$key]) ? $this->_data[$key] : null;
61 | }
62 |
63 | public function __isset($key)
64 | {
65 | return isset($this->_data[$key]);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/public/js/redmin/hashes.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#add_hash, #add_edit_hash').click(function(e) {
3 | e.preventDefault();
4 |
5 | var form = $(e.target).parents('form');
6 | var key = form.find('input[name="key"]').val().trim();
7 | var hashkey = form.find('input[name="hashkey"]').val().trim();
8 | var str = form.find('textarea[name="value"]').val().trim();
9 |
10 | if (key != '' && str != '' && hashkey != '') {
11 | $.ajax({
12 | url: baseurl+'/hashes/add/' + currentServerDb,
13 | dataType: 'json',
14 | type: 'POST',
15 | data: 'key='+key+'&value='+str+'&hashkey='+hashkey,
16 | success: function(data) {
17 | if (data) {
18 | var oldkey = form.find('input[name="oldkey"]');
19 | form.find('textarea').val('');
20 |
21 | if (oldkey.length > 0) {
22 | location.reload();
23 | } else {
24 | if (e.target.id == 'add_edit_hash') {
25 | location.href = baseurl + '/keys/view/' + currentServerDb + '/' + encodeURIComponent(key);
26 | } else {
27 | form.find('input').val('');
28 | modalPopup.alert('saved');
29 | }
30 | }
31 | } else {
32 | modalPopup.alert('error');
33 | }
34 | }
35 | });
36 | } else {
37 | modalPopup.alert('invalid')
38 | }
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/public/js/redmin/zsets.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#add_zset, #add_edit_zset').click(function(e) {
3 | e.preventDefault();
4 |
5 | var form = $(e.target).parents('form');
6 | var key = form.find('input[name="key"]').val().trim();
7 | var score = form.find('input[name="score"]').val().trim();
8 | var str = form.find('textarea[name="value"]').val().trim();
9 |
10 | if (key != '' && str != '' && score != '') {
11 | $.ajax({
12 | url: baseurl+'/zsets/add/' + currentServerDb,
13 | dataType: 'json',
14 | type: 'POST',
15 | data: 'key='+key+'&value='+str+'&score='+score,
16 | success: function(data) {
17 | if (data) {
18 | var oldkey = form.find('input[name="oldkey"]');
19 | form.find('textarea').val('');
20 |
21 | if (oldkey.length > 0) {
22 | location.reload();
23 | } else {
24 | if (e.target.id == 'add_edit_zset') {
25 | location.href = baseurl + '/keys/view/' + currentServerDb + '/' + encodeURIComponent(key);
26 | } else {
27 | form.find('input').val('');
28 | modalPopup.alert('saved');
29 | }
30 | }
31 | }
32 | else {
33 | modalPopup.alert('error');
34 | }
35 | }
36 | });
37 | } else {
38 | modalPopup.alert('invalid')
39 | }
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/views/welcome/slowlog.php:
--------------------------------------------------------------------------------
1 |
2 |
Redis Slow Log (=$this->count?> most recent)
3 |
4 |
×
5 | PHPRedmin uses Eval to fetch slowlogs
6 |
7 | support): ?>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Time
19 |
20 |
21 | Duration (Microseconds)
22 |
23 |
24 | Info
25 |
26 |
27 | slowlogs as $log): ?>
28 |
29 | =date('Y-m-d H:i:s', $log[1])?>
30 | =$log[2]?>
31 | =implode($log[3], ', ')?>
32 |
33 |
34 |
35 |
36 |
37 |
×
38 | Eval has been available since redis version 2.6.0 but your redis version is =$this->version?>
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/controllers/zsets.php:
--------------------------------------------------------------------------------
1 | router->method == Router::POST) {
10 | $value = $this->inputs->post('value', null);
11 | $key = $this->inputs->post('key', null);
12 | $score = $this->inputs->post('score', null);
13 |
14 | if (isset($value) && trim($value) != '' && isset($key) && trim($key) != '' && isset($score) && trim($score) != '') {
15 | $added = (boolean) $this->db->zAdd($key, (double) $score, $value);
16 | }
17 | }
18 |
19 | Template::factory('json')->render($added);
20 | }
21 |
22 | public function viewAction($key, $page = 0)
23 | {
24 | $count = $this->db->zSize(urldecode($key));
25 | $start = $page * 30;
26 | $values = $this->db->zRange(urldecode($key), $start, $start + 29, true);
27 |
28 | Template::factory()->render('zsets/view', array('count' => $count, 'values' => $values, 'key' => urldecode($key),
29 | 'page' => $page));
30 | }
31 |
32 | public function deleteAction($key, $value)
33 | {
34 | Template::factory('json')->render($this->db->zDelete(urldecode($key), urldecode($value)));
35 | }
36 |
37 | public function delallAction()
38 | {
39 | if ($this->router->method == Router::POST) {
40 | $results = array();
41 | $values = $this->inputs->post('values', array());
42 | $keyinfo = $this->inputs->post('keyinfo', null);
43 |
44 | foreach ($values as $key => $value) {
45 | $results[$value] = $this->db->zDelete($keyinfo, $value);
46 | }
47 |
48 | Template::factory('json')->render($results);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/controllers/gearman.php:
--------------------------------------------------------------------------------
1 | config;
8 | $worker = new GearmanWorker();
9 |
10 | $worker->addServer($config['gearman']['host'], $config['gearman']['port']);
11 | $worker->addFunction('delete_keys', array($this, 'deleteKeys'));
12 | $worker->addFunction('move_keys', array($this, 'moveKeys'));
13 |
14 | while ($worker->work());
15 | }
16 |
17 | public function deleteKeys($job)
18 | {
19 | $data = unserialize($job->workload());
20 |
21 | Log::factory()->write(Log::NOTICE, "Try to delete: {$data['key']} at {$data['server']['host']}:{$data['server']['port']}, DB: {$data['server']['database']}", 'Gearman');
22 |
23 | $db = Db::factory($data['server']);
24 | $db->changeDB($data['server']['database']);
25 |
26 | $keys = $db->keys($data['key']);
27 | $count = count($keys);
28 |
29 | if ($count) {
30 | $db->set("phpredmin:gearman:deletecount:{$data['key']}", $count);
31 | $db->del("phpredmin:gearman:deleted:{$data['key']}");
32 | $db->del("phpredmin:gearman:requests:{$data['key']}");
33 |
34 | foreach ($keys as $key) {
35 | if ($db->delete($key) !== false) {
36 | $db->incrBy("phpredmin:gearman:deleted:{$data['key']}", 1);
37 | $db->expireAt("phpredmin:gearman:deleted:{$data['key']}", strtotime('+10 minutes'));
38 | } else {
39 | Log::factory()->write(Log::INFO, "Unable to delete {$key}", 'Gearman');
40 | }
41 | }
42 |
43 | $db->del("phpredmin:gearman:deletecount:{$data['key']}");
44 | }
45 | }
46 |
47 | public function moveKeys($job)
48 | {
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/config.dist.php:
--------------------------------------------------------------------------------
1 | 'Welcome',
4 | 'default_action' => 'Index',
5 | 'production' => true,
6 | 'default_layout' => 'layout',
7 | 'timezone' => 'Europe/Amsterdam',
8 | 'auth' => array(
9 | 'username' => 'admin',
10 | 'password' => password_hash('admin', PASSWORD_DEFAULT)
11 | ),
12 | 'log' => array(
13 | 'driver' => 'file',
14 | 'threshold' => 1, /* 0: Disable Logging 1: Error 2: Notice 3: Info 4: Warning 5: Debug */
15 | 'file' => array(
16 | 'directory' => 'logs'
17 | )
18 | ),
19 | 'database' => array(
20 | 'driver' => 'redis',
21 | 'mysql' => array(
22 | 'host' => 'localhost',
23 | 'username' => 'root',
24 | 'password' => 'root'
25 | ),
26 | 'redis' => array(
27 | array(
28 | 'host' => 'localhost',
29 | 'port' => '6379',
30 | 'password' => null,
31 | 'database' => 0,
32 | 'max_databases' => 16, /* Manual configuration of max databases for Redis < 2.6 */
33 | 'stats' => array(
34 | 'enable' => 1,
35 | 'database' => 0,
36 | ),
37 | 'dbNames' => array( /* Name databases. key should be database id and value is the name */
38 | ),
39 | ),
40 | ),
41 | ),
42 | 'session' => array(
43 | 'lifetime' => 7200,
44 | 'gc_probability' => 2,
45 | 'name' => 'phpredminsession'
46 | ),
47 | 'gearman' => array(
48 | 'host' => '127.0.0.1',
49 | 'port' => 4730
50 | ),
51 | 'terminal' => array(
52 | 'enable' => true,
53 | 'history' => 200
54 | )
55 | );
56 |
57 | return $config;
58 |
--------------------------------------------------------------------------------
/libraries/inputs.php:
--------------------------------------------------------------------------------
1 | method) {
18 | case Router::POST:
19 | $result = $this->post($key, $default);
20 | break;
21 | case Router::PUT:
22 | $result = $this->put($key, $default);
23 | break;
24 | case Router::GET:
25 | $result = $this->get($key, $default);
26 | break;
27 | default:
28 | $result = $default;
29 | }
30 |
31 | return $result;
32 | }
33 |
34 | public function post($key, $default = null)
35 | {
36 | if (isset($_POST[$key])) {
37 | if (is_array($_POST[$key])) {
38 | $results = array();
39 |
40 | foreach ($_POST[$key] as $index => $value) {
41 | $results[$index] = filter_var($value, FILTER_SANITIZE_STRING);
42 | }
43 |
44 | return $results;
45 | } else {
46 | return filter_var($_POST[$key], FILTER_SANITIZE_STRING);
47 | }
48 | } else {
49 | return $default;
50 | }
51 | }
52 |
53 | public function get($key, $default = null)
54 | {
55 | $result = Router::instance()->query($key, $default);
56 | return $result ? $result : $default;
57 | }
58 |
59 | public function put($key, $default = null)
60 | {
61 | parse_str(file_get_contents("php://input"), $vars);
62 | return isset($vars[$key]) ? filter_var($vars[$key], FILTER_SANITIZE_STRING) : $default;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/controllers/cron.php:
--------------------------------------------------------------------------------
1 | app->config['database']['redis'] as $serverId => $server) {
8 | if (!empty($server['stats']['enable'])) {
9 | $time = time();
10 |
11 | $db = Db::factory($server);
12 | $info = $db->info();
13 |
14 | $statsModel = new Stats_Model($server);
15 | $statsModel->addKey('memory', $info['used_memory'], $time);
16 | $statsModel->addKey('connections', $info['total_connections_received'], $time);
17 | $statsModel->addKey('commands', $info['total_commands_processed'], $time);
18 | $statsModel->addKey('expired_keys', $info['expired_keys'], $time);
19 | $statsModel->addKey('hits', $info['keyspace_hits'], $time);
20 | $statsModel->addKey('misses', $info['keyspace_misses'], $time);
21 | $statsModel->addKey('clients', $info['connected_clients'], $time);
22 | $statsModel->addKey('user_cpu', $info['used_cpu_user'], $time);
23 | $statsModel->addKey('system_cpu', $info['used_cpu_sys'], $time);
24 | if ($info['aof_enabled']) {
25 | $statsModel->addKey('aof_size', $info['aof_current_size'], $time);
26 | $statsModel->addKey('aof_base', $info['aof_base_size'], $time);
27 | }
28 |
29 | foreach ($this->infoModel->getDbs($info) as $i) {
30 | if (preg_match('/keys=([0-9]+),expires=([0-9]+)/', $info["db{$i}"], $matches)) {
31 | $statsModel->addKey("db{$i}:keys", $matches[1], $time);
32 | $statsModel->addKey("db{$i}:expired_keys", $matches[2], $time);
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | config['timezone'])) {
20 | date_default_timezone_set(App::instance()->config['timezone']);
21 |
22 | }
23 |
24 | $authenticated = true;
25 |
26 | if (PHP_SAPI !== 'cli' && isset(App::instance()->config['auth'])) {
27 | $username = null;
28 | $password = null;
29 |
30 | $auth = App::instance()->config['auth'];
31 |
32 | if(isset($auth['username']) && isset($auth['password']))
33 | {
34 | // mod_php
35 | if (isset($_SERVER['PHP_AUTH_USER'])) {
36 | $username = $_SERVER['PHP_AUTH_USER'];
37 | $password = $_SERVER['PHP_AUTH_PW'];
38 | // most other servers
39 | } elseif (isset($_SERVER['HTTP_AUTHORIZATION']) && strpos(strtolower($_SERVER['HTTP_AUTHORIZATION']), 'basic') === 0) {
40 | list($username, $password) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
41 | }
42 |
43 | if ($username != $auth['username'] || !password_verify($password, $auth['password'])) {
44 | $authenticated = false;
45 | }
46 | }
47 | }
48 |
49 | if ($authenticated) {
50 | $error = new Error();
51 | Router::instance()->route();
52 | } else {
53 | header('WWW-Authenticate: Basic realm="PHPRedis Administrator"');
54 | header('HTTP/1.0 401 Unauthorized');
55 | echo 'Not Authorized';
56 | die();
57 | }
58 |
--------------------------------------------------------------------------------
/models/stats.php:
--------------------------------------------------------------------------------
1 | currentServer = $config;
18 | }
19 |
20 | public function resetStats()
21 | {
22 | $this->db->changeDB($this->currentServer['stats']['database']);
23 |
24 | $keys = $this->db->keys(self::STATS_MODEL_KEY . '*');
25 |
26 | foreach ($keys as $key) {
27 | $this->db->del($key);
28 | }
29 |
30 | $this->db->changeDB($this->currentServer['database']);
31 | }
32 |
33 | public function addKey($key, $value, $time)
34 | {
35 | $this->db->changeDB($this->currentServer['stats']['database']);
36 |
37 | // add value with timestamp prefix to make it unique
38 | // in other case non-unique value won't be added
39 | // @see http://redis.io/commands/zadd
40 | $this->db->zAdd(self::STATS_MODEL_KEY . $key, $time, $time . ':' . $value);
41 |
42 | $this->db->changeDB($this->currentServer['database']);
43 | }
44 |
45 | public function getKeys($key, $from, $to)
46 | {
47 | $this->db->changeDB($this->currentServer['stats']['database']);
48 |
49 | $results = array();
50 | $keys = $this->db->zRevRangeByScore(self::STATS_MODEL_KEY . $key, $to, $from, array('withscores' => true));
51 |
52 | foreach ($keys as $value => $time) {
53 | $value = explode(':', $value);
54 | $results[] = array($time, (float)$value[1]);
55 | }
56 |
57 | $this->db->changeDB($this->currentServer['database']);
58 |
59 | return $results;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/libraries/error.php:
--------------------------------------------------------------------------------
1 | config['production']) {
8 | ini_set('display_errors', 0);
9 | }
10 |
11 | // Set a custom error Handler
12 | set_error_handler(array($this, 'errorHandler'));
13 | // Set a custom Exception Handler
14 | set_exception_handler(array($this, 'exceptionHandler'));
15 | // Set Shutdown Handler
16 | register_shutdown_function(array($this, 'shutdownHandler'));
17 | }
18 |
19 | public static function shutdownHandler()
20 | {
21 | $error = error_get_last();
22 |
23 | if ($error) {
24 | $type = self::_getError($error['type']);
25 | Log::factory()->write($type, "{$error['message']} on {$error['file']}:{$error['line']}", 'Shutdown Handler');
26 | }
27 | }
28 |
29 | public function exceptionHandler(Exception $e)
30 | {
31 | Log::factory()->write(Log::ERROR, "{$e->getMessage()} on {$e->getFile()}:{$e->getLine()}", 'Exception Handler');
32 | }
33 |
34 | public function errorHandler($no, $str, $file, $line, $context)
35 | {
36 | if (!(error_reporting() & $no)) {
37 | // This error code is not included in error_reporting
38 | return;
39 | }
40 |
41 | $type = self::_getError($no);
42 |
43 | Log::factory()->write($type, "{$str} on {$file}:{$line}", 'Error Handler');
44 | }
45 |
46 | protected static function _getError($type)
47 | {
48 | switch ($type) {
49 | case E_WARNING: // 2 //
50 | return Log::WARNING;
51 | case E_NOTICE: // 8 //
52 | return Log::NOTICE;
53 | case E_CORE_WARNING: // 32 //
54 | return Log::WARNING;
55 | case E_USER_WARNING: // 512 //
56 | return Log::WARNING;
57 | case E_USER_NOTICE: // 1024 //
58 | return Log::NOTICE;
59 | case E_DEPRECATED: // 8192 //
60 | return Log::WARNING;
61 | case E_USER_DEPRECATED: // 16384 //
62 | return Log::WARNING;
63 | }
64 |
65 | return Log::ERROR;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/views/hashes/view.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 |
Edit Hash
4 | =$this->renderPartial('hashes/add', array('oldkey' => $this->key))?>
5 |
=$this->key?>
6 |
52 |
53 |
--------------------------------------------------------------------------------
/public/js/redmin/sets.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#add_set, #add_edit_set').click(function(e) {
3 | e.preventDefault();
4 |
5 | var form = $(e.target).parents('form');
6 | var key = form.find('input[name="key"]').val().trim();
7 | var str = form.find('textarea[name="value"]').val().trim();
8 |
9 | if (key != '' && str != '') {
10 | $.ajax({
11 | url: baseurl+'/sets/add/' + currentServerDb,
12 | dataType: 'json',
13 | type: 'POST',
14 | data: 'key='+key+'&value='+str,
15 | success: function(data) {
16 | if (data) {
17 | var oldkey = form.find('input[name="oldkey"]');
18 | form.find('textarea').val('');
19 |
20 | if (oldkey.length > 0) {
21 | var tr = $('.settable tr:first');
22 | $(''+str+' '+
23 | ' ').insertAfter(tr);
24 | $('.settable tr').eq(1).find('a.del').click(function(e) {
25 | deleteRow(e);
26 | });
27 | location.reload();
28 | } else {
29 | if (e.target.id == 'add_edit_set') {
30 | location.href = baseurl + '/keys/view/' + currentServerDb + '/' + encodeURIComponent(key);
31 | } else {
32 | form.find('input').val('');
33 | modalPopup.alert('saved');
34 | }
35 | }
36 | }
37 | else {
38 | modalPopup.alert('error');
39 | }
40 | }
41 | });
42 | } else {
43 | modalPopup.alert('invalid')
44 | }
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/libraries/controller.php:
--------------------------------------------------------------------------------
1 | _objects['app'] = App::instance();
9 | $this->_objects['router'] = Router::instance();
10 | $this->_objects['inputs'] = Inputs::instance();
11 | $this->_objects['session'] = Session::instance();
12 | $this->_objects['log'] = Log::factory();
13 |
14 | if (!isset($this->app->config['database']['redis'][$config['serverId']])) {
15 | $config['serverId'] = 0;
16 | }
17 |
18 | $current = $this->app->config['database']['redis'][$config['serverId']];
19 | $current['serverId'] = $config['serverId'];
20 |
21 | $this->_objects['db'] = Db::factory($current);
22 | $this->_objects['infoModel'] = new Info_Model($current);
23 |
24 | $info = $this->db->info();
25 | $dbs = $this->infoModel->getDbs($info);
26 |
27 | if (!isset($current['max_databases'])) {
28 | $databasesConfig = $this->_objects['db']->config('GET', 'databases');
29 | $current['max_databases'] = $databasesConfig['databases'];
30 | }
31 |
32 | // Take care of invalid dbId's. If invalid, set to first available database
33 | if (!is_numeric($config['dbId'])
34 | || $config['dbId'] < 0
35 | || $config['dbId'] >= $current['max_databases']
36 | ) {
37 | $config['dbId'] = $dbs[0];
38 | }
39 |
40 | $current['newDB'] = (!in_array($config['dbId'], $dbs) ? true : false);
41 |
42 | $current['database'] = $config['dbId'];
43 |
44 | // Extract number of keys
45 | foreach ($dbs as $i) {
46 | if (preg_match('/^keys=([0-9]+),expires=([0-9]+)/', $info["db{$i}"], $matches)) {
47 | $current['dbs'][$i] = array(
48 | 'id' => $i,
49 | 'keys' => $matches[1],
50 | 'name' => (isset($current['dbNames'][$i]) ? $current['dbNames'][$i] : null),
51 | );
52 | }
53 | }
54 | $this->db->select($current['database']);
55 |
56 | $this->app->current = $current;
57 | }
58 |
59 | public function __get($object)
60 | {
61 | return isset($this->_objects[$object]) ? $this->_objects[$object] : null;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/views/sets/view.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 |
Edit Set
4 | =$this->renderPartial('sets/add', array('oldkey' => $this->key))?>
5 |
=$this->key?>
6 |
51 |
52 |
--------------------------------------------------------------------------------
/public/js/nvd3/lib/fisheye.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | d3.fisheye = {
3 | scale: function(scaleType) {
4 | return d3_fisheye_scale(scaleType(), 3, 0);
5 | },
6 | circular: function() {
7 | var radius = 200,
8 | distortion = 2,
9 | k0,
10 | k1,
11 | focus = [0, 0];
12 |
13 | function fisheye(d) {
14 | var dx = d.x - focus[0],
15 | dy = d.y - focus[1],
16 | dd = Math.sqrt(dx * dx + dy * dy);
17 | if (!dd || dd >= radius) return {x: d.x, y: d.y, z: 1};
18 | var k = k0 * (1 - Math.exp(-dd * k1)) / dd * .75 + .25;
19 | return {x: focus[0] + dx * k, y: focus[1] + dy * k, z: Math.min(k, 10)};
20 | }
21 |
22 | function rescale() {
23 | k0 = Math.exp(distortion);
24 | k0 = k0 / (k0 - 1) * radius;
25 | k1 = distortion / radius;
26 | return fisheye;
27 | }
28 |
29 | fisheye.radius = function(_) {
30 | if (!arguments.length) return radius;
31 | radius = +_;
32 | return rescale();
33 | };
34 |
35 | fisheye.distortion = function(_) {
36 | if (!arguments.length) return distortion;
37 | distortion = +_;
38 | return rescale();
39 | };
40 |
41 | fisheye.focus = function(_) {
42 | if (!arguments.length) return focus;
43 | focus = _;
44 | return fisheye;
45 | };
46 |
47 | return rescale();
48 | }
49 | };
50 |
51 | function d3_fisheye_scale(scale, d, a) {
52 |
53 | function fisheye(_) {
54 | var x = scale(_),
55 | left = x < a,
56 | v,
57 | range = d3.extent(scale.range()),
58 | min = range[0],
59 | max = range[1],
60 | m = left ? a - min : max - a;
61 | if (m == 0) m = max - min;
62 | return (left ? -1 : 1) * m * (d + 1) / (d + (m / Math.abs(x - a))) + a;
63 | }
64 |
65 | fisheye.distortion = function(_) {
66 | if (!arguments.length) return d;
67 | d = +_;
68 | return fisheye;
69 | };
70 |
71 | fisheye.focus = function(_) {
72 | if (!arguments.length) return a;
73 | a = +_;
74 | return fisheye;
75 | };
76 |
77 | fisheye.copy = function() {
78 | return d3_fisheye_scale(scale.copy(), d, a);
79 | };
80 |
81 | fisheye.nice = scale.nice;
82 | fisheye.ticks = scale.ticks;
83 | fisheye.tickFormat = scale.tickFormat;
84 | return d3.rebind(fisheye, scale, "domain", "range");
85 | }
86 | })();
87 |
--------------------------------------------------------------------------------
/controllers/welcome.php:
--------------------------------------------------------------------------------
1 | render('welcome/index');
8 | }
9 |
10 | public function configAction()
11 | {
12 | $config = $this->db->config('GET', '*');
13 |
14 | Template::factory()->render('welcome/config', array('config' => $config));
15 | }
16 |
17 | public function statsAction()
18 | {
19 | Template::factory()->render('welcome/stats');
20 | }
21 |
22 | public function infoAction()
23 | {
24 | $info = $this->db->info();
25 | $uptimeDays = floor($info['uptime_in_seconds'] / 86400);
26 | $dbSize = $this->db->dbSize();
27 | $lastSave = $this->db->lastSave();
28 |
29 |
30 | Template::factory()->render('welcome/info', array('info' => $info,
31 | 'uptimeDays' => $uptimeDays,
32 | 'dbSize' => $dbSize,
33 | 'lastSave' => $lastSave));
34 | }
35 |
36 | public function saveAction($async = 0)
37 | {
38 | $saved = $async ? $this->db->bgSave() : $this->db->save();
39 | $filename = current($this->db->config('GET', 'dbfilename'));
40 |
41 | Template::factory('json')->render(array(
42 | 'status' => $saved,
43 | 'async' => $async,
44 | 'filename' => $filename,
45 | ));
46 | }
47 |
48 | public function slowlogAction()
49 | {
50 | $support = false;
51 | $slowlogs = array();
52 | $serverInfo = $this->db->info('server');
53 | $count = $this->inputs->post('count', null);
54 | $count = isset($count) ? $count : 10;
55 |
56 | if (!preg_match('/^(0|1)/', $serverInfo['redis_version']) && !preg_match('/^2\.[0-5]/', $serverInfo['redis_version'])) {
57 | $slowlogs = $this->db->eval("return redis.call('slowlog', 'get', {$count})");
58 | $support = true;
59 | }
60 |
61 | Template::factory()->render('welcome/slowlog', array('slowlogs' => $slowlogs,
62 | 'support' => $support,
63 | 'version' => $serverInfo['redis_version'],
64 | 'count' => $count));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/views/lists/view.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 |
Edit List
4 | =$this->renderPartial('lists/add', array('oldkey' => $this->key))?>
5 |
6 | Remove from List
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | All Occurrences
15 | Occurrences Count
16 |
17 |
18 |
19 |
20 | Remove
21 |
22 |
=$this->key?>
23 |
24 |
25 | Index
26 | Value
27 |
28 | values as $member => $value): ?>
29 |
30 |
31 | =$member?>
32 |
33 |
34 | =$value?>
35 |
36 |
37 |
38 |
39 | count > 30): ?>
40 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/views/navigation.php:
--------------------------------------------------------------------------------
1 |
9 |
33 | app->config['database']['redis']) > 1) : ?>
34 |
46 |
47 |
--------------------------------------------------------------------------------
/helpers/redis.php:
--------------------------------------------------------------------------------
1 | db = Db::factory(App::instance()->current);
12 | }
13 |
14 | public static function instance()
15 | {
16 | if (!isset(self::$_instance)) {
17 | self::$_instance = new self;
18 | }
19 |
20 | return self::$_instance;
21 | }
22 |
23 | public function getType($key)
24 | {
25 | switch ($this->db->type($key)) {
26 | case Redis::REDIS_STRING:
27 | return 'String';
28 | case Redis::REDIS_SET:
29 | return 'Set';
30 | case Redis::REDIS_LIST:
31 | return 'List';
32 | case Redis::REDIS_ZSET:
33 | return 'ZSet';
34 | case Redis::REDIS_HASH:
35 | return 'Hash';
36 | default:
37 | return '-';
38 | }
39 | }
40 |
41 | public function getTTL($key)
42 | {
43 | return $this->_time($this->db->ttl($key));
44 | }
45 |
46 | public function getIdleTime($key)
47 | {
48 | return $this->_time($this->db->object("idletime", $key));
49 | }
50 |
51 | public function getCount($key)
52 | {
53 | return $this->db->object("refcount", $key);
54 | }
55 |
56 | public function getEncoding($key)
57 | {
58 | return $this->db->object("encoding", $key);
59 | }
60 |
61 | public function getSize($key)
62 | {
63 | switch ($this->db->type($key)) {
64 | case Redis::REDIS_LIST:
65 | $size = $this->db->lSize($key);
66 | break;
67 | case Redis::REDIS_SET:
68 | $size = $this->db->sCard($key);
69 | break;
70 | case Redis::REDIS_HASH:
71 | $size = $this->db->hLen($key);
72 | break;
73 | case Redis::REDIS_ZSET:
74 | $size = $this->db->zCard($key);
75 | break;
76 | case Redis::REDIS_STRING:
77 | $size = $this->db->strlen($key);
78 | break;
79 | default:
80 | $size = '-';
81 | }
82 |
83 | return $size <=0 ? '-' : $size;
84 | }
85 |
86 | protected function _time($time)
87 | {
88 | if ($time <= 0) {
89 | return '-';
90 | } else {
91 | $days = floor($time / 86400);
92 |
93 | return ($days > 0 ? "{$days} Days " : '') . gmdate('H:i:s', $time);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/views/zsets/view.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 |
Edit Sorted Set
4 | =$this->renderPartial('zsets/add', array('oldkey' => $this->key))?>
5 |
=$this->key?>
6 |
46 | count > 30): ?>
47 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/controllers/hashes.php:
--------------------------------------------------------------------------------
1 | router->method == Router::POST) {
10 | $value = $this->inputs->post('value', null);
11 | $key = $this->inputs->post('key', null);
12 | $hashkey = $this->inputs->post('hashkey', null);
13 |
14 | if (isset($value) && trim($value) != '' && isset($key) && trim($key) != '' && isset($hashkey) && trim($hashkey) != '') {
15 | if ($this->db->hSet($key, $hashkey, $value) !== false) {
16 | $added = true;
17 | }
18 | }
19 | }
20 |
21 | Template::factory('json')->render($added);
22 | }
23 |
24 | public function viewAction($key)
25 | {
26 | $members = $this->db->hGetAll(urldecode($key));
27 |
28 | Template::factory()->render('hashes/view', array('members' => $members, 'key' => urldecode($key)));
29 | }
30 |
31 | public function deleteAction($key, $member)
32 | {
33 | Template::factory('json')->render($this->db->hDel(urldecode($key), urldecode($member)));
34 | }
35 |
36 | public function delallAction()
37 | {
38 | if ($this->router->method == Router::POST) {
39 | $results = array();
40 | $values = $this->inputs->post('values', array());
41 | $keyinfo = $this->inputs->post('keyinfo', null);
42 |
43 | foreach ($values as $key => $value) {
44 | $results[$value] = $this->db->hDel($keyinfo, $value);
45 | }
46 |
47 | Template::factory('json')->render($results);
48 | }
49 | }
50 |
51 | public function editAction($key, $member)
52 | {
53 | $edited = null;
54 |
55 | if ($this->router->method == Router::POST) {
56 | $newvalue = $this->inputs->post('newvalue', null);
57 | $member = $this->inputs->post('member', null);
58 | $key = $this->inputs->post('key', null);
59 |
60 | if (!isset($newvalue) || trim($newvalue) == '' || !isset($key) || trim($key) == '' ||
61 | !isset($member) || trim($member) == '') {
62 | $edited = false;
63 | } elseif ($this->db->hDel($key, $member)) {
64 | $edited = $this->db->hSet($key, $member, $newvalue);
65 | }
66 | }
67 |
68 | $value = $this->db->hGet(urldecode($key), urldecode($member));
69 |
70 | Template::factory()->render('hashes/edit', array('member' => urldecode($member), 'key' => urldecode($key), 'value' => $value, 'edited' => $edited));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/public/js/redmin/lists.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#add_list, #add_edit_list').click(function(e) {
3 | e.preventDefault();
4 |
5 | var form = $(e.target).parents('form');
6 | var key = form.find('input[name="key"]').val().trim();
7 | var str = form.find('textarea[name="value"]').val().trim();
8 | var type = form.find('select[name="position"]').val();
9 | var pivot = form.find('input[name="pivot"]');
10 |
11 | if (key != '' && str != '' && (type == 'append' || type == 'prepend' || type == 'before' || type == 'after')) {
12 | if ((type == 'before' || type == 'after') && pivot.val().trim() == '') {
13 | modalPopup.alert('invalid')
14 | } else {
15 | if (pivot.length > 0) {
16 | pivot = pivot.val().trim();
17 | }
18 | else {
19 | pivot = '';
20 | }
21 |
22 | $.ajax({
23 | url: baseurl+'/lists/add/' + currentServerDb,
24 | dataType: 'json',
25 | type: 'POST',
26 | data: 'key='+key+'&value='+str+'&type='+type+'&pivot='+pivot,
27 | success: function(data) {
28 | if (data) {
29 | var oldkey = form.find('input[name="oldkey"]');
30 | form.find('textarea').val('');
31 |
32 | if (oldkey.length > 0) {
33 | location.reload();
34 | } else {
35 | if (e.target.id == 'add_edit_list') {
36 | location.href = baseurl + '/keys/view/' + currentServerDb + '/' + encodeURIComponent(key);
37 | } else {
38 | form.find('input').val('');
39 | modalPopup.alert('saved');
40 | }
41 | }
42 | } else {
43 | modalPopup.alert('error');
44 | }
45 | }
46 | });
47 | }
48 | } else {
49 | modalPopup.alert('invalid')
50 | }
51 | });
52 |
53 | $('#list_position').change(function(e) {
54 | var val = $(e.target).val();
55 |
56 | if (val == 'after' || val == 'before') {
57 | if ($('#list_type').find('input').length <= 0) {
58 | $(' ').appendTo($('#list_type'));
59 | }
60 | } else {
61 | $('#list_type').empty();
62 | }
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/controllers/lists.php:
--------------------------------------------------------------------------------
1 | router->method == Router::POST) {
10 | $value = $this->inputs->post('value', null);
11 | $key = $this->inputs->post('key', null);
12 | $type = $this->inputs->post('type', null);
13 | $pivot = $this->inputs->post('pivot', null);
14 |
15 | if (isset($value) && trim($value) != '' && isset($key) && trim($key) != '' &&
16 | (isset($type) && in_array($type, array('before', 'after', 'prepend', 'append')))) {
17 | if (($type == 'before' || $type == 'after') && (!isset($pivot) || $pivot == '')) {
18 | $added = false;
19 | } else {
20 | switch ($type) {
21 | case 'prepend':
22 | $added = (boolean) $this->db->lPush($key, $value);
23 | break;
24 | case 'append':
25 | $added = (boolean) $this->db->rPush($key, $value);
26 | break;
27 | case 'before':
28 | $added = (boolean) $this->db->lInsert($key, Redis::BEFORE, $pivot, $value);
29 | break;
30 | case 'after':
31 | $added = (boolean) $this->db->lInsert($key, Redis::AFTER, $pivot, $value);
32 | break;
33 | }
34 | }
35 | }
36 | }
37 |
38 | Template::factory('json')->render($added);
39 | }
40 |
41 | public function viewAction($key, $page = 0)
42 | {
43 | $count = $this->db->lSize(urldecode($key));
44 | $start = $page * 30;
45 | $values = $this->db->lRange(urldecode($key), $start, $start + 29);
46 |
47 | Template::factory()->render('lists/view', array('count' => $count, 'values' => $values, 'key' => urldecode($key),
48 | 'page' => $page));
49 | }
50 |
51 | public function delAction()
52 | {
53 | if ($this->router->method == Router::POST) {
54 | $key = $this->inputs->post('key');
55 | $value = $this->inputs->post('value');
56 | $type = $this->inputs->post('type_options');
57 |
58 | if ($type == 'all') {
59 | $this->db->lRem($key, $value, 0);
60 | } elseif ($type == 'count') {
61 | $count = $this->inputs->post('count');
62 | $this->db->lRem($key, $value, $count);
63 | }
64 | }
65 |
66 | $this->router->redirect("lists/view/{$this->app->current['serverId']}/{$this->app->current['database']}/" . urlencode($key));
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/public/js/nvd3/lib/hive.js:
--------------------------------------------------------------------------------
1 | d3.hive = {};
2 |
3 | d3.hive.link = function() {
4 | var source = function(d) { return d.source; },
5 | target = function(d) { return d.target; },
6 | angle = function(d) { return d.angle; },
7 | startRadius = function(d) { return d.radius; },
8 | endRadius = startRadius,
9 | arcOffset = -Math.PI / 2;
10 |
11 | function link(d, i) {
12 | var s = node(source, this, d, i),
13 | t = node(target, this, d, i),
14 | x;
15 | if (t.a < s.a) x = t, t = s, s = x;
16 | if (t.a - s.a > Math.PI) s.a += 2 * Math.PI;
17 | var a1 = s.a + (t.a - s.a) / 3,
18 | a2 = t.a - (t.a - s.a) / 3;
19 | return s.r0 - s.r1 || t.r0 - t.r1
20 | ? "M" + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
21 | + "L" + Math.cos(s.a) * s.r1 + "," + Math.sin(s.a) * s.r1
22 | + "C" + Math.cos(a1) * s.r1 + "," + Math.sin(a1) * s.r1
23 | + " " + Math.cos(a2) * t.r1 + "," + Math.sin(a2) * t.r1
24 | + " " + Math.cos(t.a) * t.r1 + "," + Math.sin(t.a) * t.r1
25 | + "L" + Math.cos(t.a) * t.r0 + "," + Math.sin(t.a) * t.r0
26 | + "C" + Math.cos(a2) * t.r0 + "," + Math.sin(a2) * t.r0
27 | + " " + Math.cos(a1) * s.r0 + "," + Math.sin(a1) * s.r0
28 | + " " + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
29 | : "M" + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
30 | + "C" + Math.cos(a1) * s.r1 + "," + Math.sin(a1) * s.r1
31 | + " " + Math.cos(a2) * t.r1 + "," + Math.sin(a2) * t.r1
32 | + " " + Math.cos(t.a) * t.r1 + "," + Math.sin(t.a) * t.r1;
33 | }
34 |
35 | function node(method, thiz, d, i) {
36 | var node = method.call(thiz, d, i),
37 | a = +(typeof angle === "function" ? angle.call(thiz, node, i) : angle) + arcOffset,
38 | r0 = +(typeof startRadius === "function" ? startRadius.call(thiz, node, i) : startRadius),
39 | r1 = (startRadius === endRadius ? r0 : +(typeof endRadius === "function" ? endRadius.call(thiz, node, i) : endRadius));
40 | return {r0: r0, r1: r1, a: a};
41 | }
42 |
43 | link.source = function(_) {
44 | if (!arguments.length) return source;
45 | source = _;
46 | return link;
47 | };
48 |
49 | link.target = function(_) {
50 | if (!arguments.length) return target;
51 | target = _;
52 | return link;
53 | };
54 |
55 | link.angle = function(_) {
56 | if (!arguments.length) return angle;
57 | angle = _;
58 | return link;
59 | };
60 |
61 | link.radius = function(_) {
62 | if (!arguments.length) return startRadius;
63 | startRadius = endRadius = _;
64 | return link;
65 | };
66 |
67 | link.startRadius = function(_) {
68 | if (!arguments.length) return startRadius;
69 | startRadius = _;
70 | return link;
71 | };
72 |
73 | link.endRadius = function(_) {
74 | if (!arguments.length) return endRadius;
75 | endRadius = _;
76 | return link;
77 | };
78 |
79 | return link;
80 | };
81 |
--------------------------------------------------------------------------------
/controllers/sets.php:
--------------------------------------------------------------------------------
1 | router->method == Router::POST) {
10 | $value = $this->inputs->post('value', null);
11 | $key = $this->inputs->post('key', null);
12 |
13 | if (isset($value) && trim($value) != '' && isset($key) && trim($key) != '') {
14 | $added = $this->db->sAdd($key, $value);
15 | }
16 | }
17 |
18 | Template::factory('json')->render($added);
19 | }
20 |
21 | public function viewAction($key)
22 | {
23 | $members = $this->db->sMembers(urldecode($key));
24 |
25 | Template::factory()->render('sets/view', array('members' => $members, 'key' => urldecode($key)));
26 | }
27 |
28 | /**
29 | * Edit action ( edit members in Sets )
30 | *
31 | * @param string $key
32 | * @param string $member
33 | */
34 |
35 | public function editAction($key, $member)
36 | {
37 | $edited = null;
38 |
39 | if ($this->router->method == Router::POST) {
40 | $member = $this->inputs->post('oldmember', null);
41 | $newmember = $this->inputs->post('newmember', null);
42 | $key = $this->inputs->post('key', null);
43 |
44 | if (!isset($newmember) || trim($newmember) == '' || !isset($key) || trim($key) == '') {
45 | $edited = false;
46 | } elseif ($this->db->sRem($key, $member)) {
47 | $edited = $this->db->sAdd($key, $newmember);
48 | }
49 | }
50 |
51 | Template::factory()->render('sets/edit', array('member' => urldecode($member), 'key' => urldecode($key), 'edited' => $edited));
52 | }
53 |
54 | public function deleteAction($key, $value)
55 | {
56 | Template::factory('json')->render($this->db->sRem(urldecode($key), urldecode($value)));
57 | }
58 |
59 | public function delallAction()
60 | {
61 | if ($this->router->method == Router::POST) {
62 | $results = array();
63 | $values = $this->inputs->post('values', array());
64 | $keyinfo = $this->inputs->post('keyinfo', null);
65 |
66 | foreach ($values as $key => $value) {
67 | $results[$value] = $this->db->sRem($keyinfo, $value);
68 | }
69 |
70 | Template::factory('json')->render($results);
71 | }
72 | }
73 |
74 | public function moveallAction()
75 | {
76 | if ($this->router->method == Router::POST) {
77 | $results = array();
78 | $values = $this->inputs->post('values', array());
79 | $destination = $this->inputs->post('destination');
80 | $keyinfo = $this->inputs->post('keyinfo');
81 |
82 | foreach ($values as $key => $value) {
83 | $results[$value] = $this->db->sMove($value, $keyinfo, $destination);
84 | }
85 |
86 | Template::factory('json')->render($results);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/controllers/terminal.php:
--------------------------------------------------------------------------------
1 | config = App::instance()->config['terminal'];
13 | }
14 |
15 | public function indexAction()
16 | {
17 | $this->db->set("phpredmin:terminal:history:pointer", -1);
18 |
19 | Template::factory()->render('terminal/index');
20 | }
21 |
22 | public function runAction()
23 | {
24 | if (!$this->config['enable']) {
25 | echo "Terminal is not enabled";
26 | die;
27 | }
28 |
29 | $command = $this->inputs->post('command', null);
30 |
31 | if (isset($command)) {
32 | $historylimit = $this->config['history'];
33 | $historykey = "phpredmin:terminal:history";
34 |
35 | if ($historylimit > 0) {
36 | $this->db->lRem($historykey, $command, 0);
37 | $this->db->rPush($historykey, $command);
38 | $this->db->lTrim($historykey, $historylimit * -1, -1);
39 |
40 | $this->db->set("phpredmin:terminal:history:pointer", -1);
41 | } else {
42 | $this->db->del($historykey);
43 | }
44 |
45 | $command = escapeshellcmd($command);
46 | exec("redis-cli -h {$this->app->current['host']} -p {$this->app->current['port']} {$command}", $result);
47 |
48 | Template::factory('json')->render(array('result' => $result));
49 | }
50 | }
51 |
52 | public function historyAction()
53 | {
54 | $historylimit = $this->config['history'];
55 | $historykey = "phpredmin:terminal:history";
56 | $historylen = $this->db->lLen($historykey);
57 | $command = '';
58 | $reset = false;
59 |
60 | if ($historylimit > 0 && $historylen > 0) {
61 | $navigation = $this->inputs->get('navigation', self::NAVIGATION_UP);
62 | $start = $this->inputs->get('start');
63 | $pointer = $this->db->get("phpredmin:terminal:history:pointer");
64 |
65 | if ($historylen > $historylimit) {
66 | $this->db->lTrim($historykey, $historylimit * -1, -1);
67 | $historylen = $historylimit;
68 | }
69 |
70 | if ($navigation == self::NAVIGATION_UP) {
71 | if ($historylen <= ($pointer * -1)) {
72 | $pointer = $historylen * -1;
73 | } elseif (!isset($start)) {
74 | $pointer--;
75 | }
76 | } elseif ($pointer != -1) {
77 | $pointer++;
78 | } else {
79 | $reset = true;
80 | }
81 |
82 | $command = $this->db->lRange($historykey, $pointer, $pointer);
83 |
84 | $this->db->set("phpredmin:terminal:history:pointer", $pointer);
85 | }
86 |
87 | Template::factory('json')->render(array('command' => $command, 'reset' => $reset));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/public/js/redmin/modal.js:
--------------------------------------------------------------------------------
1 | var modalPopup = {
2 |
3 | // link modals to the template ids
4 | _modals : {
5 | alert : 'generalmodal',
6 | confirm : 'confirmation',
7 | addDb : 'addDB',
8 | moveKeys : 'move_confirmation'
9 | },
10 |
11 | _alertDefault : {
12 | saved : {
13 | title : 'Saved',
14 | message : 'New value inserted',
15 | btn_class : 'btn-success'
16 | },
17 | error : {
18 | title : 'Error',
19 | message : 'There was a problem saving the value',
20 | btn_class : 'btn-danger'
21 | },
22 | invalid : {
23 | title : 'Invalid',
24 | message : 'Please enter a valid input',
25 | btn_class : 'btn-danger'
26 | }
27 | },
28 |
29 | _getModal : function(type) {
30 | return $('#' + this._modals[type]);
31 | },
32 |
33 | hide : function(type) {
34 | if (typeof type === 'undefined') {
35 | type = 'alert';
36 | }
37 |
38 | this._getModal(type).modal('hide');
39 | },
40 |
41 | alert : function(type, message, title) {
42 | if (typeof type === 'undefined') {
43 | type = 'saved';
44 | }
45 | if (typeof title === 'undefined') {
46 | title = this._alertDefault[type]['title'];
47 | }
48 | if (typeof message === 'undefined') {
49 | message = this._alertDefault[type]['message'];
50 | }
51 |
52 | this._getModal('alert').find('h3').text(title);
53 | this._getModal('alert').find('p').text(message);
54 | this._getModal('alert').find('button').attr('class', 'btn ' + this._alertDefault[type]['btn_class']);
55 |
56 | this._getModal('alert').modal('show');
57 | },
58 |
59 | confirm : function(action, message, title) {
60 | if (typeof title === 'undefined') {
61 | title = 'Are you sure?';
62 | }
63 | if (typeof message === 'undefined') {
64 | message = 'You can not undo this action';
65 | }
66 |
67 | this._getModal('confirm').find('.modal-footer .save').unbind();
68 | this._getModal('confirm').find('.modal-footer .save').click(action);
69 | this._getModal('confirm').find('h3').text(title);
70 | this._getModal('confirm').find('p').text(message);
71 |
72 | this._getModal('confirm').modal('show');
73 | },
74 |
75 | addDb : function(action) {
76 | this._getModal('addDb').find('.modal-footer .save').unbind();
77 | this._getModal('addDb').find('.modal-footer .save').click(action);
78 |
79 | this._getModal('addDb').modal('show');
80 | },
81 |
82 | moveKeys : function(action, title, tip) {
83 | this._getModal('moveKeys').find('.modal-footer .movekey').unbind();
84 | this._getModal('moveKeys').find('.modal-footer .movekey').click(action);
85 | this._getModal('moveKeys').find('h3').text(title);
86 | this._getModal('moveKeys').find('input').attr('placeholder', tip);
87 |
88 | this._getModal('moveKeys').modal('show');
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/public/js/redmin/main.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('.disabled').click(function(e) {
3 | e.preventDefault();
4 | });
5 |
6 | $('#reset_stats').click(function(e) {
7 | e.preventDefault();
8 | modalPopup.confirm(
9 | function() {
10 | $.ajax({
11 | url: baseurl + '/actions/reset/' + currentServerDb,
12 | dataType: 'json',
13 | success: function(data) {
14 | location.href = baseurl + '/welcome/index/' + currentServerDb;
15 | }
16 | });
17 | }
18 | );
19 | });
20 |
21 | $('#flush_db').click(function(e) {
22 | e.preventDefault();
23 |
24 | modalPopup.confirm(
25 | function() {
26 | $.ajax({
27 | url: baseurl + '/actions/fdb/' + currentServerDb,
28 | dataType: 'json',
29 | success: function(data) {
30 | location.href = baseurl + '/welcome/index/' + currentServer + '/0';
31 | }
32 | });
33 | }
34 | );
35 | });
36 |
37 | $('#flush_all').click(function(e) {
38 | e.preventDefault();
39 | modalPopup.confirm(
40 | function() {
41 | $.ajax({
42 | url: baseurl + '/actions/fall/' + currentServerDb,
43 | dataType: 'json',
44 | success: function(data) {
45 | location.href = baseurl + '/welcome/index/' + currentServer + '/0';
46 | }
47 | });
48 | }
49 | );
50 | });
51 |
52 | function executeSave(async) {
53 | $.ajax({
54 | url: baseurl + '/welcome/save/' + currentServerDb + '/' + async,
55 | dataType: 'json',
56 | success: function(data) {
57 | modalPopup.hide('confirm');
58 | if (data.status) {
59 | modalPopup.alert('saved', 'All DBs are saved to file ' + data.filename);
60 | }
61 | else {
62 | modalPopup.alert('error', 'Problem while saving DBs to file ' + data.filename);
63 | }
64 | }
65 | });
66 | }
67 |
68 | $('#save_sync').click(function(e) {
69 | e.preventDefault();
70 |
71 | modalPopup.confirm(
72 | function() {
73 | executeSave(0);
74 | },
75 | 'You almost never want to call synchronous save in production environments where it will block all the other clients. Instead usually asynchronous save is used'
76 | );
77 | });
78 |
79 | $('#save_async').click(function(e) {
80 | e.preventDefault();
81 |
82 | executeSave(1);
83 | });
84 |
85 | $('#add_db').click(function(e) {
86 | e.preventDefault();
87 |
88 | modalPopup.addDb(
89 | function() {
90 | var dbIdx = parseInt($('#dbIdx').val());
91 | location.href = baseurl + '/welcome/index/' + currentServer + '/' + dbIdx;
92 | }
93 | );
94 | });
95 | });
--------------------------------------------------------------------------------
/views/generalmodals.php:
--------------------------------------------------------------------------------
1 |
13 |
14 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 |
47 |
48 |
Databases are not created until data is added and they are initialized. To begin,
49 | specify the database index you want to initialize. You will be redirected and able to
50 | add data to the database
51 |
52 |
53 |
54 |
Database:
55 |
56 |
57 | app->current['dbs']) && !empty($this->app->current['dbs'])): ?>
58 | app->current['max_databases']; $x++): ?>
59 | app->current['dbs'])): ?>
60 | DB =$x?>
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
73 |
74 |
--------------------------------------------------------------------------------
/public/js/nvd3/src/core.js:
--------------------------------------------------------------------------------
1 |
2 | var nv = window.nv || {};
3 |
4 | nv.version = '0.0.1a';
5 | nv.dev = true //set false when in production
6 |
7 | window.nv = nv;
8 |
9 | nv.tooltip = {}; // For the tooltip system
10 | nv.utils = {}; // Utility subsystem
11 | nv.models = {}; //stores all the possible models/components
12 | nv.charts = {}; //stores all the ready to use charts
13 | nv.graphs = []; //stores all the graphs currently on the page
14 | nv.logs = {}; //stores some statistics and potential error messages
15 |
16 | nv.dispatch = d3.dispatch('render_start', 'render_end');
17 |
18 | // *************************************************************************
19 | // Development render timers - disabled if dev = false
20 |
21 | if (nv.dev) {
22 | nv.dispatch.on('render_start', function(e) {
23 | nv.logs.startTime = +new Date();
24 | });
25 |
26 | nv.dispatch.on('render_end', function(e) {
27 | nv.logs.endTime = +new Date();
28 | nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
29 | nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
30 | });
31 | }
32 |
33 | // ********************************************
34 | // Public Core NV functions
35 |
36 | // Logs all arguments, and returns the last so you can test things in place
37 | nv.log = function() {
38 | if (nv.dev && console.log && console.log.apply)
39 | console.log.apply(console, arguments)
40 | else if (nv.dev && console.log && Function.prototype.bind) {
41 | var log = Function.prototype.bind.call(console.log, console);
42 | log.apply(console, arguments);
43 | }
44 | return arguments[arguments.length - 1];
45 | };
46 |
47 |
48 | nv.render = function render(step) {
49 | step = step || 1; // number of graphs to generate in each timout loop
50 |
51 | render.active = true;
52 | nv.dispatch.render_start();
53 |
54 | setTimeout(function() {
55 | var chart, graph;
56 |
57 | for (var i = 0; i < step && (graph = render.queue[i]); i++) {
58 | chart = graph.generate();
59 | if (typeof graph.callback == typeof(Function)) graph.callback(chart);
60 | nv.graphs.push(chart);
61 | }
62 |
63 | render.queue.splice(0, i);
64 |
65 | if (render.queue.length) setTimeout(arguments.callee, 0);
66 | else { nv.render.active = false; nv.dispatch.render_end(); }
67 | }, 0);
68 | };
69 |
70 | nv.render.active = false;
71 | nv.render.queue = [];
72 |
73 | nv.addGraph = function(obj) {
74 | if (typeof arguments[0] === typeof(Function))
75 | obj = {generate: arguments[0], callback: arguments[1]};
76 |
77 | nv.render.queue.push(obj);
78 |
79 | if (!nv.render.active) nv.render();
80 | };
81 |
82 | nv.identity = function(d) { return d; };
83 |
84 | nv.strip = function(s) { return s.replace(/(\s|&)/g,''); };
85 |
86 | function daysInMonth(month,year) {
87 | return (new Date(year, month+1, 0)).getDate();
88 | }
89 |
90 | function d3_time_range(floor, step, number) {
91 | return function(t0, t1, dt) {
92 | var time = floor(t0), times = [];
93 | if (time < t0) step(time);
94 | if (dt > 1) {
95 | while (time < t1) {
96 | var date = new Date(+time);
97 | if ((number(date) % dt === 0)) times.push(date);
98 | step(time);
99 | }
100 | } else {
101 | while (time < t1) { times.push(new Date(+time)); step(time); }
102 | }
103 | return times;
104 | };
105 | }
106 |
107 | d3.time.monthEnd = function(date) {
108 | return new Date(date.getFullYear(), date.getMonth(), 0);
109 | };
110 |
111 | d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) {
112 | date.setUTCDate(date.getUTCDate() + 1);
113 | date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear()));
114 | }, function(date) {
115 | return date.getMonth();
116 | }
117 | );
118 |
119 |
--------------------------------------------------------------------------------
/public/css/custom.css:
--------------------------------------------------------------------------------
1 | .action {
2 | color: #000;
3 | text-decoration: none;
4 | }
5 | .action:hover {
6 | color: #000;
7 | text-decoration: none;
8 | }
9 | textarea {
10 | width: 232px;
11 | }
12 | .nav-pills>li.server>a {
13 | padding-right: 6px;
14 | font-size: 90%;
15 | }
16 | .nav-pills>li.database .label {
17 | margin-top: -2px;
18 | }
19 | .terminal {
20 | margin-left: 0;
21 | background-color: #f5f5f5;
22 | color: #657b83;
23 | padding: 10px;
24 | font-family: monospace;
25 | }
26 | .terminal-console {
27 | border: #ddd 1px solid;
28 | height: 370px;
29 | border-top-left-radius: 4px;
30 | border-top-right-radius: 4px;
31 | border-bottom-right-radius: 4px;
32 | overflow-y: scroll;
33 | }
34 | .terminal-command-line {
35 | color: #002b36;
36 | background-color: #f5f5f5;
37 | margin-left: auto !important;
38 | margin-right: auto !important;
39 | border-left: #ddd 1px solid;
40 | border-right: #ddd 1px solid;
41 | border-bottom: #ddd 1px solid;
42 | border-bottom-left-radius: 4px;
43 | border-bottom-right-radius: 4px;
44 | }
45 | #terminal-input {
46 | color: #002b36;
47 | background-color: #f5f5f5;
48 | border: 0px;
49 | }
50 | .dark_theme #terminal-input { background-color: #696969 !important; color: #D2D2D2; }
51 | .dark_theme .terminal { background-color: #3C3C3C !important; color: #D2D2D2; }
52 | .dark_theme .terminal-console { border: #2D2D2D 1px solid !important; }
53 | .dark_theme .terminal-command-line {
54 | background-color: #696969 !important;
55 | border-color: #2D2D2D !important;
56 | }
57 | .terminal-prompt {
58 | padding-top: 5px;
59 | padding-left: 4px;
60 | }
61 | .ajax-loading {
62 | background-image: url(../img/ajax-loader.gif);
63 | background-repeat: no-repeat;
64 | background-position: left center;
65 | }
66 | .term_theme {
67 | display: inline-block;
68 | font-size: 1px;
69 | overflow: hidden;
70 | text-indent: 100px;
71 | width: 12px;
72 | height: 12px;
73 | }
74 | #term_light, #term_dark {
75 | outline: 1px solid #B4B4B4;
76 | border: 1px solid #fff;
77 | margin-left: 7px;
78 | }
79 | #term_light:hover, #term_dark:hover {
80 | outline-color: #3C3C3C;
81 | }
82 | #term_light {
83 | background-color: #A5A5A5;
84 | background: -moz-linear-gradient(left top, #fff, #A5A5A5);
85 | background: -ms-linear-gradient(left top, #fff, #A5A5A5);
86 | background: -webkit-linear-gradient(left top, #fff, #A5A5A5);
87 | }
88 | #term_dark {
89 | background-color: #000;
90 | background: -moz-linear-gradient(left top, #A5A5A5, #000);
91 | background: -ms-linear-gradient(left top, #A5A5A5, #000);
92 | background: -webkit-linear-gradient(left top, #A5A5A5, #000);
93 | }
94 | .terminal-clear {
95 | color: #657b83;
96 | float: left;
97 | margin: 10px;
98 | cursor: pointer;
99 | }
100 | #dbTabs {
101 | margin-right: -15px;
102 | margin-top: -42px;
103 | }
104 |
105 | #dbTabs ul {
106 | margin: 0px 0px 0px 15px;
107 | padding-top: 30px;
108 | width: 100%;
109 | }
110 |
111 | #redisTab {
112 | margin: 0px;
113 | }
114 |
115 | .tab-content {
116 | padding: 10px 0 0 20px;
117 | }
118 |
119 | #srvList {
120 | margin-right: -31px;
121 | padding: 0 31px 100% 0;
122 | border-right: 1px solid #ddd;
123 | }
124 |
125 | #mainContainer {
126 | padding-left: 1em;
127 | }
128 |
129 | #mainContainer > h3 {
130 | margin-left: -14px;
131 | border-left: 1px solid #ddd;
132 | border-top: 1px solid #ddd;
133 | padding: 10px 0 0 15px;
134 | margin-top: 0px;
135 | border-radius: 4px 0 0 0;
136 | }
137 |
138 | #dbTabs .nav-tabs > .active > a:hover {
139 | cursor: pointer;
140 | }
141 |
142 | .dropdown-menu li>a.danger-action {
143 | color: #d62728;
144 | }
145 |
146 | .dropdown-menu li>a.warning-action {
147 | color: #c67605;
148 | }
149 |
--------------------------------------------------------------------------------
/public/js/nvd3/src/utils.js:
--------------------------------------------------------------------------------
1 |
2 | nv.utils.windowSize = function() {
3 | // Sane defaults
4 | var size = {width: 640, height: 480};
5 |
6 | // Earlier IE uses Doc.body
7 | if (document.body && document.body.offsetWidth) {
8 | size.width = document.body.offsetWidth;
9 | size.height = document.body.offsetHeight;
10 | }
11 |
12 | // IE can use depending on mode it is in
13 | if (document.compatMode=='CSS1Compat' &&
14 | document.documentElement &&
15 | document.documentElement.offsetWidth ) {
16 | size.width = document.documentElement.offsetWidth;
17 | size.height = document.documentElement.offsetHeight;
18 | }
19 |
20 | // Most recent browsers use
21 | if (window.innerWidth && window.innerHeight) {
22 | size.width = window.innerWidth;
23 | size.height = window.innerHeight;
24 | }
25 | return (size);
26 | };
27 |
28 |
29 |
30 | // Easy way to bind multiple functions to window.onresize
31 | // TODO: give a way to remove a function after its bound, other than removing alkl of them
32 | nv.utils.windowResize = function(fun){
33 | var oldresize = window.onresize;
34 |
35 | window.onresize = function(e) {
36 | if (typeof oldresize == 'function') oldresize(e);
37 | fun(e);
38 | }
39 | }
40 |
41 | // Backwards compatible way to implement more d3-like coloring of graphs.
42 | // If passed an array, wrap it in a function which implements the old default
43 | // behavior
44 | nv.utils.getColor = function(color) {
45 | if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back
46 |
47 | if( Object.prototype.toString.call( color ) === '[object Array]' )
48 | return function(d, i) { return d.color || color[i % color.length]; };
49 | else
50 | return color;
51 | //can't really help it if someone passes rubbish as color
52 | }
53 |
54 | // Default color chooser uses the index of an object as before.
55 | nv.utils.defaultColor = function() {
56 | var colors = d3.scale.category20().range();
57 | return function(d, i) { return d.color || colors[i % colors.length] };
58 | }
59 |
60 |
61 | // Returns a color function that takes the result of 'getKey' for each series and
62 | // looks for a corresponding color from the dictionary,
63 | nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
64 | getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined
65 | defaultColors = defaultColors || d3.scale.category20().range(); //default color function
66 |
67 | var defIndex = defaultColors.length; //current default color (going in reverse)
68 |
69 | return function(series, index) {
70 | var key = getKey(series);
71 |
72 | if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over
73 |
74 | if (typeof dictionary[key] !== "undefined")
75 | return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key];
76 | else
77 | return defaultColors[--defIndex]; // no match in dictionary, use default color
78 | }
79 | }
80 |
81 |
82 |
83 | // From the PJAX example on d3js.org, while this is not really directly needed
84 | // it's a very cool method for doing pjax, I may expand upon it a little bit,
85 | // open to suggestions on anything that may be useful
86 | nv.utils.pjax = function(links, content) {
87 | d3.selectAll(links).on("click", function() {
88 | history.pushState(this.href, this.textContent, this.href);
89 | load(this.href);
90 | d3.event.preventDefault();
91 | });
92 |
93 | function load(href) {
94 | d3.html(href, function(fragment) {
95 | var target = d3.select(content).node();
96 | target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target);
97 | nv.utils.pjax(links, content);
98 | });
99 | }
100 |
101 | d3.select(window).on("popstate", function() {
102 | if (d3.event.state) load(d3.event.state);
103 | });
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/libraries/router.php:
--------------------------------------------------------------------------------
1 | parse();
18 | }
19 |
20 | public static function instance()
21 | {
22 | if (!self::$_instance) {
23 | self::$_instance = new self;
24 | }
25 |
26 | return self::$_instance;
27 | }
28 |
29 | protected function parse()
30 | {
31 | $this->path = '';
32 | $this->host = '';
33 | $this->method = self::GET;
34 |
35 | if (PHP_SAPI != 'cli') {
36 | $this->host = $_SERVER['HTTP_HOST'];
37 | $this->method = $_SERVER['REQUEST_METHOD'];
38 |
39 | $this->path = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['REQUEST_URI']);
40 | Log::factory()->write(Log::INFO, $_SERVER['REQUEST_URI'], 'Router');
41 |
42 | if ($this->path == $_SERVER['REQUEST_URI']) {
43 | $this->path = '';
44 | }
45 | } elseif (isset($_SERVER['argv'][1])) {
46 | $this->path = $_SERVER['argv'][1];
47 | }
48 |
49 | $this->baseUrl = '//'.$this->host;
50 | $this->url = '//'.$this->host.$_SERVER['SCRIPT_NAME'];
51 | $this->request = $this->url.$this->path;
52 |
53 | if (preg_match('/^(.*)\/(.*)$/', $_SERVER['SCRIPT_NAME'], $matches)) {
54 | $this->baseUrl .= $matches[1];
55 | }
56 |
57 | if (preg_match('/^(.*)\?(.*)$/', $this->path, $matches)) {
58 | $this->path = $matches[1];
59 |
60 | foreach (explode('&', $matches[2]) as $match) {
61 | if (preg_match('/^(.*)=(.*)$/', $match, $strings)) {
62 | if ($strings[2]) {
63 | $this->_query_strings[$strings[1]] = urldecode($strings[2]);
64 | }
65 | }
66 | }
67 | }
68 |
69 | $this->_params = explode('/', trim($this->path, '/'));
70 |
71 | if (!$this->controller = ucwords(strtolower(array_shift($this->_params)))) {
72 | $this->controller = App::instance()->config['default_controller'];
73 | }
74 |
75 | if (!$this->action = array_shift($this->_params)) {
76 | $this->action = App::instance()->config['default_action'];
77 | }
78 |
79 | if (!$this->serverId = array_shift($this->_params)) {
80 | $this->serverId = 0;
81 | }
82 |
83 | if (!$this->dbId = array_shift($this->_params)) {
84 | $this->dbId = 0;
85 | }
86 | }
87 |
88 | public function __get($key)
89 | {
90 | return isset($this->_data[$key]) ? $this->_data[$key] : null;
91 | }
92 |
93 | public function __set($key, $value)
94 | {
95 | $this->_data[$key] = $value;
96 | }
97 |
98 | public function query($key, $default = null)
99 | {
100 | return isset($this->_query_strings[$key]) ? filter_var($this->_query_strings[$key], FILTER_SANITIZE_STRING) : $default;
101 | }
102 |
103 | public function route()
104 | {
105 | $class = $this->controller.'_Controller';
106 | $method = $this->action.'Action';
107 |
108 | if (class_exists($class)) {
109 | $controller = new $class(
110 | array(
111 | 'serverId' => $this->serverId,
112 | 'dbId' => $this->dbId,
113 | )
114 | );
115 | if (method_exists($controller, $method)) {
116 | call_user_func_array(array($controller, $method), $this->_params);
117 | }
118 |
119 | return;
120 | }
121 |
122 | header("HTTP/1.0 404 Not Found");
123 | Template::factory()->render('404');
124 | }
125 |
126 | public function redirect($url)
127 | {
128 | header("Location: {$this->url}/{$url}");
129 | exit;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/public/js/redmin/terminal.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | function getCookie(name) {
4 | var value = "; " + document.cookie;
5 | var parts = value.split("; " + name + "=");
6 | if (parts.length == 2) return parts.pop().split(";").shift();
7 | }
8 |
9 | var prompt = "redis " + currentHost + ":" + currentPort + ">";
10 |
11 | if (getCookie('term_theme') == 'dark') { $("#mainContainer").addClass('dark_theme'); }
12 |
13 | $("#term_light").on("click", function(e) {
14 | e.preventDefault();
15 | $("#mainContainer").removeClass('dark_theme');
16 | document.cookie = "term_theme=light; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/";
17 | })
18 |
19 | $("#term_dark").on("click", function(e) {
20 | e.preventDefault();
21 | $("#mainContainer").addClass('dark_theme');
22 | document.cookie = "term_theme=dark; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/";
23 | })
24 |
25 | $(".terminal-clear").tooltip({title: "Clear", placement: "right"});
26 |
27 | $(".terminal-clear").on("click", function(e) {
28 | e.preventDefault();
29 | $(".terminal-console").html(prompt);
30 | $('#terminal-input').focus();
31 | })
32 | $('#terminal-input').focus();
33 |
34 | $('.terminal-prompt').ajaxStart(function() {
35 | $(this).html("");
36 | $(this).addClass('ajax-loading');
37 | });
38 |
39 | $('.terminal-prompt').ajaxStop(function() {
40 | $(this).removeClass('ajax-loading');
41 | $(this).html(">");
42 | });
43 |
44 | $('#terminal-input').on('keypress', function(e) {
45 | var key = e.keyCode;
46 | var command = $('#terminal-input').val().trim();
47 |
48 | if (key == '38' || key == '40') {
49 | if (key == '40' && command == '')
50 | return false;
51 | else if (key == '38')
52 | var navigation = 'up';
53 | else
54 | var navigation = 'down';
55 |
56 | var url = baseurl+'/terminal/history/' + currentServerDb + '?navigation=' + navigation;
57 |
58 | if (key == '38' && command == '')
59 | url += '&start=true';
60 |
61 | $.ajax({
62 | url: url,
63 | dataType: 'json',
64 | type: 'GET',
65 | success: function(data) {
66 | if (data) {
67 | if (data.reset)
68 | $('#terminal-input').val('');
69 | else
70 | $('#terminal-input').val(data.command);
71 | } else {
72 | modalPopup.alert('error', 'Could not run the command');
73 | }
74 | }
75 | });
76 | }
77 |
78 | if (key == '13') {
79 | if (command != '') {
80 | $.ajax({
81 | url: baseurl+'/terminal/run/' + currentServerDb,
82 | dataType: 'json',
83 | type: 'POST',
84 | data: 'command='+command,
85 | success: function(data) {
86 | if (data) {
87 | terminal = $(".terminal-console").html();
88 | terminal += " " + command + " ";
89 |
90 | $.each(data.result, function(key, value) {
91 | if (value.trim() != "")
92 | terminal += value + " ";
93 | });
94 |
95 | terminal += prompt;
96 | $(".terminal-console").html(terminal);
97 | $(".terminal-console").animate({ scrollTop: $('.terminal-console')[0].scrollHeight}, 1000);
98 | } else {
99 | modalPopup.alert('error', 'Could not run the command');
100 | }
101 |
102 | $('#terminal-input').val('');
103 | }
104 | });
105 | } else
106 | $(".terminal-console").animate({ scrollTop: $('.terminal-console')[0].scrollHeight}, 1000);
107 | }
108 | });
109 | });
110 |
--------------------------------------------------------------------------------
/public/js/nvd3/lib/cie.js:
--------------------------------------------------------------------------------
1 | (function(d3) {
2 | var cie = d3.cie = {};
3 |
4 | cie.lab = function(l, a, b) {
5 | return arguments.length === 1
6 | ? (l instanceof Lab ? lab(l.l, l.a, l.b)
7 | : (l instanceof Lch ? lch_lab(l.l, l.c, l.h)
8 | : rgb_lab((l = d3.rgb(l)).r, l.g, l.b)))
9 | : lab(+l, +a, +b);
10 | };
11 |
12 | cie.lch = function(l, c, h) {
13 | return arguments.length === 1
14 | ? (l instanceof Lch ? lch(l.l, l.c, l.h)
15 | : (l instanceof Lab ? lab_lch(l.l, l.a, l.b)
16 | : lab_lch((l = rgb_lab((l = d3.rgb(l)).r, l.g, l.b)).l, l.a, l.b)))
17 | : lch(+l, +c, +h);
18 | };
19 |
20 | cie.interpolateLab = function(a, b) {
21 | a = cie.lab(a);
22 | b = cie.lab(b);
23 | var al = a.l,
24 | aa = a.a,
25 | ab = a.b,
26 | bl = b.l - al,
27 | ba = b.a - aa,
28 | bb = b.b - ab;
29 | return function(t) {
30 | return lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
31 | };
32 | };
33 |
34 | cie.interpolateLch = function(a, b) {
35 | a = cie.lch(a);
36 | b = cie.lch(b);
37 | var al = a.l,
38 | ac = a.c,
39 | ah = a.h,
40 | bl = b.l - al,
41 | bc = b.c - ac,
42 | bh = b.h - ah;
43 | if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; // shortest path
44 | return function(t) {
45 | return lch_lab(al + bl * t, ac + bc * t, ah + bh * t) + "";
46 | };
47 | };
48 |
49 | function lab(l, a, b) {
50 | return new Lab(l, a, b);
51 | }
52 |
53 | function Lab(l, a, b) {
54 | this.l = l;
55 | this.a = a;
56 | this.b = b;
57 | }
58 |
59 | Lab.prototype.brighter = function(k) {
60 | return lab(Math.min(100, this.l + K * (arguments.length ? k : 1)), this.a, this.b);
61 | };
62 |
63 | Lab.prototype.darker = function(k) {
64 | return lab(Math.max(0, this.l - K * (arguments.length ? k : 1)), this.a, this.b);
65 | };
66 |
67 | Lab.prototype.rgb = function() {
68 | return lab_rgb(this.l, this.a, this.b);
69 | };
70 |
71 | Lab.prototype.toString = function() {
72 | return this.rgb() + "";
73 | };
74 |
75 | function lch(l, c, h) {
76 | return new Lch(l, c, h);
77 | }
78 |
79 | function Lch(l, c, h) {
80 | this.l = l;
81 | this.c = c;
82 | this.h = h;
83 | }
84 |
85 | Lch.prototype.brighter = function(k) {
86 | return lch(Math.min(100, this.l + K * (arguments.length ? k : 1)), this.c, this.h);
87 | };
88 |
89 | Lch.prototype.darker = function(k) {
90 | return lch(Math.max(0, this.l - K * (arguments.length ? k : 1)), this.c, this.h);
91 | };
92 |
93 | Lch.prototype.rgb = function() {
94 | return lch_lab(this.l, this.c, this.h).rgb();
95 | };
96 |
97 | Lch.prototype.toString = function() {
98 | return this.rgb() + "";
99 | };
100 |
101 | // Corresponds roughly to RGB brighter/darker
102 | var K = 18;
103 |
104 | // D65 standard referent
105 | var X = 0.950470, Y = 1, Z = 1.088830;
106 |
107 | function lab_rgb(l, a, b) {
108 | var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
109 | x = lab_xyz(x) * X;
110 | y = lab_xyz(y) * Y;
111 | z = lab_xyz(z) * Z;
112 | return d3.rgb(
113 | xyz_rgb( 3.2404542 * x - 1.5371385 * y - 0.4985314 * z),
114 | xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z),
115 | xyz_rgb( 0.0556434 * x - 0.2040259 * y + 1.0572252 * z)
116 | );
117 | }
118 |
119 | function rgb_lab(r, g, b) {
120 | r = rgb_xyz(r);
121 | g = rgb_xyz(g);
122 | b = rgb_xyz(b);
123 | var x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / X),
124 | y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / Y),
125 | z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / Z);
126 | return lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
127 | }
128 |
129 | function lab_lch(l, a, b) {
130 | var c = Math.sqrt(a * a + b * b),
131 | h = Math.atan2(b, a) / Math.PI * 180;
132 | return lch(l, c, h);
133 | }
134 |
135 | function lch_lab(l, c, h) {
136 | h = h * Math.PI / 180;
137 | return lab(l, Math.cos(h) * c, Math.sin(h) * c);
138 | }
139 |
140 | function lab_xyz(x) {
141 | return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
142 | }
143 |
144 | function xyz_lab(x) {
145 | return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
146 | }
147 |
148 | function xyz_rgb(r) {
149 | return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055));
150 | }
151 |
152 | function rgb_xyz(r) {
153 | return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
154 | }
155 | })(d3);
156 |
--------------------------------------------------------------------------------
/public/js/nvd3/src/tooltip.js:
--------------------------------------------------------------------------------
1 |
2 | /*****
3 | * A no-frills tooltip implementation.
4 | *****/
5 |
6 |
7 | (function() {
8 |
9 | var nvtooltip = window.nv.tooltip = {};
10 |
11 | nvtooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
12 |
13 | var container = document.createElement('div');
14 | container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
15 |
16 | gravity = gravity || 's';
17 | dist = dist || 20;
18 |
19 | var body = parentContainer ? parentContainer : document.getElementsByTagName('body')[0];
20 |
21 | container.innerHTML = content;
22 | container.style.left = 0;
23 | container.style.top = 0;
24 | container.style.opacity = 0;
25 |
26 | body.appendChild(container);
27 |
28 | var height = parseInt(container.offsetHeight),
29 | width = parseInt(container.offsetWidth),
30 | windowWidth = nv.utils.windowSize().width,
31 | windowHeight = nv.utils.windowSize().height,
32 | scrollTop = window.scrollY,
33 | scrollLeft = window.scrollX,
34 | left, top;
35 |
36 | windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
37 | windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
38 |
39 | var tooltipTop = function ( Elem ) {
40 | var offsetTop = top;
41 | do {
42 | if( !isNaN( Elem.offsetTop ) ) {
43 | offsetTop += (Elem.offsetTop);
44 | }
45 | } while( Elem = Elem.offsetParent );
46 | return offsetTop;
47 | }
48 |
49 | var tooltipLeft = function ( Elem ) {
50 | var offsetLeft = left;
51 | do {
52 | if( !isNaN( Elem.offsetLeft ) ) {
53 | offsetLeft += (Elem.offsetLeft);
54 | }
55 | } while( Elem = Elem.offsetParent );
56 | return offsetLeft;
57 | }
58 |
59 | switch (gravity) {
60 | case 'e':
61 | left = pos[0] - width - dist;
62 | top = pos[1] - (height / 2);
63 | var tLeft = tooltipLeft(container);
64 | var tTop = tooltipTop(container);
65 | if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
66 | if (tTop < scrollTop) top = scrollTop - tTop + top;
67 | if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
68 | break;
69 | case 'w':
70 | left = pos[0] + dist;
71 | top = pos[1] - (height / 2);
72 | if (tLeft + width > windowWidth) left = pos[0] - width - dist;
73 | if (tTop < scrollTop) top = scrollTop + 5;
74 | if (tTop + height > scrollTop + windowHeight) top = scrollTop - height - 5;
75 | break;
76 | case 'n':
77 | left = pos[0] - (width / 2) - 5;
78 | top = pos[1] + dist;
79 | var tLeft = tooltipLeft(container);
80 | var tTop = tooltipTop(container);
81 | if (tLeft < scrollLeft) left = scrollLeft + 5;
82 | if (tLeft + width > windowWidth) left = left - width/2 + 5;
83 | if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
84 | break;
85 | case 's':
86 | left = pos[0] - (width / 2);
87 | top = pos[1] - height - dist;
88 | var tLeft = tooltipLeft(container);
89 | var tTop = tooltipTop(container);
90 | if (tLeft < scrollLeft) left = scrollLeft + 5;
91 | if (tLeft + width > windowWidth) left = left - width/2 + 5;
92 | if (scrollTop > tTop) top = scrollTop;
93 | break;
94 | }
95 |
96 |
97 | container.style.left = left+'px';
98 | container.style.top = top+'px';
99 | container.style.opacity = 1;
100 | container.style.position = 'absolute'; //fix scroll bar issue
101 | container.style.pointerEvents = 'none'; //fix scroll bar issue
102 |
103 | return container;
104 | };
105 |
106 | nvtooltip.cleanup = function() {
107 |
108 | // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
109 | var tooltips = document.getElementsByClassName('nvtooltip');
110 | var purging = [];
111 | while(tooltips.length) {
112 | purging.push(tooltips[0]);
113 | tooltips[0].style.transitionDelay = '0 !important';
114 | tooltips[0].style.opacity = 0;
115 | tooltips[0].className = 'nvtooltip-pending-removal';
116 | }
117 |
118 |
119 | setTimeout(function() {
120 |
121 | while (purging.length) {
122 | var removeMe = purging.pop();
123 | removeMe.parentNode.removeChild(removeMe);
124 | }
125 | }, 500);
126 | };
127 |
128 |
129 | })();
130 |
--------------------------------------------------------------------------------
/controllers/stats.php:
--------------------------------------------------------------------------------
1 | statsModel = new Stats_Model($this->app->current);
10 | $this->template = Template::factory('json');
11 | }
12 |
13 | public function keysAction()
14 | {
15 | $result = array();
16 | $from = $this->router->query('from', strtotime('-12 hours'));
17 | $to = $this->router->query('to', time());
18 | $hits = $this->statsModel->getKeys('hits', $from, $to);
19 | $misses = $this->statsModel->getKeys('misses', $from, $to);
20 | $expiredKeys = $this->statsModel->getKeys('expired_keys', $from, $to);
21 |
22 | $result[] = array('key' => 'Keyspace Misses', 'values' => $misses);
23 | $result[] = array('key' => 'Keyspace Hits', 'values' => $hits);
24 | $result[] = array('key' => 'Expired Keys', 'values' => $expiredKeys);
25 |
26 | $this->template->render($result);
27 | }
28 |
29 | public function commandsAction()
30 | {
31 | $result = array();
32 | $from = $this->router->query('from', strtotime('-12 hours'));
33 | $to = $this->router->query('to', time());
34 | $connections = $this->statsModel->getKeys('connections', $from, $to);
35 | $commands = $this->statsModel->getKeys('commands', $from, $to);
36 |
37 | $result[] = array('key' => 'Connections Received', 'values' => $connections);
38 | $result[] = array('key' => 'Commands Processed', 'values' => $commands);
39 |
40 | $this->template->render($result);
41 | }
42 |
43 | public function clientsAction()
44 | {
45 | $result = array();
46 | $from = $this->router->query('from', strtotime('-12 hours'));
47 | $to = $this->router->query('to', time());
48 | $clients = $this->statsModel->getKeys('clients', $from, $to);
49 |
50 | $result[] = array('key' => 'Clients', 'values' => $clients);
51 |
52 | $this->template->render($result);
53 | }
54 |
55 | public function memoryAction()
56 | {
57 | $result = array();
58 | $from = $this->router->query('from', strtotime('-12 hours'));
59 | $to = $this->router->query('to', time());
60 | $memory = $this->statsModel->getKeys('memory', $from, $to);
61 |
62 | $result[] = array('key' => 'Memory Usage', 'values' => $memory);
63 |
64 | $this->template->render($result);
65 | }
66 |
67 | public function dbkeysAction()
68 | {
69 | $result = array();
70 | $from = $this->router->query('from', strtotime('-12 hours'));
71 | $to = $this->router->query('to', time());
72 |
73 | foreach ($this->infoModel->getDbs($this->db->info()) as $db) {
74 | $keys = $this->statsModel->getKeys("db{$db}:keys", $from, $to);
75 | $result[] = array('key' => "DB{$db} Keys", 'values' => $keys);
76 | }
77 |
78 | $this->template->render($result);
79 | }
80 |
81 | public function dbexpiresAction()
82 | {
83 | $result = array();
84 | $from = $this->router->query('from', strtotime('-12 hours'));
85 | $to = $this->router->query('to', time());
86 |
87 | foreach ($this->infoModel->getDbs($this->db->info()) as $db) {
88 | $keys = $this->statsModel->getKeys("db{$db}:expired_keys", $from, $to);
89 | $result[] = array('key' => "DB{$db} Expired Keys", 'values' => $keys);
90 | }
91 |
92 | $this->template->render($result);
93 | }
94 |
95 | public function cpuAction()
96 | {
97 | $result = array();
98 | $from = $this->router->query('from', strtotime('-12 hours'));
99 | $to = $this->router->query('to', time());
100 | $user_cpu = $this->statsModel->getKeys('user_cpu', $from, $to);
101 | $system_cpu = $this->statsModel->getKeys('system_cpu', $from, $to);
102 |
103 | $result[] = array('key' => 'User CPU Usage', 'values' => $user_cpu);
104 | $result[] = array('key' => 'System CPU Usage', 'values' => $system_cpu);
105 |
106 | $this->template->render($result);
107 | }
108 |
109 | public function aofAction()
110 | {
111 | $result = array();
112 | $from = $this->router->query('from', strtotime('-12 hours'));
113 | $to = $this->router->query('to', time());
114 | $aof_size = $this->statsModel->getKeys('aof_size', $from, $to);
115 | $aof_base = $this->statsModel->getKeys('aof_base', $from, $to);
116 |
117 | $result[] = array('key' => 'AOF current size', 'values' => $aof_size);
118 | $result[] = array('key' => 'AOF base size', 'values' => $aof_base);
119 |
120 | $this->template->render($result);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/views/keys/search.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 |
3 |
Search Results =count($this->keys)?> result(s) found
4 |
5 |
6 |
×
7 | Since this doesn't support pagination yet, try to limit your search. Otherwise your browser might crash
8 |
9 | search && !count($this->keys)): ?>
10 |
11 |
×
12 | Your search did not match any keys
13 |
14 |
15 | search): ?>
16 |
17 |
×
18 | Please enter valid search criteria
19 |
20 |
21 |
Redis Keys
22 |
23 |
24 |
25 |
26 |
27 | Search
28 |
29 | keys)): ?>
30 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/public/js/nvd3/src/models/distribution.js:
--------------------------------------------------------------------------------
1 |
2 | nv.models.distribution = function() {
3 |
4 | //============================================================
5 | // Public Variables with Default Settings
6 | //------------------------------------------------------------
7 |
8 | var margin = {top: 0, right: 0, bottom: 0, left: 0}
9 | , width = 400 //technically width or height depending on x or y....
10 | , size = 8
11 | , axis = 'x' // 'x' or 'y'... horizontal or vertical
12 | , getData = function(d) { return d[axis] } // defaults d.x or d.y
13 | , color = nv.utils.defaultColor()
14 | , scale = d3.scale.linear()
15 | , domain
16 | ;
17 |
18 | //============================================================
19 |
20 |
21 | //============================================================
22 | // Private Variables
23 | //------------------------------------------------------------
24 |
25 | var scale0;
26 |
27 | //============================================================
28 |
29 |
30 | function chart(selection) {
31 | selection.each(function(data) {
32 | var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
33 | naxis = axis == 'x' ? 'y' : 'x',
34 | container = d3.select(this);
35 |
36 |
37 | //------------------------------------------------------------
38 | // Setup Scales
39 |
40 | scale0 = scale0 || scale;
41 |
42 | //------------------------------------------------------------
43 |
44 |
45 | //------------------------------------------------------------
46 | // Setup containers and skeleton of chart
47 |
48 | var wrap = container.selectAll('g.nv-distribution').data([data]);
49 | var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
50 | var gEnter = wrapEnter.append('g');
51 | var g = wrap.select('g');
52 |
53 | wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
54 |
55 | //------------------------------------------------------------
56 |
57 |
58 | var distWrap = g.selectAll('g.nv-dist')
59 | .data(function(d) { return d }, function(d) { return d.key });
60 |
61 | distWrap.enter().append('g');
62 | distWrap
63 | .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
64 | .style('stroke', function(d,i) { return color(d, i) });
65 |
66 | var dist = distWrap.selectAll('line.nv-dist' + axis)
67 | .data(function(d) { return d.values })
68 | dist.enter().append('line')
69 | .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
70 | .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
71 | d3.transition(distWrap.exit().selectAll('line.nv-dist' + axis))
72 | .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
73 | .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
74 | .style('stroke-opacity', 0)
75 | .remove();
76 | dist
77 | .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
78 | .attr(naxis + '1', 0)
79 | .attr(naxis + '2', size);
80 | d3.transition(dist)
81 | .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
82 | .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
83 |
84 |
85 | scale0 = scale.copy();
86 |
87 | });
88 |
89 | return chart;
90 | }
91 |
92 |
93 | //============================================================
94 | // Expose Public Variables
95 | //------------------------------------------------------------
96 |
97 | chart.margin = function(_) {
98 | if (!arguments.length) return margin;
99 | margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
100 | margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
101 | margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
102 | margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
103 | return chart;
104 | };
105 |
106 | chart.width = function(_) {
107 | if (!arguments.length) return width;
108 | width = _;
109 | return chart;
110 | };
111 |
112 | chart.axis = function(_) {
113 | if (!arguments.length) return axis;
114 | axis = _;
115 | return chart;
116 | };
117 |
118 | chart.size = function(_) {
119 | if (!arguments.length) return size;
120 | size = _;
121 | return chart;
122 | };
123 |
124 | chart.getData = function(_) {
125 | if (!arguments.length) return getData;
126 | getData = d3.functor(_);
127 | return chart;
128 | };
129 |
130 | chart.scale = function(_) {
131 | if (!arguments.length) return scale;
132 | scale = _;
133 | return chart;
134 | };
135 |
136 | chart.color = function(_) {
137 | if (!arguments.length) return color;
138 | color = nv.utils.getColor(_);
139 | return chart;
140 | };
141 |
142 | //============================================================
143 |
144 |
145 | return chart;
146 | }
147 |
--------------------------------------------------------------------------------
/views/welcome/info.php:
--------------------------------------------------------------------------------
1 |
2 |
Redis Info
3 |
4 |
5 |
6 | Version:
7 |
8 |
9 | =$this->info['redis_version']?>
10 |
11 |
12 |
13 |
14 | Mode:
15 |
16 |
17 | =$this->info['redis_mode']?>
18 |
19 |
20 |
21 |
22 | Role:
23 |
24 |
25 | =$this->info['role']?>
26 |
27 |
28 |
29 |
30 | OS:
31 |
32 |
33 | =$this->info['os']?>
34 |
35 |
36 |
37 |
38 | Process ID:
39 |
40 |
41 | =$this->info['process_id']?>
42 |
43 |
44 |
45 |
46 | Uptime:
47 |
48 |
49 | uptimeDays > 0) {
50 | echo "{$this->uptimeDays} days ";
51 | } echo gmdate('H:i:s', $this->info['uptime_in_seconds']);?>
52 |
53 |
54 |
55 |
56 | Clients:
57 |
58 |
59 | =$this->info['connected_clients']?>
60 |
61 |
62 | info['role'] == 'master'): ?>
63 |
64 |
65 | Slaves:
66 |
67 |
68 | =$this->info['connected_slaves']?>
69 |
70 |
71 |
72 |
73 |
74 | Used Memory:
75 |
76 |
77 | =$this->info['used_memory_human']?>
78 |
79 |
80 |
81 |
82 | Used Memory Peak:
83 |
84 |
85 | =$this->info['used_memory_peak_human']?>
86 |
87 |
88 |
89 |
90 | Memory Fragmentation Ratio:
91 |
92 |
93 | =$this->info['mem_fragmentation_ratio']?>
94 |
95 |
96 |
97 |
98 | Last Save Time:
99 |
100 |
101 | =date('Y-m-d H:i:s', isset($this->info['last_save_time']) ? $this->info['last_save_time']
102 | : $this->info['rdb_last_save_time'])?>
103 |
104 |
105 |
106 |
107 | Total Connections Received:
108 |
109 |
110 | =number_format($this->info['total_connections_received'])?>
111 |
112 |
113 |
114 |
115 | Total Commands Processed:
116 |
117 |
118 | =number_format($this->info['total_commands_processed'])?>
119 |
120 |
121 |
122 |
123 | Expired Keys:
124 |
125 |
126 | =number_format($this->info['expired_keys'])?>
127 |
128 |
129 |
130 |
131 | Keyspace Hits:
132 |
133 |
134 | =number_format($this->info['keyspace_hits'])?>
135 |
136 |
137 |
138 |
139 | Keyspace Misses:
140 |
141 |
142 | =number_format($this->info['keyspace_misses'])?>
143 |
144 |
145 |
146 |
147 | System CPU Usage:
148 |
149 |
150 | =$this->info['used_cpu_sys']?>
151 |
152 |
153 |
154 |
155 | User CPU Usage:
156 |
157 |
158 | =$this->info['used_cpu_user']?>
159 |
160 |
161 |
162 |
163 | Database Size:
164 |
165 |
166 | =number_format($this->dbSize)?>
167 |
168 |
169 |
170 |
171 | Last save to disk:
172 |
173 |
174 | =date('Y-m-d H:i:s', $this->lastSave)?>
175 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/public/js/redmin/actions.js:
--------------------------------------------------------------------------------
1 | var deleteRow = function(e) {
2 | e.preventDefault();
3 |
4 | var tr = $(e.target).parents('tr');
5 | var type = $(e.target).attr('keytype');
6 | var keyinfo = $(e.target).attr('keyinfo');
7 | var id = $(e.target).attr('id');
8 |
9 | if (typeof(keyinfo) == 'undefined') {
10 | var url = baseurl+'/'+type+'/delete/'+currentServerDb+'/'+encodeURIComponent(id);
11 | } else {
12 | var url = baseurl+'/'+type+'/delete/'+currentServerDb+'/'+encodeURIComponent(keyinfo)+'/'+encodeURIComponent(id);
13 | }
14 |
15 | modalPopup.confirm(
16 | function() {
17 | $.ajax({
18 | url: url,
19 | dataType: 'json',
20 | success: function(data) {
21 | modalPopup.hide('confirm');
22 |
23 | if (data == 1) {
24 | tr.remove();
25 | }
26 | }
27 | });
28 | }
29 | );
30 | }
31 |
32 | $(document).ready(function() {
33 | $('.del').click(function(e) {
34 | deleteRow(e);
35 | });
36 |
37 | $('.keys-table td').click(function(e) {
38 | if ($(e.target).hasClass('icon') || $(e.target).hasClass('key-checkbox'))
39 | return;
40 |
41 | var input = $($(e.target).parents('tr')[0]).find("input[name=keys\\[\\]]");
42 |
43 | if (!input.hasClass('all-key-checkbox'))
44 | input.attr('checked', !input.is(':checked'));
45 | });
46 |
47 | $('#checkall').click(function(e) {
48 | $("input[name=keys\\[\\]]").attr('checked', $(e.target).is(':checked'));
49 | });
50 |
51 | $('.moveall').click(function(e) {
52 | e.preventDefault();
53 | var checkboxes = $("input[name=keys\\[\\]]:checked");
54 |
55 | if (checkboxes.length > 0) {
56 | modalPopup.moveKeys(
57 | function() {
58 | var destination = $('.modal-body input').val().trim();
59 | var type = $(e.target).attr('keytype');
60 | var keyinfo = $(e.target).attr('keyinfo');
61 |
62 | if (destination != '') {
63 | var values = [];
64 | checkboxes.each(function() {
65 | values.push($(this).val());
66 | });
67 |
68 | if (typeof(keyinfo) == 'undefined') {
69 | var postdata = {'values[]': values, 'destination': destination};
70 | } else {
71 | var postdata = {'values[]': values, 'destination': destination, 'keyinfo': keyinfo};
72 | }
73 |
74 | $.post(
75 | baseurl+'/'+type+'/moveall/'+currentServerDb,
76 | postdata,
77 | function(data) {
78 | modalPopup.hide('moveKeys');
79 |
80 | checkboxes.each(function() {
81 | if (data[$(this).val()]) {
82 | $(this).parents('tr').remove();
83 | }
84 | });
85 | }
86 | );
87 | };
88 | },
89 | $(e.target).attr('modaltitle'),
90 | $(e.target).attr('modaltip')
91 | );
92 | } else {
93 | modalPopup.alert('invalid', 'Please select key(s) to move');
94 | }
95 | });
96 |
97 |
98 | $('.delall').click(function(e) {
99 | e.preventDefault();
100 | var checkboxes = $("input[name=keys\\[\\]]:checked");
101 | var type = $(e.target).attr('keytype');
102 | var keyinfo = $(e.target).attr('keyinfo');
103 |
104 | if (checkboxes.length > 0) {
105 | modalPopup.confirm(
106 | function() {
107 | var values = [];
108 | checkboxes.each(function() {
109 | values.push($(this).val());
110 | });
111 |
112 | if (typeof(keyinfo) == 'undefined') {
113 | var postdata = {'values[]': values};
114 | } else {
115 | var postdata = {'values[]': values, 'keyinfo': keyinfo};
116 | }
117 |
118 | $.post(
119 | baseurl+'/'+type+'/delall/'+currentServerDb,
120 | postdata,
121 | function(data) {
122 | modalPopup.hide('confirm');
123 |
124 | checkboxes.each(function() {
125 | if (data[$(this).val()] == 1) {
126 | $(this).parents('tr').remove();
127 | }
128 | });
129 | }
130 | );
131 | }
132 | );
133 | } else {
134 | modalPopup.alert('invalid', 'Please select key(s) to delete');
135 | }
136 | });
137 | });
138 |
--------------------------------------------------------------------------------
/public/js/nvd3/src/models/sparkline.js:
--------------------------------------------------------------------------------
1 |
2 | nv.models.sparkline = function() {
3 |
4 | //============================================================
5 | // Public Variables with Default Settings
6 | //------------------------------------------------------------
7 |
8 | var margin = {top: 2, right: 0, bottom: 2, left: 0}
9 | , width = 400
10 | , height = 32
11 | , animate = true
12 | , x = d3.scale.linear()
13 | , y = d3.scale.linear()
14 | , getX = function(d) { return d.x }
15 | , getY = function(d) { return d.y }
16 | , color = nv.utils.getColor(['#000'])
17 | , xDomain
18 | , yDomain
19 | ;
20 |
21 | //============================================================
22 |
23 |
24 | function chart(selection) {
25 | selection.each(function(data) {
26 | var availableWidth = width - margin.left - margin.right,
27 | availableHeight = height - margin.top - margin.bottom,
28 | container = d3.select(this);
29 |
30 |
31 | //------------------------------------------------------------
32 | // Setup Scales
33 |
34 | x .domain(xDomain || d3.extent(data, getX ))
35 | .range([0, availableWidth]);
36 |
37 | y .domain(yDomain || d3.extent(data, getY ))
38 | .range([availableHeight, 0]);
39 |
40 | //------------------------------------------------------------
41 |
42 |
43 | //------------------------------------------------------------
44 | // Setup containers and skeleton of chart
45 |
46 | var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
47 | var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
48 | var gEnter = wrapEnter.append('g');
49 | var g = wrap.select('g');
50 |
51 | wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
52 |
53 | //------------------------------------------------------------
54 |
55 |
56 | var paths = wrap.selectAll('path')
57 | .data(function(d) { return [d] });
58 | paths.enter().append('path');
59 | paths.exit().remove();
60 | paths
61 | .style('stroke', function(d,i) { return d.color || color(d, i) })
62 | .attr('d', d3.svg.line()
63 | .x(function(d,i) { return x(getX(d,i)) })
64 | .y(function(d,i) { return y(getY(d,i)) })
65 | );
66 |
67 |
68 | // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
69 | var points = wrap.selectAll('circle.nv-point')
70 | .data(function(data) {
71 | var yValues = data.map(function(d, i) { return getY(d,i); });
72 | function pointIndex(index) {
73 | if (index != -1) {
74 | var result = data[index];
75 | result.pointIndex = index;
76 | return result;
77 | } else {
78 | return null;
79 | }
80 | }
81 | var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
82 | minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
83 | currentPoint = pointIndex(yValues.length - 1);
84 | return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
85 | });
86 | points.enter().append('circle');
87 | points.exit().remove();
88 | points
89 | .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
90 | .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
91 | .attr('r', 2)
92 | .attr('class', function(d,i) {
93 | return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
94 | getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
95 | });
96 | });
97 |
98 | return chart;
99 | }
100 |
101 |
102 | //============================================================
103 | // Expose Public Variables
104 | //------------------------------------------------------------
105 |
106 | chart.margin = function(_) {
107 | if (!arguments.length) return margin;
108 | margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
109 | margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
110 | margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
111 | margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
112 | return chart;
113 | };
114 |
115 | chart.width = function(_) {
116 | if (!arguments.length) return width;
117 | width = _;
118 | return chart;
119 | };
120 |
121 | chart.height = function(_) {
122 | if (!arguments.length) return height;
123 | height = _;
124 | return chart;
125 | };
126 |
127 | chart.x = function(_) {
128 | if (!arguments.length) return getX;
129 | getX = d3.functor(_);
130 | return chart;
131 | };
132 |
133 | chart.y = function(_) {
134 | if (!arguments.length) return getY;
135 | getY = d3.functor(_);
136 | return chart;
137 | };
138 |
139 | chart.xScale = function(_) {
140 | if (!arguments.length) return x;
141 | x = _;
142 | return chart;
143 | };
144 |
145 | chart.yScale = function(_) {
146 | if (!arguments.length) return y;
147 | y = _;
148 | return chart;
149 | };
150 |
151 | chart.xDomain = function(_) {
152 | if (!arguments.length) return xDomain;
153 | xDomain = _;
154 | return chart;
155 | };
156 |
157 | chart.yDomain = function(_) {
158 | if (!arguments.length) return yDomain;
159 | yDomain = _;
160 | return chart;
161 | };
162 |
163 | chart.animate = function(_) {
164 | if (!arguments.length) return animate;
165 | animate = _;
166 | return chart;
167 | };
168 |
169 | chart.color = function(_) {
170 | if (!arguments.length) return color;
171 | color = nv.utils.getColor(_);
172 | return chart;
173 | };
174 |
175 | //============================================================
176 |
177 |
178 | return chart;
179 | }
180 |
--------------------------------------------------------------------------------
/public/js/nvd3/lib/horizon.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | d3.horizon = function() {
3 | var bands = 1, // between 1 and 5, typically
4 | mode = "offset", // or mirror
5 | interpolate = "linear", // or basis, monotone, step-before, etc.
6 | x = d3_horizonX,
7 | y = d3_horizonY,
8 | w = 960,
9 | h = 40,
10 | duration = 0;
11 |
12 | var color = d3.scale.linear()
13 | .domain([-1, 0, 1])
14 | .range(["#d62728", "#fff", "#1f77b4"]);
15 |
16 | // For each small multiple…
17 | function horizon(g) {
18 | g.each(function(d, i) {
19 | var g = d3.select(this),
20 | n = 2 * bands + 1,
21 | xMin = Infinity,
22 | xMax = -Infinity,
23 | yMax = -Infinity,
24 | x0, // old x-scale
25 | y0, // old y-scale
26 | id; // unique id for paths
27 |
28 | // Compute x- and y-values along with extents.
29 | var data = d.map(function(d, i) {
30 | var xv = x.call(this, d, i),
31 | yv = y.call(this, d, i);
32 | if (xv < xMin) xMin = xv;
33 | if (xv > xMax) xMax = xv;
34 | if (-yv > yMax) yMax = -yv;
35 | if (yv > yMax) yMax = yv;
36 | return [xv, yv];
37 | });
38 |
39 | // Compute the new x- and y-scales, and transform.
40 | var x1 = d3.scale.linear().domain([xMin, xMax]).range([0, w]),
41 | y1 = d3.scale.linear().domain([0, yMax]).range([0, h * bands]),
42 | t1 = d3_horizonTransform(bands, h, mode);
43 |
44 | // Retrieve the old scales, if this is an update.
45 | if (this.__chart__) {
46 | x0 = this.__chart__.x;
47 | y0 = this.__chart__.y;
48 | t0 = this.__chart__.t;
49 | id = this.__chart__.id;
50 | } else {
51 | x0 = x1.copy();
52 | y0 = y1.copy();
53 | t0 = t1;
54 | id = ++d3_horizonId;
55 | }
56 |
57 | // We'll use a defs to store the area path and the clip path.
58 | var defs = g.selectAll("defs")
59 | .data([null]);
60 |
61 | // The clip path is a simple rect.
62 | defs.enter().append("defs").append("clipPath")
63 | .attr("id", "d3_horizon_clip" + id)
64 | .append("rect")
65 | .attr("width", w)
66 | .attr("height", h);
67 |
68 | defs.select("rect").transition()
69 | .duration(duration)
70 | .attr("width", w)
71 | .attr("height", h);
72 |
73 | // We'll use a container to clip all horizon layers at once.
74 | g.selectAll("g")
75 | .data([null])
76 | .enter().append("g")
77 | .attr("clip-path", "url(#d3_horizon_clip" + id + ")");
78 |
79 | // Instantiate each copy of the path with different transforms.
80 | var path = g.select("g").selectAll("path")
81 | .data(d3.range(-1, -bands - 1, -1).concat(d3.range(1, bands + 1)), Number);
82 |
83 | var d0 = d3_horizonArea
84 | .interpolate(interpolate)
85 | .x(function(d) { return x0(d[0]); })
86 | .y0(h * bands)
87 | .y1(function(d) { return h * bands - y0(d[1]); })
88 | (data);
89 |
90 | var d1 = d3_horizonArea
91 | .x(function(d) { return x1(d[0]); })
92 | .y1(function(d) { return h * bands - y1(d[1]); })
93 | (data);
94 |
95 | path.enter().append("path")
96 | .style("fill", color)
97 | .attr("transform", t0)
98 | .attr("d", d0);
99 |
100 | path.transition()
101 | .duration(duration)
102 | .style("fill", color)
103 | .attr("transform", t1)
104 | .attr("d", d1);
105 |
106 | path.exit().transition()
107 | .duration(duration)
108 | .attr("transform", t1)
109 | .attr("d", d1)
110 | .remove();
111 |
112 | // Stash the new scales.
113 | this.__chart__ = {x: x1, y: y1, t: t1, id: id};
114 | });
115 | d3.timer.flush();
116 | }
117 |
118 | horizon.duration = function(x) {
119 | if (!arguments.length) return duration;
120 | duration = +x;
121 | return horizon;
122 | };
123 |
124 | horizon.bands = function(x) {
125 | if (!arguments.length) return bands;
126 | bands = +x;
127 | color.domain([-bands, 0, bands]);
128 | return horizon;
129 | };
130 |
131 | horizon.mode = function(x) {
132 | if (!arguments.length) return mode;
133 | mode = x + "";
134 | return horizon;
135 | };
136 |
137 | horizon.colors = function(x) {
138 | if (!arguments.length) return color.range();
139 | color.range(x);
140 | return horizon;
141 | };
142 |
143 | horizon.interpolate = function(x) {
144 | if (!arguments.length) return interpolate;
145 | interpolate = x + "";
146 | return horizon;
147 | };
148 |
149 | horizon.x = function(z) {
150 | if (!arguments.length) return x;
151 | x = z;
152 | return horizon;
153 | };
154 |
155 | horizon.y = function(z) {
156 | if (!arguments.length) return y;
157 | y = z;
158 | return horizon;
159 | };
160 |
161 | horizon.width = function(x) {
162 | if (!arguments.length) return w;
163 | w = +x;
164 | return horizon;
165 | };
166 |
167 | horizon.height = function(x) {
168 | if (!arguments.length) return h;
169 | h = +x;
170 | return horizon;
171 | };
172 |
173 | return horizon;
174 | };
175 |
176 | var d3_horizonArea = d3.svg.area(),
177 | d3_horizonId = 0;
178 |
179 | function d3_horizonX(d) {
180 | return d[0];
181 | }
182 |
183 | function d3_horizonY(d) {
184 | return d[1];
185 | }
186 |
187 | function d3_horizonTransform(bands, h, mode) {
188 | return mode == "offset"
189 | ? function(d) { return "translate(0," + (d + (d < 0) - bands) * h + ")"; }
190 | : function(d) { return (d < 0 ? "scale(1,-1)" : "") + "translate(0," + (d - bands) * h + ")"; };
191 | }
192 | })();
193 |
--------------------------------------------------------------------------------
/controllers/keys.php:
--------------------------------------------------------------------------------
1 | inputs->get('key', null);
8 | $keys = array();
9 |
10 | if (isset($key) && trim($key) != '') {
11 | $keys = $this->db->keys($key);
12 | asort($keys);
13 | }
14 |
15 | Template::factory()->render('keys/search', array('keys' => $keys, 'search' => $key));
16 | }
17 |
18 | public function moveAction($key)
19 | {
20 | $moved = null;
21 |
22 | if ($this->router->method == Router::POST) {
23 | $db = $this->inputs->post('db', null);
24 | $key = $this->inputs->post('key', null);
25 |
26 | if (!isset($db) || trim($db) == '' || !isset($key) || trim($key) == '') {
27 | $moved = false;
28 | } else {
29 | $moved = $this->db->move($key, $db);
30 | }
31 | }
32 |
33 | Template::factory()->render('keys/move', array('moved' => $moved, 'key' => urldecode($key)));
34 | }
35 |
36 | public function renameAction($key)
37 | {
38 | $renamed = null;
39 |
40 | if ($this->router->method == Router::POST) {
41 | $newkey = $this->inputs->post('newkey', null);
42 | $key = $this->inputs->post('key', null);
43 |
44 | if (!isset($newkey) || trim($newkey) == '' || !isset($key) || trim($key) == '') {
45 | $renamed = false;
46 | } else {
47 | $renamed = $this->db->rename($key, $newkey);
48 | }
49 | }
50 |
51 | Template::factory()->render('keys/rename', array('renamed' => $renamed, 'key' => urldecode($key)));
52 | }
53 |
54 | public function expireAction($key)
55 | {
56 | $updated = null;
57 | $oldttl = $this->db->ttl(urldecode($key));
58 |
59 | if ($this->router->method == Router::POST) {
60 | $ttl = $this->inputs->post('ttl', null);
61 | $key = $this->inputs->post('key', null);
62 |
63 | if (!isset($ttl) || trim($ttl) == '' || !isset($key) || trim($key) == '') {
64 | $updated = false;
65 | } elseif ((int)$ttl > 0) {
66 | $updated = $this->db->expire($key, $ttl);
67 | } elseif ($oldttl > 0) {
68 | $updated = $this->db->persist($key);
69 | } else {
70 | $updated = true;
71 | }
72 | }
73 |
74 |
75 |
76 | Template::factory()->render('keys/ttl', array('updated' => $updated, 'key' => urldecode($key), 'ttl' => $oldttl));
77 | }
78 |
79 | public function moveallAction()
80 | {
81 | if ($this->router->method == Router::POST) {
82 | $results = array();
83 | $values = $this->inputs->post('values', array());
84 | $destination = $this->inputs->post('destination');
85 |
86 | foreach ($values as $key => $value) {
87 | $results[$value] = $this->db->move($value, $destination);
88 | }
89 |
90 | Template::factory('json')->render($results);
91 | }
92 | }
93 |
94 | public function delallAction()
95 | {
96 | if ($this->router->method == Router::POST) {
97 | $results = array();
98 | $values = $this->inputs->post('values', array());
99 |
100 | foreach ($values as $key => $value) {
101 | $results[$value] = $this->db->del($value);
102 | }
103 |
104 | Template::factory('json')->render($results);
105 | }
106 | }
107 |
108 | public function bulkdeleteAction()
109 | {
110 | if ($this->router->method == Router::POST) {
111 | $key = $this->inputs->post('key', null);
112 |
113 | if (isset($key) && trim($key) != '') {
114 | $config = App::instance()->config;
115 | $client = new GearmanClient();
116 |
117 | $client->addServer($config['gearman']['host'], $config['gearman']['port']);
118 | $client->doBackground('delete_keys',
119 | serialize(array(
120 | 'key' => $key,
121 | 'server' => $this->app->current,
122 | ))
123 | );
124 | }
125 | }
126 | }
127 |
128 | public function deleteinfoAction($key)
129 | {
130 | $this->db->incrBy("phpredmin:gearman:requests:{$key}", 1);
131 | $this->db->expireAt("phpredmin:gearman:requests:{$key}", strtotime('+10 minutes'));
132 |
133 | $key = urldecode($key);
134 | $total = $this->db->get("phpredmin:gearman:deletecount:{$key}");
135 | $count = $this->db->get("phpredmin:gearman:deleted:{$key}");
136 | $requests = $this->db->get("phpredmin:gearman:requests:{$key}");
137 |
138 | if ($total === false && $count !== false && $requests == 1) {
139 | $total = $count;
140 | }
141 |
142 | $result = array($total, $count);
143 |
144 | Template::factory('json')->render($result);
145 | }
146 |
147 | public function deleteAction($key)
148 | {
149 | Template::factory('json')->render($this->db->del(urldecode($key)));
150 | }
151 |
152 | public function viewAction($key)
153 | {
154 | switch ($this->db->type(urldecode($key))) {
155 | case Redis::REDIS_STRING:
156 | $this->router->redirect("strings/view/{$this->app->current['serverId']}/{$this->app->current['database']}/{$key}");
157 | break;
158 | case Redis::REDIS_SET:
159 | $this->router->redirect("sets/view/{$this->app->current['serverId']}/{$this->app->current['database']}/{$key}");
160 | break;
161 | case Redis::REDIS_LIST:
162 | $this->router->redirect("lists/view/{$this->app->current['serverId']}/{$this->app->current['database']}/{$key}");
163 | break;
164 | case Redis::REDIS_ZSET:
165 | $this->router->redirect("zsets/view/{$this->app->current['serverId']}/{$this->app->current['database']}/{$key}");
166 | break;
167 | case Redis::REDIS_HASH:
168 | $this->router->redirect("hashes/view/{$this->app->current['serverId']}/{$this->app->current['database']}/{$key}");
169 | break;
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/public/js/nvd3/src/models/lineWithFisheye.js:
--------------------------------------------------------------------------------
1 |
2 | nv.models.line = function() {
3 | //Default Settings
4 | var margin = {top: 0, right: 0, bottom: 0, left: 0},
5 | width = 960,
6 | height = 500,
7 | color = nv.utils.defaultColor(), // function that returns colors
8 | id = Math.floor(Math.random() * 10000), //Create semi-unique ID incase user doesn't select one
9 | getX = function(d) { return d.x }, // accessor to get the x value from a data point
10 | getY = function(d) { return d.y }, // accessor to get the y value from a data point
11 | clipEdge = false, // if true, masks lines within x and y scale
12 | interpolate = "linear"; // controls the line interpolation
13 |
14 |
15 | var scatter = nv.models.scatter()
16 | .id(id)
17 | .size(16) // default size
18 | .sizeDomain([16,256]), //set to speed up calculation, needs to be unset if there is a cstom size accessor
19 | //x = scatter.xScale(),
20 | //y = scatter.yScale(),
21 | x, y,
22 | x0, y0, timeoutID;
23 |
24 |
25 | function chart(selection) {
26 | selection.each(function(data) {
27 | var availableWidth = width - margin.left - margin.right,
28 | availableHeight = height - margin.top - margin.bottom;
29 |
30 | //get the scales inscase scatter scale was set manually
31 | x = x || scatter.xScale();
32 | y = y || scatter.yScale();
33 |
34 | //store old scales if they exist
35 | x0 = x0 || x;
36 | y0 = y0 || y;
37 |
38 |
39 | var wrap = d3.select(this).selectAll('g.nv-wrap.nv-line').data([data]);
40 | var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
41 | var defsEnter = wrapEnter.append('defs');
42 | var gEnter = wrapEnter.append('g');
43 | var g = wrap.select('g')
44 |
45 | wrapEnter.append('g').attr('class', 'nv-scatterWrap');
46 | var scatterWrap = wrap.select('.nv-scatterWrap').datum(data);
47 |
48 | gEnter.append('g').attr('class', 'nv-groups');
49 |
50 |
51 | scatter
52 | .width(availableWidth)
53 | .height(availableHeight)
54 |
55 | d3.transition(scatterWrap).call(scatter);
56 |
57 |
58 | wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
59 |
60 |
61 | defsEnter.append('clipPath')
62 | .attr('id', 'nv-edge-clip-' + id)
63 | .append('rect');
64 |
65 | wrap.select('#nv-edge-clip-' + id + ' rect')
66 | .attr('width', availableWidth)
67 | .attr('height', availableHeight);
68 |
69 | g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
70 | scatterWrap
71 | .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
72 |
73 |
74 |
75 |
76 | var groups = wrap.select('.nv-groups').selectAll('.nv-group')
77 | .data(function(d) { return d }, function(d) { return d.key });
78 | groups.enter().append('g')
79 | .style('stroke-opacity', 1e-6)
80 | .style('fill-opacity', 1e-6);
81 | d3.transition(groups.exit())
82 | .style('stroke-opacity', 1e-6)
83 | .style('fill-opacity', 1e-6)
84 | .remove();
85 | groups
86 | .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
87 | .classed('hover', function(d) { return d.hover })
88 | .style('fill', function(d,i){ return color(d, i) })
89 | .style('stroke', function(d,i){ return color(d, i) })
90 | d3.transition(groups)
91 | .style('stroke-opacity', 1)
92 | .style('fill-opacity', .5)
93 |
94 |
95 | var paths = groups.selectAll('path')
96 | .data(function(d, i) { return [d.values] });
97 | paths.enter().append('path')
98 | .attr('class', 'nv-line')
99 | .attr('d', d3.svg.line()
100 | .interpolate(interpolate)
101 | .x(function(d,i) { return x0(getX(d,i)) })
102 | .y(function(d,i) { return y0(getY(d,i)) })
103 | );
104 | d3.transition(groups.exit().selectAll('path'))
105 | .attr('d', d3.svg.line()
106 | .interpolate(interpolate)
107 | .x(function(d,i) { return x(getX(d,i)) })
108 | .y(function(d,i) { return y(getY(d,i)) })
109 | )
110 | .remove(); // redundant? line is already being removed
111 | d3.transition(paths)
112 | .attr('d', d3.svg.line()
113 | .interpolate(interpolate)
114 | .x(function(d,i) { return x(getX(d,i)) })
115 | .y(function(d,i) { return y(getY(d,i)) })
116 | );
117 |
118 |
119 | //store old scales for use in transitions on update, to animate from old to new positions
120 | x0 = x.copy();
121 | y0 = y.copy();
122 |
123 | });
124 |
125 | return chart;
126 | }
127 |
128 |
129 | chart.dispatch = scatter.dispatch;
130 |
131 | d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius');
132 |
133 | chart.margin = function(_) {
134 | if (!arguments.length) return margin;
135 | margin = _;
136 | return chart;
137 | };
138 |
139 | chart.width = function(_) {
140 | if (!arguments.length) return width;
141 | width = _;
142 | return chart;
143 | };
144 |
145 | chart.height = function(_) {
146 | if (!arguments.length) return height;
147 | height = _;
148 | return chart;
149 | };
150 |
151 | chart.x = function(_) {
152 | if (!arguments.length) return getX;
153 | getX = _;
154 | scatter.x(_);
155 | return chart;
156 | };
157 |
158 | chart.y = function(_) {
159 | if (!arguments.length) return getY;
160 | getY = _;
161 | scatter.y(_);
162 | return chart;
163 | };
164 |
165 | chart.clipEdge = function(_) {
166 | if (!arguments.length) return clipEdge;
167 | clipEdge = _;
168 | return chart;
169 | };
170 |
171 | chart.color = function(_) {
172 | if (!arguments.length) return color;
173 | color = nv.utils.getColor(_);
174 | scatter.color(color);
175 | return chart;
176 | };
177 |
178 | chart.id = function(_) {
179 | if (!arguments.length) return id;
180 | id = _;
181 | return chart;
182 | };
183 |
184 | chart.interpolate = function(_) {
185 | if (!arguments.length) return interpolate;
186 | interpolate = _;
187 | return chart;
188 | };
189 |
190 | chart.defined = function(_) {
191 | if (!arguments.length) return defined;
192 | defined = _;
193 | return chart;
194 | };
195 |
196 | return chart;
197 | }
198 |
--------------------------------------------------------------------------------
/views/welcome/stats.php:
--------------------------------------------------------------------------------
1 | addHeader(""); ?>
2 | addHeader(""); ?>
3 | addHeader(""); ?>
4 | addHeader(""); ?>
5 |
109 |
110 |
Redis Stats
111 |
112 |
×
113 | In order to view stats, you have to setup cron located in controllers directory
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | Filter
122 |
123 |
124 |
125 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/public/js/nvd3/lib/crossfilter.min.js:
--------------------------------------------------------------------------------
1 | (function(a){function b(a){return a}function c(a,b){for(var c=0,d=b.length,e=new Array(d);c>1;a(b[f])>1;c>>1)+1;while(--f>0)d(a,f,e,b);return a}function c(a,b,c){var e=c-b,f;while(--e>0)f=a[b],a[b]=a[b+e],a[b+e]=f,d(a,1,e,b);return a}function d(b,c,d,e){var f=b[--e+c],g=a(f),h;while((h=c<<1)<=d){ha(b[e+h+1])&&h++;if(g<=a(b[e+h]))break;b[e+c]=b[e+h],c=h}b[e+c]=f}return b.sort=c,b}function i(a){function c(c,d,e,f){var g=new Array(f=Math.min(e-d,f)),h,i,j,k;for(i=0;ih)g[0]=k,h=a(b(g,0,f)[0]);while(++dc&&a(b[f-1])>h;--f)b[f]=b[f-1];b[f]=g}return b}return b}function m(a){function c(a,c,e){return(e-c>1,j=i-f,k=i+f,l=b[g],m=a(l),n=b[j],o=a(n),p=b[i],q=a(p),r=b[k],s=a(r),t=b[h],u=a(t),v;m>o&&(v=l,l=n,n=v,v=m,m=o,o=v),s>u&&(v=r,r=t,t=v,v=s,s=u,u=v),m>q&&(v=l,l=p,p=v,v=m,m=q,q=v),o>q&&(v=n,n=p,p=v,v=o,o=q,q=v),m>s&&(v=l,l=r,r=v,v=m,m=s,s=v),q>s&&(v=p,p=r,r=v,v=q,q=s,s=v),o>u&&(v=n,n=t,t=v,v=o,o=u,u=v),o>q&&(v=n,n=p,p=v,v=o,o=q,q=v),s>u&&(v=r,r=t,t=v,v=s,s=u,u=v);var w=n,x=o,y=r,z=s;b[g]=l,b[j]=b[d],b[i]=p,b[k]=b[e-1],b[h]=t;var A=d+1,B=e-2,C=x<=z&&x>=z;if(C)for(var D=A;D<=B;++D){var E=b[D],F=a(E);if(Fx)for(;;){var G=a(b[B]);if(G>x){B--;continue}if(Gz)for(;;){var G=a(b[B]);if(G>z){B--;if(Bh){var H,G;while((H=a(b[A]))<=x&&H>=x)++A;while((G=a(b[B]))<=z&&G>=z)--B;for(var D=A;D<=B;D++){var E=b[D],F=a(E);if(F<=x&&F>=x)D!==A&&(b[D]=b[A],b[A]=E),A++;else if(F<=z&&F>=z)for(;;){var G=a(b[B]);if(G<=z&&G>=z){B--;if(BN)for(b=N,c=Math.min(e,O);bO)for(b=Math.max(e,O),c=f;b=N&&a>0)k[d=D[c]]||(b.push(e[d]),--a);return b}function X(a){function K(b,c,g,i){function Q(){++n===m&&(p=s(p,j<<=1),h=s(h,j),m=G(j))}var o=d,p=E(n,m),t=v,u=F,w=n,y=0,z=0,A,B,C,D,K,L;J&&(t=u=x),d=new Array(n),n=0,h=w>1?r(h,f):E(f,m),w&&(C=(B=o[0]).key);while(z=D))++z;while(zL)){h[A=c[z]+g]=n,k[A]&q||(K.value=t(K.value,e[A]));if(++z>=i)break;D=a(b[z])}Q()}while(yy)for(y=0;y1?(H=M,I=O):(n===1?(H=N,I=P):(H=x,I=x),h=null),l[A]=H}function M(a,b,c){if(a===p||J)return;var f,g,i,j;for(f=0,i=b.length;fj&&(k=s(k,j<<=1)),P(e,0,f),Q(e,0,f),o}function t(){function i(a,d,g){var i;if(h)return;for(i=d;i availableWidth && seriesPerRow > 1 ) {
89 | columnWidths = [];
90 | seriesPerRow--;
91 |
92 | for (k = 0; k < seriesWidths.length; k++) {
93 | if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
94 | columnWidths[k % seriesPerRow] = seriesWidths[k];
95 | }
96 |
97 | legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
98 | return prev + cur;
99 | });
100 | }
101 | //console.log(columnWidths, legendWidth, seriesPerRow);
102 |
103 | var xPositions = [];
104 | for (var i = 0, curX = 0; i < seriesPerRow; i++) {
105 | xPositions[i] = curX;
106 | curX += columnWidths[i];
107 | }
108 |
109 | series
110 | .attr('transform', function(d, i) {
111 | return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
112 | });
113 |
114 | //position legend as far right as possible within the total width
115 | g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
116 |
117 | height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
118 |
119 | } else {
120 |
121 | var ypos = 5,
122 | newxpos = 5,
123 | maxwidth = 0,
124 | xpos;
125 | series
126 | .attr('transform', function(d, i) {
127 | var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
128 | xpos = newxpos;
129 |
130 | if (width < margin.left + margin.right + xpos + length) {
131 | newxpos = xpos = 5;
132 | ypos += 20;
133 | }
134 |
135 | newxpos += length;
136 | if (newxpos > maxwidth) maxwidth = newxpos;
137 |
138 | return 'translate(' + xpos + ',' + ypos + ')';
139 | });
140 |
141 | //position legend as far right as possible within the total width
142 | g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
143 |
144 | height = margin.top + margin.bottom + ypos + 15;
145 |
146 | }
147 |
148 | });
149 |
150 | return chart;
151 | }
152 |
153 |
154 | //============================================================
155 | // Expose Public Variables
156 | //------------------------------------------------------------
157 |
158 | chart.dispatch = dispatch;
159 |
160 | chart.margin = function(_) {
161 | if (!arguments.length) return margin;
162 | margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
163 | margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
164 | margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
165 | margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
166 | return chart;
167 | };
168 |
169 | chart.width = function(_) {
170 | if (!arguments.length) return width;
171 | width = _;
172 | return chart;
173 | };
174 |
175 | chart.height = function(_) {
176 | if (!arguments.length) return height;
177 | height = _;
178 | return chart;
179 | };
180 |
181 | chart.key = function(_) {
182 | if (!arguments.length) return getKey;
183 | getKey = _;
184 | return chart;
185 | };
186 |
187 | chart.color = function(_) {
188 | if (!arguments.length) return color;
189 | color = nv.utils.getColor(_);
190 | return chart;
191 | };
192 |
193 | chart.align = function(_) {
194 | if (!arguments.length) return align;
195 | align = _;
196 | return chart;
197 | };
198 |
199 | //============================================================
200 |
201 |
202 | return chart;
203 | }
204 |
--------------------------------------------------------------------------------