on master help sign in
proxy any number of databases instead of one

refactor js+html for connect/disconnect to any remote db

commit2e1fe6cfdc814eddfd26bd2f980ff0973b17f7d7 authorAgentM <agentm@themactionfaction.com> dateWed 3 Aug 2016, 20:15 (10 years ago) parentb50bf7d
4 files changed+169−100
ProjectM36/Server/WebSocket.hs+26−18 ProjectM36/Server/project-m36.js+22−4 ProjectM36/Server/websocket-client.html+12−78 ProjectM36/Server/websocket-client.js+109
ProjectM36/Server/WebSocket.hs+26−18
@@ -29,30 +29,38 @@ main = do
2929 portMVar <- newEmptyMVar
3030 serverConfig <- parseConfig
3131 let serverHost = bindHost serverConfig
32− databasename = databaseName serverConfig
3332 _ <- forkFinally (launchServer serverConfig (Just portMVar)) failureHandler
3433 --wait for server to be listening
3534 serverPort <- takeMVar portMVar
3635 --this built-in server is apparently not meant for production use, but it's easier to test than starting up the wai or snap interfaces
37− WS.runServer "0.0.0.0" 8888 (websocketProxyServer databasename serverPort serverHost)
36+ putStrLn "listening on 8888"
37+ WS.runServer "0.0.0.0" 8888 (websocketProxyServer serverPort serverHost)
3838
39−websocketProxyServer :: DatabaseName -> Port -> Hostname -> WS.ServerApp
40−websocketProxyServer dbname port host pending = do
39+websocketProxyServer :: Port -> Hostname -> WS.ServerApp
40+websocketProxyServer port host pending = do
4141 conn <- WS.acceptRequest pending
42− (sessionId, dbconn) <- createConnection conn dbname port host
43− forever $ do
44− msg <- (WS.receiveData conn) :: IO T.Text
45− let tutdprefix = "executetutd:"
46− case msg of
47− _ | tutdprefix `T.isPrefixOf` msg -> do
48− let tutdString = T.drop (T.length tutdprefix) msg
49− case parseTutorialD (T.unpack tutdString) of
50− Left err -> handleOpResult conn dbconn (DisplayErrorResult ("parse error: " `T.append` T.pack (show err)))
51− Right parsed -> do
52− result <- evalTutorialD sessionId dbconn parsed
53− handleOpResult conn dbconn result
54− _ -> WS.sendTextData conn ("messagenotexpected" :: T.Text)
55− return ()
42+ let unexpectedMsg = WS.sendTextData conn ("messagenotexpected" :: T.Text)
43+ --phase 1- accept database name for connection
44+ dbmsg <- (WS.receiveData conn) :: IO T.Text
45+ let connectdbmsg = "connectdb:"
46+ if not (connectdbmsg `T.isPrefixOf` dbmsg) then unexpectedMsg >> WS.sendClose conn ("" :: T.Text)
47+ else do
48+ let dbname = T.unpack $ T.drop (T.length connectdbmsg) dbmsg
49+ (sessionId, dbconn) <- createConnection conn dbname port host
50+ --phase 2- accept tutoriald commands
51+ _ <- forever $ do
52+ msg <- (WS.receiveData conn) :: IO T.Text
53+ let tutdprefix = "executetutd:"
54+ case msg of
55+ _ | tutdprefix `T.isPrefixOf` msg -> do
56+ let tutdString = T.drop (T.length tutdprefix) msg
57+ case parseTutorialD (T.unpack tutdString) of
58+ Left err -> handleOpResult conn dbconn (DisplayErrorResult ("parse error: " `T.append` T.pack (show err)))
59+ Right parsed -> do
60+ result <- evalTutorialD sessionId dbconn parsed
61+ handleOpResult conn dbconn result
62+ _ -> unexpectedMsg
63+ pure ()
5664
5765notificationCallback :: WS.Connection -> NotificationCallback
5866notificationCallback conn notifName evaldNotif = WS.sendTextData conn (encode (object ["notificationname" .= notifName,
ProjectM36/Server/project-m36.js+22−4
@@ -1,9 +1,17 @@
1−var ProjectM36Connection = function (host, port, openCallback, errorCallback, statusCallback, closeCallback) {
1+var ProjectM36Connection = function (host, port, dbname, openCallback, errorCallback, statusCallback, closeCallback) {
22 this.host = host
33 this.port = port
4− var socket = new WebSocket("ws://" + host + ":" + port);
5− socket.onopen = function(event) { openCallback(event); };
6− socket.onerror = function(event) { errorCallback(event); };
4+ this.dbname = dbname
5+ var connectURL = "ws://" + host + ":" + port + "/";
6+ var socket = new WebSocket("ws://localhost:8888/");
7+ var self = this;
8+ socket.onopen = function(event) {
9+ self.socket.send("connectdb:" + self.dbname);
10+ openCallback(event);
11+ };
12+ socket.onerror = function(event) {
13+ errorCallback(event);
14+ };
715 var self = this;
816 socket.onmessage = function(event) {
917 var msg = JSON.parse(event.data);
@@ -23,6 +31,16 @@ var ProjectM36Status = function (relationResult, acknowledgementResult, errorRes
2331 this.error = errorResult;
2432}
2533
34+ProjectM36Connection.prototype.close = function()
35+{
36+ this.socket.close();
37+}
38+
39+ProjectM36Connection.prototype.readyState = function()
40+{
41+ return this.socket.readyState;
42+}
43+
2644ProjectM36Connection.prototype.handleResponse = function(message)
2745{
2846 var relation = message["displayrelation"];
ProjectM36/Server/websocket-client.html+12−78
@@ -1,85 +1,12 @@
11<html>
2+<!-- add server-side security (no file access but allowing date examples loading)
3+add connection parameters with reasonable defaults
4+-->
25<head>
36<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
47<title>Project:M36 WebSocket Client</title>
58<script type="text/javascript" src="project-m36.js"></script>
6−<script>
7−
8−function setStatus(text)
9−{
10− var newtext = document.createTextNode(text);
11− var statusnode = document.getElementById("status");
12− while( statusnode.firstChild ) {
13− statusnode.removeChild( statusnode.firstChild );
14− }
15− statusnode.appendChild(newtext);
16−}
17−
18−function appendResult(title, result)
19−{
20−//prepend the page with an additional relation result
21−var sheet = document.getElementById("sheet");
22−var template = document.getElementById("sectiontemplate").cloneNode(true);
23−template.removeAttribute("id");
24−var tutd = document.createElement("span");
25−tutd.textContent = title
26−template.getElementsByClassName("title")[0].appendChild(tutd);
27−template.getElementsByClassName("result")[0].appendChild(result);
28−if(result.nodeName == "TABLE") // show some relation statistics
29−{
30− var tupleCount = result.querySelectorAll(".result > table > tbody > tr").length
31− var attrCount = result.querySelectorAll(".result > table > thead > tr > th").length
32− var attrText = attrCount + " attribute" + (attrCount == 1 ? "" : "s")
33− var tupleText = tupleCount + " tuple" + (tupleCount == 1 ? "" : "s")
34− template.getElementsByClassName("relinfo")[0].textContent = attrText + ", " + tupleText;
35−}
36−var interactor = document.getElementById("interactor");
37−sheet.insertBefore(template, interactor);
38−window.scrollTo(0,document.body.scrollHeight);
39−}
40−
41−function updateStatus(status)
42−{
43−var tutd = document.getElementById("tutd").value;
44−if(status.relation)
45−{
46− var relastable = conn.generateRelation(status.relation);
47− appendResult(tutd, relastable);
48− mungeEmptyRows();
49−}
50−if(status.acknowledgement)
51−{
52−var ok = document.createElement("span");
53−ok.textContent="OK";
54− appendResult(tutd, ok);
55−}
56−if(status.error)
57−{
58−var error = document.createElement("span");
59−error.textContent=status.error;
60−appendResult(tutd, error);
61−}
62−}
63−
64−var conn = new ProjectM36Connection("127.0.0.1", "8888",
65− function(e) {setStatus("connected")},
66− function(e) {console.log("connection error"+e)},
67− updateStatus,
68− function(e) { setStatus("disconnected")});
69−
70−function execTutorialD()
71−{
72−var tutd = document.getElementById("tutd").value;
73−conn.executeTutorialD(tutd);
74−return false;
75−}
76−
77−function hideParent(element)
78−{
79−var deleteNode = element.parentNode;
80− element.parentNode.parentNode.removeChild(deleteNode)
81−}
82−</script>
9+<script type="text/javascript" src="websocket-client.js"></script>
8310<style>
8411#sectiontemplate
8512{
@@ -129,6 +56,11 @@ margin: 3px;
12956 background-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0));
13057}
13158
59+#connection input
60+{
61+ width: 100px;
62+}
63+
13264</style>
13365</head>
13466<body>
@@ -136,7 +68,9 @@ margin: 3px;
13668<p>Try the <a href="https://github.com/agentm/project-m36/blob/master/docs/15_minute_tutorial.markdown">15 minute TutorialD Tutorial</a>.</p>
13769<div id="sheet">
13870 <div id="interactor">
139− <p>Status:<span id="status">Fresh</span></p>
71+ <form onsubmit="return connectOrDisconnect(this);" id="connection">
72+ <label>Connection:</label><input type="text" name="host" value="localhost" placeholder="Host"/>:<input type="number" placeholder="Port" name="port" min="0" max="32767" value="8888"/>/<input type="text" placeholder="Database Name" name="dbname" value="test"/><input type="submit" name="connect" value="Connect"/>
73+ </form>
14074 <form onsubmit="return execTutorialD();">
14175 <input type="text" id="tutd"/>
14276 <input type="submit"/>
ProjectM36/Server/websocket-client.jsnew file+109
@@ -0,0 +1,109 @@
1+function appendResult(title, result)
2+{
3+ //prepend the page with an additional relation result
4+ var sheet = document.getElementById("sheet");
5+ var template = document.getElementById("sectiontemplate").cloneNode(true);
6+ template.removeAttribute("id");
7+ var tutd = document.createElement("span");
8+ tutd.textContent = title
9+ template.getElementsByClassName("title")[0].appendChild(tutd);
10+ template.getElementsByClassName("result")[0].appendChild(result);
11+ if(result.nodeName == "TABLE") // show some relation statistics
12+ {
13+ var tupleCount = result.querySelectorAll(".result > table > tbody > tr").length
14+ var attrCount = result.querySelectorAll(".result > table > thead > tr > th").length
15+ var attrText = attrCount + " attribute" + (attrCount == 1 ? "" : "s")
16+ var tupleText = tupleCount + " tuple" + (tupleCount == 1 ? "" : "s")
17+ template.getElementsByClassName("relinfo")[0].textContent = attrText + ", " + tupleText;
18+ }
19+ var interactor = document.getElementById("interactor");
20+ sheet.insertBefore(template, interactor);
21+ window.scrollTo(0,document.body.scrollHeight);
22+}
23+
24+function updateStatus(status)
25+{
26+ var tutd = document.getElementById("tutd").value;
27+ if(status.relation)
28+ {
29+ var relastable = conn.generateRelation(status.relation);
30+ appendResult(tutd, relastable);
31+ mungeEmptyRows();
32+ }
33+ if(status.acknowledgement)
34+ {
35+ var ok = document.createElement("span");
36+ ok.textContent="OK";
37+ appendResult(tutd, ok);
38+ }
39+ if(status.error)
40+ {
41+ var error = document.createElement("span");
42+ error.textContent=status.error;
43+ appendResult(tutd, error);
44+ }
45+}
46+
47+var conn;
48+
49+function connectOrDisconnect(form)
50+{
51+ var formin = form.elements;
52+ var host = formin["host"].value;
53+ var port = formin["port"].value;
54+ var dbname = formin["dbname"].value;
55+
56+ if(window.conn && window.conn.readyState() == 1)
57+ {
58+ //disconnect
59+ window.conn.close();
60+ toggleConnectionFields(form, true, "Connect");
61+ }
62+ else
63+ {
64+ //connect
65+ window.conn = new ProjectM36Connection(host, port, dbname,
66+ connectionOpened,
67+ function(e) {console.log("connection error"+e)},
68+ updateStatus,
69+ function(e) {});
70+ toggleConnectionFields(form, false, "Connecting...");
71+ }
72+ return false;
73+}
74+
75+function toggleConnectionFields(form, enabled, status)
76+{
77+ form.elements["connect"].value = status;
78+ var readonlyElements = [form.elements["host"], form.elements["port"], form.elements["dbname"]];
79+ for(var ein=0; ein < readonlyElements - 1; ein++)
80+ {
81+ var e = readonlyElements[ein];
82+ if(enabled)
83+ {
84+ e.removeAttribute("readonly");
85+ }
86+ else
87+ {
88+ e.setAttribute("readonly", "readonly");
89+ }
90+ }
91+}
92+
93+function connectionOpened(event)
94+{
95+ toggleConnectionFields(document.getElementById("connection"), false, "Disconnect");
96+}
97+
98+function execTutorialD()
99+{
100+ var tutd = document.getElementById("tutd").value;
101+ conn.executeTutorialD(tutd);
102+ return false;
103+}
104+
105+function hideParent(element)
106+{
107+ var deleteNode = element.parentNode;
108+ element.parentNode.parentNode.removeChild(deleteNode)
109+}