packages feed

mywatch 0.1.0 → 0.1.1

raw patch · 7 files changed

+79/−28 lines, 7 files

Files

ChangeLog.md view
@@ -1,3 +1,20 @@+0.1.1+=====++  * Request `HEAD /server/:server/processlist.json` before+    showing the server to user. This is to hide servers which+    are not allowed by Sproxy to this user.++  * Added a workaround for buggy haskell mysql package+    causing a heisenbug that random sections of the+    configuration files were not found by libmysqlclient.++  * Added a workaround for the way MariaDB's libmysqlclient+    processes SSL options. SSL now works with MariaDB's+    libmysqlclient.++  * Fixed parsing of `GRANT` queries (they have `NULL` states).+ 0.1.0 ===== 
index.html view
@@ -8,7 +8,7 @@   <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>MySQL Process Overview</title>+  <title>MyWatch</title> </head>  <body>
mywatch.cabal view
@@ -1,5 +1,5 @@ name: mywatch-version: 0.1.0+version: 0.1.1 synopsis: View MySQL processes description:   View queries on multiple MySQL servers. Designed to work behind Sproxy.@@ -26,7 +26,7 @@  executable mywatch     default-language: Haskell2010-    ghc-options: -Wall -static+    ghc-options: -Wall -static -threaded     hs-source-dirs: src     main-is: Main.hs     other-modules:
src/Application.hs view
@@ -18,14 +18,14 @@ import Data.Text.Lazy (Text) import Database.MySQL.Simple (Connection, query_) import GHC.Generics (Generic)-import Network.HTTP.Types (notFound404)+import Network.HTTP.Types (ok200, notFound404, StdMethod(HEAD)) import Network.Wai (Application, Middleware) import Network.Wai.Middleware.RequestLogger (Destination(Handle),   mkRequestLogger, RequestLoggerSettings(destination, outputFormat),   OutputFormat(CustomOutputFormat)) import Network.Wai.Middleware.Static (addBase, hasPrefix, staticPolicy, (>->)) import System.IO (stderr)-import Web.Scotty (ScottyM, ActionM, middleware, json, file, get,+import Web.Scotty (ScottyM, ActionM, middleware, json, file, addroute, get,   status, text, param, scottyApp) import qualified Data.HashMap.Lazy as HM @@ -51,8 +51,11 @@   get "/index.html" $ file index_html    get "/serverlist.json" $ json (sort $ HM.keys ps)-  get "/server/:server/processlist.json" $ apiGetProcesses ps+  get "/server/:server/processlist.json" $ apiGetProcessList ps +  -- Used by client to see which servers are really allowed by Sproxy+  addroute HEAD "/server/:server/processlist.json" $ apiCanProcessList ps+ data Process = Process {     id      :: Int   , user    :: Text@@ -60,17 +63,24 @@   , db      :: Maybe Text   , command :: Text   , time    :: Int-  , state   :: Text+  , state   :: Maybe Text   , info    :: Text } deriving (Generic) instance ToJSON Process -apiGetProcesses :: Pools -> ActionM ()-apiGetProcesses ps = do+apiCanProcessList :: Pools -> ActionM ()+apiCanProcessList ps = do   server <- param "server"   case HM.lookup server ps of     Nothing -> status notFound404 >> text server-    Just p -> do+    Just _  -> status ok200++apiGetProcessList :: Pools -> ActionM ()+apiGetProcessList ps = do+  server <- param "server"+  case HM.lookup server ps of+    Nothing -> status notFound404 >> text server+    Just p  -> do       res <- withDB p $ \c ->               query_ c "SELECT \               \id, user, host, db, command, time, state, info \
src/Main.hs view
@@ -7,8 +7,9 @@ import Data.ByteString.Char8 (pack) import Data.Either.Utils (forceEither) import Data.Maybe (fromJust)+import Data.List (isPrefixOf) import Data.Version (showVersion)-import Database.MySQL.Base (ConnectInfo(..))+import Database.MySQL.Base (ConnectInfo(..), defaultSSLInfo) import Database.MySQL.Base.Types (Option(ReadDefaultFile, ReadDefaultGroup)) import Paths_mywatch (getDataDir, version) -- from cabal import System.Environment (getArgs)@@ -51,17 +52,22 @@       port = O.getArg args $ O.longOption "port"       socket = fromJust $ O.getArg args $ O.longOption "socket"       datadir = fromJust $ O.getArg args $ O.longOption "datadir"-    servers <- filter ("client" /=) . Cf.sections . forceEither <$> Cf.readfile Cf.emptyCP file++    cf <- forceEither <$> Cf.readfile Cf.emptyCP file     let+      servers = filter ("client" /=) . Cf.sections $ cf       myInfo = map (\g -> ConnectInfo {         connectDatabase = "",         connectHost     = "",         connectPassword = "",         connectPath     = "",         connectPort     = 0,-        connectSSL      = Nothing,+        -- FIXME: https://jira.mariadb.org/browse/MDEV-10246+        connectSSL      = if any (isPrefixOf "ssl") (forceEither $ Cf.options cf g)+                          then Just defaultSSLInfo else Nothing,         connectUser     = "",-        connectOptions  = [ ReadDefaultFile file, ReadDefaultGroup (pack g) ]+        -- FIXME: Work aroung buggy mysql: unsafeUseAsCString creates garbage.+        connectOptions  = [ ReadDefaultFile file, ReadDefaultGroup (pack $ g ++ "\0") ]       }) servers       listen = maybe (Right socket) (Left . read) port     server listen myInfo datadir
src/Server.hs view
@@ -5,7 +5,6 @@  import Control.Exception.Base (throwIO, catch, bracket) import Data.Bits ((.|.))-import Data.ByteString.Lazy (fromStrict) import Data.List (find) import Data.Maybe (fromJust) import Data.Pool (createPool, destroyAllResources)@@ -22,6 +21,7 @@ import System.IO.Error (isDoesNotExistError) import System.Posix.Files (removeLink, setFileMode, socketMode, ownerReadMode,   ownerWriteMode, groupReadMode, groupWriteMode)+import qualified Data.ByteString.Lazy as LBS import qualified Data.HashMap.Lazy as HM import qualified Database.MySQL.Simple as MySQL @@ -52,7 +52,8 @@   where     isGroup (ReadDefaultGroup _) = True     isGroup _ = False-    getName (ReadDefaultGroup n) = fromStrict n+    -- FIXME: Removing trailing zero added for buggy mysql in Main.hs.+    getName (ReadDefaultGroup n) = LBS.takeWhile (0 /=) . LBS.fromStrict $ n     getName _ = error "Cannot happen"  
static/mywatch.js view
@@ -56,20 +56,37 @@     method: "GET",     error: commonError,     success: function(servers) {+      var total = servers.length;+      var available = [];+      var checked = 0;       $.each(servers, function(i, s) {-        $('#serverList>ul')-          .append('<li><a href="#">' + s +-            '</a></li>')-      });-      $("#serverList a").on("click", function() {-        var server = $(this).text();-        $(this).parent().parent().find('.active').removeClass('active');-        $(this).parent().addClass('active');-        clearInterval(interval);-        getProcessList(server);-        interval = setInterval(getProcessList, 60 * 1000, server);+        $.ajax({+          url: "server/" + s + "/processlist.json",+          method: "HEAD",+          success: function() {+            available.push(s);+          },+          complete: function() {+            var menu = $('#serverList>ul');+            checked++;+            if (checked === total) {+              $.each(available.sort(), function(i, s) {+                menu.append('<li><a href="#">' + s + '</a></li>')+              });+              $("#serverList a").on("click", function() {+                var server = $(this).text();+                document.title = server + ' — ' + 'MyWatch';+                $(this).parent().parent().find('.active').removeClass('active');+                $(this).parent().addClass('active');+                clearInterval(interval);+                getProcessList(server);+                interval = setInterval(getProcessList, 60 * 1000, server);+              });+              info.hide();+            }+          }+        });       });-      info.hide();     }   }); });