Improve UI

master
Arinerron 2018-08-03 19:52:45 -07:00
parent 0a2fe2a0b1
commit 0b77a88e72
15 changed files with 426 additions and 107 deletions

View File

@ -31,7 +31,7 @@ class API:
Main HTTP API (Flask)
'''
callbacks = {'public' : {}, 'private' : {}, 'ui' : {}}
callbacks = {'public' : {}, 'private' : {}}
def validateToken(self, token):
'''
@ -45,6 +45,30 @@ class API:
except TypeError:
return False
def guessMime(path):
'''
Guesses the mime type from the input filename
'''
mimetypes = {
'html' : 'text/html',
'js' : 'application/javascript',
'css' : 'text/css',
'png' : 'image/png',
'jpg' : 'image/jpeg'
}
for mimetype in mimetypes:
logger.debug(path + ' endswith .' + mimetype + '?')
if path.endswith('.%s' % mimetype):
logger.debug('- True!')
return mimetypes[mimetype]
else:
logger.debug('- no')
logger.debug('%s not in %s' % (path, mimetypes))
return 'text/plain'
def __init__(self, debug):
'''
Initialize the api server, preping variables for later use
@ -76,6 +100,7 @@ class API:
self.i2pEnabled = config.get('i2p.host', False)
self.mimeType = 'text/plain'
self.overrideCSP = False
with open('data/time-bypass.txt', 'w') as bypass:
bypass.write(self.timeBypassToken)
@ -106,14 +131,15 @@ class API:
#else:
# resp.headers['server'] = 'Onionr'
resp.headers['Content-Type'] = self.mimeType
if not self.overrideCSP:
resp.headers["Content-Security-Policy"] = "default-src 'none'; script-src 'none'; object-src 'none'; style-src data: 'unsafe-inline'; img-src data:; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'none'"
resp.headers['X-Frame-Options'] = 'deny'
resp.headers['X-Content-Type-Options'] = "nosniff"
resp.headers['server'] = 'Onionr'
# reset to text/plain to help prevent browser attacks
if self.mimeType != 'text/plain':
self.mimeType = 'text/plain'
self.overrideCSP = False
return resp
@ -153,10 +179,12 @@ class API:
def ui_private(path):
startTime = math.floor(time.time())
'''
if request.args.get('timingToken') is None:
timingToken = ''
else:
timingToken = request.args.get('timingToken')
'''
if not config.get("www.ui.run", True):
abort(403)
@ -166,14 +194,21 @@ class API:
else:
self.validateHost('public')
'''
endTime = math.floor(time.time())
elapsed = endTime - startTime
if not hmac.compare_digest(timingToken, self.timeBypassToken):
if elapsed < self._privateDelayTime:
time.sleep(self._privateDelayTime - elapsed)
'''
return send_from_directory('static-data/www/ui/dist/', path)
logger.debug('Serving %s' % path)
self.mimeType = API.guessMime(path)
self.overrideCSP = True
return send_from_directory('static-data/www/ui/dist/', path, mimetype = API.guessMime(path))
@app.route('/client/')
def private_handler():

View File

@ -198,6 +198,9 @@ class Onionr:
'kex': self.doKEX,
'pex': self.doPEX,
'ui' : self.openUI,
'gui' : self.openUI,
'getpassword': self.printWebPassword
}
@ -705,5 +708,12 @@ class Onionr:
else:
logger.error('%s add-file <filename>' % sys.argv[0], timestamp = False)
def openUI(self):
import webbrowser
url = 'http://127.0.0.1:%s/ui/index.html?timingToken=%s' % (config.get('client.port', 59496), self.onionrUtils.getTimeBypassToken())
print('Opening %s ...' % url)
webbrowser.open(url, new = 1, autoraise = True)
if __name__ == "__main__":
Onionr()

View File

@ -54,6 +54,8 @@ class OnionrUtils:
except Exception as error:
logger.error('Failed to fetch time bypass token.', error = error)
return self.timingToken
def getRoundedEpoch(self, roundS=60):
'''
Returns the epoch, rounded down to given seconds (Default 60)

View File

