changeset 288:9233d7ee3c09

mod_admin_web: Initial PoC commit
author Florian Zeitz <florob@babelmonkeys.de>
date Fri, 17 Dec 2010 03:34:53 +0100
parents 6144fe6161f1
children 415034fd38c2
files mod_admin_web/admin_web/get_deps.lua mod_admin_web/admin_web/mod_admin_web.lua mod_admin_web/admin_web/www_files/index.html mod_admin_web/admin_web/www_files/js/main.js mod_admin_web/admin_web/www_files/style.css
diffstat 5 files changed, 405 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_admin_web/admin_web/get_deps.lua	Fri Dec 17 03:34:53 2010 +0100
@@ -0,0 +1,30 @@
+#!/usr/bin/env lua
+
+files = {
+	["www_files/strophejs.tar.gz"] = "http://download.github.com/metajack-strophejs-release-1.0-0-g1581b37.tar.gz";
+	["www_files/js/jquery-1.4.4.min.js"] = "http://code.jquery.com/jquery-1.4.4.min.js";
+}
+
+function fetch(url)
+	local http = require "socket.http";
+	local body, status = http.request(url);
+	if status == 200 then
+		return body;
+	end
+	return false, "HTTP status code: "..tostring(status);
+end
+
+for filename, url in pairs(files) do
+	file = io.open(filename, "w+");
+	data, error = fetch(url);
+	if data then
+		file:write(data);
+	else
+		print("Error: " .. error);
+	end
+	file:close();
+end
+
+os.execute("cd www_files && tar xzf strophejs.tar.gz");
+os.execute("cd www_files/metajack-strophejs-3ada7f5 && make strophe.js && cp strophe.js ../js/strophe.js");
+os.execute("rm -r www_files/strophejs.tar.gz www_files/metajack-strophejs-3ada7f5");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_admin_web/admin_web/mod_admin_web.lua	Fri Dec 17 03:34:53 2010 +0100
@@ -0,0 +1,150 @@
+-- Copyright (C) 2010 Florian Zeitz
+--
+-- This file is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+-- <session xmlns="http://prosody.im/streams/s2s" jid="example.com">
+--   <encrypted/>
+--   <compressed/>
+--   <in/> / <out/>
+-- </session>
+
+local stanza = require "util.stanza";
+local uuid_generate = require "util.uuid".generate;
+local httpserver = require "net.httpserver";
+local lfs = require "lfs";
+local open = io.open;
+local stat = lfs.attributes;
+
+local host = module:get_host();
+local service = config.get("*", "core", "webadmin_pubsub_host") or ("pubsub." .. host);
+
+local http_base = (prosody.paths.plugins or "./plugins/") .. "admin_web/www_files";
+
+local xmlns_sessions = "http://prosody.im/streams/s2s";
+
+local response_400 = { status = "400 Bad Request", body = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(" };
+local response_403 = { status = "403 Forbidden", body = "<h1>Forbidden</h1>You don't have permission to view the contents of this directory :(" };
+local response_404 = { status = "404 Not Found", body = "<h1>Page Not Found</h1>Sorry, we couldn't find what you were looking for :(" };
+
+local mime_map = {
+	html = "text/html";
+	xml = "text/xml";
+	js = "text/javascript";
+	css = "text/css";
+};
+
+local idmap = {};
+
+function add_host(session, type)
+	local name = (type == "out" and session.to_host) or (type == "in" and session.from_host);
+	local id = idmap[name.."_"..type];
+	if not id then
+		id = uuid_generate();
+		idmap[name.."_"..type] = id;
+	end
+	local item = stanza.stanza("item", { id = id }):tag("session", {xmlns = xmlns_sessions, jid = name})
+		:tag(type):up();
+	if session.secure then
+		item:tag("encrypted"):up();
+	end
+	if session.compressed then
+		item:tag("compressed"):up();
+	end
+	hosts[service].modules.pubsub.service:publish(xmlns_sessions, host, id, item);
+	module:log("debug", "Added host " .. name .. " s2s" .. type);
+end
+
+function del_host(session, type)
+	local name = (type == "out" and session.to_host) or (type == "in" and session.from_host);
+	local id = idmap[name.."_"..type];
+	if id then
+		local notifier = stanza.stanza("retract", { id = id });
+		hosts[service].modules.pubsub.service:retract(xmlns_sessions, host, id, notifier);
+	end
+end
+
+local function preprocess_path(path)
+	if path:sub(1,1) ~= "/" then
+		path = "/"..path;
+	end
+	local level = 0;
+	for component in path:gmatch("([^/]+)/") do
+		if component == ".." then
+			level = level - 1;
+		elseif component ~= "." then
+			level = level + 1;
+		end
+		if level < 0 then
+			return nil;
+		end
+	end
+	return path;
+end
+
+function serve_file(path)
+	local full_path = http_base..path;
+	if stat(full_path, "mode") == "directory" then
+		if stat(full_path.."/index.html", "mode") == "file" then
+			return serve_file(path.."/index.html");
+		end
+		return response_403;
+	end
+	local f, err = open(full_path, "rb");
+	if not f then return response_404; end
+	local data = f:read("*a");
+	f:close();
+	if not data then
+		return response_403;
+	end
+	local ext = path:match("%.([^.]*)$");
+	local mime = mime_map[ext]; -- Content-Type should be nil when not known
+	return {
+		headers = { ["Content-Type"] = mime; };
+		body = data;
+	};
+end
+
+local function handle_file_request(method, body, request)
+	local path = preprocess_path(request.url.path);
+	if not path then return response_400; end
+	path = path:gsub("^/[^/]+", ""); -- Strip /admin/
+	return serve_file(path);
+end
+
+function module.load()
+	local host_session = prosody.hosts[host];
+	local http_conf = config.get("*", "core", "webadmin_http_ports");
+
+	hosts[service].modules.pubsub.service:create(xmlns_sessions, host);
+
+	for remotehost, session in pairs(host_session.s2sout) do
+		if session.type ~= "s2sout_unauthed" then
+			add_host(session, "out");
+		end
+	end
+	for session in pairs(incoming_s2s) do
+		if session.to_host == host then
+			add_host(session, "in");
+		end
+	end
+
+	httpserver.new_from_config(http_conf, handle_file_request, { base = "admin" });
+end
+
+module:hook("s2sout-established", function(event)
+	add_host(event.session, "out");
+end);
+
+module:hook("s2sin-established", function(event)
+	add_host(event.session, "in");
+end);
+
+module:hook("s2sout-destroyed", function(event)
+	del_host(event.session, "out");
+end);
+
+module:hook("s2sin-destroyed", function(event)
+	del_host(event.session, "in");
+end);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_admin_web/admin_web/www_files/index.html	Fri Dec 17 03:34:53 2010 +0100
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+	"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>S2S</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
+<script type="text/javascript" src="js/jquery-1.4.4.min.js"></script>
+<script type="text/javascript" src="js/strophe.js"></script>
+<script type="text/javascript" src="js/main.js"></script>
+</head>
+<body>
+<div id='login'>
+  <form id='cred' name='cred'>
+    <label for='jid'>JID:</label><br/>
+    <input type='text' id='jid' />
+    <br />
+    <label for='pass'>Password:</label><br/>
+    <input type='password' id='pass' />
+    <br />
+    <input type='submit' id='connect' value='connect' />
+  </form>
+</div>
+
+<div id='main'>
+  <div class="container">
+    Incomming S2S connections:
+    <ul id="s2sin"></ul>
+  </div>
+  <div class="container">
+    Outgoing S2S connections:
+    <ul id="s2sout"></ul>
+  </div>
+</div>
+
+<div id='log_container'>
+  <a id='log_toggle' href='#'>Status Log :</a>
+  <div id='log'></div>
+</div>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_admin_web/admin_web/www_files/js/main.js	Fri Dec 17 03:34:53 2010 +0100
@@ -0,0 +1,136 @@
+var BOSH_SERVICE = 'http://localhost:5280/http-bind/';
+var show_log = false;
+
+Strophe.addNamespace('S2SPUBSUB', 'http://prosody.im/streams/s2s');
+Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
+Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps');
+
+var localJID = null;
+var connection   = null;
+
+var pubsubHost = null; /* TODO: Replace this inside Lua */
+
+function log(msg) {
+    var entry = $('<div></div>').append(document.createTextNode(msg));
+    $('#log').append(entry);
+}
+
+function rawInput(data) {
+    log('RECV: ' + data);
+}
+
+function rawOutput(data) {
+    log('SENT: ' + data);
+}
+
+function _cbNewS2S(e) {
+    var items, retract, id, jid;
+    items = e.getElementsByTagName('item');
+    for (i = 0; i < items.length; i++) {
+        id = items[i].attributes['id'].value;
+        jid = items[i].getElementsByTagName('session')[0].attributes['jid'].value;
+        if (items[i].getElementsByTagName('out')[0]) {
+            $('#s2sout').append('<li id="' + id + '">' + jid + '</li>');
+        } else {
+            $('#s2sin').append('<li id="' + id + '">' + jid + '</li>');
+        }
+    }
+    retract = e.getElementsByTagName('retract')[0];
+    if (retract) {
+        id = retract.attributes['id'].value;
+        $('#' + id).remove();
+    }
+    return true;
+}
+
+function onConnect(status) {
+    if (status == Strophe.Status.CONNECTING) {
+        log('Strophe is connecting.');
+    } else if (status == Strophe.Status.CONNFAIL) {
+        log('Strophe failed to connect.');
+        showConnect();
+    } else if (status == Strophe.Status.DISCONNECTING) {
+        log('Strophe is disconnecting.');
+    } else if (status == Strophe.Status.DISCONNECTED) {
+        log('Strophe is disconnected.');
+        showConnect();
+    } else if (status == Strophe.Status.AUTHFAIL) {
+        log('Authentication failed');
+        if (connection) {
+            connection.disconnect();
+        }
+    } else if (status == Strophe.Status.CONNECTED) {
+        log('Strophe is connected.');
+        showDisconnect();
+	pubsubHost = 'pubsub.' + connection.domain;
+        connection.send($iq({to: pubsubHost, type: 'set', id: connection.getUniqueId()}).c('pubsub', {xmlns: Strophe.NS.PUBSUB})
+                .c('subscribe', {node: Strophe.NS.S2SPUBSUB, jid: connection.jid}));
+        connection.addHandler(_cbNewS2S, Strophe.NS.PUBSUB + '#event', 'message');
+        connection.sendIQ($iq({to: pubsubHost, type: 'get', id: connection.getUniqueId()}).c('pubsub', {xmlns: Strophe.NS.PUBSUB})
+                .c('items', {node: Strophe.NS.S2SPUBSUB}), _cbNewS2S);
+    }
+}
+
+function showConnect() {
+    var jid = $('#jid');
+    var pass = $('#pass');
+    var button = $('#connect').get(0);
+
+    button.value = 'connect';
+    pass.show();
+    jid.show();
+    $('.container').hide();
+    $('#cred label').show();
+    $('#cred br').show();
+    $('ul').empty();
+}
+
+function showDisconnect() {
+    var jid = $('#jid');
+    var pass = $('#pass');
+    var button = $('#connect').get(0);
+
+    button.value = 'disconnect';
+    pass.hide();
+    jid.hide();
+    $('.container').show();
+    $('#cred label').hide();
+    $('#cred br').hide();
+}
+
+$(document).ready(function () {
+    connection = new Strophe.Connection(BOSH_SERVICE);
+    if (show_log) {
+        $('#log_container').show();
+        connection.rawInput = rawInput;
+        connection.rawOutput = rawOutput;
+    }
+
+    $("#log_toggle").click(function () {
+        $("#log").toggle();
+      });
+
+    $('#cred').bind('submit', function (event) {
+        var button = $('#connect').get(0);
+        var jid = $('#jid');
+        var pass = $('#pass');
+        localJID = jid.get(0).value;
+
+        if (button.value == 'connect') {
+            $('#log').empty();
+            connection.connect(localJID,
+               pass.get(0).value,
+               onConnect);
+        } else {
+            connection.disconnect();
+        }
+        event.preventDefault();
+    });
+
+});
+
+window.onunload = window.onbeforeunload = function() {
+    if (connection) {
+        connection.disconnect();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_admin_web/admin_web/www_files/style.css	Fri Dec 17 03:34:53 2010 +0100
@@ -0,0 +1,47 @@
+label {
+  margin-right: 0.5em
+}
+
+input, textarea, select {
+  margin: 0.25em;
+  margin-left: 1em
+}
+
+body {
+  padding: 1em;
+  background: #f29b00;
+  color: #000000
+}
+
+a {
+  color: #0000FF
+}
+
+#log_container {
+  clear: both;
+  display: none
+}
+
+.container {
+  margin: 2em;
+  padding: 0.5em;
+  border: solid;
+  float: left;
+  display: none
+}
+
+#login{
+  float: left;
+  margin-right: 2em;
+  padding: 1em;
+  background: #6197DF;
+  color: #000000
+}
+
+#cred input[type="submit"] {
+  margin-left: 0em
+}
+
+#main {
+  float: left
+}