server: console is served at /console Closes #16 (#22)

This commit is contained in:
Rakesh Emmadi
2018-06-29 16:35:09 +05:30
committed by Vamshi Surabhi
parent db1a098b3b
commit 128ed2388b
6 changed files with 92 additions and 478 deletions

View File

@@ -33,13 +33,14 @@ data RavenOptions
data ServeOptions
= ServeOptions
{ soPort :: !Int
, soConnParams :: !Q.ConnParams
, soTxIso :: !Q.TxIsolation
, soRootDir :: !(Maybe String)
, soAccessKey :: !(Maybe AccessKey)
, soCorsConfig :: !CorsConfig
, soWebHook :: !(Maybe T.Text)
{ soPort :: !Int
, soConnParams :: !Q.ConnParams
, soTxIso :: !Q.TxIsolation
, soRootDir :: !(Maybe String)
, soAccessKey :: !(Maybe AccessKey)
, soCorsConfig :: !CorsConfig
, soWebHook :: !(Maybe T.Text)
, soEnableConsole :: !Bool
} deriving (Show, Eq)
data RavenMode
@@ -70,6 +71,7 @@ parseRavenMode = subparser
<*> parseAccessKey
<*> parseCorsConfig
<*> parseWebHook
<*> parseEnableConsole
parseArgs :: IO RavenOptions
parseArgs = execParser opts
@@ -101,7 +103,7 @@ main = withStdoutLogger ravenLogGen $ \rlogger -> do
return $ mkConnInfo rci
printConnInfo ci
case ravenMode of
ROServe (ServeOptions port cp isoL mRootDir mAccessKey corsCfg mWebHook) -> do
ROServe (ServeOptions port cp isoL mRootDir mAccessKey corsCfg mWebHook enableConsole) -> do
am <- either ((>> exitFailure) . putStrLn) return $
mkAuthMode mAccessKey mWebHook
initialise ci
@@ -109,7 +111,7 @@ main = withStdoutLogger ravenLogGen $ \rlogger -> do
pool <- Q.initPGPool ci cp
runSpockNoBanner port $ do
putStrLn $ "server: running on port " ++ show port
spockT id $ app isoL mRootDir rlogger pool am corsCfg
spockT id $ app isoL mRootDir rlogger pool am corsCfg enableConsole
ROExport -> do
res <- runTx ci fetchMetadata
either ((>> exitFailure) . printJSON) printJSON res

View File

@@ -1,13 +1,14 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Hasura.Server.App where
import Control.Concurrent.MVar
import Control.Lens
import Control.Lens hiding ((.=))
import Data.Char (isSpace)
import Data.IORef
@@ -15,7 +16,6 @@ import Crypto.Hash (Digest, SHA1, hash)
import Data.Aeson hiding (json)
import qualified Data.ByteString.Lazy as BL
import Data.CaseInsensitive (CI (..), original)
import qualified Data.FileEmbed as FE
import qualified Data.HashMap.Strict as M
import qualified Data.String.Conversions as CS
import qualified Data.Text as T
@@ -28,6 +28,7 @@ import qualified Network.HTTP.Client.TLS as HT
import Network.Wai (strictRequestBody)
import qualified Network.Wreq as Wq
import qualified Network.Wreq.Types as WqT
import qualified Data.FileEmbed as FE
import Web.Spock.Core
@@ -37,28 +38,28 @@ import qualified Network.Wai.Middleware.Static as MS
import qualified Data.Text.Encoding.Error as TE
import qualified Database.PG.Query as Q
import qualified Hasura.GraphQL.Execute as GE
import qualified Hasura.GraphQL.Execute.Result as GE
import qualified Hasura.GraphQL.Schema as GS
import qualified Hasura.GraphQL.Execute as GE
import qualified Hasura.GraphQL.Execute.Result as GE
import qualified Hasura.GraphQL.Schema as GS
import Hasura.Prelude hiding (get, put)
import Hasura.RQL.DDL.Schema.Table
import Hasura.RQL.DML.Explain
import Hasura.RQL.DML.QueryTemplate
import Hasura.RQL.Types
import Hasura.Server.Init
import Hasura.Server.Logging
import Hasura.Prelude hiding (get, put)
import Hasura.Server.Middleware (corsMiddleware,
import Hasura.Server.Middleware (corsMiddleware,
mkDefaultCorsPolicy)
import Hasura.Server.Query
import Hasura.Server.Utils
import Hasura.SQL.Types
landingPage :: String
landingPage = $(FE.embedStringFile "src-rsr/landing_page.html")
type RavenLogger = ServerLogger (BL.ByteString, Either QErr BL.ByteString)
consoleHTML :: T.Text
consoleHTML = $(FE.embedStringFile "src-rsr/console.html")
ravenLogGen :: LogDetailG (BL.ByteString, Either QErr BL.ByteString)
ravenLogGen _ (reqBody, res) =
@@ -361,8 +362,9 @@ app
-> Q.PGPool
-> AuthMode
-> CorsConfig
-> Bool
-> SpockT IO ()
app isoLevel mRootDir logger pool mode corsCfg = do
app isoLevel mRootDir logger pool mode corsCfg enableConsole = do
cacheRef <- lift $ do
pgResp <- liftIO $ runExceptT $ Q.runTx pool (Q.Serializable, Nothing) $ do
Q.catchE defaultTxErrorHandler initStateTx
@@ -376,13 +378,14 @@ app isoLevel mRootDir logger pool mode corsCfg = do
liftIO $ putStrLn "HasuraDB is now waiting for connections"
maybe (return ()) (middleware . MS.staticPolicy . MS.addBase) mRootDir
-- cors middleware
unless (ccDisabled corsCfg) $
middleware $ corsMiddleware (mkDefaultCorsPolicy $ ccDomain corsCfg)
get root $ html $ T.pack landingPage
-- API Console and Root Dir
if enableConsole then serveApiConsole consoleHTML
else maybe (return ()) (middleware . MS.staticPolicy . MS.addBase) mRootDir
get ("v1/template" <//> var) $ tmpltGetOrDeleteH serverCtx
post ("v1/template" <//> var) $ tmpltPutOrPostH serverCtx
put ("v1/template" <//> var) $ tmpltPutOrPostH serverCtx
@@ -428,3 +431,7 @@ app isoLevel mRootDir logger pool mode corsCfg = do
mkQTemplateAction tmpltName tmpltArgs =
v1QueryHandler $ RQExecuteQueryTemplate $
ExecQueryTemplate (TQueryName tmpltName) tmpltArgs
serveApiConsole htmlFile = do
get root $ redirect "/console"
get ("console" <//> wildcard) $ const $ html htmlFile

View File

@@ -171,3 +171,8 @@ parseWebHook = optional $ strOption ( long "auth-hook" <>
metavar "AUTHENTICATION WEB HOOK" <>
help "The authentication webhook, required to authenticate requests"
)
parseEnableConsole :: Parser Bool
parseEnableConsole = switch ( long "enable-console" <>
help "Enable API Console"
)

View File

@@ -0,0 +1,49 @@
<html lang="en-us">
<head>
<link rel="icon" type="image/png" href="https://storage.googleapis.com/hasura-graphql-engine/console/assets/favicon.png" />
<script>
window.__env = {
consoleMode: "hasuradb",
urlPrefix: "/console"
};
</script>
</head>
<body>
<style>
.mainContent {
display: 'none';
opacity: 0;
transition: opacity .20s linear;
}
.mainContent.show {
display: 'block';
opacity: 1;
transition: opacity .20s linear;
}
</style>
<div id="loading">
<div class="page-loading" style="
min-height: 100vh;
width: 100%;
display: flex;
align-items: center;
font-family: sans-serif;
justify-content: center;
">
<span class="" style="
font-size: 2em;
margin-top: -3em;
color: #848484;
">
Loading...
</span>
</div>
</div>
<div id="content" class="mainContent"></div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
<link rel="stylesheet" href="https://storage.googleapis.com/hasura-graphql-engine/console/1.0-dev/main.css" charset="UTF-8"/>
<script src="https://storage.googleapis.com/hasura-graphql-engine/console/1.0-dev/vendor.js" charset="UTF-8"></script>
<script src="https://storage.googleapis.com/hasura-graphql-engine/console/1.0-dev/main.js" charset="UTF-8"></script>
</body>
</html>

View File

@@ -1,449 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimal-ui">
<title>Hi! Your GraphQL endpoint on Postgres is ready. </title>
<style>
@font-face {
font-family: octicons-anchor;
src: url(data:font/woff;charset=utf-8;base64,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) format('woff');
}
body {
background-color: white;
max-width: 790px;
margin: 0 auto;
padding: 30px 0;
}
.markdown-body {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
color: #333;
overflow: hidden;
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
font-size: 16px;
line-height: 1.6;
word-wrap: break-word;
}
.markdown-body a {
background: transparent;
}
.markdown-body a:active,
.markdown-body a:hover {
outline: 0;
}
.markdown-body strong {
font-weight: bold;
}
.markdown-body h1 {
font-size: 2em;
margin: 0.67em 0;
}
.markdown-body img {
border: 0;
}
.markdown-body hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
.markdown-body pre {
overflow: auto;
}
.markdown-body code,
.markdown-body kbd,
.markdown-body pre {
font-family: monospace, monospace;
font-size: 1em;
}
.markdown-body input {
color: inherit;
font: inherit;
margin: 0;
}
.markdown-body html input[disabled] {
cursor: default;
}
.markdown-body input {
line-height: normal;
}
.markdown-body input[type="checkbox"] {
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0;
}
.markdown-body table {
border-collapse: collapse;
border-spacing: 0;
}
.markdown-body td,
.markdown-body th {
padding: 0;
}
.markdown-body * {
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.markdown-body input {
font: 13px/1.4 Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
}
.markdown-body a {
color: #4183c4;
text-decoration: none;
}
.markdown-body a:hover,
.markdown-body a:focus,
.markdown-body a:active {
text-decoration: underline;
}
.markdown-body hr {
height: 0;
margin: 15px 0;
overflow: hidden;
background: transparent;
border: 0;
border-bottom: 1px solid #ddd;
}
.markdown-body hr:before {
display: table;
content: "";
}
.markdown-body hr:after {
display: table;
clear: both;
content: "";
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 15px;
margin-bottom: 15px;
line-height: 1.1;
}
.markdown-body h1 {
font-size: 30px;
}
.markdown-body h2 {
font-size: 21px;
}
.markdown-body h3 {
font-size: 16px;
}
.markdown-body h4 {
font-size: 14px;
}
.markdown-body h5 {
font-size: 12px;
}
.markdown-body h6 {
font-size: 11px;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
position: relative;
margin-top: 1em;
margin-bottom: 16px;
font-weight: bold;
line-height: 1.4;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
display: none;
color: #000;
vertical-align: middle;
}
.markdown-body h1:hover .anchor,
.markdown-body h2:hover .anchor,
.markdown-body h3:hover .anchor,
.markdown-body h4:hover .anchor,
.markdown-body h5:hover .anchor,
.markdown-body h6:hover .anchor {
height: 1em;
padding-left: 8px;
margin-left: -30px;
line-height: 1;
text-decoration: none;
}
.markdown-body h1:hover .anchor .octicon-link,
.markdown-body h2:hover .anchor .octicon-link,
.markdown-body h3:hover .anchor .octicon-link,
.markdown-body h4:hover .anchor .octicon-link,
.markdown-body h5:hover .anchor .octicon-link,
.markdown-body h6:hover .anchor .octicon-link {
display: inline-block;
}
.markdown-body h1 {
padding-bottom: 0.3em;
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
}
.markdown-body h2 {
padding-bottom: 0.3em;
font-size: 1.75em;
line-height: 1.225;
border-bottom: 1px solid #eee;
}
.markdown-body h3 {
font-size: 1.5em;
line-height: 1.43;
}
.markdown-body h4 {
font-size: 1.25em;
}
.markdown-body h5 {
font-size: 1em;
}
.markdown-body h6 {
font-size: 1em;
color: #777;
}
.markdown-body pre>code {
padding: 0;
margin: 0;
font-size: 100%;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.markdown-body .highlight {
margin-bottom: 16px;
}
.markdown-body .highlight pre,
.markdown-body pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
}
.markdown-body .highlight pre {
margin-bottom: 0;
word-break: normal;
}
.markdown-body pre {
word-wrap: normal;
}
.markdown-body pre code {
display: inline;
max-width: initial;
padding: 0;
margin: 0;
overflow: initial;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
.markdown-body pre code:before,
.markdown-body pre code:after {
content: normal;
}
.markdown-body {
padding-left: 30px;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
position: relative;
}
.markdown-body ul,
.markdown-body ol {
padding: 0;
margin-top: 0;
margin-bottom: 0;
}
.markdown-body ol ol,
.markdown-body ul ol {
list-style-type: lower-roman;
}
.markdown-body ul ul ol,
.markdown-body ul ol ol,
.markdown-body ol ul ol,
.markdown-body ol ol ol {
list-style-type: lower-alpha;
}
.markdown-body ul,
.markdown-body ol,
.markdown-body ul,
.markdown-body ol {
padding-left: 2em;
}
.markdown-body ul ul,
.markdown-body ul ol,
.markdown-body ol ol,
.markdown-body ol ul {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body li>p {
margin-top: 16px;
}
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #333;
background: #f8f8f8;
-webkit-text-size-adjust: none;
}
.hljs-variable {
color: #008080;
}
</style>
<style>
.copy-div {
position: relative;
}
.copy {
position: absolute;
top: calc(50% - 14px);
right: 20px;
cursor: pointer;
background: #fff !important;
padding: 5px 10px;
font-size: 0.7em;
border-radius: 2px;
}
</style>
<script>
window.onload = function() {
var curUrl = window.location.href;
document.getElementById('init').innerHTML = `hasura init --directory my-project --endpoint ${curUrl}`;
};
var copyElements = ['code-install-copy', 'code-console-copy', 'init-copy'];
function changeCopyButtonText(id) {
copyElements.forEach(function (element) {
if (element !== id){
document.getElementById(element).innerHTML = 'Copy';
} else {
document.getElementById(element).innerHTML = 'Copied';
}
});
}
function code_console_copy_copied() {
changeCopyButtonText('code-console-copy');
};
function code_install_copy_copied() {
changeCopyButtonText('code-install-copy');
};
function init_copy_copied() {
changeCopyButtonText('init-copy');
};
</script>
</head>
<body>
<article class="markdown-body">
<h2 id="hi!--your-graphql-endpoint-on-postgres-is-ready.-"><a class="header-link" href="#hi!--your-graphql-endpoint-on-postgres-is-ready.-"></a>Hi! Your GraphQL endpoint on Postgres is ready. </h2>
<p>Now, start building your schema and exploring your GraphQL APIs:</p>
<h4 id="1.-install-the-hasura-cli">Step 1: Install the Hasura CLI</h4>
<h5 id="mac"><a class="header-link" href="#mac"></a>Mac / Linux</h5>
<div class="copy-div">
<pre class="hljs" id="code-install-cli">curl -L https://cli.hasura.io/install.sh | bash</pre>
<a class="copy" data-clipboard-target="#code-install-cli" onclick="code_install_copy_copied()" id="code-install-copy">Copy</a>
</div>
<h5 id="linux">Windows</h5>
<ul class="list">
<li>Download the hasura installer for <a href="https://cli.hasura.io/install/windows-amd64">64-bit</a> or <a href="https://cli.hasura.io/install/windows-386">32-bit</a>.</li>
<li>Run the hasura command in your shell (recommended: <a href="https://git-scm.com/download/win">git-bash</a>).</li>
</ul>
<h4 id="2.-initialize-a-project">Step 2: Initialize a project</h4>
<div class="copy-div">
<pre class="hljs" id="init"></pre>
<a class="copy" data-clipboard-target="#init" onclick="init_copy_copied()" id="init-copy">Copy</a>
</div>
<h4 id="3.-open-the-hasura-console">Step 3: Open the Hasura Console</h4>
<div class="copy-div">
<pre class="hljs" id="code-console">cd my-project && hasura console</pre>
<a class="copy" data-clipboard-target="#code-console" onclick="code_console_copy_copied()" id="code-console-copy">Copy</a>
</div>
</article>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js"></script>
<script>
new ClipboardJS('.copy');
</script>
</body>
</html>

View File

@@ -235,7 +235,7 @@ raven_app rlogger pool =
do
_ <- liftIO $ runExceptT $ Q.runTx pool defTxMode resetStateTx
let corsCfg = CorsConfig "*" True -- cors is disabled
spockAsApp $ spockT id $ app Q.Serializable Nothing rlogger pool AMNoAuth corsCfg -- no access key and no webhook
spockAsApp $ spockT id $ app Q.Serializable Nothing rlogger pool AMNoAuth corsCfg True -- no access key and no webhook
main :: IO ()
main = withStdoutLogger ravenLogGen $ \rlogger -> do