new: pwngrid web client
This commit is contained in:
parent
e2be21004d
commit
df01a03a4b
pwnagotchi
grid.py
ui/web
@ -23,8 +23,10 @@ def call(path, obj=None):
|
|||||||
url = '%s%s' % (API_ADDRESS, path)
|
url = '%s%s' % (API_ADDRESS, path)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
r = requests.get(url, headers=None)
|
r = requests.get(url, headers=None)
|
||||||
else:
|
elif isinstance(obj, dict):
|
||||||
r = requests.post(url, headers=None, json=obj)
|
r = requests.post(url, headers=None, json=obj)
|
||||||
|
else:
|
||||||
|
r = requests.post(url, headers=None, data=obj)
|
||||||
|
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise Exception("(status %d) %s" % (r.status_code, r.text))
|
raise Exception("(status %d) %s" % (r.status_code, r.text))
|
||||||
@ -39,6 +41,10 @@ def set_advertisement_data(data):
|
|||||||
return call("/mesh/data", obj=data)
|
return call("/mesh/data", obj=data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_advertisement_data():
|
||||||
|
return call("/mesh/data")
|
||||||
|
|
||||||
|
|
||||||
def peers():
|
def peers():
|
||||||
return call("/mesh/peers")
|
return call("/mesh/peers")
|
||||||
|
|
||||||
@ -95,3 +101,15 @@ def report_ap(essid, bssid):
|
|||||||
def inbox(page=1, with_pager=False):
|
def inbox(page=1, with_pager=False):
|
||||||
obj = call("/inbox?p=%d" % page)
|
obj = call("/inbox?p=%d" % page)
|
||||||
return obj["messages"] if not with_pager else obj
|
return obj["messages"] if not with_pager else obj
|
||||||
|
|
||||||
|
|
||||||
|
def inbox_message(id):
|
||||||
|
return call("/inbox/%d" % int(id))
|
||||||
|
|
||||||
|
|
||||||
|
def mark_message(id, mark):
|
||||||
|
return call("/inbox/%d/%s" % (int(id), str(mark)))
|
||||||
|
|
||||||
|
|
||||||
|
def send_message(to, message):
|
||||||
|
return call("/unit/%s/inbox" % to, message)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import base64
|
||||||
import _thread
|
import _thread
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/14888799/disable-console-messages-in-flask-server
|
# https://stackoverflow.com/questions/14888799/disable-console-messages-in-flask-server
|
||||||
@ -7,12 +8,15 @@ logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
|||||||
os.environ['WERKZEUG_RUN_MAIN'] = 'true'
|
os.environ['WERKZEUG_RUN_MAIN'] = 'true'
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
|
import pwnagotchi.grid as grid
|
||||||
import pwnagotchi.ui.web as web
|
import pwnagotchi.ui.web as web
|
||||||
from pwnagotchi import plugins
|
from pwnagotchi import plugins
|
||||||
|
|
||||||
from flask import send_file
|
from flask import send_file
|
||||||
from flask import request
|
from flask import request
|
||||||
|
from flask import jsonify
|
||||||
from flask import abort
|
from flask import abort
|
||||||
|
from flask import redirect
|
||||||
from flask import render_template, render_template_string
|
from flask import render_template, render_template_string
|
||||||
|
|
||||||
|
|
||||||
@ -24,6 +28,15 @@ class Handler:
|
|||||||
self._app.add_url_rule('/ui', 'ui', self.ui)
|
self._app.add_url_rule('/ui', 'ui', self.ui)
|
||||||
self._app.add_url_rule('/shutdown', 'shutdown', self.shutdown, methods=['POST'])
|
self._app.add_url_rule('/shutdown', 'shutdown', self.shutdown, methods=['POST'])
|
||||||
self._app.add_url_rule('/restart', 'restart', self.restart, methods=['POST'])
|
self._app.add_url_rule('/restart', 'restart', self.restart, methods=['POST'])
|
||||||
|
|
||||||
|
# inbox
|
||||||
|
self._app.add_url_rule('/inbox', 'inbox', self.inbox)
|
||||||
|
self._app.add_url_rule('/inbox/profile', 'inbox_profile', self.inbox_profile)
|
||||||
|
self._app.add_url_rule('/inbox/<id>', 'show_message', self.show_message)
|
||||||
|
self._app.add_url_rule('/inbox/<id>/<mark>', 'mark_message', self.mark_message)
|
||||||
|
self._app.add_url_rule('/inbox/new', 'new_message', self.new_message)
|
||||||
|
self._app.add_url_rule('/inbox/send', 'send_message', self.send_message, methods=['POST'])
|
||||||
|
|
||||||
# plugins
|
# plugins
|
||||||
self._app.add_url_rule('/plugins', 'plugins', self.plugins, strict_slashes=False,
|
self._app.add_url_rule('/plugins', 'plugins', self.plugins, strict_slashes=False,
|
||||||
defaults={'name': None, 'subpath': None})
|
defaults={'name': None, 'subpath': None})
|
||||||
@ -37,6 +50,81 @@ class Handler:
|
|||||||
other_mode='AUTO' if self._agent.mode == 'manual' else 'MANU',
|
other_mode='AUTO' if self._agent.mode == 'manual' else 'MANU',
|
||||||
fingerprint=self._agent.fingerprint())
|
fingerprint=self._agent.fingerprint())
|
||||||
|
|
||||||
|
def inbox(self):
|
||||||
|
page = request.args.get("p", default=1, type=int)
|
||||||
|
inbox = {
|
||||||
|
"pages": 1,
|
||||||
|
"records": 0,
|
||||||
|
"messages": []
|
||||||
|
}
|
||||||
|
error = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
inbox = grid.inbox(page, with_pager=True)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception('error while reading pwnmail inbox')
|
||||||
|
error = str(e)
|
||||||
|
|
||||||
|
return render_template('inbox.html',
|
||||||
|
name=pwnagotchi.name(),
|
||||||
|
page=page,
|
||||||
|
error=error,
|
||||||
|
inbox=inbox)
|
||||||
|
|
||||||
|
def inbox_profile(self):
|
||||||
|
data = {}
|
||||||
|
error = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = grid.get_advertisement_data()
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception('error while reading pwngrid data')
|
||||||
|
error = str(e)
|
||||||
|
|
||||||
|
return render_template('profile.html',
|
||||||
|
name=pwnagotchi.name(),
|
||||||
|
fingerprint=self._agent.fingerprint(),
|
||||||
|
data=data,
|
||||||
|
error=error)
|
||||||
|
|
||||||
|
def show_message(self, id):
|
||||||
|
message = {}
|
||||||
|
error = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = grid.inbox_message(id)
|
||||||
|
if message['data']:
|
||||||
|
message['data'] = base64.b64decode(message['data']).decode("utf-8")
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception('error while reading pwnmail message %d' % int(id))
|
||||||
|
error = str(e)
|
||||||
|
|
||||||
|
return render_template('message.html',
|
||||||
|
name=pwnagotchi.name(),
|
||||||
|
error=error,
|
||||||
|
message=message)
|
||||||
|
|
||||||
|
def new_message(self):
|
||||||
|
to = request.args.get("to", default="")
|
||||||
|
return render_template('new_message.html', to=to)
|
||||||
|
|
||||||
|
def send_message(self):
|
||||||
|
to = request.form["to"]
|
||||||
|
message = request.form["message"]
|
||||||
|
error = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
grid.send_message(to, message)
|
||||||
|
except Exception as e:
|
||||||
|
error = str(e)
|
||||||
|
|
||||||
|
return jsonify({"error": error})
|
||||||
|
|
||||||
|
def mark_message(self, id, mark):
|
||||||
|
logging.info("marking message %d as %s" % (int(id), mark))
|
||||||
|
grid.mark_message(id, mark)
|
||||||
|
return redirect("/inbox")
|
||||||
|
|
||||||
def plugins(self, name, subpath):
|
def plugins(self, name, subpath):
|
||||||
if name is None:
|
if name is None:
|
||||||
# show plugins overview
|
# show plugins overview
|
||||||
|
@ -66,3 +66,11 @@ div.status {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.read {
|
||||||
|
color: #777 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.messagebody {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
232
pwnagotchi/ui/web/static/js/jquery.timeago.js
Normal file
232
pwnagotchi/ui/web/static/js/jquery.timeago.js
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
/**
|
||||||
|
* Timeago is a jQuery plugin that makes it easy to support automatically
|
||||||
|
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
|
||||||
|
*
|
||||||
|
* @name timeago
|
||||||
|
* @version 1.6.7
|
||||||
|
* @requires jQuery >=1.5.0 <4.0
|
||||||
|
* @author Ryan McGeary
|
||||||
|
* @license MIT License - http://www.opensource.org/licenses/mit-license.php
|
||||||
|
*
|
||||||
|
* For usage and examples, visit:
|
||||||
|
* http://timeago.yarp.com/
|
||||||
|
*
|
||||||
|
* Copyright (c) 2008-2019, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module.
|
||||||
|
define(['jquery'], factory);
|
||||||
|
} else if (typeof module === 'object' && typeof module.exports === 'object') {
|
||||||
|
factory(require('jquery'));
|
||||||
|
} else {
|
||||||
|
// Browser globals
|
||||||
|
factory(jQuery);
|
||||||
|
}
|
||||||
|
}(function ($) {
|
||||||
|
$.timeago = function(timestamp) {
|
||||||
|
if (timestamp instanceof Date) {
|
||||||
|
return inWords(timestamp);
|
||||||
|
} else if (typeof timestamp === "string") {
|
||||||
|
return inWords($.timeago.parse(timestamp));
|
||||||
|
} else if (typeof timestamp === "number") {
|
||||||
|
return inWords(new Date(timestamp));
|
||||||
|
} else {
|
||||||
|
return inWords($.timeago.datetime(timestamp));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var $t = $.timeago;
|
||||||
|
|
||||||
|
$.extend($.timeago, {
|
||||||
|
settings: {
|
||||||
|
refreshMillis: 60000,
|
||||||
|
allowPast: true,
|
||||||
|
allowFuture: false,
|
||||||
|
localeTitle: false,
|
||||||
|
cutoff: 0,
|
||||||
|
autoDispose: true,
|
||||||
|
strings: {
|
||||||
|
prefixAgo: null,
|
||||||
|
prefixFromNow: null,
|
||||||
|
suffixAgo: "ago",
|
||||||
|
suffixFromNow: "from now",
|
||||||
|
inPast: 'any moment now',
|
||||||
|
seconds: "less than a minute",
|
||||||
|
minute: "about a minute",
|
||||||
|
minutes: "%d minutes",
|
||||||
|
hour: "about an hour",
|
||||||
|
hours: "about %d hours",
|
||||||
|
day: "a day",
|
||||||
|
days: "%d days",
|
||||||
|
month: "about a month",
|
||||||
|
months: "%d months",
|
||||||
|
year: "about a year",
|
||||||
|
years: "%d years",
|
||||||
|
wordSeparator: " ",
|
||||||
|
numbers: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
inWords: function(distanceMillis) {
|
||||||
|
if (!this.settings.allowPast && ! this.settings.allowFuture) {
|
||||||
|
throw 'timeago allowPast and allowFuture settings can not both be set to false.';
|
||||||
|
}
|
||||||
|
|
||||||
|
var $l = this.settings.strings;
|
||||||
|
var prefix = $l.prefixAgo;
|
||||||
|
var suffix = $l.suffixAgo;
|
||||||
|
if (this.settings.allowFuture) {
|
||||||
|
if (distanceMillis < 0) {
|
||||||
|
prefix = $l.prefixFromNow;
|
||||||
|
suffix = $l.suffixFromNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.settings.allowPast && distanceMillis >= 0) {
|
||||||
|
return this.settings.strings.inPast;
|
||||||
|
}
|
||||||
|
|
||||||
|
var seconds = Math.abs(distanceMillis) / 1000;
|
||||||
|
var minutes = seconds / 60;
|
||||||
|
var hours = minutes / 60;
|
||||||
|
var days = hours / 24;
|
||||||
|
var years = days / 365;
|
||||||
|
|
||||||
|
function substitute(stringOrFunction, number) {
|
||||||
|
var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
|
||||||
|
var value = ($l.numbers && $l.numbers[number]) || number;
|
||||||
|
return string.replace(/%d/i, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
|
||||||
|
seconds < 90 && substitute($l.minute, 1) ||
|
||||||
|
minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
|
||||||
|
minutes < 90 && substitute($l.hour, 1) ||
|
||||||
|
hours < 24 && substitute($l.hours, Math.round(hours)) ||
|
||||||
|
hours < 42 && substitute($l.day, 1) ||
|
||||||
|
days < 30 && substitute($l.days, Math.round(days)) ||
|
||||||
|
days < 45 && substitute($l.month, 1) ||
|
||||||
|
days < 365 && substitute($l.months, Math.round(days / 30)) ||
|
||||||
|
years < 1.5 && substitute($l.year, 1) ||
|
||||||
|
substitute($l.years, Math.round(years));
|
||||||
|
|
||||||
|
var separator = $l.wordSeparator || "";
|
||||||
|
if ($l.wordSeparator === undefined) { separator = " "; }
|
||||||
|
return $.trim([prefix, words, suffix].join(separator));
|
||||||
|
},
|
||||||
|
|
||||||
|
parse: function(iso8601) {
|
||||||
|
var s = $.trim(iso8601);
|
||||||
|
s = s.replace(/\.\d+/,""); // remove milliseconds
|
||||||
|
s = s.replace(/-/,"/").replace(/-/,"/");
|
||||||
|
s = s.replace(/T/," ").replace(/Z/," UTC");
|
||||||
|
s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
|
||||||
|
s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900
|
||||||
|
return new Date(s);
|
||||||
|
},
|
||||||
|
datetime: function(elem) {
|
||||||
|
var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
|
||||||
|
return $t.parse(iso8601);
|
||||||
|
},
|
||||||
|
isTime: function(elem) {
|
||||||
|
// jQuery's `is()` doesn't play well with HTML5 in IE
|
||||||
|
return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// functions that can be called via $(el).timeago('action')
|
||||||
|
// init is default when no action is given
|
||||||
|
// functions are called with context of a single element
|
||||||
|
var functions = {
|
||||||
|
init: function() {
|
||||||
|
functions.dispose.call(this);
|
||||||
|
var refresh_el = $.proxy(refresh, this);
|
||||||
|
refresh_el();
|
||||||
|
var $s = $t.settings;
|
||||||
|
if ($s.refreshMillis > 0) {
|
||||||
|
this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: function(timestamp) {
|
||||||
|
var date = (timestamp instanceof Date) ? timestamp : $t.parse(timestamp);
|
||||||
|
$(this).data('timeago', { datetime: date });
|
||||||
|
if ($t.settings.localeTitle) {
|
||||||
|
$(this).attr("title", date.toLocaleString());
|
||||||
|
}
|
||||||
|
refresh.apply(this);
|
||||||
|
},
|
||||||
|
updateFromDOM: function() {
|
||||||
|
$(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) });
|
||||||
|
refresh.apply(this);
|
||||||
|
},
|
||||||
|
dispose: function () {
|
||||||
|
if (this._timeagoInterval) {
|
||||||
|
window.clearInterval(this._timeagoInterval);
|
||||||
|
this._timeagoInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.timeago = function(action, options) {
|
||||||
|
var fn = action ? functions[action] : functions.init;
|
||||||
|
if (!fn) {
|
||||||
|
throw new Error("Unknown function name '"+ action +"' for timeago");
|
||||||
|
}
|
||||||
|
// each over objects here and call the requested function
|
||||||
|
this.each(function() {
|
||||||
|
fn.call(this, options);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
var $s = $t.settings;
|
||||||
|
|
||||||
|
//check if it's still visible
|
||||||
|
if ($s.autoDispose && !$.contains(document.documentElement,this)) {
|
||||||
|
//stop if it has been removed
|
||||||
|
$(this).timeago("dispose");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = prepareData(this);
|
||||||
|
|
||||||
|
if (!isNaN(data.datetime)) {
|
||||||
|
if ( $s.cutoff === 0 || Math.abs(distance(data.datetime)) < $s.cutoff) {
|
||||||
|
$(this).text(inWords(data.datetime));
|
||||||
|
} else {
|
||||||
|
if ($(this).attr('title').length > 0) {
|
||||||
|
$(this).text($(this).attr('title'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareData(element) {
|
||||||
|
element = $(element);
|
||||||
|
if (!element.data("timeago")) {
|
||||||
|
element.data("timeago", { datetime: $t.datetime(element) });
|
||||||
|
var text = $.trim(element.text());
|
||||||
|
if ($t.settings.localeTitle) {
|
||||||
|
element.attr("title", element.data('timeago').datetime.toLocaleString());
|
||||||
|
} else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
|
||||||
|
element.attr("title", text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return element.data("timeago");
|
||||||
|
}
|
||||||
|
|
||||||
|
function inWords(date) {
|
||||||
|
return $t.inWords(distance(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
function distance(date) {
|
||||||
|
return (new Date().getTime() - date.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix for IE6 suckage
|
||||||
|
document.createElement("abbr");
|
||||||
|
document.createElement("time");
|
||||||
|
}));
|
80
pwnagotchi/ui/web/templates/inbox.html
Normal file
80
pwnagotchi/ui/web/templates/inbox.html
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>{{ name }} Inbox</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css"/>
|
||||||
|
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
|
||||||
|
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/jquery.timeago.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$.mobile.ajaxEnabled = false;
|
||||||
|
|
||||||
|
jQuery(document).ready(function() {
|
||||||
|
jQuery("time.timeago").timeago();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div data-role="page">
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="error">{{ error }}</div>
|
||||||
|
{% else %}
|
||||||
|
{% if inbox.records == 0 %}
|
||||||
|
<small>Inbox is empty.</small>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<div data-role="footer">
|
||||||
|
<div data-role="navbar">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/inbox" id="email" class="ui-btn-active" data-icon="bars">Inbox</a></li>
|
||||||
|
<li><a href="/inbox/new" id="new" data-icon="mail">New</a></li>
|
||||||
|
<li><a href="/inbox/profile" id="profile" data-icon="user">Profile</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="inbox" data-role="listview" data-inset="true">
|
||||||
|
{% for message in inbox.messages %}
|
||||||
|
<li class="message">
|
||||||
|
<a href="/inbox/{{ message.id }}" class="{{ 'unread' if not message.seen_at else 'read' }}">
|
||||||
|
<h2>{{ message.sender_name }}@{{ message.sender }}</h2>
|
||||||
|
<p>
|
||||||
|
Received
|
||||||
|
<time class="timeago" datetime="{{ message.created_at }}">{{ message.created_at }}</time>
|
||||||
|
{% if message.seen_at %}, seen
|
||||||
|
<time class="timeago" datetime="{{ message.seen_at }}">{{ message.seen_at }}</time>
|
||||||
|
{% endif %}.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if inbox.pages > 1 %}
|
||||||
|
<div data-role="navbar">
|
||||||
|
<ul>
|
||||||
|
{% if page > 1 %}
|
||||||
|
<li><a href="/inbox?p={{ page - 1 }}" class="ui-btn">Prev</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if page < inbox.pages %}
|
||||||
|
<li><a href="/inbox?p={{ page + 1 }}" class="ui-btn">Next</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
70
pwnagotchi/ui/web/templates/message.html
Normal file
70
pwnagotchi/ui/web/templates/message.html
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>Message from {{ message.sender_name }}</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css"/>
|
||||||
|
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
|
||||||
|
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/jquery.timeago.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$.mobile.ajaxEnabled = false;
|
||||||
|
|
||||||
|
jQuery(document).ready(function() {
|
||||||
|
jQuery("time.timeago").timeago();
|
||||||
|
// mark the message as read
|
||||||
|
jQuery.get("/inbox/{{ message.id }}/seen", function(res){
|
||||||
|
console.log(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div data-role="page">
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="error">{{ error }}</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<div data-role="footer">
|
||||||
|
<div data-role="navbar">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/inbox" id="email" data-icon="back">Back</a></li>
|
||||||
|
<li><a href="/inbox/new?to={{ message.sender }}" id="reply" data-icon="edit">Reply</a></li>
|
||||||
|
<li><a href="/inbox/{{ message.id }}/deleted" id="delete" data-icon="delete"
|
||||||
|
onclick="return confirm('Are you sure?')">Delete</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="messagebody">
|
||||||
|
<span style="color: #888">
|
||||||
|
Message from {{ message.sender_name }}@{{ message.sender }}, received
|
||||||
|
<time class="timeago" datetime="{{ message.created_at }}">{{ message.created_at }}</time>
|
||||||
|
{% if message.seen_at %}, seen
|
||||||
|
<time class="timeago" datetime="{{ message.seen_at }}">{{ message.seen_at }}</time>{% endif %}.</span>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{% if message.data %}
|
||||||
|
<span style="white-space: pre-line">{{ message.data }}</span>
|
||||||
|
{% else %}
|
||||||
|
<small>This message is empty.</small>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
81
pwnagotchi/ui/web/templates/new_message.html
Normal file
81
pwnagotchi/ui/web/templates/new_message.html
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
{% if to %}
|
||||||
|
<title>Reply to {{ to }}</title>
|
||||||
|
{% else %}
|
||||||
|
<title>New Message</title>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css"/>
|
||||||
|
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
|
||||||
|
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/jquery.timeago.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$.mobile.ajaxEnabled = false;
|
||||||
|
|
||||||
|
$(function(){
|
||||||
|
$("#message_form").submit(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var form = $(this);
|
||||||
|
var url = form.attr('action');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: url,
|
||||||
|
data: form.serialize(),
|
||||||
|
success: function(data) {
|
||||||
|
if( data.error ) {
|
||||||
|
if( data.error.indexOf('404') != -1 )
|
||||||
|
alert('Fingerprint not found.');
|
||||||
|
else if( data.error.indexOf('aborted') != -1 )
|
||||||
|
alert('Empty or invalid message.');
|
||||||
|
else
|
||||||
|
alert(data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert("Message sent!");
|
||||||
|
document.location.href = "/inbox";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div data-role="page">
|
||||||
|
|
||||||
|
<div data-role="footer">
|
||||||
|
<div data-role="navbar">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/inbox" id="email" data-icon="back">Back</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 1em">
|
||||||
|
<form id="message_form" method="POST" action="/inbox/send">
|
||||||
|
<label for="to">To:</label>
|
||||||
|
<input type="text" name="to" id="to" value="{{ to }}">
|
||||||
|
|
||||||
|
<label for="message">Message:</label>
|
||||||
|
<textarea cols="40" rows="8" name="message" id="message"></textarea>
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
|
<input type="submit" class="button" value="Send"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
46
pwnagotchi/ui/web/templates/profile.html
Normal file
46
pwnagotchi/ui/web/templates/profile.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>Profile</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css"/>
|
||||||
|
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
|
||||||
|
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/jquery.timeago.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$.mobile.ajaxEnabled = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div data-role="page">
|
||||||
|
|
||||||
|
<div data-role="footer">
|
||||||
|
<div data-role="navbar">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/inbox" id="email" data-icon="back">Back</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 1em">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<h4 id="name">{{ name }}</h4>
|
||||||
|
|
||||||
|
<label for="fingerprint">Fingerprint</label>
|
||||||
|
<h4 id="fingerprint">{{ fingerprint }}</h4>
|
||||||
|
|
||||||
|
<label for="data">Data</label>
|
||||||
|
<pre><code id="data">{{ data }}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user