@ -7,12 +7,13 @@
</div>
<div class="col-10">
<div class="row">
<div class="col">
<div class="col col-auto">
<a class="onionr-post-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')">$user-name</a>
<a class="onionr-post-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>
</div>
<div class="text-right pl-3 pr-3">
<div class="onionr-post-date" data-placement="top" data-toggle="tooltip" title="$date">$date-relative</div>
<div class="col col-auto text-right ml-auto pl-0">
<div class="onionr-post-date text-right" data-placement="top" data-toggle="tooltip" title="$date">$date-relative</div>
</div>
</div>

View File

@ -60,6 +60,12 @@ body {
width: 100%;
}
.h-divider {
margin: 5px 15px;
height: 1px;
width: 100%;
}
/* profile */
.onionr-profile-user-icon {
@ -71,4 +77,3 @@ body {
.onionr-profile-username {
text-align: center;
}

View File

@ -30,3 +30,7 @@ body {
border-top: 1px solid black;
font-size: 15pt;
}
.h-divider {
border-top:1px solid gray;
}

View File

@ -36,19 +36,21 @@
<body>
<div class="container">
<div class="row">
<div class="d-none d-lg-block col-lg-3">
<div class="col-12 col-lg-3">
<div class="onionr-profile">
<div class="row">
<div class="col-12">
<div class="col-4 col-lg-12">
<img id="onionr-profile-user-icon" class="onionr-profile-user-icon" src="img/default.png">
</div>
<div class="col-12">
<h2 id="onionr-profile-username" class="onionr-profile-username">arinerron</h2>
<div class="col-8 col-lg-12">
<h2 id="onionr-profile-username" class="onionr-profile-username text-left text-lg-center text-sm-left">arinerron</h2>
</div>
</div>
</div>
</div>
<div class="h-divider d-block d-lg-none pb-3"></div>
<div class="col-sm-12 col-lg-6">
<div class="row" id="onionr-timeline-posts">

View File

@ -1,26 +1,91 @@
/* handy localstorage functions for quick usage */
function set(key, val) {
return localStorage.setItem(key, val);
}
function get(key, df) { // df is default
value = localStorage.getItem(key);
if(value == null)
value = df;
return value;
}
function remove(key) {
return localStorage.removeItem(key);
}
var usermap = JSON.parse(get('usermap', '{}'));
function getUserMap() {
return usermap;
}
function deserializeUser(id) {
var serialized = getUserMap()[id]
var user = new User();
user.setName(serialized['name']);
user.setID(serialized['id']);
user.setIcon(serialized['icon']);
}
/* returns a relative date format, e.g. "5 minutes" */
function timeSince(date) {
function timeSince(date, size) {
// taken from https://stackoverflow.com/a/3177838/3678023
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1)
return interval + " years";
interval = Math.floor(seconds / 2592000);
if (interval > 1)
return interval + " months";
interval = Math.floor(seconds / 86400);
if (interval > 1)
return interval + " days";
interval = Math.floor(seconds / 3600);
if (interval > 1)
return interval + " hours";
interval = Math.floor(seconds / 60);
if (interval > 1)
return interval + " minutes";
if (size === null)
size = 'desktop';
return Math.floor(seconds) + " seconds";
var dates = {
'mobile' : {
'yr' : 'yrs',
'mo' : 'mo',
'd' : 'd',
'hr' : 'h',
'min' : 'm',
'secs' : 's',
'sec' : 's',
},
'desktop' : {
'yr' : ' years',
'mo' : ' months',
'd' : ' days',
'hr' : ' hours',
'min' : ' minutes',
'secs' : ' seconds',
'sec' : ' second',
},
};
if (interval > 1)
return interval + dates[size]['yr'];
interval = Math.floor(seconds / 2592000);
if (interval > 1)
return interval + dates[size]['mo'];
interval = Math.floor(seconds / 86400);
if (interval > 1)
return interval + dates[size]['d'];
interval = Math.floor(seconds / 3600);
if (interval > 1)
return interval + dates[size]['hr'];
interval = Math.floor(seconds / 60);
if (interval > 1)
return interval + dates[size]['min'];
if(Math.floor(seconds) !== 1)
return Math.floor(seconds) + dates[size]['secs'];
return '1' + dates[size]['sec'];
}
/* replace all instances of string */
@ -71,6 +136,19 @@ class User {
getIcon() {
return this.image;
}
serialize() {
return {
'name' : this.getName(),
'id' : this.getID(),
'icon' : this.getIcon()
};
}
remember() {
usermap[this.getID()] = this.serialize();
set('usermap', JSON.stringify(usermap));
}
}
/* post class */
@ -86,12 +164,13 @@ class Post {
</div>\
<div class="col-10">\
<div class="row">\
<div class="col">\
<div class="col col-auto">\
<a class="onionr-post-user-name" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')">$user-name</a>\
<a class="onionr-post-user-id" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>\
</div>\
<div class="text-right pl-3 pr-3">\
<div class="onionr-post-date" data-placement="top" data-toggle="tooltip" title="$date">$date-relative</div>\
\
<div class="col col-auto text-right ml-auto pl-0">\
<div class="onionr-post-date text-right" data-placement="top" data-toggle="tooltip" title="$date">$date-relative</div>\
</div>\
</div>\
\
@ -110,6 +189,8 @@ class Post {
<!-- END POST -->\
';
var device = (jQuery(document).width() < 768 ? 'mobile' : 'desktop');
postTemplate = postTemplate.replaceAll('$user-name-url', encodeHTML(encodeURL(this.getUser().getName())));
postTemplate = postTemplate.replaceAll('$user-name', encodeHTML(this.getUser().getName()));
postTemplate = postTemplate.replaceAll('$user-id-url', encodeHTML(encodeURL(this.getUser().getID())));
@ -117,7 +198,7 @@ class Post {
postTemplate = postTemplate.replaceAll('$user-id', encodeHTML(this.getUser().getID()));
postTemplate = postTemplate.replaceAll('$user-image', encodeHTML(this.getUser().getIcon()));
postTemplate = postTemplate.replaceAll('$content', encodeHTML(this.getContent()));
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate()) + ' ago');
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : ''));
postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString());
return postTemplate;
@ -150,21 +231,3 @@ class Post {
return this.date;
}
}
/* handy localstorage functions for quick usage */
function set(key, val) {
return localStorage.setItem(key, val);
}
function get(key, df) { // df is default
value = localStorage.getItem(key);
if(value == null)
value = df;
return value;
}
function remove(key) {
return localStorage.removeItem(key);
}

