├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config └── redis-manager.php ├── package.json ├── public ├── css │ └── app.css ├── fonts │ └── vendor │ │ ├── element-ui │ │ └── lib │ │ │ └── theme-chalk │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ │ └── font-awesome │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── img │ └── logo.svg ├── js │ └── app.js └── mix-manifest.json ├── resources ├── assets │ ├── img │ │ └── logo.svg │ ├── js │ │ ├── app.js │ │ ├── components │ │ │ ├── App.vue │ │ │ ├── Charts │ │ │ │ └── LineChart.js │ │ │ ├── Layout.vue │ │ │ └── Siderbar.vue │ │ ├── elementui.js │ │ ├── pages │ │ │ ├── Console.vue │ │ │ ├── Info.vue │ │ │ ├── Keys.vue │ │ │ ├── create │ │ │ │ ├── Hash.vue │ │ │ │ ├── Index.vue │ │ │ │ ├── List.vue │ │ │ │ ├── Set.vue │ │ │ │ ├── String.vue │ │ │ │ └── Zset.vue │ │ │ ├── edit │ │ │ │ ├── Hash.vue │ │ │ │ ├── Index.vue │ │ │ │ ├── List.vue │ │ │ │ ├── Set.vue │ │ │ │ ├── String.vue │ │ │ │ └── Zset.vue │ │ │ └── metrics │ │ │ │ ├── CPU.vue │ │ │ │ ├── Clients.vue │ │ │ │ ├── Index.vue │ │ │ │ ├── Memory.vue │ │ │ │ └── Throughput.vue │ │ ├── redis.js │ │ └── router.js │ └── sass │ │ └── app.scss └── views │ └── app.blade.php ├── routes └── web.php ├── src ├── DataType │ ├── DataType.php │ ├── Hashes.php │ ├── Lists.php │ ├── Sets.php │ ├── SortedSets.php │ └── Strings.php ├── Formatter │ └── Information.php ├── Http │ ├── Controllers │ │ └── RedisController.php │ └── Middleware │ │ └── Authenticate.php ├── RedisManager.php └── RedisManagerServiceProvider.php └── webpack.mix.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | [ 5 | "component", 6 | [ 7 | { 8 | "libraryName": "element-ui", 9 | "styleLibraryName": "theme-chalk" 10 | } 11 | ] 12 | ] 13 | ] 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /node_modules 3 | composer.lock 4 | .DS_Store 5 | Thumbs.db 6 | phpunit.xml 7 | /.idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jens Segers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

redis-manager

2 | 3 |

