mywatch 0.2.1 → 0.3.0
raw patch · 8 files changed
+150/−108 lines, 8 filesdep +filepath
Dependencies added: filepath
Files
- ChangeLog.md +9/−0
- README.md +6/−5
- app.html +60/−0
- index.html +0/−60
- mywatch.cabal +5/−4
- src/Application.hs +2/−1
- src/Server.hs +1/−1
- static/mywatch.js +67/−37
ChangeLog.md view
@@ -1,3 +1,12 @@+0.3.0+=====++ * Support Sproxy's end-point `/.sproxy/access` to find servers, available+ to the current user, in one single HTTP request. This requires+ Sproxy2 >= 1.95.0. Fallback to old multiple-request way if that+ end-point returns error.++ 0.2.1 =====
README.md view
@@ -2,7 +2,7 @@ ======== Web application for viewing and killing MySQL queries on multiple-servers. Designed to work behind [Sproxy](https://github.com/zalora/sproxy).+servers. Designed to work behind [Sproxy](http://hackage.haskell.org/package/sproxy2). Requirements@@ -17,7 +17,7 @@ Installation ============ - $ git clone https://github.com/zalora/mywatch.git+ $ git clone https://github.com/ip1981/mywatch.git $ cd mywatch $ cabal install @@ -69,8 +69,9 @@ ==================== * To access the service allow `GET` for `/`, `/static/%` and `/serverlist.json`.-* To see processes on a particular MySQL server allow `GET` and `HEAD` for- `/server/:server/processlist.json`.+* To see processes on a particular MySQL server allow `GET` and (optional)+ `HEAD` for `/server/:server/processlist.json`. The latter is only required with+ Sproxy2 before version 1.95.0 * To kill processes allow `DELETE` for `/server/:server/process/%` @@ -83,7 +84,7 @@ To be able to kill queries a procedure named `mywatch_kill` must exist in the `mysql` database. MyWatch invokes `CALL mysql.mywatch_kill(id)` for killing queries. It's up to you how to implement this routine, for a-safe example see [sql/mywatch_kill.sql](sql/mywatch_kill.sql). Of cource,+safe example see [sql/mywatch_kill.sql](sql/mywatch_kill.sql). Of course, MyWatch should be granted to execute this procedure. If this procedure does not exist, MyWatch will not show this possibility in user interface, API will work, but result in Internal Server Error (HTTP 500). There is no
+ app.html view
@@ -0,0 +1,60 @@+<!DOCTYPE html>+<html lang="en">++<head>+ <meta charset="utf-8">+ <meta http-equiv="X-UA-Compatible" content="IE=edge">+ <meta name="viewport" content="width=device-width, initial-scale=1">+ <link href="static/external/bootstrap/css/bootstrap.min.css" rel="stylesheet">+ <link href="static/external/bootstrap/css/bootstrap-theme.min.css" rel="stylesheet">+ <link href="static/mywatch.css" rel="stylesheet">+ <title>MyWatch</title>+</head>++<body>+ <nav class="navbar navbar-inverse">+ <div class="container-fluid">+ <div class="navbar-header">+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#serverList">+ <span class="icon-bar"></span>+ <span class="icon-bar"></span>+ <span class="icon-bar"></span>+ </button><a class="navbar-brand" href="/">Servers</a></div>+ <div id="serverList" class="collapse navbar-collapse">+ <ul class="nav navbar-nav"></ul>+ </div>+ </div>+ </nav>++ <div id="info" class="container">+ <h1>Loading, please wait...</h1>+ <div></div>+ </div>++ <div id="main" style="display: none;" class="container-fluid">+ <div class="table-responsive">+ <table id="processList" class="table table-striped table-hover">+ <thead>+ <tr>+ <th>id</th>+ <th>user</th>+ <th>host</th>+ <th>db</th>+ <th>command</th>+ <th>time</th>+ <th>state</th>+ <th>info</th>+ </tr>+ </thead>+ <tbody></tbody>+ </table>+ </div>+ </div>++ <script src="static/external/jquery-2.2.4.min.js"></script>+ <script src="static/external/bootstrap/js/bootstrap.min.js"></script>+ <script src="static/mywatch.js"></script>++</body>++</html>
− index.html
@@ -1,60 +0,0 @@-<!DOCTYPE html>-<html lang="en">--<head>- <meta charset="utf-8">- <meta http-equiv="X-UA-Compatible" content="IE=edge">- <meta name="viewport" content="width=device-width, initial-scale=1">- <link href="static/external/bootstrap/css/bootstrap.min.css" rel="stylesheet">- <link href="static/external/bootstrap/css/bootstrap-theme.min.css" rel="stylesheet">- <link href="static/mywatch.css" rel="stylesheet">- <title>MyWatch</title>-</head>--<body>- <nav class="navbar navbar-inverse">- <div class="container-fluid">- <div class="navbar-header">- <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#serverList">- <span class="icon-bar"></span>- <span class="icon-bar"></span>- <span class="icon-bar"></span>- </button><a class="navbar-brand" href="/">Servers</a></div>- <div id="serverList" class="collapse navbar-collapse">- <ul class="nav navbar-nav"></ul>- </div>- </div>- </nav>-- <div id="info" class="container">- <h1>Loading, please wait...</h1>- <div></div>- </div>-- <div id="main" style="display: none;" class="container-fluid">- <div class="table-responsive">- <table id="processList" class="table table-striped table-hover">- <thead>- <tr>- <th>id</th>- <th>user</th>- <th>host</th>- <th>db</th>- <th>command</th>- <th>time</th>- <th>state</th>- <th>info</th>- </tr>- </thead>- <tbody></tbody>- </table>- </div>- </div>-- <script src="static/external/jquery-2.2.4.min.js"></script>- <script src="static/external/bootstrap/js/bootstrap.min.js"></script>- <script src="static/mywatch.js"></script>--</body>--</html>
mywatch.cabal view
@@ -1,5 +1,5 @@ name: mywatch-version: 0.2.1+version: 0.3.0 synopsis: Web application to view and kill MySQL queries description: View and kill queries on multiple MySQL servers.@@ -8,13 +8,13 @@ license-file: LICENSE author: Igor Pashev <pashev.igor@gmail.com> maintainer: Igor Pashev <pashev.igor@gmail.com>-copyright: 2016, Zalora South East Asia Pte. Ltd+copyright: 2016-2017, Zalora South East Asia Pte. Ltd category: Databases, Web build-type: Simple extra-source-files: README.md ChangeLog.md cabal-version: >= 1.20 data-files:- index.html+ app.html static/external/bootstrap/css/*.min.css static/external/bootstrap/js/*.min.js static/external/jquery-2.2.4.min.js@@ -26,7 +26,7 @@ source-repository head type: git- location: https://github.com/zalora/mywatch.git+ location: https://github.com/ip1981/mywatch.git executable mywatch default-language: Haskell2010@@ -45,6 +45,7 @@ , data-default-class , docopt >= 0.7 , fast-logger+ , filepath , http-types >= 0.9 , interpolatedstring-perl6 >= 1.0 , MissingH
src/Application.hs view
@@ -24,6 +24,7 @@ mkRequestLogger, RequestLoggerSettings(destination, outputFormat), OutputFormat(CustomOutputFormat)) import Network.Wai.Middleware.Static (addBase, hasPrefix, staticPolicy, (>->))+import System.FilePath.Posix ((</>)) import System.IO (stderr) import Web.Scotty (ScottyM, ActionM, middleware, json, file, addroute, get, delete, status, text, param, scottyApp)@@ -47,7 +48,7 @@ middleware $ Gzip.gzip Gzip.def {Gzip.gzipFiles = Gzip.GzipCompress} middleware $ staticPolicy (hasPrefix "static" >-> addBase dataDir)- get "/" $ file (dataDir ++ "/" ++ "index.html")+ get "/" $ file (dataDir </> "app.html") get "/serverlist.json" $ json (sort $ HM.keys ps) get "/server/:server/processlist.json" $ apiGetProcessList ps
src/Server.hs view
@@ -48,7 +48,7 @@ runSettingsSocket defaultSettings sock =<< app mysql dataDir) getGroup :: ConnectInfo -> Text-getGroup ci = decodeUtf8 . getName . fromJust . find isGroup . connectOptions $ ci+getGroup = decodeUtf8 . getName . fromJust . find isGroup . connectOptions where isGroup (ReadDefaultGroup _) = True isGroup _ = False
static/mywatch.js view
@@ -9,7 +9,7 @@ var cankill; var interval;- var server;+ var selected; var plCols = plHeader.children().map(function() { return $(this).text();@@ -28,10 +28,10 @@ function switchServer() { cankill = undefined; clearInterval(interval);- if ('' !== server) {- document.title = server + ' — ' + 'MyWatch';+ if ('' !== selected) {+ document.title = selected + ' — ' + 'MyWatch'; serverList.find('.active').removeClass('active');- var s = $('a[href="#' + server + '"]');+ var s = $('a[href="#' + selected + '"]'); if (s) { s.parent().addClass('active'); getProcessList();@@ -43,14 +43,14 @@ } function onHash() {- server = location.hash.substring(1);+ selected = location.hash.substring(1); switchServer(); }; window.onhashchange = onHash; function kill(id) { $.ajax({- url: 'server/' + server + '/process/' + id,+ url: 'server/' + selected + '/process/' + id, method: 'DELETE', success: function() { $('#' + id).fadeOut(300, function() {@@ -104,7 +104,7 @@ function getProcessList() { function get() { $.ajax({- url: 'server/' + server + '/processlist.json',+ url: 'server/' + selected + '/processlist.json', method: 'GET', error: commonError, success: showProcessList@@ -112,7 +112,7 @@ } if (typeof cankill === 'undefined') { $.ajax({- url: 'server/' + server + '/process/0',+ url: 'server/' + selected + '/process/0', method: 'DELETE', complete: function(jqXHR) { cankill = (200 === jqXHR.status);@@ -124,38 +124,68 @@ } }; + function showAvailable(available) {+ available.sort().forEach(function(s) {+ serverList.append('<li><a href="#' + s + '">' + s + '</a></li>');+ });+ serverList.find('a').on('click', function() {+ if ($(this).text() === selected) {+ getProcessList();+ }+ });+ info.hide();+ onHash();+ };++ function getAvailableFallback(servers) {+ var total = servers.length;+ var available = [];+ var checked = 0;+ $.each(servers, function(i, s) {+ $.ajax({+ url: 'server/' + s + '/processlist.json',+ method: 'HEAD',+ success: function() {+ available.push(s);+ },+ complete: function() {+ checked++;+ if (checked === total) {+ showAvailable(available);+ }+ }+ });+ });+ };++ function getAvailable(servers) {+ var total = servers.length;+ var available = [];+ var data = {};+ servers.forEach(function(tag) {+ data[tag] = {+ method: 'GET',+ path: '/server/' + tag + '/processlist.json'+ };+ });++ $.ajax({+ url: '.sproxy/access',+ method: 'POST',+ contentType: 'application/json',+ data: JSON.stringify(data),+ error: function() {+ getAvailableFallback(servers)+ },+ success: showAvailable+ });+ };+ $.ajax({ url: 'serverlist.json', method: 'GET', error: commonError,- success: function(servers) {- var total = servers.length;- var available = [];- var checked = 0;- $.each(servers, function(i, s) {- $.ajax({- url: 'server/' + s + '/processlist.json',- method: 'HEAD',- success: function() {- available.push(s);- },- complete: function() {- checked++;- if (checked === total) {- $.each(available.sort(), function(i, s) {- serverList.append('<li><a href="#' + s + '">' + s + '</a></li>')- });- serverList.find('a').on('click', function() {- if ($(this).text() === server) {- getProcessList();- }- });- info.hide();- onHash();- }- }- });- });- }+ success: getAvailable });+ });