View File

@ -1,13 +1,89 @@
/* write a random post to the page, for testing */
var verbs =
[
["go to", "goes to", "going to", "went to", "gone to"],
["look at", "looks at", "looking at", "looked at", "looked at"],
["choose", "chooses", "choosing", "chose", "chosen"],
["torrent", "downloads", "downloading", "torrented", "downloaded"],
["detonate", "detonates", "detonating", "detonated", "detonated"],
["run", "runs", "running", "ran", "running"],
["program", "programs", "programming", "coded", "programmed"],
["start", "starts", "starting", "started", "started"]
];
var tenses =
[
{name:"Present", singular:1, plural:0, format:"%subject %verb %complement"},
{name:"Present", singular:1, plural:0, format:"%subject %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement lol"},
{name:"Past", singular:3, plural:3, format:"%subject %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement lol"},
{name:"Past", singular:3, plural:3, format:"%subject %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement lol"},
{name:"Present Continues", singular:2, plural:2, format:"%subject %be %verb %complement"}
];
var subjects =
[
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"My cat", be:"is", singular:0},
{name:"My cat", be:"is", singular:0},
{name:"My dog", be:"is", singular:0},
{name:"My dog", be:"is", singular:0},
{name:"My mom", be:"is", singular:0},
{name:"My dad", be:"is", singular:0},
{name:"You", be:"are", singular:0},
{name:"He", be:"is", singular:1}
];
var complementsForVerbs =
[
["cinema", "Egypt", "home", "concert"],
["for a map", "them", "the stars", "the lake"],
["a book for reading", "a dvd for tonight"],
["the virus", "the malware", "that 0day", "Onionr"],
["a bomb", "a nuke", "some C4", "some ammonium nitrate"],
["the race", "towards someone", "to the stars", "on top of your roof"],
["Onionr", "the malware", "some software", "Onionr"],
["Onionr", "Onionr", "the race", "the timer"],
]
Array.prototype.random = function(){return this[Math.floor(Math.random() * this.length)];};
function generate(){
var index = Math.floor(verbs.length * Math.random());
var tense = tenses.random();
var subject = subjects.random();
var verb = verbs[index];
var complement = complementsForVerbs[index];
var str = tense.format;
str = str.replace("%subject", subject.name).replace("%be", subject.be);
str = str.replace("%verb", verb[subject.singular ? tense.singular : tense.plural]);
str = str.replace("%complement", complement.random());
return str;
}
var curDate = new Date()
function addRandomPost() {
var post = new Post();
var user = new User();
var items = ['arinerron', 'beardog108', 'samyk', 'snowden', 'aaronswartz'];
user.setName(items[Math.floor(Math.random()*items.length)]);
user.setID('i-eat-waffles-often-its-actually-crazy-like-i-dont-know-wow');
post.setContent('spammm ' + btoa(Math.random() + ' wow'));
post.setContent(generate());
post.setUser(user);
post.setPostDate(new Date(new Date() - (Math.random() * 1000000)));
curDate = new Date(curDate - (Math.random() * 1000000));
post.setPostDate(curDate);
document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML();
}