4 | 5 | StyleCI 6 | 7 | 8 | Packagist 9 | 10 | 11 | Total Downloads 12 | 13 | 14 | 15 | `Redis-manager` gives your laravel application a redis web administration interface that allows you to easily manipulate the most commonly used data types for redis (strings, hashes, lists, sets, sorted sets). 16 | 17 | It also provides a web-style command-line tool that works like redis-cli that can run most of the redis commands. 18 | 19 | `Redis-manager` allows you to easily monitor several redis system status, including memory usage, cpu usage, and the throughput of each command. 20 | 21 | Here is the living [demo](https://zsong.me/redis-manager/) 22 | 23 | > redis-manager reads laravel's redis configuration located in the `config/database.php` 24 | 25 | # Installation 26 | 27 | You may use Composer to install Redis-manager into your Laravel project: 28 | 29 | ```shell 30 | composer require encore/redis-manager 31 | 32 | ``` 33 | 34 | After installing `redis-manager`, publish its assets using the vendor:publish Artisan command: 35 | 36 | ```shell 37 | php artisan vendor:publish --provider="Encore\RedisManager\RedisManagerServiceProvider" 38 | ``` 39 | 40 | After installation, open `http://your-server/redis-manager` to access `redis-manager`. 41 | 42 | ## Configuration 43 | 44 | The config file was published at `config/redis-manager.php`, and the default contents of the configuration: 45 | ```php 46 | 'redis-manager', 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Redis Manager Middleware 64 | |-------------------------------------------------------------------------- 65 | | 66 | | The Redis Manager's route middleware. 67 | | 68 | */ 69 | 70 | 'middleware' => [], 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | Redis Manager Results Per Page 75 | |-------------------------------------------------------------------------- 76 | | 77 | | Here you can configure for the number of results will show in the 78 | | Redis Manager search page. 79 | | 80 | */ 81 | 82 | 'results_per_page' => 50, 83 | 84 | /* 85 | |-------------------------------------------------------------------------- 86 | | Redis Manager Disable Commands 87 | |-------------------------------------------------------------------------- 88 | | 89 | | The commands listed here was disabled when you use Redis Manager Console 90 | | to run commands. Feel free to add commands here which you do not want 91 | | users to use. 92 | | 93 | */ 94 | 95 | 'disable_commands' => [ 96 | 'flushdb' 97 | ] 98 | ]; 99 | 100 | ``` 101 | 102 | ## Authentication 103 | 104 | By default, you will only be able to access `redis-manager` in the `local` environment. To define a more specific access policy for it, you should use the `RedisManager::auth` method. The auth method accepts a callback which should return `true` or `false`, indicating whether the user should have access to `redis-manager`: 105 | 106 | ```php 107 | RedisManager::auth(function ($request) { 108 | // return true / false; 109 | }); 110 | ``` 111 | 112 | ## License 113 | 114 | `Redis manager` is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 115 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "encore/redis-manager", 3 | "description": "Redis manager for laravel", 4 | "type": "library", 5 | "keywords": ["laravel", "redis", "manager"], 6 | "homepage": "https://github.com/z-song/redis-manager", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "z-song", 11 | "email": "zosong@126.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.0.0", 16 | "illuminate/contracts": "~5.5", 17 | "illuminate/support": "~5.5", 18 | "predis/predis": "^1.1" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~6.0", 22 | "laravel/laravel": "~5.5" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Encore\\RedisManager\\": "src/" 27 | } 28 | }, 29 | "extra": { 30 | "laravel": { 31 | "providers": [ 32 | "Encore\\RedisManager\\RedisManagerServiceProvider" 33 | ] 34 | 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/redis-manager.php: -------------------------------------------------------------------------------- 1 | 'redis-manager', 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Redis Manager Middleware 19 | |-------------------------------------------------------------------------- 20 | | 21 | | The Redis Manager's route middleware. 22 | | 23 | */ 24 | 25 | 'middleware' => ['web'], 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Redis Manager Results Per Page 30 | |-------------------------------------------------------------------------- 31 | | 32 | | Here you can configure for the number of results will show in the 33 | | Redis Manager search page. 34 | | 35 | */ 36 | 37 | 'results_per_page' => 50, 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Redis Manager Disable Commands 42 | |-------------------------------------------------------------------------- 43 | | 44 | | The commands listed here was disabled when you use Redis Manager Console 45 | | to run commands. Feel free to add commands here which you do not want 46 | | users to use. 47 | | 48 | */ 49 | 50 | 'disable_commands' => [ 51 | 'flushdb', 52 | ], 53 | ]; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 11 | "profile": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --profile --config=node_modules/laravel-mix/setup/webpack.config.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.15.3", 15 | "chart.js": "^2.7.1", 16 | "element-ui": "^2.0.0", 17 | "font-awesome": "^4.7.0", 18 | "lodash": "^4.16.2", 19 | "vue": "^2.2.0", 20 | "vue-chartjs": "^3.0.2", 21 | "vue-router": "^2.2.0" 22 | }, 23 | "devDependencies": { 24 | "babel-plugin-component": "^0.10.1", 25 | "babel-preset-env": "^1.6.1", 26 | "cross-env": "^5.0.1", 27 | "es6-promise": "^4.0.5", 28 | "laravel-mix": "^1.4.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /public/css/app.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:FontAwesome;src:url(/fonts/vendor/font-awesome/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(/fonts/vendor/font-awesome/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713) format("embedded-opentype"),url(/fonts/vendor/font-awesome/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(/fonts/vendor/font-awesome/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(/fonts/vendor/font-awesome/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(/fonts/vendor/font-awesome/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde) format("svg");font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\F000"}.fa-music:before{content:"\F001"}.fa-search:before{content:"\F002"}.fa-envelope-o:before{content:"\F003"}.fa-heart:before{content:"\F004"}.fa-star:before{content:"\F005"}.fa-star-o:before{content:"\F006"}.fa-user:before{content:"\F007"}.fa-film:before{content:"\F008"}.fa-th-large:before{content:"\F009"}.fa-th:before{content:"\F00A"}.fa-th-list:before{content:"\F00B"}.fa-check:before{content:"\F00C"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\F00D"}.fa-search-plus:before{content:"\F00E"}.fa-search-minus:before{content:"\F010"}.fa-power-off:before{content:"\F011"}.fa-signal:before{content:"\F012"}.fa-cog:before,.fa-gear:before{content:"\F013"}.fa-trash-o:before{content:"\F014"}.fa-home:before{content:"\F015"}.fa-file-o:before{content:"\F016"}.fa-clock-o:before{content:"\F017"}.fa-road:before{content:"\F018"}.fa-download:before{content:"\F019"}.fa-arrow-circle-o-down:before{content:"\F01A"}.fa-arrow-circle-o-up:before{content:"\F01B"}.fa-inbox:before{content:"\F01C"}.fa-play-circle-o:before{content:"\F01D"}.fa-repeat:before,.fa-rotate-right:before{content:"\F01E"}.fa-refresh:before{content:"\F021"}.fa-list-alt:before{content:"\F022"}.fa-lock:before{content:"\F023"}.fa-flag:before{content:"\F024"}.fa-headphones:before{content:"\F025"}.fa-volume-off:before{content:"\F026"}.fa-volume-down:before{content:"\F027"}.fa-volume-up:before{content:"\F028"}.fa-qrcode:before{content:"\F029"}.fa-barcode:before{content:"\F02A"}.fa-tag:before{content:"\F02B"}.fa-tags:before{content:"\F02C"}.fa-book:before{content:"\F02D"}.fa-bookmark:before{content:"\F02E"}.fa-print:before{content:"\F02F"}.fa-camera:before{content:"\F030"}.fa-font:before{content:"\F031"}.fa-bold:before{content:"\F032"}.fa-italic:before{content:"\F033"}.fa-text-height:before{content:"\F034"}.fa-text-width:before{content:"\F035"}.fa-align-left:before{content:"\F036"}.fa-align-center:before{content:"\F037"}.fa-align-right:before{content:"\F038"}.fa-align-justify:before{content:"\F039"}.fa-list:before{content:"\F03A"}.fa-dedent:before,.fa-outdent:before{content:"\F03B"}.fa-indent:before{content:"\F03C"}.fa-video-camera:before{content:"\F03D"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\F03E"}.fa-pencil:before{content:"\F040"}.fa-map-marker:before{content:"\F041"}.fa-adjust:before{content:"\F042"}.fa-tint:before{content:"\F043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\F044"}.fa-share-square-o:before{content:"\F045"}.fa-check-square-o:before{content:"\F046"}.fa-arrows:before{content:"\F047"}.fa-step-backward:before{content:"\F048"}.fa-fast-backward:before{content:"\F049"}.fa-backward:before{content:"\F04A"}.fa-play:before{content:"\F04B"}.fa-pause:before{content:"\F04C"}.fa-stop:before{content:"\F04D"}.fa-forward:before{content:"\F04E"}.fa-fast-forward:before{content:"\F050"}.fa-step-forward:before{content:"\F051"}.fa-eject:before{content:"\F052"}.fa-chevron-left:before{content:"\F053"}.fa-chevron-right:before{content:"\F054"}.fa-plus-circle:before{content:"\F055"}.fa-minus-circle:before{content:"\F056"}.fa-times-circle:before{content:"\F057"}.fa-check-circle:before{content:"\F058"}.fa-question-circle:before{content:"\F059"}.fa-info-circle:before{content:"\F05A"}.fa-crosshairs:before{content:"\F05B"}.fa-times-circle-o:before{content:"\F05C"}.fa-check-circle-o:before{content:"\F05D"}.fa-ban:before{content:"\F05E"}.fa-arrow-left:before{content:"\F060"}.fa-arrow-right:before{content:"\F061"}.fa-arrow-up:before{content:"\F062"}.fa-arrow-down:before{content:"\F063"}.fa-mail-forward:before,.fa-share:before{content:"\F064"}.fa-expand:before{content:"\F065"}.fa-compress:before{content:"\F066"}.fa-plus:before{content:"\F067"}.fa-minus:before{content:"\F068"}.fa-asterisk:before{content:"\F069"}.fa-exclamation-circle:before{content:"\F06A"}.fa-gift:before{content:"\F06B"}.fa-leaf:before{content:"\F06C"}.fa-fire:before{content:"\F06D"}.fa-eye:before{content:"\F06E"}.fa-eye-slash:before{content:"\F070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\F071"}.fa-plane:before{content:"\F072"}.fa-calendar:before{content:"\F073"}.fa-random:before{content:"\F074"}.fa-comment:before{content:"\F075"}.fa-magnet:before{content:"\F076"}.fa-chevron-up:before{content:"\F077"}.fa-chevron-down:before{content:"\F078"}.fa-retweet:before{content:"\F079"}.fa-shopping-cart:before{content:"\F07A"}.fa-folder:before{content:"\F07B"}.fa-folder-open:before{content:"\F07C"}.fa-arrows-v:before{content:"\F07D"}.fa-arrows-h:before{content:"\F07E"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\F080"}.fa-twitter-square:before{content:"\F081"}.fa-facebook-square:before{content:"\F082"}.fa-camera-retro:before{content:"\F083"}.fa-key:before{content:"\F084"}.fa-cogs:before,.fa-gears:before{content:"\F085"}.fa-comments:before{content:"\F086"}.fa-thumbs-o-up:before{content:"\F087"}.fa-thumbs-o-down:before{content:"\F088"}.fa-star-half:before{content:"\F089"}.fa-heart-o:before{content:"\F08A"}.fa-sign-out:before{content:"\F08B"}.fa-linkedin-square:before{content:"\F08C"}.fa-thumb-tack:before{content:"\F08D"}.fa-external-link:before{content:"\F08E"}.fa-sign-in:before{content:"\F090"}.fa-trophy:before{content:"\F091"}.fa-github-square:before{content:"\F092"}.fa-upload:before{content:"\F093"}.fa-lemon-o:before{content:"\F094"}.fa-phone:before{content:"\F095"}.fa-square-o:before{content:"\F096"}.fa-bookmark-o:before{content:"\F097"}.fa-phone-square:before{content:"\F098"}.fa-twitter:before{content:"\F099"}.fa-facebook-f:before,.fa-facebook:before{content:"\F09A"}.fa-github:before{content:"\F09B"}.fa-unlock:before{content:"\F09C"}.fa-credit-card:before{content:"\F09D"}.fa-feed:before,.fa-rss:before{content:"\F09E"}.fa-hdd-o:before{content:"\F0A0"}.fa-bullhorn:before{content:"\F0A1"}.fa-bell:before{content:"\F0F3"}.fa-certificate:before{content:"\F0A3"}.fa-hand-o-right:before{content:"\F0A4"}.fa-hand-o-left:before{content:"\F0A5"}.fa-hand-o-up:before{content:"\F0A6"}.fa-hand-o-down:before{content:"\F0A7"}.fa-arrow-circle-left:before{content:"\F0A8"}.fa-arrow-circle-right:before{content:"\F0A9"}.fa-arrow-circle-up:before{content:"\F0AA"}.fa-arrow-circle-down:before{content:"\F0AB"}.fa-globe:before{content:"\F0AC"}.fa-wrench:before{content:"\F0AD"}.fa-tasks:before{content:"\F0AE"}.fa-filter:before{content:"\F0B0"}.fa-briefcase:before{content:"\F0B1"}.fa-arrows-alt:before{content:"\F0B2"}.fa-group:before,.fa-users:before{content:"\F0C0"}.fa-chain:before,.fa-link:before{content:"\F0C1"}.fa-cloud:before{content:"\F0C2"}.fa-flask:before{content:"\F0C3"}.fa-cut:before,.fa-scissors:before{content:"\F0C4"}.fa-copy:before,.fa-files-o:before{content:"\F0C5"}.fa-paperclip:before{content:"\F0C6"}.fa-floppy-o:before,.fa-save:before{content:"\F0C7"}.fa-square:before{content:"\F0C8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\F0C9"}.fa-list-ul:before{content:"\F0CA"}.fa-list-ol:before{content:"\F0CB"}.fa-strikethrough:before{content:"\F0CC"}.fa-underline:before{content:"\F0CD"}.fa-table:before{content:"\F0CE"}.fa-magic:before{content:"\F0D0"}.fa-truck:before{content:"\F0D1"}.fa-pinterest:before{content:"\F0D2"}.fa-pinterest-square:before{content:"\F0D3"}.fa-google-plus-square:before{content:"\F0D4"}.fa-google-plus:before{content:"\F0D5"}.fa-money:before{content:"\F0D6"}.fa-caret-down:before{content:"\F0D7"}.fa-caret-up:before{content:"\F0D8"}.fa-caret-left:before{content:"\F0D9"}.fa-caret-right:before{content:"\F0DA"}.fa-columns:before{content:"\F0DB"}.fa-sort:before,.fa-unsorted:before{content:"\F0DC"}.fa-sort-desc:before,.fa-sort-down:before{content:"\F0DD"}.fa-sort-asc:before,.fa-sort-up:before{content:"\F0DE"}.fa-envelope:before{content:"\F0E0"}.fa-linkedin:before{content:"\F0E1"}.fa-rotate-left:before,.fa-undo:before{content:"\F0E2"}.fa-gavel:before,.fa-legal:before{content:"\F0E3"}.fa-dashboard:before,.fa-tachometer:before{content:"\F0E4"}.fa-comment-o:before{content:"\F0E5"}.fa-comments-o:before{content:"\F0E6"}.fa-bolt:before,.fa-flash:before{content:"\F0E7"}.fa-sitemap:before{content:"\F0E8"}.fa-umbrella:before{content:"\F0E9"}.fa-clipboard:before,.fa-paste:before{content:"\F0EA"}.fa-lightbulb-o:before{content:"\F0EB"}.fa-exchange:before{content:"\F0EC"}.fa-cloud-download:before{content:"\F0ED"}.fa-cloud-upload:before{content:"\F0EE"}.fa-user-md:before{content:"\F0F0"}.fa-stethoscope:before{content:"\F0F1"}.fa-suitcase:before{content:"\F0F2"}.fa-bell-o:before{content:"\F0A2"}.fa-coffee:before{content:"\F0F4"}.fa-cutlery:before{content:"\F0F5"}.fa-file-text-o:before{content:"\F0F6"}.fa-building-o:before{content:"\F0F7"}.fa-hospital-o:before{content:"\F0F8"}.fa-ambulance:before{content:"\F0F9"}.fa-medkit:before{content:"\F0FA"}.fa-fighter-jet:before{content:"\F0FB"}.fa-beer:before{content:"\F0FC"}.fa-h-square:before{content:"\F0FD"}.fa-plus-square:before{content:"\F0FE"}.fa-angle-double-left:before{content:"\F100"}.fa-angle-double-right:before{content:"\F101"}.fa-angle-double-up:before{content:"\F102"}.fa-angle-double-down:before{content:"\F103"}.fa-angle-left:before{content:"\F104"}.fa-angle-right:before{content:"\F105"}.fa-angle-up:before{content:"\F106"}.fa-angle-down:before{content:"\F107"}.fa-desktop:before{content:"\F108"}.fa-laptop:before{content:"\F109"}.fa-tablet:before{content:"\F10A"}.fa-mobile-phone:before,.fa-mobile:before{content:"\F10B"}.fa-circle-o:before{content:"\F10C"}.fa-quote-left:before{content:"\F10D"}.fa-quote-right:before{content:"\F10E"}.fa-spinner:before{content:"\F110"}.fa-circle:before{content:"\F111"}.fa-mail-reply:before,.fa-reply:before{content:"\F112"}.fa-github-alt:before{content:"\F113"}.fa-folder-o:before{content:"\F114"}.fa-folder-open-o:before{content:"\F115"}.fa-smile-o:before{content:"\F118"}.fa-frown-o:before{content:"\F119"}.fa-meh-o:before{content:"\F11A"}.fa-gamepad:before{content:"\F11B"}.fa-keyboard-o:before{content:"\F11C"}.fa-flag-o:before{content:"\F11D"}.fa-flag-checkered:before{content:"\F11E"}.fa-terminal:before{content:"\F120"}.fa-code:before{content:"\F121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\F122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\F123"}.fa-location-arrow:before{content:"\F124"}.fa-crop:before{content:"\F125"}.fa-code-fork:before{content:"\F126"}.fa-chain-broken:before,.fa-unlink:before{content:"\F127"}.fa-question:before{content:"\F128"}.fa-info:before{content:"\F129"}.fa-exclamation:before{content:"\F12A"}.fa-superscript:before{content:"\F12B"}.fa-subscript:before{content:"\F12C"}.fa-eraser:before{content:"\F12D"}.fa-puzzle-piece:before{content:"\F12E"}.fa-microphone:before{content:"\F130"}.fa-microphone-slash:before{content:"\F131"}.fa-shield:before{content:"\F132"}.fa-calendar-o:before{content:"\F133"}.fa-fire-extinguisher:before{content:"\F134"}.fa-rocket:before{content:"\F135"}.fa-maxcdn:before{content:"\F136"}.fa-chevron-circle-left:before{content:"\F137"}.fa-chevron-circle-right:before{content:"\F138"}.fa-chevron-circle-up:before{content:"\F139"}.fa-chevron-circle-down:before{content:"\F13A"}.fa-html5:before{content:"\F13B"}.fa-css3:before{content:"\F13C"}.fa-anchor:before{content:"\F13D"}.fa-unlock-alt:before{content:"\F13E"}.fa-bullseye:before{content:"\F140"}.fa-ellipsis-h:before{content:"\F141"}.fa-ellipsis-v:before{content:"\F142"}.fa-rss-square:before{content:"\F143"}.fa-play-circle:before{content:"\F144"}.fa-ticket:before{content:"\F145"}.fa-minus-square:before{content:"\F146"}.fa-minus-square-o:before{content:"\F147"}.fa-level-up:before{content:"\F148"}.fa-level-down:before{content:"\F149"}.fa-check-square:before{content:"\F14A"}.fa-pencil-square:before{content:"\F14B"}.fa-external-link-square:before{content:"\F14C"}.fa-share-square:before{content:"\F14D"}.fa-compass:before{content:"\F14E"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\F150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\F151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\F152"}.fa-eur:before,.fa-euro:before{content:"\F153"}.fa-gbp:before{content:"\F154"}.fa-dollar:before,.fa-usd:before{content:"\F155"}.fa-inr:before,.fa-rupee:before{content:"\F156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\F157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\F158"}.fa-krw:before,.fa-won:before{content:"\F159"}.fa-bitcoin:before,.fa-btc:before{content:"\F15A"}.fa-file:before{content:"\F15B"}.fa-file-text:before{content:"\F15C"}.fa-sort-alpha-asc:before{content:"\F15D"}.fa-sort-alpha-desc:before{content:"\F15E"}.fa-sort-amount-asc:before{content:"\F160"}.fa-sort-amount-desc:before{content:"\F161"}.fa-sort-numeric-asc:before{content:"\F162"}.fa-sort-numeric-desc:before{content:"\F163"}.fa-thumbs-up:before{content:"\F164"}.fa-thumbs-down:before{content:"\F165"}.fa-youtube-square:before{content:"\F166"}.fa-youtube:before{content:"\F167"}.fa-xing:before{content:"\F168"}.fa-xing-square:before{content:"\F169"}.fa-youtube-play:before{content:"\F16A"}.fa-dropbox:before{content:"\F16B"}.fa-stack-overflow:before{content:"\F16C"}.fa-instagram:before{content:"\F16D"}.fa-flickr:before{content:"\F16E"}.fa-adn:before{content:"\F170"}.fa-bitbucket:before{content:"\F171"}.fa-bitbucket-square:before{content:"\F172"}.fa-tumblr:before{content:"\F173"}.fa-tumblr-square:before{content:"\F174"}.fa-long-arrow-down:before{content:"\F175"}.fa-long-arrow-up:before{content:"\F176"}.fa-long-arrow-left:before{content:"\F177"}.fa-long-arrow-right:before{content:"\F178"}.fa-apple:before{content:"\F179"}.fa-windows:before{content:"\F17A"}.fa-android:before{content:"\F17B"}.fa-linux:before{content:"\F17C"}.fa-dribbble:before{content:"\F17D"}.fa-skype:before{content:"\F17E"}.fa-foursquare:before{content:"\F180"}.fa-trello:before{content:"\F181"}.fa-female:before{content:"\F182"}.fa-male:before{content:"\F183"}.fa-gittip:before,.fa-gratipay:before{content:"\F184"}.fa-sun-o:before{content:"\F185"}.fa-moon-o:before{content:"\F186"}.fa-archive:before{content:"\F187"}.fa-bug:before{content:"\F188"}.fa-vk:before{content:"\F189"}.fa-weibo:before{content:"\F18A"}.fa-renren:before{content:"\F18B"}.fa-pagelines:before{content:"\F18C"}.fa-stack-exchange:before{content:"\F18D"}.fa-arrow-circle-o-right:before{content:"\F18E"}.fa-arrow-circle-o-left:before{content:"\F190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\F191"}.fa-dot-circle-o:before{content:"\F192"}.fa-wheelchair:before{content:"\F193"}.fa-vimeo-square:before{content:"\F194"}.fa-try:before,.fa-turkish-lira:before{content:"\F195"}.fa-plus-square-o:before{content:"\F196"}.fa-space-shuttle:before{content:"\F197"}.fa-slack:before{content:"\F198"}.fa-envelope-square:before{content:"\F199"}.fa-wordpress:before{content:"\F19A"}.fa-openid:before{content:"\F19B"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\F19C"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\F19D"}.fa-yahoo:before{content:"\F19E"}.fa-google:before{content:"\F1A0"}.fa-reddit:before{content:"\F1A1"}.fa-reddit-square:before{content:"\F1A2"}.fa-stumbleupon-circle:before{content:"\F1A3"}.fa-stumbleupon:before{content:"\F1A4"}.fa-delicious:before{content:"\F1A5"}.fa-digg:before{content:"\F1A6"}.fa-pied-piper-pp:before{content:"\F1A7"}.fa-pied-piper-alt:before{content:"\F1A8"}.fa-drupal:before{content:"\F1A9"}.fa-joomla:before{content:"\F1AA"}.fa-language:before{content:"\F1AB"}.fa-fax:before{content:"\F1AC"}.fa-building:before{content:"\F1AD"}.fa-child:before{content:"\F1AE"}.fa-paw:before{content:"\F1B0"}.fa-spoon:before{content:"\F1B1"}.fa-cube:before{content:"\F1B2"}.fa-cubes:before{content:"\F1B3"}.fa-behance:before{content:"\F1B4"}.fa-behance-square:before{content:"\F1B5"}.fa-steam:before{content:"\F1B6"}.fa-steam-square:before{content:"\F1B7"}.fa-recycle:before{content:"\F1B8"}.fa-automobile:before,.fa-car:before{content:"\F1B9"}.fa-cab:before,.fa-taxi:before{content:"\F1BA"}.fa-tree:before{content:"\F1BB"}.fa-spotify:before{content:"\F1BC"}.fa-deviantart:before{content:"\F1BD"}.fa-soundcloud:before{content:"\F1BE"}.fa-database:before{content:"\F1C0"}.fa-file-pdf-o:before{content:"\F1C1"}.fa-file-word-o:before{content:"\F1C2"}.fa-file-excel-o:before{content:"\F1C3"}.fa-file-powerpoint-o:before{content:"\F1C4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\F1C5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\F1C6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\F1C7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\F1C8"}.fa-file-code-o:before{content:"\F1C9"}.fa-vine:before{content:"\F1CA"}.fa-codepen:before{content:"\F1CB"}.fa-jsfiddle:before{content:"\F1CC"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\F1CD"}.fa-circle-o-notch:before{content:"\F1CE"}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:"\F1D0"}.fa-empire:before,.fa-ge:before{content:"\F1D1"}.fa-git-square:before{content:"\F1D2"}.fa-git:before{content:"\F1D3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\F1D4"}.fa-tencent-weibo:before{content:"\F1D5"}.fa-qq:before{content:"\F1D6"}.fa-wechat:before,.fa-weixin:before{content:"\F1D7"}.fa-paper-plane:before,.fa-send:before{content:"\F1D8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\F1D9"}.fa-history:before{content:"\F1DA"}.fa-circle-thin:before{content:"\F1DB"}.fa-header:before{content:"\F1DC"}.fa-paragraph:before{content:"\F1DD"}.fa-sliders:before{content:"\F1DE"}.fa-share-alt:before{content:"\F1E0"}.fa-share-alt-square:before{content:"\F1E1"}.fa-bomb:before{content:"\F1E2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\F1E3"}.fa-tty:before{content:"\F1E4"}.fa-binoculars:before{content:"\F1E5"}.fa-plug:before{content:"\F1E6"}.fa-slideshare:before{content:"\F1E7"}.fa-twitch:before{content:"\F1E8"}.fa-yelp:before{content:"\F1E9"}.fa-newspaper-o:before{content:"\F1EA"}.fa-wifi:before{content:"\F1EB"}.fa-calculator:before{content:"\F1EC"}.fa-paypal:before{content:"\F1ED"}.fa-google-wallet:before{content:"\F1EE"}.fa-cc-visa:before{content:"\F1F0"}.fa-cc-mastercard:before{content:"\F1F1"}.fa-cc-discover:before{content:"\F1F2"}.fa-cc-amex:before{content:"\F1F3"}.fa-cc-paypal:before{content:"\F1F4"}.fa-cc-stripe:before{content:"\F1F5"}.fa-bell-slash:before{content:"\F1F6"}.fa-bell-slash-o:before{content:"\F1F7"}.fa-trash:before{content:"\F1F8"}.fa-copyright:before{content:"\F1F9"}.fa-at:before{content:"\F1FA"}.fa-eyedropper:before{content:"\F1FB"}.fa-paint-brush:before{content:"\F1FC"}.fa-birthday-cake:before{content:"\F1FD"}.fa-area-chart:before{content:"\F1FE"}.fa-pie-chart:before{content:"\F200"}.fa-line-chart:before{content:"\F201"}.fa-lastfm:before{content:"\F202"}.fa-lastfm-square:before{content:"\F203"}.fa-toggle-off:before{content:"\F204"}.fa-toggle-on:before{content:"\F205"}.fa-bicycle:before{content:"\F206"}.fa-bus:before{content:"\F207"}.fa-ioxhost:before{content:"\F208"}.fa-angellist:before{content:"\F209"}.fa-cc:before{content:"\F20A"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\F20B"}.fa-meanpath:before{content:"\F20C"}.fa-buysellads:before{content:"\F20D"}.fa-connectdevelop:before{content:"\F20E"}.fa-dashcube:before{content:"\F210"}.fa-forumbee:before{content:"\F211"}.fa-leanpub:before{content:"\F212"}.fa-sellsy:before{content:"\F213"}.fa-shirtsinbulk:before{content:"\F214"}.fa-simplybuilt:before{content:"\F215"}.fa-skyatlas:before{content:"\F216"}.fa-cart-plus:before{content:"\F217"}.fa-cart-arrow-down:before{content:"\F218"}.fa-diamond:before{content:"\F219"}.fa-ship:before{content:"\F21A"}.fa-user-secret:before{content:"\F21B"}.fa-motorcycle:before{content:"\F21C"}.fa-street-view:before{content:"\F21D"}.fa-heartbeat:before{content:"\F21E"}.fa-venus:before{content:"\F221"}.fa-mars:before{content:"\F222"}.fa-mercury:before{content:"\F223"}.fa-intersex:before,.fa-transgender:before{content:"\F224"}.fa-transgender-alt:before{content:"\F225"}.fa-venus-double:before{content:"\F226"}.fa-mars-double:before{content:"\F227"}.fa-venus-mars:before{content:"\F228"}.fa-mars-stroke:before{content:"\F229"}.fa-mars-stroke-v:before{content:"\F22A"}.fa-mars-stroke-h:before{content:"\F22B"}.fa-neuter:before{content:"\F22C"}.fa-genderless:before{content:"\F22D"}.fa-facebook-official:before{content:"\F230"}.fa-pinterest-p:before{content:"\F231"}.fa-whatsapp:before{content:"\F232"}.fa-server:before{content:"\F233"}.fa-user-plus:before{content:"\F234"}.fa-user-times:before{content:"\F235"}.fa-bed:before,.fa-hotel:before{content:"\F236"}.fa-viacoin:before{content:"\F237"}.fa-train:before{content:"\F238"}.fa-subway:before{content:"\F239"}.fa-medium:before{content:"\F23A"}.fa-y-combinator:before,.fa-yc:before{content:"\F23B"}.fa-optin-monster:before{content:"\F23C"}.fa-opencart:before{content:"\F23D"}.fa-expeditedssl:before{content:"\F23E"}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:"\F240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\F241"}.fa-battery-2:before,.fa-battery-half:before{content:"\F242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\F243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\F244"}.fa-mouse-pointer:before{content:"\F245"}.fa-i-cursor:before{content:"\F246"}.fa-object-group:before{content:"\F247"}.fa-object-ungroup:before{content:"\F248"}.fa-sticky-note:before{content:"\F249"}.fa-sticky-note-o:before{content:"\F24A"}.fa-cc-jcb:before{content:"\F24B"}.fa-cc-diners-club:before{content:"\F24C"}.fa-clone:before{content:"\F24D"}.fa-balance-scale:before{content:"\F24E"}.fa-hourglass-o:before{content:"\F250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\F251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\F252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\F253"}.fa-hourglass:before{content:"\F254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\F255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\F256"}.fa-hand-scissors-o:before{content:"\F257"}.fa-hand-lizard-o:before{content:"\F258"}.fa-hand-spock-o:before{content:"\F259"}.fa-hand-pointer-o:before{content:"\F25A"}.fa-hand-peace-o:before{content:"\F25B"}.fa-trademark:before{content:"\F25C"}.fa-registered:before{content:"\F25D"}.fa-creative-commons:before{content:"\F25E"}.fa-gg:before{content:"\F260"}.fa-gg-circle:before{content:"\F261"}.fa-tripadvisor:before{content:"\F262"}.fa-odnoklassniki:before{content:"\F263"}.fa-odnoklassniki-square:before{content:"\F264"}.fa-get-pocket:before{content:"\F265"}.fa-wikipedia-w:before{content:"\F266"}.fa-safari:before{content:"\F267"}.fa-chrome:before{content:"\F268"}.fa-firefox:before{content:"\F269"}.fa-opera:before{content:"\F26A"}.fa-internet-explorer:before{content:"\F26B"}.fa-television:before,.fa-tv:before{content:"\F26C"}.fa-contao:before{content:"\F26D"}.fa-500px:before{content:"\F26E"}.fa-amazon:before{content:"\F270"}.fa-calendar-plus-o:before{content:"\F271"}.fa-calendar-minus-o:before{content:"\F272"}.fa-calendar-times-o:before{content:"\F273"}.fa-calendar-check-o:before{content:"\F274"}.fa-industry:before{content:"\F275"}.fa-map-pin:before{content:"\F276"}.fa-map-signs:before{content:"\F277"}.fa-map-o:before{content:"\F278"}.fa-map:before{content:"\F279"}.fa-commenting:before{content:"\F27A"}.fa-commenting-o:before{content:"\F27B"}.fa-houzz:before{content:"\F27C"}.fa-vimeo:before{content:"\F27D"}.fa-black-tie:before{content:"\F27E"}.fa-fonticons:before{content:"\F280"}.fa-reddit-alien:before{content:"\F281"}.fa-edge:before{content:"\F282"}.fa-credit-card-alt:before{content:"\F283"}.fa-codiepie:before{content:"\F284"}.fa-modx:before{content:"\F285"}.fa-fort-awesome:before{content:"\F286"}.fa-usb:before{content:"\F287"}.fa-product-hunt:before{content:"\F288"}.fa-mixcloud:before{content:"\F289"}.fa-scribd:before{content:"\F28A"}.fa-pause-circle:before{content:"\F28B"}.fa-pause-circle-o:before{content:"\F28C"}.fa-stop-circle:before{content:"\F28D"}.fa-stop-circle-o:before{content:"\F28E"}.fa-shopping-bag:before{content:"\F290"}.fa-shopping-basket:before{content:"\F291"}.fa-hashtag:before{content:"\F292"}.fa-bluetooth:before{content:"\F293"}.fa-bluetooth-b:before{content:"\F294"}.fa-percent:before{content:"\F295"}.fa-gitlab:before{content:"\F296"}.fa-wpbeginner:before{content:"\F297"}.fa-wpforms:before{content:"\F298"}.fa-envira:before{content:"\F299"}.fa-universal-access:before{content:"\F29A"}.fa-wheelchair-alt:before{content:"\F29B"}.fa-question-circle-o:before{content:"\F29C"}.fa-blind:before{content:"\F29D"}.fa-audio-description:before{content:"\F29E"}.fa-volume-control-phone:before{content:"\F2A0"}.fa-braille:before{content:"\F2A1"}.fa-assistive-listening-systems:before{content:"\F2A2"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:"\F2A3"}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:"\F2A4"}.fa-glide:before{content:"\F2A5"}.fa-glide-g:before{content:"\F2A6"}.fa-sign-language:before,.fa-signing:before{content:"\F2A7"}.fa-low-vision:before{content:"\F2A8"}.fa-viadeo:before{content:"\F2A9"}.fa-viadeo-square:before{content:"\F2AA"}.fa-snapchat:before{content:"\F2AB"}.fa-snapchat-ghost:before{content:"\F2AC"}.fa-snapchat-square:before{content:"\F2AD"}.fa-pied-piper:before{content:"\F2AE"}.fa-first-order:before{content:"\F2B0"}.fa-yoast:before{content:"\F2B1"}.fa-themeisle:before{content:"\F2B2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\F2B3"}.fa-fa:before,.fa-font-awesome:before{content:"\F2B4"}.fa-handshake-o:before{content:"\F2B5"}.fa-envelope-open:before{content:"\F2B6"}.fa-envelope-open-o:before{content:"\F2B7"}.fa-linode:before{content:"\F2B8"}.fa-address-book:before{content:"\F2B9"}.fa-address-book-o:before{content:"\F2BA"}.fa-address-card:before,.fa-vcard:before{content:"\F2BB"}.fa-address-card-o:before,.fa-vcard-o:before{content:"\F2BC"}.fa-user-circle:before{content:"\F2BD"}.fa-user-circle-o:before{content:"\F2BE"}.fa-user-o:before{content:"\F2C0"}.fa-id-badge:before{content:"\F2C1"}.fa-drivers-license:before,.fa-id-card:before{content:"\F2C2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\F2C3"}.fa-quora:before{content:"\F2C4"}.fa-free-code-camp:before{content:"\F2C5"}.fa-telegram:before{content:"\F2C6"}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:"\F2C7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\F2C8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\F2C9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\F2CA"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\F2CB"}.fa-shower:before{content:"\F2CC"}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:"\F2CD"}.fa-podcast:before{content:"\F2CE"}.fa-window-maximize:before{content:"\F2D0"}.fa-window-minimize:before{content:"\F2D1"}.fa-window-restore:before{content:"\F2D2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\F2D3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\F2D4"}.fa-bandcamp:before{content:"\F2D5"}.fa-grav:before{content:"\F2D6"}.fa-etsy:before{content:"\F2D7"}.fa-imdb:before{content:"\F2D8"}.fa-ravelry:before{content:"\F2D9"}.fa-eercast:before{content:"\F2DA"}.fa-microchip:before{content:"\F2DB"}.fa-snowflake-o:before{content:"\F2DC"}.fa-superpowers:before{content:"\F2DD"}.fa-wpexplorer:before{content:"\F2DE"}.fa-meetup:before{content:"\F2E0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | /*# sourceMappingURL=app.css.map*/ -------------------------------------------------------------------------------- /public/fonts/vendor/element-ui/lib/theme-chalk/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-song/redis-manager/7888a01db0a78056e04dce757ea396d1d2f6e0fb/public/fonts/vendor/element-ui/lib/theme-chalk/element-icons.ttf -------------------------------------------------------------------------------- /public/fonts/vendor/element-ui/lib/theme-chalk/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-song/redis-manager/7888a01db0a78056e04dce757ea396d1d2f6e0fb/public/fonts/vendor/element-ui/lib/theme-chalk/element-icons.woff -------------------------------------------------------------------------------- /public/fonts/vendor/font-awesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-song/redis-manager/7888a01db0a78056e04dce757ea396d1d2f6e0fb/public/fonts/vendor/font-awesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/vendor/font-awesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-song/redis-manager/7888a01db0a78056e04dce757ea396d1d2f6e0fb/public/fonts/vendor/font-awesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/vendor/font-awesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-song/redis-manager/7888a01db0a78056e04dce757ea396d1d2f6e0fb/public/fonts/vendor/font-awesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/vendor/font-awesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-song/redis-manager/7888a01db0a78056e04dce757ea396d1d2f6e0fb/public/fonts/vendor/font-awesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/app.js": "/js/app.js?id=e6bc45bd5d6373fb5e32", 3 | "/css/app.css": "/css/app.css?id=67967ba67a8828022cac", 4 | "/js/app.js.map": "/js/app.js.map?id=cb6c7470a2d39a7d8325", 5 | "/css/app.css.map": "/css/app.css.map?id=3303343d4ed19631681f" 6 | } -------------------------------------------------------------------------------- /resources/assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue'; 3 | import router from './router'; 4 | import Redis from './redis'; 5 | import './elementui'; 6 | import App from './components/App.vue'; 7 | 8 | Vue.prototype.$redis = Redis.create(); 9 | 10 | window.Bus = new Vue({ name: 'Bus' }); 11 | 12 | const app = new Vue({ 13 | el: '#app', 14 | router, 15 | render: h => h(App), 16 | }); 17 | -------------------------------------------------------------------------------- /resources/assets/js/components/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /resources/assets/js/components/Charts/LineChart.js: -------------------------------------------------------------------------------- 1 | import { Line, mixins } from 'vue-chartjs' 2 | const { reactiveProp } = mixins 3 | 4 | export default { 5 | extends: Line, 6 | mixins: [reactiveProp], 7 | props: ['options'], 8 | mounted () { 9 | this.renderChart(this.chartData, this.options) 10 | } 11 | } -------------------------------------------------------------------------------- /resources/assets/js/components/Layout.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 58 | 59 | -------------------------------------------------------------------------------- /resources/assets/js/components/Siderbar.vue: -------------------------------------------------------------------------------- 1 | 32 | 41 | 44 | -------------------------------------------------------------------------------- /resources/assets/js/elementui.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import { 4 | Button, Select, Option, Input, Dropdown, DropdownMenu, DropdownItem, Menu, 5 | MenuItem, Submenu, ButtonGroup, Table, TableColumn, Tag, Row, Col, Card, 6 | Alert, Form, FormItem, InputNumber, Dialog, Header, Container, Aside, Main, 7 | Loading, MessageBox, Message 8 | } from 'element-ui' 9 | 10 | Vue.use(Button); 11 | Vue.use(Select); 12 | Vue.use(Option); 13 | Vue.use(Input); 14 | Vue.use(Dropdown); 15 | Vue.use(DropdownMenu); 16 | Vue.use(DropdownItem); 17 | Vue.use(Menu); 18 | Vue.use(MenuItem); 19 | Vue.use(Submenu); 20 | Vue.use(ButtonGroup); 21 | Vue.use(Table); 22 | Vue.use(TableColumn); 23 | Vue.use(Tag); 24 | Vue.use(Row); 25 | Vue.use(Col); 26 | Vue.use(Card); 27 | Vue.use(Alert); 28 | Vue.use(Form); 29 | Vue.use(FormItem); 30 | Vue.use(InputNumber); 31 | Vue.use(Dialog); 32 | Vue.use(Header); 33 | Vue.use(Container); 34 | Vue.use(Aside); 35 | Vue.use(Aside); 36 | Vue.use(Main); 37 | 38 | Vue.use(Loading.directive); 39 | 40 | Vue.prototype.$loading = Loading.service; 41 | Vue.prototype.$confirm = MessageBox.confirm; 42 | Vue.prototype.$message = Message; -------------------------------------------------------------------------------- /resources/assets/js/pages/Console.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | < 80 | 81 | -------------------------------------------------------------------------------- /resources/assets/js/pages/Info.vue: -------------------------------------------------------------------------------- 1 | 148 | 180 | 216 | -------------------------------------------------------------------------------- /resources/assets/js/pages/Keys.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 83 | 84 | -------------------------------------------------------------------------------- /resources/assets/js/pages/create/Hash.vue: -------------------------------------------------------------------------------- 1 | 32 | 45 | 110 | -------------------------------------------------------------------------------- /resources/assets/js/pages/create/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 30 | 31 | 63 | 64 | -------------------------------------------------------------------------------- /resources/assets/js/pages/create/List.vue: -------------------------------------------------------------------------------- 1 | 36 | 49 | 116 | -------------------------------------------------------------------------------- /resources/assets/js/pages/create/Set.vue: -------------------------------------------------------------------------------- 1 | 38 | 59 | 146 | -------------------------------------------------------------------------------- /resources/assets/js/pages/create/String.vue: -------------------------------------------------------------------------------- 1 | 20 | 25 | 77 | -------------------------------------------------------------------------------- /resources/assets/js/pages/create/Zset.vue: -------------------------------------------------------------------------------- 1 | 33 | 46 | 116 | -------------------------------------------------------------------------------- /resources/assets/js/pages/edit/Hash.vue: -------------------------------------------------------------------------------- 1 | 71 | 90 | 230 | -------------------------------------------------------------------------------- /resources/assets/js/pages/edit/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 29 | 30 | 89 | 90 | -------------------------------------------------------------------------------- /resources/assets/js/pages/edit/List.vue: -------------------------------------------------------------------------------- 1 | 75 | 88 | 234 | -------------------------------------------------------------------------------- /resources/assets/js/pages/edit/Set.vue: -------------------------------------------------------------------------------- 1 | 40 | 61 | 149 | -------------------------------------------------------------------------------- /resources/assets/js/pages/edit/String.vue: -------------------------------------------------------------------------------- 1 | 26 | 36 | 93 | -------------------------------------------------------------------------------- /resources/assets/js/pages/edit/Zset.vue: -------------------------------------------------------------------------------- 1 | 71 | 86 | 217 | -------------------------------------------------------------------------------- /resources/assets/js/pages/metrics/CPU.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 157 | 158 | -------------------------------------------------------------------------------- /resources/assets/js/pages/metrics/Clients.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 157 | 158 | -------------------------------------------------------------------------------- /resources/assets/js/pages/metrics/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /resources/assets/js/pages/metrics/Memory.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 164 | 165 | -------------------------------------------------------------------------------- /resources/assets/js/pages/metrics/Throughput.vue: -------------------------------------------------------------------------------- 1 | 28 | 60 | 90 | -------------------------------------------------------------------------------- /resources/assets/js/redis.js: -------------------------------------------------------------------------------- 1 | 2 | import axios from 'axios'; 3 | 4 | export default class { 5 | 6 | constructor() { 7 | this.$http = axios.create({baseURL: window.basePath + '/api/'}); 8 | 9 | this.conn = 'default'; 10 | } 11 | 12 | static create() { 13 | return new this; 14 | } 15 | 16 | getConnection() { 17 | return localStorage.getItem('conn') || 'default' 18 | } 19 | 20 | connections() { 21 | return this.$http.get('/connections'); 22 | } 23 | 24 | info(section = null) { 25 | return this.$http.get('/info', { 26 | params: {section, conn: this.getConnection() }, 27 | }); 28 | } 29 | 30 | expire(key, seconds) { 31 | const params = { 32 | key, 33 | seconds, 34 | conn: this.getConnection() 35 | } 36 | 37 | return this.$http.put('/expire', params); 38 | } 39 | 40 | get(key) { 41 | return this.$http.get('/key', { 42 | params: { key, conn: this.getConnection() }, 43 | }); 44 | } 45 | 46 | del(keys) { 47 | return this.$http.delete('/keys', { 48 | params: { keys, conn: this.getConnection() }, 49 | }); 50 | } 51 | 52 | set(key, value) { 53 | const params = { 54 | type: 'string', 55 | key, 56 | value, 57 | conn: this.getConnection(), 58 | }; 59 | 60 | return this.$http.post('/keys', params); 61 | } 62 | 63 | setex(key, seconds, value) { 64 | const params = { 65 | type: 'string', 66 | key, 67 | value, 68 | seconds, 69 | conn: this.getConnection(), 70 | }; 71 | 72 | return this.$http.post('/keys', params); 73 | } 74 | 75 | hgetall(key) { 76 | return this.get(key); 77 | } 78 | 79 | hmset(key, dic, seconds = null) { 80 | const params = { 81 | type: 'hash', 82 | key, 83 | dic, 84 | seconds, 85 | conn: this.getConnection(), 86 | }; 87 | 88 | return this.$http.post('/keys', params); 89 | } 90 | 91 | hset(key, name, value) { 92 | const dic = { 93 | name, 94 | value, 95 | }; 96 | 97 | return this.hmset(key, [dic], null); 98 | } 99 | 100 | hdel(key, field) { 101 | 102 | const type = 'hash' 103 | const conn = this.getConnection() 104 | 105 | return this.$http.delete('/keys/item', { 106 | params: { key, field, type, conn }, 107 | }); 108 | } 109 | 110 | smembers(key) { 111 | return this.get(key); 112 | } 113 | 114 | sadd(key, members, seconds) { 115 | const params = { 116 | type: 'set', 117 | key, 118 | members, 119 | seconds, 120 | conn: this.getConnection(), 121 | }; 122 | 123 | return this.$http.post('/keys', params); 124 | } 125 | 126 | srem(key, member) { 127 | const params = { 128 | type: 'set', 129 | action: 'srem', 130 | key, 131 | member, 132 | conn: this.getConnection(), 133 | }; 134 | 135 | return this.$http.put('/keys', params); 136 | } 137 | 138 | lall(key) { 139 | return this.get(key) 140 | } 141 | 142 | lpush(key, members, seconds) { 143 | const params = { 144 | type: 'list', 145 | action: 'lpush', 146 | key, 147 | members, 148 | seconds, 149 | conn: this.getConnection(), 150 | }; 151 | 152 | return this.$http.put('/keys', params); 153 | } 154 | 155 | rpush(key, members, seconds) { 156 | const params = { 157 | type: 'list', 158 | action: 'rpush', 159 | key, 160 | members, 161 | seconds, 162 | conn: this.getConnection(), 163 | }; 164 | 165 | return this.$http.put('/keys', params); 166 | } 167 | 168 | lstore(key, members, seconds) { 169 | const params = { 170 | type: 'list', 171 | action: 'rpush', 172 | key, 173 | members, 174 | seconds, 175 | conn: this.getConnection(), 176 | }; 177 | 178 | return this.$http.post('/keys', params); 179 | } 180 | 181 | ldel(key, index) { 182 | 183 | const type = 'list' 184 | const conn = this.getConnection() 185 | 186 | return this.$http.delete('/keys/item', { 187 | params: { key, index, type, conn }, 188 | }); 189 | } 190 | 191 | lset(key, index, value) { 192 | const params = { 193 | type: 'list', 194 | action: 'lset', 195 | key, 196 | value, 197 | index, 198 | conn: this.getConnection(), 199 | }; 200 | 201 | return this.$http.put('/keys', params); 202 | } 203 | 204 | zall(key) { 205 | return this.get(key); 206 | } 207 | 208 | zadd(key, members, seconds = null) { 209 | const params = { 210 | type: 'zset', 211 | key, 212 | members, 213 | seconds, 214 | conn: this.getConnection(), 215 | }; 216 | 217 | return this.$http.post('/keys', params); 218 | } 219 | 220 | zrem(key, member) { 221 | const params = { 222 | type: 'zset', 223 | action: 'zrem', 224 | key, 225 | member, 226 | conn: this.getConnection(), 227 | }; 228 | 229 | return this.$http.put('/keys', params); 230 | } 231 | 232 | zset(key, member, score) { 233 | const params = { 234 | type: 'zset', 235 | action: 'zset', 236 | key, 237 | member, 238 | score, 239 | conn: this.getConnection(), 240 | }; 241 | 242 | return this.$http.put('/keys', params); 243 | } 244 | 245 | scan(pattern) { 246 | const params = { 247 | pattern, 248 | conn: this.getConnection(), 249 | }; 250 | 251 | return this.$http.get('/scan', { params }); 252 | } 253 | 254 | eval(command, db = null) { 255 | const params = { 256 | command, 257 | db, 258 | conn: this.getConnection(), 259 | }; 260 | 261 | return this.$http.post('/eval', params); 262 | } 263 | 264 | } 265 | -------------------------------------------------------------------------------- /resources/assets/js/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | 4 | Vue.use(Router); 5 | 6 | export default new Router({ 7 | mode: 'history', 8 | base: window.basePath, 9 | routes: [ 10 | { path: '/', component: require('./pages/Keys.vue'), }, 11 | { 12 | path: '/create', 13 | component: require('./pages/create/Index.vue'), 14 | children: [ 15 | { path: '/', redirect: 'string' }, 16 | { path: 'string', component: require('./pages/create/String.vue') }, 17 | { path: 'hash', component: require('./pages/create/Hash.vue') }, 18 | { path: 'list', component: require('./pages/create/List.vue') }, 19 | { path: 'set', component: require('./pages/create/Set.vue') }, 20 | { path: 'zset', component: require('./pages/create/Zset.vue') }, 21 | ], 22 | }, 23 | { 24 | path: '/edit', 25 | component: require('./pages/edit/Index.vue'), 26 | children: [ 27 | { path: '/', redirect: 'string' }, 28 | { path: 'string', component: require('./pages/edit/String.vue') }, 29 | { path: 'hash', component: require('./pages/edit/Hash.vue') }, 30 | { path: 'list', component: require('./pages/edit/List.vue') }, 31 | { path: 'set', component: require('./pages/edit/Set.vue') }, 32 | { path: 'zset', component: require('./pages/edit/Zset.vue') }, 33 | ], 34 | }, 35 | { path: '/info', component: require('./pages/Info.vue'), }, 36 | { path: '/console', component: require('./pages/Console.vue'), }, 37 | { 38 | path: '/metrics', 39 | component: require('./pages/metrics/Index.vue'), 40 | children: [ 41 | { path: '/', redirect: 'memory', }, 42 | { path: 'memory', component: require('./pages/metrics/Memory.vue'), }, 43 | { path: 'cpu', component: require('./pages/metrics/Cpu.vue'), }, 44 | { path: 'clients', component: require('./pages/metrics/Clients.vue'), }, 45 | { path: 'throughput', component: require('./pages/metrics/Throughput.vue'), },], 46 | }, 47 | ], 48 | }); 49 | -------------------------------------------------------------------------------- /resources/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "~font-awesome/css/font-awesome"; 3 | 4 | /* theme color */ 5 | $--color-primary: teal; -------------------------------------------------------------------------------- /resources/views/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ config('app.name', 'Laravel') }} 12 | 13 | 14 | 15 | 18 | 19 | 20 |

21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | 'api'], function (Router $router) { 7 | $router->get('/connections', 'RedisController@connections'); 8 | $router->get('/scan', 'RedisController@scan'); 9 | $router->delete('/keys', 'RedisController@destroy'); 10 | $router->post('/keys', 'RedisController@store'); 11 | $router->put('/keys', 'RedisController@update'); 12 | $router->get('/key', 'RedisController@key'); 13 | $router->get('/info', 'RedisController@info'); 14 | $router->delete('/keys/item', 'RedisController@remove'); 15 | $router->put('/expire', 'RedisController@expire'); 16 | $router->post('/eval', 'RedisController@eval'); 17 | }); 18 | 19 | Route::get('/{view?}', 'RedisController@index')->where('view', '(.*)'); 20 | -------------------------------------------------------------------------------- /src/DataType/DataType.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 22 | } 23 | 24 | /** 25 | * Get redis connection. 26 | * 27 | * @return Connection 28 | */ 29 | public function getConnection() 30 | { 31 | return $this->connection; 32 | } 33 | 34 | /** 35 | * @param string $key 36 | * 37 | * @return mixed 38 | */ 39 | abstract public function fetch(string $key); 40 | 41 | /** 42 | * @param array $params 43 | * 44 | * @return mixed 45 | */ 46 | abstract public function update(array $params); 47 | 48 | /** 49 | * @param array $params 50 | * 51 | * @return mixed 52 | */ 53 | abstract public function store(array $params); 54 | 55 | /** 56 | * Returns the remaining time to live of a key that has a timeout. 57 | * 58 | * @param string $key 59 | * 60 | * @return int 61 | */ 62 | public function ttl($key) 63 | { 64 | return $this->getConnection()->ttl($key); 65 | } 66 | 67 | /** 68 | * Set a timeout on key. 69 | * 70 | * @param string $key 71 | * @param int $expire 72 | * 73 | * @return void 74 | */ 75 | public function setTtl($key, $expire) 76 | { 77 | if (is_null($expire)) { 78 | return; 79 | } 80 | 81 | $expire = (int) $expire; 82 | 83 | if ($expire > 0) { 84 | $this->getConnection()->expire($key, $expire); 85 | } else { 86 | $this->getConnection()->persist($key); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/DataType/Hashes.php: -------------------------------------------------------------------------------- 1 | getConnection()->hgetall($key); 13 | } 14 | 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function update(array $params) 19 | { 20 | $key = array_get($params, 'key'); 21 | 22 | if (array_has($params, 'field')) { 23 | $field = array_get($params, 'field'); 24 | $value = array_get($params, 'value'); 25 | 26 | $this->getConnection()->hset($key, $field, $value); 27 | } 28 | 29 | if (array_has($params, '_editable')) { 30 | $value = array_get($params, 'value'); 31 | $field = array_get($params, 'pk'); 32 | 33 | $this->getConnection()->hset($key, $field, $value); 34 | } 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function store(array $params) 41 | { 42 | $key = array_get($params, 'key'); 43 | $seconds = array_get($params, 'seconds'); 44 | $dic = array_get($params, 'dic'); 45 | 46 | $fields = []; 47 | 48 | foreach ($dic as $item) { 49 | $fields[$item['name']] = $item['value']; 50 | } 51 | 52 | $this->getConnection()->hmset($key, $fields); 53 | 54 | if ($seconds > 0) { 55 | $this->getConnection()->expire($key, $seconds); 56 | } 57 | } 58 | 59 | /** 60 | * Remove a field from a hash. 61 | * 62 | * @param array $params 63 | * 64 | * @return int 65 | */ 66 | public function remove(array $params) 67 | { 68 | $key = array_get($params, 'key'); 69 | $field = array_get($params, 'field'); 70 | 71 | return $this->getConnection()->hdel($key, [$field]); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/DataType/Lists.php: -------------------------------------------------------------------------------- 1 | getConnection()->lrange($key, 0, -1); 13 | } 14 | 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function update(array $params) 19 | { 20 | $key = array_get($params, 'key'); 21 | 22 | $action = array_get($params, 'action'); 23 | 24 | if (in_array($action, ['lpush', 'rpush'])) { 25 | $members = array_get($params, 'members'); 26 | $this->getConnection()->{$action}($key, $members); 27 | } 28 | 29 | if ($action == 'lset') { 30 | $value = array_get($params, 'value'); 31 | $index = array_get($params, 'index'); 32 | 33 | $this->getConnection()->lset($key, $index, $value); 34 | } 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function store(array $params) 41 | { 42 | $key = array_get($params, 'key'); 43 | $members = array_get($params, 'members'); 44 | $expire = array_get($params, 'expire'); 45 | $action = array_get($params, 'action', 'rpush'); 46 | 47 | $members = array_column($members, 'value'); 48 | 49 | $this->getConnection()->{$action}($key, $members); 50 | 51 | if ($expire > 0) { 52 | $this->getConnection()->expire($key, $expire); 53 | } 54 | } 55 | 56 | /** 57 | * Remove a member from list by index. 58 | * 59 | * @param array $params 60 | * 61 | * @return mixed 62 | */ 63 | public function remove(array $params) 64 | { 65 | $key = array_get($params, 'key'); 66 | $index = array_get($params, 'index'); 67 | 68 | $lua = <<<'LUA' 69 | redis.call('lset', KEYS[1], ARGV[1], '__DELETED__'); 70 | redis.call('lrem', KEYS[1], 1, '__DELETED__'); 71 | LUA; 72 | 73 | return $this->getConnection()->eval($lua, 1, $key, $index); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/DataType/Sets.php: -------------------------------------------------------------------------------- 1 | getConnection()->smembers($key); 13 | } 14 | 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function update(array $params) 19 | { 20 | $key = array_get($params, 'key'); 21 | $member = array_get($params, 'member'); 22 | $action = array_get($params, 'action'); 23 | 24 | if ($action === 'srem') { 25 | $this->getConnection()->srem($key, $member); 26 | } 27 | 28 | if ($action === 'sadd') { 29 | $this->getConnection()->sadd($key, [$member]); 30 | } 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function store(array $params) 37 | { 38 | $key = array_get($params, 'key'); 39 | $members = array_get($params, 'members'); 40 | $seconds = array_get($params, 'seconds'); 41 | 42 | $this->getConnection()->sadd($key, $members); 43 | 44 | if ($seconds > 0) { 45 | $this->getConnection()->expire($key, $seconds); 46 | } else { 47 | $this->getConnection()->persist($key); 48 | } 49 | } 50 | 51 | /** 52 | * Remove a member from a set. 53 | * 54 | * @param array $params 55 | * 56 | * @return int 57 | */ 58 | public function remove(array $params) 59 | { 60 | $key = array_get($params, 'key'); 61 | $member = array_get($params, 'member'); 62 | 63 | return $this->getConnection()->srem($key, $member); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/DataType/SortedSets.php: -------------------------------------------------------------------------------- 1 | getConnection()->zrange($key, 0, -1, ['WITHSCORES' => true]); 13 | } 14 | 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function update(array $params) 19 | { 20 | $key = array_get($params, 'key'); 21 | $member = array_get($params, 'member'); 22 | $action = array_get($params, 'action'); 23 | 24 | if ($action === 'zrem') { 25 | $this->getConnection()->zrem($key, $member); 26 | } 27 | 28 | if ($action === 'zset') { 29 | $score = array_get($params, 'score'); 30 | $this->getConnection()->zadd($key, [$member => $score]); 31 | } 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function store(array $params) 38 | { 39 | $key = array_get($params, 'key'); 40 | $members = array_get($params, 'members'); 41 | $expire = array_get($params, 'expire'); 42 | 43 | $fields = []; 44 | 45 | foreach ($members as $member) { 46 | $fields[$member['member']] = $member['score']; 47 | } 48 | 49 | $this->getConnection()->zadd($key, $fields); 50 | 51 | if ($expire > 0) { 52 | $this->getConnection()->expire($key, $expire); 53 | } 54 | } 55 | 56 | /** 57 | * Remove a member from a sorted set. 58 | * 59 | * @param array $params 60 | * 61 | * @return int 62 | */ 63 | public function remove(array $params) 64 | { 65 | $key = array_get($params, 'key'); 66 | $member = array_get($params, 'member'); 67 | 68 | return $this->getConnection()->zrem($key, $member); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/DataType/Strings.php: -------------------------------------------------------------------------------- 1 | getConnection()->get($key); 13 | } 14 | 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function update(array $params) 19 | { 20 | $this->store($params); 21 | } 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function store(array $params) 27 | { 28 | $key = array_get($params, 'key'); 29 | $value = array_get($params, 'value'); 30 | $seconds = array_get($params, 'seconds'); 31 | 32 | $this->getConnection()->set($key, $value); 33 | 34 | if ($seconds > 0) { 35 | $this->getConnection()->expire($key, $seconds); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Formatter/Information.php: -------------------------------------------------------------------------------- 1 | static::formatBytes($info['Memory']['used_memory']), 18 | 'used_memory_rss' => static::formatBytes($info['Memory']['used_memory_rss']), 19 | 'used_memory_peak' => static::formatBytes($info['Memory']['used_memory_peak']), 20 | ]; 21 | 22 | return $info; 23 | } 24 | 25 | /** 26 | * Format commandstats information. 27 | * 28 | * @param array $info 29 | * 30 | * @return static 31 | */ 32 | public static function commandstats($info) 33 | { 34 | $commands = collect($info['Commandstats'])->mapWithKeys(function ($value, $key) { 35 | preg_match('/calls=(\d+),usec=(\d+),usec_per_call=(.*)/', $value, $match); 36 | 37 | list($_, $calls, $usec, $usec_per_call) = $match; 38 | 39 | return [substr($key, 8) => compact('calls', 'usec', 'usec_per_call')]; 40 | }); 41 | 42 | return $commands; 43 | } 44 | 45 | /** 46 | * Format cpu information. 47 | * 48 | * @param array $info 49 | * 50 | * @return mixed 51 | */ 52 | public static function cpu($info) 53 | { 54 | return $info['CPU']; 55 | } 56 | 57 | /** 58 | * Format clients information. 59 | * 60 | * @param array $info 61 | * 62 | * @return mixed 63 | */ 64 | public static function clients($info) 65 | { 66 | return $info['Clients']; 67 | } 68 | 69 | /** 70 | * Format bytes to MB size. 71 | * 72 | * @param int $bytes 73 | * @param int $precision 74 | * 75 | * @return float 76 | */ 77 | public static function formatBytes($bytes, $precision = 2) 78 | { 79 | $bytes = $bytes / (1024 * 1024); 80 | 81 | return round($bytes, $precision); 82 | } 83 | 84 | /** 85 | * @param $method 86 | * @param $arguments 87 | * 88 | * @return mixed 89 | */ 90 | public static function __callStatic($method, $arguments) 91 | { 92 | return $arguments[0]; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Http/Controllers/RedisController.php: -------------------------------------------------------------------------------- 1 | middleware(Authenticate::class); 20 | } 21 | 22 | /** 23 | * Index page. 24 | * 25 | * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 26 | */ 27 | public function index() 28 | { 29 | return view('redis-manager::app'); 30 | } 31 | 32 | /** 33 | * Get redis connections. 34 | * 35 | * @return Collection 36 | */ 37 | public function connections() 38 | { 39 | $config = config('database.redis'); 40 | 41 | return collect($config)->filter(function ($conn) { 42 | return is_array($conn); 43 | })->keys(); 44 | } 45 | 46 | /** 47 | * @param Request $request 48 | * 49 | * @return array|\Predis\Pipeline\Pipeline 50 | */ 51 | public function scan(Request $request) 52 | { 53 | $manager = $this->manager(); 54 | 55 | return $manager->scan( 56 | $request->get('pattern', '*'), 57 | $request->get('count', config('redis-manager.results_per_page', 50)) 58 | ); 59 | } 60 | 61 | /** 62 | * @param Request $request 63 | * 64 | * @return array 65 | */ 66 | public function info(Request $request) 67 | { 68 | $section = $request->get('section'); 69 | 70 | return $this->manager()->getInformation($section); 71 | } 72 | 73 | /** 74 | * @param Request $request 75 | * 76 | * @return mixed 77 | */ 78 | public function store(Request $request) 79 | { 80 | $type = $request->get('type'); 81 | 82 | return $this->manager()->{$type}()->store($request->all()); 83 | } 84 | 85 | /** 86 | * @param Request $request 87 | * 88 | * @return int 89 | */ 90 | public function destroy(Request $request) 91 | { 92 | return $this->manager()->del($request->get('keys')); 93 | } 94 | 95 | /** 96 | * @param Request $request 97 | * 98 | * @return array 99 | */ 100 | public function key(Request $request) 101 | { 102 | return $this->manager()->fetch($request->get('key')); 103 | } 104 | 105 | /** 106 | * @param Request $request 107 | * 108 | * @return mixed 109 | */ 110 | public function remove(Request $request) 111 | { 112 | $type = $request->get('type'); 113 | 114 | return $this->manager()->{$type}()->remove($request->all()); 115 | } 116 | 117 | /** 118 | * @param Request $request 119 | * 120 | * @return mixed 121 | */ 122 | public function update(Request $request) 123 | { 124 | return $this->manager()->update($request); 125 | } 126 | 127 | /** 128 | * @param Request $request 129 | * 130 | * @return int 131 | */ 132 | public function expire(Request $request) 133 | { 134 | return $this->manager()->expire($request->get('key'), $request->get('seconds')); 135 | } 136 | 137 | /** 138 | * Execute a redis command. 139 | * 140 | * @param Request $request 141 | * 142 | * @return array 143 | */ 144 | public function eval(Request $request) 145 | { 146 | $command = $request->get('command'); 147 | $db = $request->get('db'); 148 | 149 | try { 150 | $result = $this->manager()->execute($command, $db); 151 | } catch (\Exception $exception) { 152 | return [ 153 | 'success' => false, 154 | 'data' => $exception->getMessage(), 155 | 'command' => $command, 156 | ]; 157 | } 158 | 159 | if (is_string($result) && Str::startsWith($result, ['ERR ', 'WRONGTYPE '])) { 160 | return [ 161 | 'success' => false, 162 | 'data' => $result, 163 | 'command' => $command, 164 | ]; 165 | } 166 | 167 | return [ 168 | 'success' => true, 169 | 'data' => $result, 170 | 'command' => $command, 171 | ]; 172 | } 173 | 174 | /** 175 | * Get the redis manager instance. 176 | * 177 | * @return RedisManager 178 | */ 179 | protected function manager() 180 | { 181 | $conn = \request()->get('conn'); 182 | 183 | return RedisManager::instance($conn); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | Strings::class, 29 | 'hash' => Hashes::class, 30 | 'set' => Sets::class, 31 | 'zset' => SortedSets::class, 32 | 'list' => Lists::class, 33 | ]; 34 | 35 | /** 36 | * @var RedisManager 37 | */ 38 | protected static $instance; 39 | 40 | /** 41 | * @var string 42 | */ 43 | protected $connection; 44 | 45 | /** 46 | * The callback that should be used to authenticate redis-manager users. 47 | * 48 | * @var \Closure 49 | */ 50 | public static $authUsing; 51 | 52 | /** 53 | * Get instance of redis manager. 54 | * 55 | * @param string $connection 56 | * 57 | * @return RedisManager 58 | */ 59 | public static function instance($connection = 'default') 60 | { 61 | if (!static::$instance instanceof self) { 62 | static::$instance = new static($connection); 63 | } 64 | 65 | return static::$instance; 66 | } 67 | 68 | /** 69 | * RedisManager constructor. 70 | * 71 | * @param string $connection 72 | */ 73 | public function __construct($connection = 'default') 74 | { 75 | $this->connection = $connection; 76 | } 77 | 78 | /** 79 | * Determine if the given request can access redis-manager. 80 | * 81 | * @param \Illuminate\Http\Request $request 82 | * 83 | * @return bool 84 | */ 85 | public static function check($request) 86 | { 87 | return (static::$authUsing ?: function () { 88 | return app()->environment('local'); 89 | })($request); 90 | } 91 | 92 | /** 93 | * Set the callback that should be used to authenticate redis-manager users. 94 | * 95 | * @param \Closure $callback 96 | * 97 | * @return static 98 | */ 99 | public static function auth(\Closure $callback) 100 | { 101 | static::$authUsing = $callback; 102 | 103 | return new static(); 104 | } 105 | 106 | /** 107 | * @return Lists 108 | */ 109 | public function list() 110 | { 111 | return new Lists($this->getConnection()); 112 | } 113 | 114 | /** 115 | * @return Strings 116 | */ 117 | public function string() 118 | { 119 | return new Strings($this->getConnection()); 120 | } 121 | 122 | /** 123 | * @return Hashes 124 | */ 125 | public function hash() 126 | { 127 | return new Hashes($this->getConnection()); 128 | } 129 | 130 | /** 131 | * @return Sets 132 | */ 133 | public function set() 134 | { 135 | return new Sets($this->getConnection()); 136 | } 137 | 138 | /** 139 | * @return SortedSets 140 | */ 141 | public function zset() 142 | { 143 | return new SortedSets($this->getConnection()); 144 | } 145 | 146 | /** 147 | * Get connection collections. 148 | * 149 | * @return Collection 150 | */ 151 | public function getConnections() 152 | { 153 | return collect(config('database.redis'))->filter(function ($conn) { 154 | return is_array($conn); 155 | }); 156 | } 157 | 158 | /** 159 | * Get a registered connection instance. 160 | * 161 | * @param string $connection 162 | * 163 | * @return Connection 164 | */ 165 | public function getConnection($connection = null) 166 | { 167 | if ($connection) { 168 | $this->connection = $connection; 169 | } 170 | 171 | return Redis::connection($this->connection); 172 | } 173 | 174 | /** 175 | * Get information of redis instance. 176 | * 177 | * @param mixed $section 178 | * 179 | * @return array 180 | */ 181 | public function getInformation($section = null) 182 | { 183 | if ($section) { 184 | $info = $this->getConnection()->info($section); 185 | 186 | return Information::$section($info); 187 | } 188 | 189 | return $this->getConnection()->info(); 190 | } 191 | 192 | /** 193 | * Scan keys in redis by giving pattern. 194 | * 195 | * @param string $pattern 196 | * @param int $count 197 | * 198 | * @return array|\Predis\Pipeline\Pipeline 199 | */ 200 | public function scan($pattern = '*', $count = 100) 201 | { 202 | $client = $this->getConnection(); 203 | $keys = []; 204 | 205 | foreach (new Keyspace($client->client(), $pattern) as $item) { 206 | $keys[] = $item; 207 | 208 | if (count($keys) == $count) { 209 | break; 210 | } 211 | } 212 | 213 | $script = <<<'LUA' 214 | local type = redis.call('type', KEYS[1]) 215 | local ttl = redis.call('ttl', KEYS[1]) 216 | 217 | return {KEYS[1], type, ttl} 218 | LUA; 219 | 220 | $keys = $client->pipeline(function (Pipeline $pipe) use ($keys, $script) { 221 | foreach ($keys as $key) { 222 | $pipe->eval($script, 1, $key); 223 | } 224 | }); 225 | 226 | return collect($keys)->map(function ($key) { 227 | return [ 228 | 'key' => $key[0], 229 | 'type' => (string) $key[1], 230 | 'ttl' => $key[2], 231 | ]; 232 | }); 233 | } 234 | 235 | /** 236 | * Fetch value of a giving key. 237 | * 238 | * @param string $key 239 | * 240 | * @return array 241 | */ 242 | public function fetch($key) 243 | { 244 | if (!$this->getConnection()->exists($key)) { 245 | return []; 246 | } 247 | 248 | $type = $this->getConnection()->type($key)->__toString(); 249 | 250 | /** @var DataType $class */ 251 | $class = $this->{$type}(); 252 | 253 | $value = $class->fetch($key); 254 | $expire = $class->ttl($key); 255 | 256 | return compact('key', 'value', 'expire', 'type'); 257 | } 258 | 259 | /** 260 | * Update a specified key. 261 | * 262 | * @param Request $request 263 | * 264 | * @return bool 265 | */ 266 | public function update(Request $request) 267 | { 268 | $key = $request->get('key'); 269 | $type = $request->get('type'); 270 | 271 | /** @var DataType $class */ 272 | $class = $this->{$type}(); 273 | 274 | $class->update($request->all()); 275 | 276 | $class->setTtl($key, $request->get('ttl')); 277 | } 278 | 279 | /** 280 | * Remove the specified key. 281 | * 282 | * @param array $keys 283 | * 284 | * @return int 285 | */ 286 | public function del($keys) 287 | { 288 | return $this->getConnection()->del($keys); 289 | } 290 | 291 | /** 292 | * 运行redis命令. 293 | * 294 | * @param string $command 295 | * 296 | * @throws \Exception 297 | * 298 | * @return mixed 299 | */ 300 | public function execute($command, $db) 301 | { 302 | $command = explode(' ', trim($command)); 303 | 304 | if ($this->commandDisabled($command[0])) { 305 | throw new \Exception("Command [{$command[0]}] is disabled!"); 306 | } 307 | 308 | $client = $this->getConnection(); 309 | 310 | if ($db !== null) { 311 | $client->select($db); 312 | } 313 | 314 | return $client->executeRaw($command); 315 | } 316 | 317 | /** 318 | * Determine if giving command is disabled. 319 | * 320 | * @param string $command 321 | * 322 | * @return bool 323 | */ 324 | protected function commandDisabled(string $command) 325 | { 326 | $disabled = config('redis-manager.disable_commands'); 327 | 328 | $disabled = array_map('strtoupper', (array) $disabled); 329 | 330 | return in_array(strtoupper($command), $disabled); 331 | } 332 | 333 | /** 334 | * @param $key 335 | * @param int $seconds 336 | * 337 | * @return int 338 | */ 339 | public function expire($key, $seconds = -1) 340 | { 341 | if ($seconds > 0) { 342 | return $this->getConnection()->expire($key, $seconds); 343 | } else { 344 | return $this->getConnection()->persist($key); 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/RedisManagerServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerRoutes(); 16 | $this->registerResources(); 17 | 18 | $this->definePublishing(); 19 | } 20 | 21 | /** 22 | * Register the Redis manager routes. 23 | * 24 | * @return void 25 | */ 26 | protected function registerRoutes() 27 | { 28 | Route::group([ 29 | 'prefix' => config('redis-manager.base_path', 'redis-manager'), 30 | 'namespace' => 'Encore\RedisManager\Http\Controllers', 31 | 'middleware' => config('redis-manager.middleware', 'web'), 32 | ], function () { 33 | $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); 34 | }); 35 | } 36 | 37 | /** 38 | * Register the Redis manager resources. 39 | * 40 | * @return void 41 | */ 42 | protected function registerResources() 43 | { 44 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'redis-manager'); 45 | } 46 | 47 | /** 48 | * Define the publishing. 49 | * 50 | * @return void 51 | */ 52 | public function definePublishing() 53 | { 54 | $this->publishes([ 55 | __DIR__.'/../public' => public_path('vendor/redis-manager'), 56 | ], 'redis-manager-assets'); 57 | 58 | $this->publishes([ 59 | __DIR__.'/../public/fonts' => public_path('fonts'), 60 | ], 'redis-manager-assets'); 61 | 62 | if ($this->app->runningInConsole()) { 63 | $this->publishes([ 64 | __DIR__.'/../config/redis-manager.php' => config_path('redis-manager.php'), 65 | ], 'redis-manager-config'); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | const webpack = require('webpack'); 3 | 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Mix Asset Management 7 | |-------------------------------------------------------------------------- 8 | | 9 | | Mix provides a clean, fluent API for defining some Webpack build steps 10 | | for your Laravel application. By default, we are compiling the Sass 11 | | file for the application as well as bundling up all the JS files. 12 | | 13 | */ 14 | 15 | mix.setPublicPath('public') 16 | .js('resources/assets/js/app.js', 'public/js') 17 | .sass('resources/assets/sass/app.scss', 'public/css') 18 | .copy('resources/assets/img', 'public/img') 19 | .sourceMaps() 20 | .version() 21 | 22 | .copy('public/fonts', '../../../public/fonts') 23 | .copy('public', '../../../public/vendor/redis-manager'); 24 | 25 | mix.webpackConfig({ 26 | plugins: [ 27 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) 28 | ], 29 | devtool: "source-map", 30 | resolve: { 31 | alias: { 32 | 'vue$': 'vue/dist/vue.runtime.esm.js' 33 | } 34 | } 35 | }); 36 | --------------------------------------------------------------------------------