View File

@ -60,6 +60,12 @@ body {
width: 100%;
}
.h-divider {
margin: 5px 15px;
height: 1px;
width: 100%;
}
/* profile */
.onionr-profile-user-icon {
@ -71,4 +77,3 @@ body {
.onionr-profile-username {
text-align: center;
}

View File

@ -30,3 +30,7 @@ body {
border-top: 1px solid black;
font-size: 15pt;
}
.h-divider {
border-top:1px solid gray;
}

View File

@ -6,19 +6,21 @@
<body>
<div class="container">
<div class="row">
<div class="d-none d-lg-block col-lg-3">
<div class="col-12 col-lg-3">
<div class="onionr-profile">
<div class="row">
<div class="col-12">
<div class="col-4 col-lg-12">
<img id="onionr-profile-user-icon" class="onionr-profile-user-icon" src="img/default.png">
</div>
<div class="col-12">
<h2 id="onionr-profile-username" class="onionr-profile-username">arinerron</h2>
<div class="col-8 col-lg-12">
<h2 id="onionr-profile-username" class="onionr-profile-username text-left text-lg-center text-sm-left">arinerron</h2>
</div>
</div>
</div>
</div>
<div class="h-divider d-block d-lg-none pb-3"></div>
<div class="col-sm-12 col-lg-6">
<div class="row" id="onionr-timeline-posts">

View File

@ -32,28 +32,60 @@ function deserializeUser(id) {
}
/* returns a relative date format, e.g. "5 minutes" */
function timeSince(date) {
function timeSince(date, size) {
// taken from https://stackoverflow.com/a/3177838/3678023
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1)
return interval + " years";
interval = Math.floor(seconds / 2592000);
if (interval > 1)
return interval + " months";
interval = Math.floor(seconds / 86400);
if (interval > 1)
return interval + " days";
interval = Math.floor(seconds / 3600);
if (interval > 1)
return interval + " hours";
interval = Math.floor(seconds / 60);
if (interval > 1)
return interval + " minutes";
if (size === null)
size = 'desktop';
return Math.floor(seconds) + " seconds";
var dates = {
'mobile' : {
'yr' : 'yrs',
'mo' : 'mo',
'd' : 'd',
'hr' : 'h',
'min' : 'm',
'secs' : 's',
'sec' : 's',
},
'desktop' : {
'yr' : ' years',
'mo' : ' months',
'd' : ' days',
'hr' : ' hours',
'min' : ' minutes',
'secs' : ' seconds',
'sec' : ' second',
},
};
if (interval > 1)
return interval + dates[size]['yr'];
interval = Math.floor(seconds / 2592000);
if (interval > 1)
return interval + dates[size]['mo'];
interval = Math.floor(seconds / 86400);
if (interval > 1)
return interval + dates[size]['d'];
interval = Math.floor(seconds / 3600);
if (interval > 1)
return interval + dates[size]['hr'];
interval = Math.floor(seconds / 60);
if (interval > 1)
return interval + dates[size]['min'];
if(Math.floor(seconds) !== 1)
return Math.floor(seconds) + dates[size]['secs'];
return '1' + dates[size]['sec'];
}
/* replace all instances of string */
@ -125,6 +157,8 @@ class Post {
getHTML() {
var postTemplate = '<$= jsTemplate('onionr-timeline-post') $>';
var device = (jQuery(document).width() < 768 ? 'mobile' : 'desktop');
postTemplate = postTemplate.replaceAll('$user-name-url', encodeHTML(encodeURL(this.getUser().getName())));
postTemplate = postTemplate.replaceAll('$user-name', encodeHTML(this.getUser().getName()));
postTemplate = postTemplate.replaceAll('$user-id-url', encodeHTML(encodeURL(this.getUser().getID())));
@ -132,7 +166,7 @@ class Post {
postTemplate = postTemplate.replaceAll('$user-id', encodeHTML(this.getUser().getID()));
postTemplate = postTemplate.replaceAll('$user-image', encodeHTML(this.getUser().getIcon()));
postTemplate = postTemplate.replaceAll('$content', encodeHTML(this.getContent()));
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate()) + ' ago');
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : ''));
postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString());
return postTemplate;

View File

@ -1,13 +1,89 @@
/* write a random post to the page, for testing */
var verbs =
[
["go to", "goes to", "going to", "went to", "gone to"],
["look at", "looks at", "looking at", "looked at", "looked at"],
["choose", "chooses", "choosing", "chose", "chosen"],
["torrent", "downloads", "downloading", "torrented", "downloaded"],
["detonate", "detonates", "detonating", "detonated", "detonated"],
["run", "runs", "running", "ran", "running"],
["program", "programs", "programming", "coded", "programmed"],
["start", "starts", "starting", "started", "started"]
];
var tenses =
[
{name:"Present", singular:1, plural:0, format:"%subject %verb %complement"},
{name:"Present", singular:1, plural:0, format:"%subject %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement lol"},
{name:"Past", singular:3, plural:3, format:"%subject %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement lol"},
{name:"Past", singular:3, plural:3, format:"%subject %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement"},
{name:"Past", singular:3, plural:3, format:"%subject just %verb %complement lol"},
{name:"Present Continues", singular:2, plural:2, format:"%subject %be %verb %complement"}
];
var subjects =
[
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"I", be:"am", singular:0},
{name:"My cat", be:"is", singular:0},
{name:"My cat", be:"is", singular:0},
{name:"My dog", be:"is", singular:0},
{name:"My dog", be:"is", singular:0},
{name:"My mom", be:"is", singular:0},
{name:"My dad", be:"is", singular:0},
{name:"You", be:"are", singular:0},
{name:"He", be:"is", singular:1}
];
var complementsForVerbs =
[
["the cinema", "Egypt", "the house", "the concert"],
["for a map", "them", "the stars", "the lake"],
["a book for reading", "a dvd for tonight"],
["the virus", "the malware", "that 0day", "Onionr"],
["a bomb", "a nuke", "some C4", "some ammonium nitrate"],
["the race", "towards someone", "to the stars", "on top of your roof"],
["Onionr", "the malware", "some software", "Onionr"],
["Onionr", "Onionr", "the race", "the timer"],
]
Array.prototype.random = function(){return this[Math.floor(Math.random() * this.length)];};
function generate(){
var index = Math.floor(verbs.length * Math.random());
var tense = tenses.random();
var subject = subjects.random();
var verb = verbs[index];
var complement = complementsForVerbs[index];
var str = tense.format;
str = str.replace("%subject", subject.name).replace("%be", subject.be);
str = str.replace("%verb", verb[subject.singular ? tense.singular : tense.plural]);
str = str.replace("%complement", complement.random());
return str;
}
var curDate = new Date()
function addRandomPost() {
var post = new Post();
var user = new User();
var items = ['arinerron', 'beardog108', 'samyk', 'snowden', 'aaronswartz'];
user.setName(items[Math.floor(Math.random()*items.length)]);
user.setID('i-eat-waffles-often-its-actually-crazy-like-i-dont-know-wow');
post.setContent('spammm ' + btoa(Math.random() + ' wow'));
post.setContent(generate());
post.setUser(user);
post.setPostDate(new Date(new Date() - (Math.random() * 1000000)));
curDate = new Date(curDate - (Math.random() * 1000000));
post.setPostDate(curDate);
document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML();
}