Compare commits
No commits in common. "master" and "devel-count" have entirely different histories.
master
...
devel-coun
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,10 +2,9 @@
|
|||||||
/.python-version
|
/.python-version
|
||||||
/htmlcov
|
/htmlcov
|
||||||
/venv
|
/venv
|
||||||
__target__
|
__javascript__
|
||||||
__pycache__
|
__pycache__
|
||||||
refs
|
refs
|
||||||
node_modules
|
|
||||||
|
|
||||||
\#*
|
\#*
|
||||||
.#*
|
.#*
|
||||||
|
4
HOWTO.md
4
HOWTO.md
@ -12,10 +12,6 @@ Install the Python dependencies. (If doing this in a virtualenv, add the virtual
|
|||||||
cd /path/to/Eos
|
cd /path/to/Eos
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
Install the node dependencies to build the JavaScript code.
|
|
||||||
|
|
||||||
npm install @babel/core @babel/cli @babel/preset-env babelify browserify
|
|
||||||
|
|
||||||
Build the JavaScript code.
|
Build the JavaScript code.
|
||||||
|
|
||||||
./build_js.sh
|
./build_js.sh
|
||||||
|
15
README.md
15
README.md
@ -1,6 +1,6 @@
|
|||||||
# Eos: Modular verifiable elections
|
# Eos: Modular verifiable elections
|
||||||
|
|
||||||
Work in progress – Both API and GUI are sufficiently complete to have seen experimental use
|
Work in progress – An API for elections and cryptography is more or less complete, focus is now on building a functional user interface
|
||||||
|
|
||||||
## Comparison with competitors
|
## Comparison with competitors
|
||||||
|
|
||||||
@ -16,9 +16,18 @@ Eye Candy | No | Yes!
|
|||||||
|
|
||||||
## Cryptographic details and references
|
## Cryptographic details and references
|
||||||
|
|
||||||
Eos aims to be implementation-agnostic with respect to cryptographic details, with the included *eos.psr* package providing an example implementation.
|
Eos aims to be implementation-agnostic with respect to cryptographic details. The included *eos.psr* package provides an example implementation with the following particulars:
|
||||||
|
|
||||||
For details of the implementation, refer to the [*Eos Voting Technical Report*](https://drive.google.com/open?id=1jjM5hkIBSZ8LryI12yPsuWv32Id7VjTC).
|
* ElGamal encryption
|
||||||
|
* MENEZES, Alfred J., Paul C. VAN OORSCHOT and Scott A. VANSTONE. *Handbook of Applied Cryptography*. CRC Press, 2001. Fifth printing. ISBN 978-0-8493-8523-0. Available from: http://cacr.uwaterloo.ca/hac/
|
||||||
|
* Distributed threshold ElGamal due to **P**edersen (1991)
|
||||||
|
* PEDERSEN, Torben Pryds. ‘A Threshold Cryptosystem without a Trusted Party’. In: D.W. Davies, ed. *Advances in Cryptology — EUROCRYPT '91*. Berlin: Springer, 1991. pp. 522–526. Lecture Notes in Computer Science, vol. 547. ISBN 978-3-540-46416-7. Available from: https://doi.org/10.1007/3-540-46416-6_47
|
||||||
|
* **S**igned ElGamal due to Schnorr and Jakobsson (2000)
|
||||||
|
* SCHNORR, Claus Peter and Markus JAKOBSSON. ‘Security of Signed ElGamal Encryption’. In: T. OKAMOTO, ed. *Advances in Cryptology – ASIACRYPT 2000*. Berlin: Springer-Verlag, 2000. pp. 73–89. Lecture Notes in Computer Science, vol. 1976. ISBN 978-3-540-44448-0. Available from: https://doi.org/10.1007/3-540-44448-3_7
|
||||||
|
* **R**andomised partial checking (RPC) due to Jakobsson, Juels and Rivest (2002)
|
||||||
|
* JAKOBSSON, Markus, Ari JUELS and Ronald L. RIVEST. ‘Making Mix Nets Robust For Electronic Voting By Randomized Partial Checking’. In: *Proceedings of the 11th USENIX Security Symposium*. pp. 339–353. Berkeley: USENIX Association, 2002. Available from: https://www.usenix.org/event/sec02/full_papers/jakobsson/jakobsson.pdf
|
||||||
|
* Taking note of points raised by Khazaei and Wikström (2013)
|
||||||
|
* KHAZAEI, Shahram and Douglas WIKSTRÖM. ‘Randomized Partial Checking Revisited’. In: E. DAWSON, ed. *Topics in Cryptology – CT-RSA 2013*. Berlin: Springer-Verlag, 2013. pp. 115–128. Lecture Notes in Computer Science, vol. 7779. ISBN 978-3-642-36095-4. Available from: https://doi.org/10.1007/978-3-642-36095-4_8
|
||||||
|
|
||||||
## Mother of all disclaimers
|
## Mother of all disclaimers
|
||||||
|
|
||||||
|
27
build_js.sh
27
build_js.sh
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -20,14 +20,21 @@ FLAGS="-k -mc -o"
|
|||||||
#for f in eos.js eos.js_tests; do
|
#for f in eos.js eos.js_tests; do
|
||||||
for f in eos.js_tests; do
|
for f in eos.js_tests; do
|
||||||
transcrypt -b -n $FLAGS $f.py || exit 1
|
transcrypt -b -n $FLAGS $f.py || exit 1
|
||||||
|
|
||||||
|
# Javascript identifiers cannot contain dots
|
||||||
|
perl -0777 -pi -e 's/eos.js/eosjs/g' eos/__javascript__/$f.js
|
||||||
|
|
||||||
|
# __pragma__ sometimes stops working???
|
||||||
|
perl -0777 -pi -e "s/__pragma__ \('.*?'\)//gs" eos/__javascript__/$f.js
|
||||||
|
|
||||||
|
# Transcrypt by default suppresses stack traces for some reason??
|
||||||
|
perl -0777 -pi -e 's/__except0__.__cause__ = null;//g' eos/__javascript__/$f.js
|
||||||
|
|
||||||
|
# Fix handling of properties, Transcrypt bug #407
|
||||||
|
perl -0777 -pi -e 's/var __get__ = function \(self, func, quotedFuncName\) \{/var __get__ = function (self, func, quotedFuncName) { if(typeof(func) != "function"){return func;}/g' eos/__javascript__/$f.js
|
||||||
|
perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__impl__(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js
|
||||||
|
perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__implpy_(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js
|
||||||
done
|
done
|
||||||
|
|
||||||
# Transcrypt syntax errors
|
cp eos/__javascript__/eos.js_tests.js eosweb/core/static/js/eosjs.js
|
||||||
perl -0777 -pi -e 's/import \{, /import \{/g' __target__/eos*.js
|
perl -0777 -pi -e 's/eosjs_tests/eosjs/g' eosweb/core/static/js/eosjs.js
|
||||||
|
|
||||||
# Add export
|
|
||||||
echo >> __target__/eos.js_tests.js
|
|
||||||
echo 'export {eos, __kwargtrans__};' >> __target__/eos.js_tests.js
|
|
||||||
|
|
||||||
# Convert to ES5
|
|
||||||
./node_modules/.bin/browserify -t babelify -r ./__target__/eos.js_tests.js:eosjs > eosweb/core/static/js/eosjs.js
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -29,9 +29,6 @@ class NullEncryptedAnswer(EncryptedAnswer):
|
|||||||
|
|
||||||
def decrypt(self):
|
def decrypt(self):
|
||||||
return None, self.answer
|
return None, self.answer
|
||||||
|
|
||||||
def deaudit(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
class Ballot(EmbeddedObject):
|
class Ballot(EmbeddedObject):
|
||||||
#_id = UUIDField()
|
#_id = UUIDField()
|
||||||
@ -49,11 +46,8 @@ class Ballot(EmbeddedObject):
|
|||||||
|
|
||||||
return Ballot(encrypted_answers=encrypted_answers_deaudit, election_id=self.election_id, election_hash=self.election_hash)
|
return Ballot(encrypted_answers=encrypted_answers_deaudit, election_id=self.election_id, election_hash=self.election_hash)
|
||||||
|
|
||||||
class Vote(TopLevelObject):
|
class Vote(EmbeddedObject):
|
||||||
_ver = StringField(default='0.6')
|
_ver = StringField(default='0.5')
|
||||||
|
|
||||||
_id = UUIDField()
|
|
||||||
voter_id = UUIDField()
|
|
||||||
|
|
||||||
ballot = EmbeddedObjectField()
|
ballot = EmbeddedObjectField()
|
||||||
cast_at = DateTimeField()
|
cast_at = DateTimeField()
|
||||||
@ -63,10 +57,8 @@ class Vote(TopLevelObject):
|
|||||||
cast_fingerprint = BlobField(is_protected=True)
|
cast_fingerprint = BlobField(is_protected=True)
|
||||||
|
|
||||||
class Voter(EmbeddedObject):
|
class Voter(EmbeddedObject):
|
||||||
_ver = StringField(default='0.6')
|
|
||||||
|
|
||||||
_id = UUIDField()
|
_id = UUIDField()
|
||||||
votes = RelatedObjectListField(related_type=Vote, object_type=None, this_field='_id', related_field='voter_id')
|
votes = EmbeddedObjectListField()
|
||||||
|
|
||||||
class User(EmbeddedObject):
|
class User(EmbeddedObject):
|
||||||
admins = []
|
admins = []
|
||||||
@ -79,9 +71,6 @@ class User(EmbeddedObject):
|
|||||||
if admin.matched_by(self):
|
if admin.matched_by(self):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __getstate__(self):
|
|
||||||
return {k: v for k, v in self.__dict__.items() if k != '_instance'}
|
|
||||||
|
|
||||||
def generate_password():
|
def generate_password():
|
||||||
if is_python:
|
if is_python:
|
||||||
@ -101,6 +90,18 @@ class EmailUser(User):
|
|||||||
if not isinstance(other, EmailUser):
|
if not isinstance(other, EmailUser):
|
||||||
return False
|
return False
|
||||||
return self.email.lower() == other.email.lower() and self.password == other.password
|
return self.email.lower() == other.email.lower() and self.password == other.password
|
||||||
|
|
||||||
|
def send_email(self, host, port, username, password, from_email, content):
|
||||||
|
#__pragma__('skip')
|
||||||
|
import smtplib
|
||||||
|
#__pragma__('noskip')
|
||||||
|
with smtplib.SMTP(host, port) as smtp:
|
||||||
|
if username is not None:
|
||||||
|
smtp.login(username, password)
|
||||||
|
smtp.sendmail(from_email, [self.email], content)
|
||||||
|
|
||||||
|
def email_password(self, host, port, username, password, from_email):
|
||||||
|
self.send_email(host, port, username, password, from_email, 'Subject: Registered to vote in {1}\nFrom: {4}\nTo: {2}\n\nDear {0},\n\nYou are registered to vote in the election {1}. Your log in details are as follows:\n\nEmail: {2}\nPassword: {3}'.format(self.name, self.recurse_parents(Election).name, self.email, self.password, from_email))
|
||||||
|
|
||||||
class UserVoter(Voter):
|
class UserVoter(Voter):
|
||||||
user = EmbeddedObjectField()
|
user = EmbeddedObjectField()
|
||||||
@ -110,15 +111,14 @@ class UserVoter(Voter):
|
|||||||
return self.user.name
|
return self.user.name
|
||||||
|
|
||||||
class Question(EmbeddedObject):
|
class Question(EmbeddedObject):
|
||||||
_ver = StringField(default='0.7')
|
|
||||||
|
|
||||||
prompt = StringField()
|
prompt = StringField()
|
||||||
description = StringField()
|
|
||||||
|
|
||||||
class Result(EmbeddedObject):
|
class Result(EmbeddedObject):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class ListChoiceQuestion(Question):
|
class ListChoiceQuestion(Question):
|
||||||
|
_ver = StringField(default='0.5')
|
||||||
|
|
||||||
choices = EmbeddedObjectListField()
|
choices = EmbeddedObjectListField()
|
||||||
min_choices = IntField()
|
min_choices = IntField()
|
||||||
max_choices = IntField()
|
max_choices = IntField()
|
||||||
@ -214,8 +214,6 @@ class STVResult(Result):
|
|||||||
random = BlobField()
|
random = BlobField()
|
||||||
|
|
||||||
class Election(TopLevelObject):
|
class Election(TopLevelObject):
|
||||||
_ver = StringField(default='0.9')
|
|
||||||
|
|
||||||
_id = UUIDField()
|
_id = UUIDField()
|
||||||
workflow = EmbeddedObjectField(Workflow) # Once saved, we don't care what kind of workflow it is
|
workflow = EmbeddedObjectField(Workflow) # Once saved, we don't care what kind of workflow it is
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@ -224,13 +222,6 @@ class Election(TopLevelObject):
|
|||||||
questions = EmbeddedObjectListField()
|
questions = EmbeddedObjectListField()
|
||||||
results = EmbeddedObjectListField(is_hashed=False)
|
results = EmbeddedObjectListField(is_hashed=False)
|
||||||
|
|
||||||
is_voters_public = BooleanField(is_hashed=False, default=False)
|
|
||||||
is_votes_public = BooleanField(is_hashed=False, default=False)
|
|
||||||
|
|
||||||
def can_audit(self):
|
|
||||||
"""Can prepared votes be audited?"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def verify(self):
|
def verify(self):
|
||||||
#__pragma__('skip')
|
#__pragma__('skip')
|
||||||
from eos.core.hashing import SHA256
|
from eos.core.hashing import SHA256
|
||||||
@ -238,7 +229,7 @@ class Election(TopLevelObject):
|
|||||||
election_hash = SHA256().update_obj(self).hash_as_b64()
|
election_hash = SHA256().update_obj(self).hash_as_b64()
|
||||||
|
|
||||||
for voter in self.voters:
|
for voter in self.voters:
|
||||||
for vote in voter.votes.get_all():
|
for vote in voter.votes:
|
||||||
if vote.ballot.election_id != self._id:
|
if vote.ballot.election_id != self._id:
|
||||||
raise Exception('Invalid election ID on ballot')
|
raise Exception('Invalid election ID on ballot')
|
||||||
if vote.ballot.election_hash != election_hash:
|
if vote.ballot.election_hash != election_hash:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -26,25 +26,25 @@ class ElectionTestCase(EosTestCase):
|
|||||||
cls.db_connect_and_reset()
|
cls.db_connect_and_reset()
|
||||||
|
|
||||||
def do_task_assert(self, election, task, next_task):
|
def do_task_assert(self, election, task, next_task):
|
||||||
self.assertEqual(election.workflow.get_task(task).status, WorkflowTaskStatus.READY)
|
self.assertEqual(election.workflow.get_task(task).status, WorkflowTask.Status.READY)
|
||||||
if next_task is not None:
|
if next_task is not None:
|
||||||
self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTaskStatus.NOT_READY)
|
self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTask.Status.NOT_READY)
|
||||||
election.workflow.get_task(task).enter()
|
election.workflow.get_task(task).enter()
|
||||||
self.assertEqual(election.workflow.get_task(task).status, WorkflowTaskStatus.EXITED)
|
self.assertEqual(election.workflow.get_task(task).status, WorkflowTask.Status.EXITED)
|
||||||
if next_task is not None:
|
if next_task is not None:
|
||||||
self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTaskStatus.READY)
|
self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTask.Status.READY)
|
||||||
|
|
||||||
@py_only
|
@py_only
|
||||||
def test_run_election(self):
|
def test_run_election(self):
|
||||||
# Set up election
|
# Set up election
|
||||||
election = Election()
|
election = Election()
|
||||||
election.workflow = BaseWorkflow()
|
election.workflow = WorkflowBase()
|
||||||
|
|
||||||
# Check _instance
|
# Check _instance
|
||||||
self.assertEqual(election.workflow._instance, (election, 'workflow'))
|
self.assertEqual(election.workflow._instance, (election, 'workflow'))
|
||||||
|
|
||||||
# Check workflow behaviour
|
# Check workflow behaviour
|
||||||
self.assertEqual(election.workflow.get_task('eos.base.workflow.TaskConfigureElection').status, WorkflowTaskStatus.READY)
|
self.assertEqual(election.workflow.get_task('eos.base.workflow.TaskConfigureElection').status, WorkflowTask.Status.READY)
|
||||||
self.assertEqual(election.workflow.get_task('does.not.exist'), None)
|
self.assertEqual(election.workflow.get_task('does.not.exist'), None)
|
||||||
|
|
||||||
# Set election details
|
# Set election details
|
||||||
@ -95,10 +95,10 @@ class ElectionTestCase(EosTestCase):
|
|||||||
answer = ApprovalAnswer(choices=VOTES[i][j])
|
answer = ApprovalAnswer(choices=VOTES[i][j])
|
||||||
encrypted_answer = NullEncryptedAnswer(answer=answer)
|
encrypted_answer = NullEncryptedAnswer(answer=answer)
|
||||||
ballot.encrypted_answers.append(encrypted_answer)
|
ballot.encrypted_answers.append(encrypted_answer)
|
||||||
vote = Vote(voter_id=election.voters[i]._id, ballot=ballot, cast_at=DateTimeField.now())
|
vote = Vote(ballot=ballot, cast_at=DateTimeField.now())
|
||||||
vote.save()
|
election.voters[i].votes.append(vote)
|
||||||
|
|
||||||
#election.save()
|
election.save()
|
||||||
|
|
||||||
# Close voting
|
# Close voting
|
||||||
self.do_task_assert(election, 'eos.base.workflow.TaskCloseVoting', 'eos.base.workflow.TaskDecryptVotes')
|
self.do_task_assert(election, 'eos.base.workflow.TaskCloseVoting', 'eos.base.workflow.TaskDecryptVotes')
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# pyRCV - Preferential voting counting
|
# pyRCV - Preferential voting counting
|
||||||
# Copyright © 2016-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2016–2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -41,10 +41,10 @@ def writeBLT(election, q_num, seats, withdrawn=[]):
|
|||||||
|
|
||||||
for candidate in flat_choices:
|
for candidate in flat_choices:
|
||||||
if candidate.party:
|
if candidate.party:
|
||||||
electionLines.append('"{} – {}"'.format(candidate.name, candidate.party))
|
electionLines.append("'{} – {}'".format(candidate.name, candidate.party))
|
||||||
else:
|
else:
|
||||||
electionLines.append('"{}"'.format(candidate.name))
|
electionLines.append("'{}'".format(candidate.name))
|
||||||
|
|
||||||
electionLines.append('"{} – {}"'.format(election.name, question.prompt))
|
electionLines.append("'{} – {}'".format(election.name, question.prompt))
|
||||||
|
|
||||||
return electionLines
|
return electionLines
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -17,19 +17,19 @@
|
|||||||
from eos.core.objects import *
|
from eos.core.objects import *
|
||||||
from eos.core.tasks import *
|
from eos.core.tasks import *
|
||||||
|
|
||||||
class WorkflowTaskStatus(EosEnum):
|
|
||||||
UNKNOWN = 0
|
|
||||||
NOT_READY = 10
|
|
||||||
READY = 20
|
|
||||||
ENTERED = 30
|
|
||||||
#COMPLETE = 40
|
|
||||||
EXITED = 50
|
|
||||||
|
|
||||||
class WorkflowTask(EmbeddedObject):
|
class WorkflowTask(EmbeddedObject):
|
||||||
|
class Status:
|
||||||
|
UNKNOWN = 0
|
||||||
|
NOT_READY = 10
|
||||||
|
READY = 20
|
||||||
|
ENTERED = 30
|
||||||
|
#COMPLETE = 40
|
||||||
|
EXITED = 50
|
||||||
|
|
||||||
depends_on = []
|
depends_on = []
|
||||||
provides = []
|
provides = []
|
||||||
|
|
||||||
status = EnumField(WorkflowTaskStatus, is_hashed=False, default=WorkflowTaskStatus.UNKNOWN)
|
status = IntField(default=0, is_hashed=False)
|
||||||
exited_at = DateTimeField(is_hashed=False)
|
exited_at = DateTimeField(is_hashed=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -40,8 +40,8 @@ class WorkflowTask(EmbeddedObject):
|
|||||||
|
|
||||||
self.workflow = self.recurse_parents(Workflow)
|
self.workflow = self.recurse_parents(Workflow)
|
||||||
|
|
||||||
if self.status == WorkflowTaskStatus.UNKNOWN:
|
if self.status == WorkflowTask.Status.UNKNOWN:
|
||||||
self.status = WorkflowTaskStatus.READY if self.are_dependencies_met() else WorkflowTaskStatus.NOT_READY
|
self.status = WorkflowTask.Status.READY if self.are_dependencies_met() else WorkflowTask.Status.NOT_READY
|
||||||
|
|
||||||
self.listeners = {
|
self.listeners = {
|
||||||
'enter': [],
|
'enter': [],
|
||||||
@ -51,20 +51,15 @@ class WorkflowTask(EmbeddedObject):
|
|||||||
# Helpers
|
# Helpers
|
||||||
|
|
||||||
def on_dependency_exit():
|
def on_dependency_exit():
|
||||||
self.status = WorkflowTaskStatus.READY if self.are_dependencies_met() else WorkflowTaskStatus.NOT_READY
|
self.status = WorkflowTask.Status.READY if self.are_dependencies_met() else WorkflowTask.Status.NOT_READY
|
||||||
for depends_on_desc in self.depends_on:
|
for depends_on_desc in self.depends_on:
|
||||||
for depends_on_task in self.workflow.get_tasks(depends_on_desc):
|
for depends_on_task in self.workflow.get_tasks(depends_on_desc):
|
||||||
depends_on_task.listeners['exit'].append(on_dependency_exit)
|
depends_on_task.listeners['exit'].append(on_dependency_exit)
|
||||||
|
|
||||||
def are_dependencies_met(self):
|
def are_dependencies_met(self):
|
||||||
for depends_on_desc in self.depends_on:
|
for depends_on_desc in self.depends_on:
|
||||||
depends_on_tasks = list(self.workflow.get_tasks(depends_on_desc))
|
for depends_on_task in self.workflow.get_tasks(depends_on_desc):
|
||||||
|
if depends_on_task.status is not WorkflowTask.Status.EXITED:
|
||||||
if len(depends_on_tasks) == 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for depends_on_task in depends_on_tasks:
|
|
||||||
if depends_on_task.status is not WorkflowTaskStatus.EXITED:
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -76,10 +71,10 @@ class WorkflowTask(EmbeddedObject):
|
|||||||
self.exit()
|
self.exit()
|
||||||
|
|
||||||
def enter(self):
|
def enter(self):
|
||||||
if self.status is not WorkflowTaskStatus.READY:
|
if self.status is not WorkflowTask.Status.READY:
|
||||||
raise Exception('Attempted to enter a task when not ready')
|
raise Exception('Attempted to enter a task when not ready')
|
||||||
|
|
||||||
self.status = WorkflowTaskStatus.ENTERED
|
self.status = WorkflowTask.Status.ENTERED
|
||||||
self.fire_event('enter')
|
self.fire_event('enter')
|
||||||
self.on_enter()
|
self.on_enter()
|
||||||
|
|
||||||
@ -91,10 +86,10 @@ class WorkflowTask(EmbeddedObject):
|
|||||||
self.exited_at = DateTimeField.now()
|
self.exited_at = DateTimeField.now()
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
if self.status is not WorkflowTaskStatus.ENTERED:
|
if self.status is not WorkflowTask.Status.ENTERED:
|
||||||
raise Exception('Attempted to exit a task when not entered')
|
raise Exception('Attempted to exit a task when not entered')
|
||||||
|
|
||||||
self.status = WorkflowTaskStatus.EXITED
|
self.status = WorkflowTask.Status.EXITED
|
||||||
self.fire_event('exit')
|
self.fire_event('exit')
|
||||||
self.on_exit()
|
self.on_exit()
|
||||||
|
|
||||||
@ -151,7 +146,7 @@ class TaskConfigureElection(WorkflowTask):
|
|||||||
label = 'Freeze the election'
|
label = 'Freeze the election'
|
||||||
|
|
||||||
#def on_enter(self):
|
#def on_enter(self):
|
||||||
# self.status = WorkflowTaskStatus.COMPLETE
|
# self.status = WorkflowTask.Status.COMPLETE
|
||||||
|
|
||||||
class TaskOpenVoting(WorkflowTask):
|
class TaskOpenVoting(WorkflowTask):
|
||||||
label = 'Open voting'
|
label = 'Open voting'
|
||||||
@ -172,8 +167,8 @@ class TaskDecryptVotes(WorkflowTask):
|
|||||||
election.results.append(EosObject.lookup('eos.base.election.RawResult')())
|
election.results.append(EosObject.lookup('eos.base.election.RawResult')())
|
||||||
|
|
||||||
for voter in election.voters:
|
for voter in election.voters:
|
||||||
if len(voter.votes.get_all()) > 0:
|
if len(voter.votes) > 0:
|
||||||
vote = voter.votes.get_all()[-1]
|
vote = voter.votes[-1]
|
||||||
ballot = vote.ballot
|
ballot = vote.ballot
|
||||||
for q_num in range(len(ballot.encrypted_answers)):
|
for q_num in range(len(ballot.encrypted_answers)):
|
||||||
plaintexts, answer = ballot.encrypted_answers[q_num].decrypt()
|
plaintexts, answer = ballot.encrypted_answers[q_num].decrypt()
|
||||||
@ -189,9 +184,7 @@ class TaskReleaseResults(WorkflowTask):
|
|||||||
# Concrete workflows
|
# Concrete workflows
|
||||||
# ==================
|
# ==================
|
||||||
|
|
||||||
class BaseWorkflow(Workflow):
|
class WorkflowBase(Workflow):
|
||||||
"""Base workflow, with no encryption"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from eos.core.objects import *
|
from eos.core.objects import EosObject
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ class BigInt(EosObject):
|
|||||||
def nbits(self):
|
def nbits(self):
|
||||||
return self.impl.bitLength()
|
return self.impl.bitLength()
|
||||||
|
|
||||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
def serialise(self, for_hash=False, should_protect=False):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from eos.core.objects import *
|
from eos.core.objects import EosObject
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ class BigInt(EosObject):
|
|||||||
def nbits(self):
|
def nbits(self):
|
||||||
return math.ceil(math.log2(self.impl)) if self.impl > 0 else 0
|
return math.ceil(math.log2(self.impl)) if self.impl > 0 else 0
|
||||||
|
|
||||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
def serialise(self, for_hash=False, should_protect=False):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -27,18 +27,12 @@ class DBProvider:
|
|||||||
def get_all(self, collection):
|
def get_all(self, collection):
|
||||||
raise Exception('Not implemented')
|
raise Exception('Not implemented')
|
||||||
|
|
||||||
def get_all_by_fields(self, collection, fields):
|
|
||||||
raise Exception('Not implemented')
|
|
||||||
|
|
||||||
def get_by_id(self, collection, _id):
|
def get_by_id(self, collection, _id):
|
||||||
raise Exception('Not implemented')
|
raise Exception('Not implemented')
|
||||||
|
|
||||||
def update_by_id(self, collection, _id, value):
|
def update_by_id(self, collection, _id, value):
|
||||||
raise Exception('Not implemented')
|
raise Exception('Not implemented')
|
||||||
|
|
||||||
def delete_by_id(self, collection, _id):
|
|
||||||
raise Exception('Not implemented')
|
|
||||||
|
|
||||||
def reset_db(self):
|
def reset_db(self):
|
||||||
raise Exception('Not implemented')
|
raise Exception('Not implemented')
|
||||||
|
|
||||||
@ -49,18 +43,12 @@ class DummyProvider(DBProvider):
|
|||||||
def get_all(self, collection):
|
def get_all(self, collection):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_all_by_fields(self, collection, fields):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_by_id(self, collection, _id):
|
def get_by_id(self, collection, _id):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def update_by_id(self, collection, _id, value):
|
def update_by_id(self, collection, _id, value):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def delete_by_id(self, collection, _id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def reset_db(self):
|
def reset_db(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -26,25 +26,12 @@ class MongoDBProvider(eos.core.db.DBProvider):
|
|||||||
def get_all(self, collection):
|
def get_all(self, collection):
|
||||||
return self.db[collection].find()
|
return self.db[collection].find()
|
||||||
|
|
||||||
def get_all_by_fields(self, collection, fields):
|
|
||||||
query = {}
|
|
||||||
if '_id' in fields:
|
|
||||||
query['_id'] = fields.pop('_id')
|
|
||||||
if 'type' in fields:
|
|
||||||
query['type'] = fields.pop('type')
|
|
||||||
for field in fields:
|
|
||||||
query['value.' + field] = fields[field]
|
|
||||||
return self.db[collection].find(query)
|
|
||||||
|
|
||||||
def get_by_id(self, collection, _id):
|
def get_by_id(self, collection, _id):
|
||||||
return self.db[collection].find_one(_id)
|
return self.db[collection].find_one(_id)
|
||||||
|
|
||||||
def update_by_id(self, collection, _id, value):
|
def update_by_id(self, collection, _id, value):
|
||||||
self.db[collection].replace_one({'_id': _id}, value, upsert=True)
|
self.db[collection].replace_one({'_id': _id}, value, upsert=True)
|
||||||
|
|
||||||
def delete_by_id(self, collection, _id):
|
|
||||||
self.db[collection].delete_one({'_id': _id})
|
|
||||||
|
|
||||||
def reset_db(self):
|
def reset_db(self):
|
||||||
self.client.drop_database(self.db_name)
|
self.client.drop_database(self.db_name)
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -34,24 +34,6 @@ class PostgreSQLDBProvider(eos.core.db.DBProvider):
|
|||||||
self.cur.execute(SQL('SELECT data FROM {}').format(Identifier(table)))
|
self.cur.execute(SQL('SELECT data FROM {}').format(Identifier(table)))
|
||||||
return [x[0] for x in self.cur.fetchall()]
|
return [x[0] for x in self.cur.fetchall()]
|
||||||
|
|
||||||
def get_all_by_fields(self, table, fields):
|
|
||||||
def does_match(val):
|
|
||||||
if '_id' in fields and val['_id'] != fields.pop('_id'):
|
|
||||||
return False
|
|
||||||
if 'type' in fields and val['type'] != fields.pop('type'):
|
|
||||||
return False
|
|
||||||
for field in fields:
|
|
||||||
if val['value'][field] != fields[field]:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
# TODO: Make this much better
|
|
||||||
result = []
|
|
||||||
for val in self.get_all(table):
|
|
||||||
if does_match(val):
|
|
||||||
result.append(val)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_by_id(self, table, _id):
|
def get_by_id(self, table, _id):
|
||||||
self.create_table(table)
|
self.create_table(table)
|
||||||
self.cur.execute(SQL('SELECT data FROM {} WHERE _id = %s').format(Identifier(table)), (_id,))
|
self.cur.execute(SQL('SELECT data FROM {} WHERE _id = %s').format(Identifier(table)), (_id,))
|
||||||
@ -62,11 +44,6 @@ class PostgreSQLDBProvider(eos.core.db.DBProvider):
|
|||||||
self.cur.execute(SQL('INSERT INTO {} (_id, data) VALUES (%s, %s) ON CONFLICT (_id) DO UPDATE SET data = excluded.data').format(Identifier(table)), (_id, psycopg2.extras.Json(value)))
|
self.cur.execute(SQL('INSERT INTO {} (_id, data) VALUES (%s, %s) ON CONFLICT (_id) DO UPDATE SET data = excluded.data').format(Identifier(table)), (_id, psycopg2.extras.Json(value)))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
def delete_by_id(self, table, _id):
|
|
||||||
self.create_table(table)
|
|
||||||
self.cur.execute(SQL('DELETE FROM {} WHERE _id = %s').format(Identifier(table)), (_id))
|
|
||||||
self.conn.commit()
|
|
||||||
|
|
||||||
def reset_db(self):
|
def reset_db(self):
|
||||||
self.cur.execute('DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO postgres; GRANT ALL ON SCHEMA public TO public')
|
self.cur.execute('DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO postgres; GRANT ALL ON SCHEMA public TO public')
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -71,22 +71,19 @@ class SHA256:
|
|||||||
|
|
||||||
def update_obj(self, *values):
|
def update_obj(self, *values):
|
||||||
for value in values:
|
for value in values:
|
||||||
self.update_text(EosObject.to_json(EosObject.serialise_and_wrap(value, None, SerialiseOptions(for_hash=True))))
|
self.update_text(EosObject.to_json(EosObject.serialise_and_wrap(value, None, True)))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def update_obj_raw(self, *values):
|
def update_obj_raw(self, *values):
|
||||||
for value in values:
|
for value in values:
|
||||||
self.update_text(EosObject.to_json(EosObject.serialise_and_wrap(value, None, SerialiseOptions(for_hash=False))))
|
self.update_text(EosObject.to_json(EosObject.serialise_and_wrap(value, None, False)))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def hash_as_b64(self, short=False):
|
def hash_as_b64(self):
|
||||||
if is_python:
|
if is_python:
|
||||||
b64 = base64.b64encode(self.impl.digest()).decode('utf-8')
|
return base64.b64encode(self.impl.digest()).decode('utf-8')
|
||||||
else:
|
else:
|
||||||
b64 = self.impl.getHash('B64')
|
return self.impl.getHash('B64')
|
||||||
if short:
|
|
||||||
return b64[:10]
|
|
||||||
return b64
|
|
||||||
|
|
||||||
def hash_as_hex(self):
|
def hash_as_hex(self):
|
||||||
if is_python:
|
if is_python:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -74,31 +74,9 @@ class Field:
|
|||||||
self.default = kwargs['default'] if 'default' in kwargs else kwargs['py_default'] if 'py_default' in kwargs else None
|
self.default = kwargs['default'] if 'default' in kwargs else kwargs['py_default'] if 'py_default' in kwargs else None
|
||||||
self.is_protected = kwargs['is_protected'] if 'is_protected' in kwargs else False
|
self.is_protected = kwargs['is_protected'] if 'is_protected' in kwargs else False
|
||||||
self.is_hashed = kwargs['is_hashed'] if 'is_hashed' in kwargs else not self.is_protected
|
self.is_hashed = kwargs['is_hashed'] if 'is_hashed' in kwargs else not self.is_protected
|
||||||
|
|
||||||
def object_get(self, obj):
|
|
||||||
return obj._field_values[self.real_name]
|
|
||||||
|
|
||||||
def object_set(self, obj, value):
|
|
||||||
obj._field_values[self.real_name] = value
|
|
||||||
|
|
||||||
if isinstance(value, EosObject):
|
|
||||||
value._instance = (obj, self.real_name)
|
|
||||||
if not value._inited:
|
|
||||||
value.post_init()
|
|
||||||
|
|
||||||
def object_init(self, obj, value):
|
|
||||||
self.object_set(obj, value)
|
|
||||||
|
|
||||||
class SerialiseOptions:
|
|
||||||
def __init__(self, for_hash=False, should_protect=False, combine_related=False):
|
|
||||||
self.for_hash = for_hash
|
|
||||||
self.should_protect = should_protect
|
|
||||||
self.combine_related = combine_related
|
|
||||||
|
|
||||||
SerialiseOptions.DEFAULT = SerialiseOptions()
|
|
||||||
|
|
||||||
class PrimitiveField(Field):
|
class PrimitiveField(Field):
|
||||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
def serialise(self, value, for_hash=False, should_protect=False):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def deserialise(self, value):
|
def deserialise(self, value):
|
||||||
@ -115,8 +93,8 @@ class EmbeddedObjectField(Field):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.object_type = object_type
|
self.object_type = object_type
|
||||||
|
|
||||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
def serialise(self, value, for_hash=False, should_protect=False):
|
||||||
return EosObject.serialise_and_wrap(value, self.object_type, options)
|
return EosObject.serialise_and_wrap(value, self.object_type, for_hash, should_protect)
|
||||||
|
|
||||||
def deserialise(self, value):
|
def deserialise(self, value):
|
||||||
return EosObject.deserialise_and_unwrap(value, self.object_type)
|
return EosObject.deserialise_and_unwrap(value, self.object_type)
|
||||||
@ -126,8 +104,8 @@ class ListField(Field):
|
|||||||
super().__init__(default=EosList, *args, **kwargs)
|
super().__init__(default=EosList, *args, **kwargs)
|
||||||
self.element_field = element_field
|
self.element_field = element_field
|
||||||
|
|
||||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
def serialise(self, value, for_hash=False, should_protect=False):
|
||||||
return [self.element_field.serialise(x, options) for x in (value.impl if isinstance(value, EosList) else value)]
|
return [self.element_field.serialise(x, for_hash, should_protect) for x in (value.impl if isinstance(value, EosList) else value)]
|
||||||
|
|
||||||
def deserialise(self, value):
|
def deserialise(self, value):
|
||||||
return EosList([self.element_field.deserialise(x) for x in value])
|
return EosList([self.element_field.deserialise(x) for x in value])
|
||||||
@ -137,68 +115,29 @@ class EmbeddedObjectListField(Field):
|
|||||||
super().__init__(default=EosList, *args, **kwargs)
|
super().__init__(default=EosList, *args, **kwargs)
|
||||||
self.object_type = object_type
|
self.object_type = object_type
|
||||||
|
|
||||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
def serialise(self, value, for_hash=False, should_protect=False):
|
||||||
# TNYI: Doesn't know how to deal with iterators like EosList
|
# TNYI: Doesn't know how to deal with iterators like EosList
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return [EosObject.serialise_and_wrap(x, self.object_type, options) for x in (value.impl if isinstance(value, EosList) else value)]
|
return [EosObject.serialise_and_wrap(x, self.object_type, for_hash, should_protect) for x in (value.impl if isinstance(value, EosList) else value)]
|
||||||
|
|
||||||
def deserialise(self, value):
|
def deserialise(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return EosList([EosObject.deserialise_and_unwrap(x, self.object_type) for x in value])
|
return EosList([EosObject.deserialise_and_unwrap(x, self.object_type) for x in value])
|
||||||
|
|
||||||
class RelatedObjectListManager:
|
if is_python:
|
||||||
def __init__(self, field, obj):
|
class UUIDField(Field):
|
||||||
self.field = field
|
def __init__(self, *args, **kwargs):
|
||||||
self.obj = obj
|
|
||||||
|
|
||||||
def get_all(self):
|
|
||||||
query = {self.field.related_field: getattr(self.obj, self.field.this_field)}
|
|
||||||
return self.field.related_type.get_all_by_fields(**query)
|
|
||||||
|
|
||||||
class RelatedObjectListField(Field):
|
|
||||||
def __init__(self, object_type=None, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.related_type = kwargs['related_type']
|
|
||||||
self.object_type = kwargs['object_type'] if 'object_type' in kwargs else None
|
|
||||||
self.this_field = kwargs['this_field'] if 'this_field' in kwargs else '_id'
|
|
||||||
self.related_field = kwargs['related_field']
|
|
||||||
|
|
||||||
def object_get(self, obj):
|
|
||||||
return RelatedObjectListManager(self, obj)
|
|
||||||
|
|
||||||
def object_set(self, obj, value):
|
|
||||||
raise Exception('Cannot directly set related field')
|
|
||||||
|
|
||||||
def object_init(self, obj, value):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
|
||||||
if not options.combine_related:
|
|
||||||
return None
|
|
||||||
return EmbeddedObjectListField(object_type=self.object_type).serialise(value.get_all(), options)
|
|
||||||
|
|
||||||
def deserialise(self, value):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
return EosList([EosObject.deserialise_and_unwrap(x, self.object_type) for x in value])
|
|
||||||
|
|
||||||
class UUIDField(Field):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
if is_python:
|
|
||||||
super().__init__(default=uuid.uuid4, *args, **kwargs)
|
super().__init__(default=uuid.uuid4, *args, **kwargs)
|
||||||
else:
|
|
||||||
super().__init__(*args, **kwargs)
|
def serialise(self, value, for_hash=False, should_protect=False):
|
||||||
|
return str(value)
|
||||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
|
||||||
return str(value)
|
def deserialise(self, value):
|
||||||
|
|
||||||
def deserialise(self, value):
|
|
||||||
if is_python:
|
|
||||||
return uuid.UUID(value)
|
return uuid.UUID(value)
|
||||||
else:
|
else:
|
||||||
return value
|
UUIDField = PrimitiveField
|
||||||
|
|
||||||
class DateTimeField(Field):
|
class DateTimeField(Field):
|
||||||
def pad(self, number):
|
def pad(self, number):
|
||||||
@ -206,7 +145,7 @@ class DateTimeField(Field):
|
|||||||
return '0' + str(number)
|
return '0' + str(number)
|
||||||
return str(number)
|
return str(number)
|
||||||
|
|
||||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
def serialise(self, value, for_hash=False, should_protect=False):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -276,13 +215,12 @@ class EosObject(metaclass=EosObjectType):
|
|||||||
return EosObject.objects[name]
|
return EosObject.objects[name]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def serialise_and_wrap(value, object_type=None, options=SerialiseOptions.DEFAULT):
|
def serialise_and_wrap(value, object_type=None, for_hash=False, should_protect=False):
|
||||||
if object_type:
|
if object_type:
|
||||||
if value:
|
if value:
|
||||||
return value.serialise(options)
|
return value.serialise(for_hash, should_protect)
|
||||||
if value:
|
return None
|
||||||
return {'type': value._name, 'value': (value.serialise(options) if value else None)}
|
return {'type': value._name, 'value': (value.serialise(for_hash, should_protect) if value else None)}
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def deserialise_and_unwrap(value, object_type=None):
|
def deserialise_and_unwrap(value, object_type=None):
|
||||||
@ -362,16 +300,7 @@ class DocumentObjectType(EosObjectType):
|
|||||||
fields = {}
|
fields = {}
|
||||||
if hasattr(cls, '_fields'):
|
if hasattr(cls, '_fields'):
|
||||||
fields = cls._fields.copy() if is_python else Object.create(cls._fields)
|
fields = cls._fields.copy() if is_python else Object.create(cls._fields)
|
||||||
|
for attr in list(dir(cls)):
|
||||||
if is_python:
|
|
||||||
attrs = list(dir(cls))
|
|
||||||
else:
|
|
||||||
# We want the raw Javascript name for getOwnPropertyDescriptor
|
|
||||||
__pragma__('jsiter')
|
|
||||||
attrs = [x for x in cls]
|
|
||||||
__pragma__('nojsiter')
|
|
||||||
|
|
||||||
for attr in attrs:
|
|
||||||
if not is_python:
|
if not is_python:
|
||||||
# We must skip things with getters or else they will be called here (too soon)
|
# We must skip things with getters or else they will be called here (too soon)
|
||||||
if Object.getOwnPropertyDescriptor(cls, attr).js_get:
|
if Object.getOwnPropertyDescriptor(cls, attr).js_get:
|
||||||
@ -398,9 +327,14 @@ class DocumentObjectType(EosObjectType):
|
|||||||
if is_python:
|
if is_python:
|
||||||
def make_property(name, field):
|
def make_property(name, field):
|
||||||
def field_getter(self):
|
def field_getter(self):
|
||||||
return field.object_get(self)
|
return self._field_values[name]
|
||||||
def field_setter(self, value):
|
def field_setter(self, value):
|
||||||
field.object_set(self, value)
|
self._field_values[name] = value
|
||||||
|
|
||||||
|
if isinstance(value, EosObject):
|
||||||
|
value._instance = (self, name)
|
||||||
|
if not value._inited:
|
||||||
|
value.post_init()
|
||||||
return property(field_getter, field_setter)
|
return property(field_getter, field_setter)
|
||||||
|
|
||||||
for attr, val in fields.items():
|
for attr, val in fields.items():
|
||||||
@ -419,8 +353,6 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._json = None
|
|
||||||
|
|
||||||
self._field_values = {}
|
self._field_values = {}
|
||||||
|
|
||||||
# Different to Python
|
# Different to Python
|
||||||
@ -430,11 +362,15 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
def make_property(name, field):
|
def make_property(name, field):
|
||||||
# TNYI: Transcrypt doesn't pass self
|
|
||||||
def field_getter():
|
def field_getter():
|
||||||
return field.object_get(self)
|
return self._field_values[name]
|
||||||
def field_setter(value):
|
def field_setter(value):
|
||||||
field.object_set(self, value)
|
self._field_values[name] = value
|
||||||
|
|
||||||
|
if isinstance(value, EosObject):
|
||||||
|
value._instance = (self, name)
|
||||||
|
if not value._inited:
|
||||||
|
value.post_init()
|
||||||
return (field_getter, field_setter)
|
return (field_getter, field_setter)
|
||||||
prop = make_property(val.real_name, val)
|
prop = make_property(val.real_name, val)
|
||||||
# TNYI: No support for property()
|
# TNYI: No support for property()
|
||||||
@ -450,19 +386,15 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if val.internal_name in kwargs:
|
if val.internal_name in kwargs:
|
||||||
val.object_init(self, kwargs[val.internal_name])
|
setattr(self, val.real_name, kwargs[val.internal_name])
|
||||||
else:
|
else:
|
||||||
default = val.default
|
default = val.default
|
||||||
if default is not None and callable(default):
|
if default is not None and callable(default):
|
||||||
default = default()
|
default = default()
|
||||||
val.object_init(self, default)
|
setattr(self, val.real_name, default)
|
||||||
|
|
||||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
def serialise(self, for_hash=False, should_protect=False):
|
||||||
if self._ver != self._fields['_ver'].default:
|
return {val.real_name: val.serialise(getattr(self, val.real_name), for_hash, should_protect) for attr, val in self._fields.items() if ((val.is_hashed or not for_hash) and (not should_protect or not val.is_protected))}
|
||||||
# Different version, use stored JSON
|
|
||||||
return self._json
|
|
||||||
|
|
||||||
return {val.real_name: val.serialise(getattr(self, val.real_name), options) for attr, val in self._fields.items() if ((val.is_hashed or not options.for_hash) and (not options.should_protect or not val.is_protected))}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deserialise(cls, value):
|
def deserialise(cls, value):
|
||||||
@ -473,11 +405,7 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
for attr, val in cls._fields.items():
|
for attr, val in cls._fields.items():
|
||||||
if attr in value:
|
if attr in value:
|
||||||
attrs[val.internal_name] = val.deserialise(value[val.real_name])
|
attrs[val.internal_name] = val.deserialise(value[val.real_name])
|
||||||
inst = cls(**attrs)
|
return cls(**attrs)
|
||||||
|
|
||||||
inst._json = value
|
|
||||||
|
|
||||||
return inst
|
|
||||||
|
|
||||||
class TopLevelObjectType(DocumentObjectType):
|
class TopLevelObjectType(DocumentObjectType):
|
||||||
def __new__(meta, name, bases, attrs):
|
def __new__(meta, name, bases, attrs):
|
||||||
@ -499,26 +427,14 @@ class TopLevelObjectType(DocumentObjectType):
|
|||||||
|
|
||||||
class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType):
|
class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType):
|
||||||
def save(self):
|
def save(self):
|
||||||
if self._ver != self._fields['_ver'].default:
|
#res = db[self._name].replace_one({'_id': self.serialise()['_id']}, self.serialise(), upsert=True)
|
||||||
# Different version, unable to save
|
#res = dbinfo.db[self._db_name].replace_one({'_id': self._fields['_id'].serialise(self._id)}, EosObject.serialise_and_wrap(self), upsert=True)
|
||||||
raise Exception('Attempted to save older vesion object')
|
|
||||||
|
|
||||||
dbinfo.provider.update_by_id(self._db_name, self._fields['_id'].serialise(self._id), EosObject.serialise_and_wrap(self))
|
dbinfo.provider.update_by_id(self._db_name, self._fields['_id'].serialise(self._id), EosObject.serialise_and_wrap(self))
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
dbinfo.provider.delete_by_id(self._db_name, self._fields['_id'].serialise(self._id))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
def get_all(cls):
|
||||||
return [EosObject.deserialise_and_unwrap(x) for x in dbinfo.provider.get_all(cls._db_name)]
|
return [EosObject.deserialise_and_unwrap(x) for x in dbinfo.provider.get_all(cls._db_name)]
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_all_by_fields(cls, **fields):
|
|
||||||
for field in fields:
|
|
||||||
if not isinstance(fields[field], str):
|
|
||||||
fields[field] = str(fields[field])
|
|
||||||
return [EosObject.deserialise_and_unwrap(x) for x in dbinfo.provider.get_all_by_fields(cls._db_name, fields)]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_id(cls, _id):
|
def get_by_id(cls, _id):
|
||||||
if not isinstance(_id, str):
|
if not isinstance(_id, str):
|
||||||
@ -527,61 +443,3 @@ class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType):
|
|||||||
|
|
||||||
class EmbeddedObject(DocumentObject):
|
class EmbeddedObject(DocumentObject):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Enums
|
|
||||||
# =====
|
|
||||||
|
|
||||||
class EosEnumType(EosObjectType):
|
|
||||||
def __new__(meta, name, bases, attrs):
|
|
||||||
cls = EosObjectType.__new__(meta, name, bases, attrs)
|
|
||||||
|
|
||||||
cls._values = {}
|
|
||||||
|
|
||||||
for attr in list(dir(cls)):
|
|
||||||
val = getattr(cls, attr);
|
|
||||||
if isinstance(val, int):
|
|
||||||
instance = cls(attr, val)
|
|
||||||
setattr(cls, attr, instance)
|
|
||||||
cls._values[val] = instance
|
|
||||||
|
|
||||||
return cls
|
|
||||||
|
|
||||||
class EosEnum(EosObject, metaclass=EosEnumType):
|
|
||||||
def __init__(self, name, value):
|
|
||||||
super().__init__()
|
|
||||||
self.name = name
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
return False
|
|
||||||
return self.value == other.value
|
|
||||||
def __ne__(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
return True
|
|
||||||
return self.value != other.value
|
|
||||||
def __gt__(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
raise TypeError
|
|
||||||
return self.value > other.value
|
|
||||||
def __lt__(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
raise TypeError
|
|
||||||
return self.value < other.value
|
|
||||||
def __ge__(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
raise TypeError
|
|
||||||
return self.value >= other.value
|
|
||||||
def __le__(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
raise TypeError
|
|
||||||
return self.value <= other.value
|
|
||||||
|
|
||||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def deserialise(cls, value):
|
|
||||||
return cls._values[value]
|
|
||||||
|
|
||||||
EnumField = EmbeddedObjectField
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -16,34 +16,26 @@
|
|||||||
|
|
||||||
from eos.core.objects import *
|
from eos.core.objects import *
|
||||||
|
|
||||||
class TaskStatus(EosEnum):
|
|
||||||
UNKNOWN = 0
|
|
||||||
|
|
||||||
READY = 20
|
|
||||||
PROCESSING = 30
|
|
||||||
COMPLETE = 50
|
|
||||||
|
|
||||||
FAILED = -10
|
|
||||||
TIMEOUT = -20
|
|
||||||
|
|
||||||
def is_error(self):
|
|
||||||
return self.value < 0
|
|
||||||
|
|
||||||
class Task(TopLevelObject):
|
class Task(TopLevelObject):
|
||||||
label = 'Unknown task'
|
class Status:
|
||||||
_ver = StringField(default='0.8')
|
UNKNOWN = 0
|
||||||
|
|
||||||
|
READY = 20
|
||||||
|
PROCESSING = 30
|
||||||
|
COMPLETE = 50
|
||||||
|
|
||||||
|
FAILED = -10
|
||||||
|
TIMEOUT = -20
|
||||||
|
|
||||||
_id = UUIDField()
|
_id = UUIDField()
|
||||||
run_strategy = EmbeddedObjectField()
|
run_strategy = EmbeddedObjectField()
|
||||||
|
|
||||||
run_at = DateTimeField()
|
run_at = DateTimeField()
|
||||||
|
|
||||||
timeout = IntField(default=3600) # seconds
|
|
||||||
|
|
||||||
started_at = DateTimeField()
|
started_at = DateTimeField()
|
||||||
completed_at = DateTimeField()
|
completed_at = DateTimeField()
|
||||||
|
|
||||||
status = EnumField(TaskStatus, default=TaskStatus.UNKNOWN)
|
status = IntField(default=0)
|
||||||
messages = ListField(StringField())
|
messages = ListField(StringField())
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -51,12 +43,6 @@ class Task(TopLevelObject):
|
|||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def complete(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def error(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class DummyTask(Task):
|
class DummyTask(Task):
|
||||||
_db_name = Task._db_name
|
_db_name = Task._db_name
|
||||||
@ -80,12 +66,9 @@ class TaskScheduler:
|
|||||||
tasks = Task.get_all()
|
tasks = Task.get_all()
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if task.status == TaskStatus.READY:
|
if task.status == Task.Status.READY:
|
||||||
pending_tasks.append(task)
|
pending_tasks.append(task)
|
||||||
|
|
||||||
# Sort them to ensure we iterate over them in the correct order
|
|
||||||
pending_tasks.sort(key=lambda task: task.run_at.timestamp() if task.run_at else 0)
|
|
||||||
|
|
||||||
return pending_tasks
|
return pending_tasks
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -94,7 +77,7 @@ class TaskScheduler:
|
|||||||
tasks = Task.get_all()
|
tasks = Task.get_all()
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if task.status == TaskStatus.PROCESSING:
|
if task.status == Task.Status.PROCESSING:
|
||||||
active_tasks.append(task)
|
active_tasks.append(task)
|
||||||
|
|
||||||
return active_tasks
|
return active_tasks
|
||||||
@ -105,7 +88,7 @@ class TaskScheduler:
|
|||||||
tasks = Task.get_all()
|
tasks = Task.get_all()
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if task.status == TaskStatus.COMPLETE or task.status.is_error():
|
if task.status == Task.Status.COMPLETE or task.status < 0:
|
||||||
completed_tasks.append(task)
|
completed_tasks.append(task)
|
||||||
|
|
||||||
if limit:
|
if limit:
|
||||||
@ -116,16 +99,6 @@ class TaskScheduler:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def tick():
|
def tick():
|
||||||
now = DateTimeField.now()
|
|
||||||
|
|
||||||
for task in TaskScheduler.pending_tasks():
|
for task in TaskScheduler.pending_tasks():
|
||||||
if task.run_at and task.run_at < now:
|
if task.run_at and task.run_at < DateTimeField.now():
|
||||||
task.run()
|
task.run()
|
||||||
|
|
||||||
for task in TaskScheduler.active_tasks():
|
|
||||||
if task.timeout and (now - task.started_at).total_seconds() > task.timeout:
|
|
||||||
task.status = TaskStatus.TIMEOUT
|
|
||||||
task.completed_at = DateTimeField.now()
|
|
||||||
task.messages.append('Elapsed time exceeded timeout')
|
|
||||||
task.save()
|
|
||||||
task.error()
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,19 +19,17 @@ from eos.core.objects import *
|
|||||||
|
|
||||||
class DirectRunStrategy(RunStrategy):
|
class DirectRunStrategy(RunStrategy):
|
||||||
def run(self, task):
|
def run(self, task):
|
||||||
task.status = TaskStatus.PROCESSING
|
task.status = Task.Status.PROCESSING
|
||||||
task.started_at = DateTimeField.now()
|
task.started_at = DateTimeField.now()
|
||||||
task.save()
|
task.save()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
task._run()
|
task._run()
|
||||||
task.status = TaskStatus.COMPLETE
|
task.status = Task.Status.COMPLETE
|
||||||
task.completed_at = DateTimeField.now()
|
task.completed_at = DateTimeField.now()
|
||||||
task.save()
|
task.save()
|
||||||
|
|
||||||
task.complete()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
task.status = TaskStatus.FAILED
|
task.status = Task.Status.FAILED
|
||||||
task.completed_at = DateTimeField.now()
|
task.completed_at = DateTimeField.now()
|
||||||
if is_python:
|
if is_python:
|
||||||
#__pragma__('skip')
|
#__pragma__('skip')
|
||||||
@ -41,5 +39,3 @@ class DirectRunStrategy(RunStrategy):
|
|||||||
else:
|
else:
|
||||||
task.messages.append(repr(e))
|
task.messages.append(repr(e))
|
||||||
task.save()
|
task.save()
|
||||||
|
|
||||||
task.error()
|
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
# Eos - Verifiable elections
|
|
||||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from eos.core.tasks import *
|
|
||||||
from eos.core.objects import *
|
|
||||||
|
|
||||||
import threading
|
|
||||||
|
|
||||||
class ThreadingRunStrategy(RunStrategy):
|
|
||||||
def run(self, task):
|
|
||||||
def _run():
|
|
||||||
task.status = TaskStatus.PROCESSING
|
|
||||||
task.started_at = DateTimeField.now()
|
|
||||||
task.save()
|
|
||||||
|
|
||||||
try:
|
|
||||||
task._run()
|
|
||||||
task.status = TaskStatus.COMPLETE
|
|
||||||
task.completed_at = DateTimeField.now()
|
|
||||||
task.save()
|
|
||||||
|
|
||||||
task.complete()
|
|
||||||
except Exception as e:
|
|
||||||
task.status = TaskStatus.FAILED
|
|
||||||
task.completed_at = DateTimeField.now()
|
|
||||||
if is_python:
|
|
||||||
#__pragma__('skip')
|
|
||||||
import traceback
|
|
||||||
#__pragma__('noskip')
|
|
||||||
task.messages.append(traceback.format_exc())
|
|
||||||
else:
|
|
||||||
task.messages.append(repr(e))
|
|
||||||
task.save()
|
|
||||||
|
|
||||||
task.error()
|
|
||||||
|
|
||||||
thread = threading.Thread(target=_run, args=())
|
|
||||||
thread.start()
|
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -133,7 +133,6 @@ class TaskTestCase(EosTestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.db_connect_and_reset()
|
cls.db_connect_and_reset()
|
||||||
|
|
||||||
@py_only
|
|
||||||
def test_normal(self):
|
def test_normal(self):
|
||||||
class TaskNormal(Task):
|
class TaskNormal(Task):
|
||||||
result = StringField()
|
result = StringField()
|
||||||
@ -145,12 +144,11 @@ class TaskTestCase(EosTestCase):
|
|||||||
task.save()
|
task.save()
|
||||||
task.run()
|
task.run()
|
||||||
|
|
||||||
self.assertEqual(task.status, TaskStatus.COMPLETE)
|
self.assertEqual(task.status, Task.Status.COMPLETE)
|
||||||
self.assertEqual(len(task.messages), 1)
|
self.assertEqual(len(task.messages), 1)
|
||||||
self.assertEqual(task.messages[0], 'Hello World')
|
self.assertEqual(task.messages[0], 'Hello World')
|
||||||
self.assertEqual(task.result, 'Success')
|
self.assertEqual(task.result, 'Success')
|
||||||
|
|
||||||
@py_only
|
|
||||||
def test_error(self):
|
def test_error(self):
|
||||||
class TaskError(Task):
|
class TaskError(Task):
|
||||||
def _run(self):
|
def _run(self):
|
||||||
@ -160,6 +158,6 @@ class TaskTestCase(EosTestCase):
|
|||||||
task.save()
|
task.save()
|
||||||
task.run()
|
task.run()
|
||||||
|
|
||||||
self.assertEqual(task.status, TaskStatus.FAILED)
|
self.assertEqual(task.status, Task.Status.FAILED)
|
||||||
self.assertEqual(len(task.messages), 1)
|
self.assertEqual(len(task.messages), 1)
|
||||||
self.assertTrue('Test exception' in task.messages[0])
|
self.assertTrue('Test exception' in task.messages[0])
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -14,23 +14,8 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import eos.core.objects
|
import eos.js
|
||||||
import eos.core.bigint
|
|
||||||
import eos.core.hashing
|
|
||||||
import eos.core.tests
|
import eos.core.tests
|
||||||
import eos.core.tasks
|
|
||||||
import eos.core.tasks.direct
|
|
||||||
|
|
||||||
import eos.base.election
|
|
||||||
import eos.base.workflow
|
|
||||||
|
|
||||||
import eos.psr.bitstream
|
|
||||||
import eos.psr.crypto
|
|
||||||
import eos.psr.election
|
|
||||||
import eos.psr.mixnet
|
|
||||||
import eos.psr.workflow
|
|
||||||
|
|
||||||
import eos.redditauth.election
|
|
||||||
|
|
||||||
import eos.base.tests
|
import eos.base.tests
|
||||||
import eos.psr.tests
|
import eos.psr.tests
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
# Eos - Verifiable elections
|
|
||||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from eos.base.election import *
|
|
||||||
from eos.core.objects import *
|
|
||||||
|
|
||||||
class NationStatesUser(User):
|
|
||||||
username = StringField()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.username
|
|
||||||
|
|
||||||
def matched_by(self, other):
|
|
||||||
if not isinstance(other, NationStatesUser):
|
|
||||||
return False
|
|
||||||
return other.username.lower().strip().replace(' ', '_') == self.username.lower().strip().replace(' ', '_')
|
|
@ -153,7 +153,7 @@ class BitStream(EosObject):
|
|||||||
bs.seek(0)
|
bs.seek(0)
|
||||||
return bs
|
return bs
|
||||||
|
|
||||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
def serialise(self):
|
||||||
return self.impl
|
return self.impl
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -173,7 +173,7 @@ class InfiniteHashBitStream(BitStream):
|
|||||||
# 11000110110
|
# 11000110110
|
||||||
# ^----
|
# ^----
|
||||||
if nbits is None:
|
if nbits is None:
|
||||||
raise Exception('Cannot read indefinite amount from InfiniteHashBitStream')
|
nbits = self.remaining
|
||||||
while nbits > self.remaining:
|
while nbits > self.remaining:
|
||||||
self.ctr += 1
|
self.ctr += 1
|
||||||
self.sha.update_text(str(self.ctr))
|
self.sha.update_text(str(self.ctr))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -144,7 +144,7 @@ class EGPrivateKey(EmbeddedObject):
|
|||||||
result.commitmentA = pow(self.public_key.group.g, w, self.public_key.group.p)
|
result.commitmentA = pow(self.public_key.group.g, w, self.public_key.group.p)
|
||||||
result.commitmentB = pow(ciphertext.gamma, w, self.public_key.group.p)
|
result.commitmentB = pow(ciphertext.gamma, w, self.public_key.group.p)
|
||||||
|
|
||||||
result.challenge = SHA256().update_obj(ciphertext).update_obj(result.commitmentA).update_obj(result.commitmentB).update_obj(result.message).hash_as_bigint()
|
result.challenge = SHA256().update_obj(ciphertext).update_obj(result.commitmentA).update_obj(result.commitmentB).hash_as_bigint()
|
||||||
|
|
||||||
result.response = w + self.x * result.challenge
|
result.response = w + self.x * result.challenge
|
||||||
|
|
||||||
@ -175,8 +175,6 @@ class EGCiphertext(EmbeddedObject):
|
|||||||
return ct.gamma == self.gamma and ct.delta == self.delta
|
return ct.gamma == self.gamma and ct.delta == self.delta
|
||||||
|
|
||||||
class EGProvedPlaintext(EmbeddedObject):
|
class EGProvedPlaintext(EmbeddedObject):
|
||||||
_ver = StringField(default='0.6')
|
|
||||||
|
|
||||||
message = EmbeddedObjectField(BigInt)
|
message = EmbeddedObjectField(BigInt)
|
||||||
|
|
||||||
ciphertext = EmbeddedObjectField()
|
ciphertext = EmbeddedObjectField()
|
||||||
@ -259,8 +257,7 @@ class PedersenVSSPrivateKey(EmbeddedObject):
|
|||||||
def get_modified_secret(self):
|
def get_modified_secret(self):
|
||||||
mod_s = self.x
|
mod_s = self.x
|
||||||
for j in range(1, threshold + 1): # 1 to threshold
|
for j in range(1, threshold + 1): # 1 to threshold
|
||||||
# TODO
|
...
|
||||||
pass
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
if (
|
if (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -96,8 +96,8 @@ class MixingTrustee(Trustee):
|
|||||||
# Use the raw ballots from voters
|
# Use the raw ballots from voters
|
||||||
orig_answers = []
|
orig_answers = []
|
||||||
for voter in self.recurse_parents(Election).voters:
|
for voter in self.recurse_parents(Election).voters:
|
||||||
if len(voter.votes.get_all()) > 0:
|
if len(voter.votes) > 0:
|
||||||
vote = voter.votes.get_all()[-1]
|
vote = voter.votes[-1]
|
||||||
ballot = vote.ballot
|
ballot = vote.ballot
|
||||||
orig_answers.append(ballot.encrypted_answers[question_num])
|
orig_answers.append(ballot.encrypted_answers[question_num])
|
||||||
return orig_answers
|
return orig_answers
|
||||||
@ -195,8 +195,8 @@ class InternalMixingTrustee(MixingTrustee):
|
|||||||
else:
|
else:
|
||||||
orig_answers = []
|
orig_answers = []
|
||||||
for voter in election.voters:
|
for voter in election.voters:
|
||||||
if len(voter.votes.get_all()) > 0:
|
if len(voter.votes) > 0:
|
||||||
ballot = voter.votes.get_all()[-1].ballot
|
ballot = voter.votes[-1].ballot
|
||||||
orig_answers.append(ballot.encrypted_answers[question])
|
orig_answers.append(ballot.encrypted_answers[question])
|
||||||
shuffled_answers, commitments = self.mixnets[question].shuffle(orig_answers)
|
shuffled_answers, commitments = self.mixnets[question].shuffle(orig_answers)
|
||||||
self.mixed_questions.append(EosList(shuffled_answers))
|
self.mixed_questions.append(EosList(shuffled_answers))
|
||||||
@ -225,20 +225,12 @@ class InternalMixingTrustee(MixingTrustee):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
class PSRElection(Election):
|
class PSRElection(Election):
|
||||||
is_voters_public = BooleanField(is_hashed=False, default=True)
|
|
||||||
is_votes_public = BooleanField(is_hashed=False, default=True)
|
|
||||||
|
|
||||||
sk = EmbeddedObjectField(SEGPrivateKey, is_protected=True) # TODO: Threshold
|
sk = EmbeddedObjectField(SEGPrivateKey, is_protected=True) # TODO: Threshold
|
||||||
|
|
||||||
public_key = EmbeddedObjectField(SEGPublicKey)
|
public_key = EmbeddedObjectField(SEGPublicKey)
|
||||||
mixing_trustees = EmbeddedObjectListField()
|
mixing_trustees = EmbeddedObjectListField()
|
||||||
|
|
||||||
def can_audit(self):
|
|
||||||
"""Overrides Election.can_audit"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def verify(self):
|
def verify(self):
|
||||||
"""Overrides Election.verify"""
|
|
||||||
# Verify ballots
|
# Verify ballots
|
||||||
super().verify()
|
super().verify()
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -224,13 +224,13 @@ class ElectionTestCase(EosTestCase):
|
|||||||
cls.db_connect_and_reset()
|
cls.db_connect_and_reset()
|
||||||
|
|
||||||
def do_task_assert(self, election, task, next_task):
|
def do_task_assert(self, election, task, next_task):
|
||||||
self.assertEqual(election.workflow.get_task(task).status, WorkflowTaskStatus.READY)
|
self.assertEqual(election.workflow.get_task(task).status, WorkflowTask.Status.READY)
|
||||||
if next_task is not None:
|
if next_task is not None:
|
||||||
self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTaskStatus.NOT_READY)
|
self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTask.Status.NOT_READY)
|
||||||
election.workflow.get_task(task).enter()
|
election.workflow.get_task(task).enter()
|
||||||
self.assertEqual(election.workflow.get_task(task).status, WorkflowTaskStatus.EXITED)
|
self.assertEqual(election.workflow.get_task(task).status, WorkflowTask.Status.EXITED)
|
||||||
if next_task is not None:
|
if next_task is not None:
|
||||||
self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTaskStatus.READY)
|
self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTask.Status.READY)
|
||||||
|
|
||||||
@py_only
|
@py_only
|
||||||
def test_run_election(self):
|
def test_run_election(self):
|
||||||
@ -278,10 +278,10 @@ class ElectionTestCase(EosTestCase):
|
|||||||
answer = ApprovalAnswer(choices=VOTES[i][j])
|
answer = ApprovalAnswer(choices=VOTES[i][j])
|
||||||
encrypted_answer = BlockEncryptedAnswer.encrypt(election.sk.public_key, answer)
|
encrypted_answer = BlockEncryptedAnswer.encrypt(election.sk.public_key, answer)
|
||||||
ballot.encrypted_answers.append(encrypted_answer)
|
ballot.encrypted_answers.append(encrypted_answer)
|
||||||
vote = Vote(voter_id=election.voters[i]._id, ballot=ballot, cast_at=DateTimeField.now())
|
vote = Vote(ballot=ballot, cast_at=DateTimeField.now())
|
||||||
vote.save()
|
election.voters[i].votes.append(vote)
|
||||||
|
|
||||||
#election.save()
|
election.save()
|
||||||
|
|
||||||
# Close voting
|
# Close voting
|
||||||
self.do_task_assert(election, 'eos.base.workflow.TaskCloseVoting', 'eos.psr.workflow.TaskMixVotes')
|
self.do_task_assert(election, 'eos.base.workflow.TaskCloseVoting', 'eos.psr.workflow.TaskMixVotes')
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -43,9 +43,9 @@ class BasePyTestCase(TestCase):
|
|||||||
class BaseJSTestCase(TestCase):
|
class BaseJSTestCase(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
with open('eosweb/core/static/js/eosjs.js', 'r') as f:
|
with open('eos/__javascript__/eos.js_tests.js', 'r') as f:
|
||||||
code = f.read()
|
code = f.read()
|
||||||
cls.ctx = execjs.get().compile('var window={},navigator={};' + code + 'var eosjs=require("eosjs");var test=eosjs.' + cls.module + '.' + cls.name + '();test.setUpClass();')
|
cls.ctx = execjs.get().compile('var window={},navigator={};' + code + 'var test=window.eosjs_tests.' + cls.module + '.__all__.' + cls.name + '();test.setUpClass();')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_method(cls, method):
|
def add_method(cls, method):
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
"semantic": "semantic-ui#^2.2.13",
|
"semantic": "semantic-ui#^2.2.13",
|
||||||
"nunjucks": "^3.0.1",
|
"nunjucks": "^3.0.1",
|
||||||
"dragula.js": "dragula#^3.7.2",
|
"dragula.js": "dragula#^3.7.2",
|
||||||
"fingerprintjs2": "^1.5.1",
|
"fingerprintjs2": "^1.5.1"
|
||||||
"progress-tracker": "^1.4.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
# Eos - Verifiable elections
|
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import premailer
|
|
||||||
import sass
|
|
||||||
|
|
||||||
import flask
|
|
||||||
import flask_mail
|
|
||||||
|
|
||||||
from eos.base.election import *
|
|
||||||
|
|
||||||
def send_email(title, html_text, body_text, recipients):
|
|
||||||
# Prepare email
|
|
||||||
css = sass.compile(string=flask.render_template('email/base.scss'))
|
|
||||||
html = flask.render_template(
|
|
||||||
'email/base.html',
|
|
||||||
title=title,
|
|
||||||
css=css,
|
|
||||||
text=html_text
|
|
||||||
)
|
|
||||||
html = premailer.Premailer(html, strip_important=False).transform()
|
|
||||||
|
|
||||||
body = flask.render_template(
|
|
||||||
'email/base.txt',
|
|
||||||
title=title,
|
|
||||||
text=body_text
|
|
||||||
)
|
|
||||||
|
|
||||||
# Send email
|
|
||||||
mail = flask_mail.Mail(flask.current_app)
|
|
||||||
msg = flask_mail.Message(
|
|
||||||
title,
|
|
||||||
recipients=recipients,
|
|
||||||
body=body,
|
|
||||||
html=html
|
|
||||||
)
|
|
||||||
mail.send(msg)
|
|
||||||
|
|
||||||
def voter_email_password(election, voter):
|
|
||||||
send_email(
|
|
||||||
'Registered to vote: {}'.format(election.name),
|
|
||||||
'<p>Dear {},</p><p>You are registered to vote in <i>{}</i>.</p><p>Your login details are as follows:</p><p>Email: <code>{}</code></p><p>Password: <code>{}</code></p>'.format(voter.name, election.name, voter.user.email, voter.user.password),
|
|
||||||
'Dear {},\n\nYou are registered to vote in "{}".\n\nYour login details are as follows:\n\nEmail: {}\nPassword: {}'.format(voter.name, election.name, voter.user.email, voter.user.password),
|
|
||||||
[voter.user.email]
|
|
||||||
)
|
|
||||||
|
|
||||||
def task_email_failure(task):
|
|
||||||
send_email(
|
|
||||||
'Task failed: {}'.format(task.label),
|
|
||||||
'<p>The task <i>{}</i> failed execution. The output was:</p><pre>{}</pre>'.format(task.label, '\n'.join(task.messages)),
|
|
||||||
'The task "{}" failed execution. The output was:\n\n{}'.format(task.label, '\n'.join(task.messages)),
|
|
||||||
[admin.email for admin in flask.current_app.config['ADMINS'] if isinstance(admin, EmailUser)]
|
|
||||||
)
|
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
import flask
|
import flask
|
||||||
import flask_session
|
|
||||||
import timeago
|
import timeago
|
||||||
|
|
||||||
from eos.core.objects import *
|
from eos.core.objects import *
|
||||||
@ -29,10 +28,6 @@ from eos.psr.election import *
|
|||||||
from eos.psr.mixnet import *
|
from eos.psr.mixnet import *
|
||||||
from eos.psr.workflow import *
|
from eos.psr.workflow import *
|
||||||
|
|
||||||
from eosweb.core.tasks import *
|
|
||||||
|
|
||||||
from . import emails
|
|
||||||
|
|
||||||
import eos.core.hashing
|
import eos.core.hashing
|
||||||
import eosweb
|
import eosweb
|
||||||
|
|
||||||
@ -45,7 +40,6 @@ import json
|
|||||||
import os
|
import os
|
||||||
import pytz
|
import pytz
|
||||||
import subprocess
|
import subprocess
|
||||||
import uuid
|
|
||||||
|
|
||||||
app = flask.Flask(__name__, static_folder=None)
|
app = flask.Flask(__name__, static_folder=None)
|
||||||
|
|
||||||
@ -63,16 +57,6 @@ if 'EOSWEB_SETTINGS' in os.environ:
|
|||||||
# Connect to database
|
# Connect to database
|
||||||
db_connect(app.config['DB_NAME'], app.config['DB_URI'], app.config['DB_TYPE'])
|
db_connect(app.config['DB_NAME'], app.config['DB_URI'], app.config['DB_TYPE'])
|
||||||
|
|
||||||
# Configure sessions
|
|
||||||
if app.config['DB_TYPE'] == 'mongodb':
|
|
||||||
app.config['SESSION_TYPE'] = 'mongodb'
|
|
||||||
app.config['SESSION_MONGODB'] = dbinfo.provider.client
|
|
||||||
app.config['SESSION_MONGODB_DB'] = dbinfo.provider.db_name
|
|
||||||
elif app.config['DB_TYPE'] == 'postgresql':
|
|
||||||
app.config['SESSION_TYPE'] = 'sqlalchemy'
|
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = app.config['DB_URI'] + app.config['DB_NAME']
|
|
||||||
flask_session.Session(app)
|
|
||||||
|
|
||||||
# Set configs
|
# Set configs
|
||||||
User.admins = app.config['ADMINS']
|
User.admins = app.config['ADMINS']
|
||||||
|
|
||||||
@ -115,10 +99,49 @@ def run_tests(prefix, lang):
|
|||||||
import eos.tests
|
import eos.tests
|
||||||
eos.tests.run_tests(prefix, lang)
|
eos.tests.run_tests(prefix, lang)
|
||||||
|
|
||||||
# Create the session databases (SQL only)
|
# TODO: Will remove this once we have a web UI
|
||||||
@app.cli.command('sessdb')
|
@app.cli.command('drop_db_and_setup')
|
||||||
def sessdb():
|
def setup_test_election():
|
||||||
app.session_interface.db.create_all()
|
# DANGER!
|
||||||
|
dbinfo.provider.reset_db()
|
||||||
|
|
||||||
|
# Set up election
|
||||||
|
election = PSRElection()
|
||||||
|
election.workflow = PSRWorkflow()
|
||||||
|
|
||||||
|
# Set election details
|
||||||
|
election.name = 'Test Election'
|
||||||
|
|
||||||
|
from eos.redditauth.election import RedditUser
|
||||||
|
election.voters.append(UserVoter(user=EmailUser(name='Alice', email='alice@localhost')))
|
||||||
|
election.voters.append(UserVoter(user=EmailUser(name='Bob', email='bob@localhost')))
|
||||||
|
election.voters.append(UserVoter(user=EmailUser(name='Carol', email='carol@localhost')))
|
||||||
|
election.voters.append(UserVoter(user=RedditUser(username='RunasSudo')))
|
||||||
|
|
||||||
|
for voter in election.voters:
|
||||||
|
if isinstance(voter, UserVoter):
|
||||||
|
if isinstance(voter.user, EmailUser):
|
||||||
|
voter.user.email_password(app.config['SMTP_HOST'], app.config['SMTP_PORT'], app.config['SMTP_USER'], app.config['SMTP_PASS'], app.config['SMTP_FROM'])
|
||||||
|
|
||||||
|
election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting'))
|
||||||
|
election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting'))
|
||||||
|
|
||||||
|
election.sk = EGPrivateKey.generate()
|
||||||
|
election.public_key = election.sk.public_key
|
||||||
|
|
||||||
|
question = PreferentialQuestion(prompt='President', choices=[
|
||||||
|
Ticket(name='ACME Party', choices=[
|
||||||
|
Choice(name='John Smith'),
|
||||||
|
Choice(name='Joe Bloggs', party='Independent ACME')
|
||||||
|
]),
|
||||||
|
Choice(name='John Q. Public')
|
||||||
|
], min_choices=0, max_choices=3, randomise_choices=True)
|
||||||
|
election.questions.append(question)
|
||||||
|
|
||||||
|
question = ApprovalQuestion(prompt='Chairman', choices=[Choice(name='John Doe'), Choice(name='Andrew Citizen')], min_choices=0, max_choices=1)
|
||||||
|
election.questions.append(question)
|
||||||
|
|
||||||
|
election.save()
|
||||||
|
|
||||||
@app.cli.command('verify_election')
|
@app.cli.command('verify_election')
|
||||||
@click.option('--electionid', default=None)
|
@click.option('--electionid', default=None)
|
||||||
@ -135,8 +158,7 @@ def verify_election(electionid):
|
|||||||
@click.option('--electionid', default=None)
|
@click.option('--electionid', default=None)
|
||||||
@click.option('--qnum', default=0)
|
@click.option('--qnum', default=0)
|
||||||
@click.option('--randfile', default=None)
|
@click.option('--randfile', default=None)
|
||||||
@click.option('--seats', default=1)
|
def tally_stv_election(electionid, qnum, randfile):
|
||||||
def tally_stv_election(electionid, qnum, randfile, numseats):
|
|
||||||
election = Election.get_by_id(electionid)
|
election = Election.get_by_id(electionid)
|
||||||
|
|
||||||
with open(randfile, 'r') as f:
|
with open(randfile, 'r') as f:
|
||||||
@ -145,22 +167,8 @@ def tally_stv_election(electionid, qnum, randfile, numseats):
|
|||||||
election_id=election._id,
|
election_id=election._id,
|
||||||
q_num=qnum,
|
q_num=qnum,
|
||||||
random=dat,
|
random=dat,
|
||||||
num_seats=numseats,
|
num_seats=7,
|
||||||
status=TaskStatus.READY,
|
status=Task.Status.READY,
|
||||||
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
|
||||||
)
|
|
||||||
task.save()
|
|
||||||
task.run()
|
|
||||||
|
|
||||||
@app.cli.command('run_task')
|
|
||||||
@click.option('--electionid', default=None)
|
|
||||||
@click.option('--task_name', default=None)
|
|
||||||
def tally_stv_election(electionid, task_name):
|
|
||||||
election = Election.get_by_id(electionid)
|
|
||||||
task = WorkflowTaskEntryWebTask(
|
|
||||||
election_id=election._id,
|
|
||||||
workflow_task=task_name,
|
|
||||||
status=TaskStatus.READY,
|
|
||||||
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
||||||
)
|
)
|
||||||
task.save()
|
task.save()
|
||||||
@ -183,6 +191,10 @@ def tick_scheduler():
|
|||||||
|
|
||||||
# === Views ===
|
# === Views ===
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return flask.render_template('index.html')
|
||||||
|
|
||||||
def using_election(func):
|
def using_election(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapped(election_id, **kwargs):
|
def wrapped(election_id, **kwargs):
|
||||||
@ -192,83 +204,17 @@ def using_election(func):
|
|||||||
|
|
||||||
def election_admin(func):
|
def election_admin(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapped(*args, **kwargs):
|
def wrapped(election, **kwargs):
|
||||||
if 'user' in flask.session and flask.session['user'].is_admin():
|
if 'user' in flask.session and flask.session['user'].is_admin():
|
||||||
return func(*args, **kwargs)
|
return func(election, **kwargs)
|
||||||
else:
|
else:
|
||||||
return flask.Response('Administrator credentials required', 403)
|
return flask.Response('Administrator credentials required', 403)
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
elections = Election.get_all()
|
|
||||||
elections.sort(key=lambda e: e.name)
|
|
||||||
|
|
||||||
elections_open = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskCloseVoting').status == WorkflowTaskStatus.READY]
|
|
||||||
|
|
||||||
elections_soon = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskOpenVoting').status != WorkflowTaskStatus.EXITED and e.workflow.get_task('eos.base.workflow.TaskOpenVoting').get_entry_task()]
|
|
||||||
elections_soon.sort(key=lambda e: e.workflow.get_task('eos.base.workflow.TaskOpenVoting').get_entry_task().run_at)
|
|
||||||
|
|
||||||
elections_closed = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskCloseVoting').status == WorkflowTaskStatus.EXITED]
|
|
||||||
elections_closed.sort(key=lambda e: e.workflow.get_task('eos.base.workflow.TaskCloseVoting').exited_at, reverse=True)
|
|
||||||
elections_closed = elections_closed[:5]
|
|
||||||
|
|
||||||
return flask.render_template('index.html', elections_open=elections_open, elections_soon=elections_soon, elections_closed=elections_closed)
|
|
||||||
|
|
||||||
@app.route('/elections')
|
|
||||||
def elections():
|
|
||||||
elections = Election.get_all()
|
|
||||||
elections.sort(key=lambda e: e.name)
|
|
||||||
|
|
||||||
return flask.render_template('elections.html', elections=elections)
|
|
||||||
|
|
||||||
@app.route('/elections/batch', methods=['GET', 'POST'])
|
|
||||||
@election_admin
|
|
||||||
def elections_batch():
|
|
||||||
if flask.request.method == 'POST':
|
|
||||||
# Execute
|
|
||||||
for k, v in flask.request.form.items():
|
|
||||||
if k.startswith('election_') and v:
|
|
||||||
election_id = k[9:]
|
|
||||||
election = Election.get_by_id(election_id)
|
|
||||||
for workflow_task in election.workflow.tasks:
|
|
||||||
if workflow_task.status == eos.base.workflow.WorkflowTaskStatus.READY:
|
|
||||||
task = WorkflowTaskEntryWebTask(
|
|
||||||
election_id=election._id,
|
|
||||||
workflow_task=workflow_task._name,
|
|
||||||
status=TaskStatus.READY,
|
|
||||||
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
|
||||||
)
|
|
||||||
task.run()
|
|
||||||
break
|
|
||||||
|
|
||||||
elections = []
|
|
||||||
for election in Election.get_all():
|
|
||||||
if any(workflow_task.status == eos.base.workflow.WorkflowTaskStatus.READY for workflow_task in election.workflow.tasks):
|
|
||||||
elections.append(election)
|
|
||||||
|
|
||||||
elections.sort(key=lambda e: e.name)
|
|
||||||
|
|
||||||
return flask.render_template('elections_batch.html', elections=elections)
|
|
||||||
|
|
||||||
@app.route('/election/<election_id>/')
|
@app.route('/election/<election_id>/')
|
||||||
@using_election
|
@using_election
|
||||||
def election_api_json(election):
|
def election_api_json(election):
|
||||||
is_full = 'full' in flask.request.args
|
return flask.Response(EosObject.to_json(EosObject.serialise_and_wrap(election, should_protect=True, for_hash=('full' not in flask.request.args))), mimetype='application/json')
|
||||||
|
|
||||||
serialised = EosObject.serialise_and_wrap(election, None, SerialiseOptions(should_protect=True, for_hash=(not is_full), combine_related=True))
|
|
||||||
|
|
||||||
# Protect voters, votes if required
|
|
||||||
if not election.is_voters_public:
|
|
||||||
if 'voters' in serialised['value']:
|
|
||||||
del serialised['value']['voters']
|
|
||||||
if not election.is_votes_public:
|
|
||||||
if 'voters' in serialised['value']:
|
|
||||||
for voter in serialised['value']['voters']:
|
|
||||||
if 'votes' in voter['value']:
|
|
||||||
del voter['value']['votes']
|
|
||||||
|
|
||||||
return flask.Response(EosObject.to_json(serialised), mimetype='application/json')
|
|
||||||
|
|
||||||
@app.route('/election/<election_id>/view')
|
@app.route('/election/<election_id>/view')
|
||||||
@using_election
|
@using_election
|
||||||
@ -291,20 +237,7 @@ def election_view_questions(election):
|
|||||||
@app.route('/election/<election_id>/view/ballots')
|
@app.route('/election/<election_id>/view/ballots')
|
||||||
@using_election
|
@using_election
|
||||||
def election_view_ballots(election):
|
def election_view_ballots(election):
|
||||||
if election.is_voters_public or ('user' in flask.session and flask.session['user'].is_admin()):
|
return flask.render_template('election/view/ballots.html', election=election)
|
||||||
return flask.render_template('election/view/ballots.html', election=election)
|
|
||||||
|
|
||||||
return flask.Response('Voters not public', 403)
|
|
||||||
|
|
||||||
@app.route('/election/<election_id>/voter/<voter_id>')
|
|
||||||
@using_election
|
|
||||||
def election_voter_view(election, voter_id):
|
|
||||||
if (election.is_voters_public and election.is_votes_public) or ('user' in flask.session and flask.session['user'].is_admin()):
|
|
||||||
voter_id = uuid.UUID(voter_id)
|
|
||||||
voter = next(voter for voter in election.voters if voter._id == voter_id)
|
|
||||||
return flask.render_template('election/voter/view.html', election=election, voter=voter)
|
|
||||||
|
|
||||||
return flask.Response('Voters not public', 403)
|
|
||||||
|
|
||||||
@app.route('/election/<election_id>/view/trustees')
|
@app.route('/election/<election_id>/view/trustees')
|
||||||
@using_election
|
@using_election
|
||||||
@ -322,13 +255,13 @@ def election_admin_summary(election):
|
|||||||
@election_admin
|
@election_admin
|
||||||
def election_admin_enter_task(election):
|
def election_admin_enter_task(election):
|
||||||
workflow_task = election.workflow.get_task(flask.request.args['task_name'])
|
workflow_task = election.workflow.get_task(flask.request.args['task_name'])
|
||||||
if workflow_task.status != WorkflowTaskStatus.READY:
|
if workflow_task.status != WorkflowTask.Status.READY:
|
||||||
return flask.Response('Task is not yet ready or has already exited', 409)
|
return flask.Response('Task is not yet ready or has already exited', 409)
|
||||||
|
|
||||||
task = WorkflowTaskEntryWebTask(
|
task = WorkflowTaskEntryTask(
|
||||||
election_id=election._id,
|
election_id=election._id,
|
||||||
workflow_task=workflow_task._name,
|
workflow_task=workflow_task._name,
|
||||||
status=TaskStatus.READY,
|
status=Task.Status.READY,
|
||||||
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
||||||
)
|
)
|
||||||
task.run()
|
task.run()
|
||||||
@ -341,31 +274,25 @@ def election_admin_enter_task(election):
|
|||||||
def election_admin_schedule_task(election):
|
def election_admin_schedule_task(election):
|
||||||
workflow_task = election.workflow.get_task(flask.request.form['task_name'])
|
workflow_task = election.workflow.get_task(flask.request.form['task_name'])
|
||||||
|
|
||||||
task = WorkflowTaskEntryWebTask(
|
task = WorkflowTaskEntryTask(
|
||||||
election_id=election._id,
|
election_id=election._id,
|
||||||
workflow_task=workflow_task._name,
|
workflow_task=workflow_task._name,
|
||||||
run_at=DateTimeField().deserialise(flask.request.form['datetime']),
|
run_at=DateTimeField().deserialise(flask.request.form['datetime']),
|
||||||
status=TaskStatus.READY,
|
status=Task.Status.READY,
|
||||||
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
||||||
)
|
)
|
||||||
task.save()
|
task.save()
|
||||||
|
|
||||||
return flask.redirect(flask.url_for('election_admin_summary', election_id=election._id))
|
return flask.redirect(flask.url_for('election_admin_summary', election_id=election._id))
|
||||||
|
|
||||||
@app.route('/election/<election_id>/stage_ballot', methods=['POST'])
|
|
||||||
@using_election
|
|
||||||
def election_api_stage_ballot(election):
|
|
||||||
flask.session['staged_ballot'] = json.loads(flask.request.data)
|
|
||||||
return 'OK'
|
|
||||||
|
|
||||||
@app.route('/election/<election_id>/cast_ballot', methods=['POST'])
|
@app.route('/election/<election_id>/cast_ballot', methods=['POST'])
|
||||||
@using_election
|
@using_election
|
||||||
def election_api_cast_vote(election):
|
def election_api_cast_vote(election):
|
||||||
if election.workflow.get_task('eos.base.workflow.TaskOpenVoting').status < WorkflowTaskStatus.EXITED or election.workflow.get_task('eos.base.workflow.TaskCloseVoting').status > WorkflowTaskStatus.READY:
|
if election.workflow.get_task('eos.base.workflow.TaskOpenVoting').status < WorkflowTask.Status.EXITED or election.workflow.get_task('eos.base.workflow.TaskCloseVoting').status > WorkflowTask.Status.READY:
|
||||||
# Voting is not yet open or has closed
|
# Voting is not yet open or has closed
|
||||||
return flask.Response('Voting is not yet open or has closed', 409)
|
return flask.Response('Voting is not yet open or has closed', 409)
|
||||||
|
|
||||||
data = flask.session['staged_ballot']
|
data = json.loads(flask.request.data)
|
||||||
|
|
||||||
if 'user' not in flask.session:
|
if 'user' not in flask.session:
|
||||||
# User is not authenticated
|
# User is not authenticated
|
||||||
@ -383,7 +310,7 @@ def election_api_cast_vote(election):
|
|||||||
|
|
||||||
# Cast the vote
|
# Cast the vote
|
||||||
ballot = EosObject.deserialise_and_unwrap(data['ballot'])
|
ballot = EosObject.deserialise_and_unwrap(data['ballot'])
|
||||||
vote = Vote(voter_id=voter._id, ballot=ballot, cast_at=DateTimeField.now())
|
vote = Vote(ballot=ballot, cast_at=DateTimeField.now())
|
||||||
|
|
||||||
# Store data
|
# Store data
|
||||||
if app.config['CAST_FINGERPRINT']:
|
if app.config['CAST_FINGERPRINT']:
|
||||||
@ -394,13 +321,13 @@ def election_api_cast_vote(election):
|
|||||||
else:
|
else:
|
||||||
vote.cast_ip = flask.request.remote_addr
|
vote.cast_ip = flask.request.remote_addr
|
||||||
|
|
||||||
vote.save()
|
voter.votes.append(vote)
|
||||||
|
|
||||||
del flask.session['staged_ballot']
|
election.save()
|
||||||
|
|
||||||
return flask.Response(json.dumps({
|
return flask.Response(json.dumps({
|
||||||
'voter': EosObject.serialise_and_wrap(voter, None, SerialiseOptions(should_protect=True)),
|
'voter': EosObject.serialise_and_wrap(voter, should_protect=True),
|
||||||
'vote': EosObject.serialise_and_wrap(vote, None, SerialiseOptions(should_protect=True))
|
'vote': EosObject.serialise_and_wrap(vote, should_protect=True)
|
||||||
}), mimetype='application/json')
|
}), mimetype='application/json')
|
||||||
|
|
||||||
@app.route('/election/<election_id>/export/question/<int:q_num>/<format>')
|
@app.route('/election/<election_id>/export/question/<int:q_num>/<format>')
|
||||||
@ -411,12 +338,6 @@ def election_api_export_question(election, q_num, format):
|
|||||||
resp.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
resp.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@app.route('/task/<task_id>')
|
|
||||||
@election_admin
|
|
||||||
def task_view(task_id):
|
|
||||||
task = Task.get_by_id(task_id)
|
|
||||||
return flask.render_template('task/view.html', task=task)
|
|
||||||
|
|
||||||
@app.route('/auditor')
|
@app.route('/auditor')
|
||||||
def auditor():
|
def auditor():
|
||||||
return flask.render_template('election/auditor.html')
|
return flask.render_template('election/auditor.html')
|
||||||
@ -427,28 +348,14 @@ def debug():
|
|||||||
|
|
||||||
@app.route('/auth/login')
|
@app.route('/auth/login')
|
||||||
def login():
|
def login():
|
||||||
flask.session['login_next'] = flask.request.referrer
|
|
||||||
return flask.render_template('auth/login.html')
|
return flask.render_template('auth/login.html')
|
||||||
|
|
||||||
@app.route('/auth/stage_next', methods=['POST'])
|
|
||||||
def auth_stage_next():
|
|
||||||
flask.session['login_next'] = flask.request.data
|
|
||||||
return 'OK'
|
|
||||||
|
|
||||||
@app.route('/auth/logout')
|
@app.route('/auth/logout')
|
||||||
def logout():
|
def logout():
|
||||||
flask.session['user'] = None
|
flask.session['user'] = None
|
||||||
if flask.request.referrer:
|
#return flask.redirect(flask.request.args['next'] if 'next' in flask.request.args else '/')
|
||||||
return flask.redirect(flask.request.referrer)
|
# I feel like there's some kind of exploit here, so we'll leave this for now
|
||||||
else:
|
return flask.redirect('/')
|
||||||
return flask.redirect('/')
|
|
||||||
|
|
||||||
@app.route('/auth/login_callback')
|
|
||||||
def login_callback():
|
|
||||||
if 'login_next' in flask.session and flask.session['login_next']:
|
|
||||||
return flask.redirect(flask.session['login_next'])
|
|
||||||
else:
|
|
||||||
return flask.redirect('/')
|
|
||||||
|
|
||||||
@app.route('/auth/login_complete')
|
@app.route('/auth/login_complete')
|
||||||
def login_complete():
|
def login_complete():
|
||||||
@ -466,21 +373,13 @@ def email_login():
|
|||||||
def email_authenticate():
|
def email_authenticate():
|
||||||
user = None
|
user = None
|
||||||
|
|
||||||
for u in app.config['ADMINS']:
|
for election in Election.get_all():
|
||||||
if isinstance(u, EmailUser):
|
for voter in election.voters:
|
||||||
if u.email.lower() == flask.request.form['email'].lower():
|
if isinstance(voter.user, EmailUser):
|
||||||
if u.password == flask.request.form['password']:
|
if voter.user.email.lower() == flask.request.form['email'].lower():
|
||||||
user = u
|
if voter.user.password == flask.request.form['password']:
|
||||||
break
|
user = voter.user
|
||||||
|
break
|
||||||
if user is None:
|
|
||||||
for election in Election.get_all():
|
|
||||||
for voter in election.voters:
|
|
||||||
if isinstance(voter.user, EmailUser):
|
|
||||||
if voter.user.email.lower() == flask.request.form['email'].lower():
|
|
||||||
if voter.user.password == flask.request.form['password']:
|
|
||||||
user = voter.user
|
|
||||||
break
|
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return flask.render_template('auth/email/login.html', error='The email or password you entered was invalid. Please check your details and try again. If the issue persists, contact the election administrator.')
|
return flask.render_template('auth/email/login.html', error='The email or password you entered was invalid. Please check your details and try again. If the issue persists, contact the election administrator.')
|
||||||
@ -493,7 +392,7 @@ def email_authenticate():
|
|||||||
|
|
||||||
for app_name in app.config['APPS']:
|
for app_name in app.config['APPS']:
|
||||||
app_main = importlib.import_module(app_name + '.main')
|
app_main = importlib.import_module(app_name + '.main')
|
||||||
app.register_blueprint(app_main.blueprint)
|
app_main.main(app)
|
||||||
|
|
||||||
# === Model-Views ===
|
# === Model-Views ===
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -26,18 +26,6 @@
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monoout {
|
|
||||||
font-family: monospace;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
padding: .78571429em 1em;
|
|
||||||
border: 1px solid rgba(34,36,38,.15);
|
|
||||||
color: rgba(0,0,0,.87);
|
|
||||||
border-radius: .28571429rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superem {
|
.superem {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@ -61,11 +49,6 @@ time[title] {
|
|||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix nested selectable tables */
|
|
||||||
.ui.table.selectable tr > td.selectable:hover {
|
|
||||||
background: initial !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
body, html {
|
body, html {
|
||||||
/* Default height: 100% causes blank pages */
|
/* Default height: 100% causes blank pages */
|
||||||
@ -135,29 +118,15 @@ time[title] {
|
|||||||
padding: 0.5em 0 0.5em 0.5em;
|
padding: 0.5em 0 0.5em 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-choices .number {
|
.ticket-choices .number, .ticket-choices .content {
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticket-choices .content {
|
|
||||||
padding: 0 0 0 0.5em;
|
padding: 0 0 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preferential-choice .number {
|
.preferential-choice .number {
|
||||||
|
width: 2em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preferential-choice .number select {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferential-choice .content {
|
|
||||||
/* Vertically center */
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferential-choice .party-name, .preferential-choice .ticket-party-name {
|
.preferential-choice .party-name, .preferential-choice .ticket-party-name {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
@ -170,7 +139,3 @@ time[title] {
|
|||||||
.ticket > .content > .party-name {
|
.ticket > .content > .party-name {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#selections-make-help {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 60 KiB |
@ -1,956 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="700"
|
|
||||||
height="372.06461"
|
|
||||||
viewBox="0 0 185.20833 98.442096"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
|
||||||
sodipodi:docname="guide.svg"
|
|
||||||
inkscape:export-filename="/home/runassudo/git/Eos/eosweb/core/static/img/guide.png"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96">
|
|
||||||
<defs
|
|
||||||
id="defs2">
|
|
||||||
<clipPath
|
|
||||||
clipPathUnits="userSpaceOnUse"
|
|
||||||
id="clipPath987">
|
|
||||||
<rect
|
|
||||||
style="fill:#008000;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
|
||||||
id="rect989"
|
|
||||||
width="91.290924"
|
|
||||||
height="6.1393275"
|
|
||||||
x="9.0702581"
|
|
||||||
y="78.243805" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="1"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="0.70710678"
|
|
||||||
inkscape:cx="293.72104"
|
|
||||||
inkscape:cy="292.29832"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:window-height="708"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
units="px"
|
|
||||||
inkscape:snap-global="false"
|
|
||||||
fit-margin-top="16"
|
|
||||||
fit-margin-bottom="16"
|
|
||||||
fit-margin-left="16"
|
|
||||||
fit-margin-right="16" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-6.1610108,-74.519826)">
|
|
||||||
<g
|
|
||||||
id="g1063"
|
|
||||||
transform="translate(5.4050422)">
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path873"
|
|
||||||
d="M 13.985119,122.37498 H 172.73512"
|
|
||||||
style="fill:none;stroke:#cccccc;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
<circle
|
|
||||||
r="3.5907738"
|
|
||||||
cy="122.37498"
|
|
||||||
cx="13.985118"
|
|
||||||
id="path851"
|
|
||||||
style="fill:#5fbcd3;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
|
||||||
<circle
|
|
||||||
style="fill:#5fbcd3;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
|
||||||
id="circle863"
|
|
||||||
cx="45.735119"
|
|
||||||
cy="122.37498"
|
|
||||||
r="3.5907738" />
|
|
||||||
<circle
|
|
||||||
r="3.5907738"
|
|
||||||
cy="122.37498"
|
|
||||||
cx="77.485115"
|
|
||||||
id="circle865"
|
|
||||||
style="fill:#5fbcd3;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
|
||||||
<circle
|
|
||||||
style="fill:#5fbcd3;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
|
||||||
id="circle867"
|
|
||||||
cx="109.23512"
|
|
||||||
cy="122.37498"
|
|
||||||
r="3.5907738" />
|
|
||||||
<circle
|
|
||||||
r="3.5907738"
|
|
||||||
cy="122.37498"
|
|
||||||
cx="140.98512"
|
|
||||||
id="circle869"
|
|
||||||
style="fill:#5fbcd3;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
|
||||||
<circle
|
|
||||||
style="fill:#5fbcd3;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
|
||||||
id="circle871"
|
|
||||||
cx="172.73515"
|
|
||||||
cy="122.37498"
|
|
||||||
r="3.5907738" />
|
|
||||||
<text
|
|
||||||
id="text877"
|
|
||||||
y="124.16092"
|
|
||||||
x="13.872649"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.55025291px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.1387551"
|
|
||||||
xml:space="preserve"><tspan
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.55025291px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;stroke-width:0.1387551"
|
|
||||||
y="124.16092"
|
|
||||||
x="13.872649"
|
|
||||||
id="tspan875"
|
|
||||||
sodipodi:role="line">1</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.55025291px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.1387551"
|
|
||||||
x="45.62265"
|
|
||||||
y="124.16092"
|
|
||||||
id="text881"><tspan
|
|
||||||
y="124.16092"
|
|
||||||
x="45.62265"
|
|
||||||
id="tspan899"
|
|
||||||
sodipodi:role="line">2</tspan></text>
|
|
||||||
<text
|
|
||||||
id="text885"
|
|
||||||
y="124.16092"
|
|
||||||
x="77.37265"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.55025291px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.1387551"
|
|
||||||
xml:space="preserve"><tspan
|
|
||||||
y="124.16092"
|
|
||||||
x="77.37265"
|
|
||||||
id="tspan901"
|
|
||||||
sodipodi:role="line">3</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.55025291px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.1387551"
|
|
||||||
x="109.12263"
|
|
||||||
y="124.16092"
|
|
||||||
id="text889"><tspan
|
|
||||||
y="124.16092"
|
|
||||||
x="109.12263"
|
|
||||||
id="tspan903"
|
|
||||||
sodipodi:role="line">4</tspan></text>
|
|
||||||
<text
|
|
||||||
id="text893"
|
|
||||||
y="124.16092"
|
|
||||||
x="140.87263"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.55025291px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.1387551"
|
|
||||||
xml:space="preserve"><tspan
|
|
||||||
y="124.16092"
|
|
||||||
x="140.87263"
|
|
||||||
id="tspan905"
|
|
||||||
sodipodi:role="line">5</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.55025291px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.1387551"
|
|
||||||
x="172.62267"
|
|
||||||
y="124.16092"
|
|
||||||
id="text897"><tspan
|
|
||||||
y="124.16092"
|
|
||||||
x="172.62267"
|
|
||||||
id="tspan907"
|
|
||||||
sodipodi:role="line">6</tspan></text>
|
|
||||||
<image
|
|
||||||
width="68.262505"
|
|
||||||
height="36.35281"
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
xlink:href="data:image/png;base64,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
|
|
||||||
"
|
|
||||||
id="image946"
|
|
||||||
x="107.57722"
|
|
||||||
y="132.37578" />
|
|
||||||
<image
|
|
||||||
width="83.738396"
|
|
||||||
height="26.784683"
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
xlink:href="data:image/png;base64,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
|
|
||||||
2rQp3N3d8fjxY0RGRmLPnj0mqacseJnaSkRERERU0oxKxFatWqWShAHyHluelJQk/b969eq4e/eu
|
|
||||||
tPzKK6/IqrtGjRr47rvvsHTpUmzYsAFZWdrv96xcuTJGjBiBUaNGmewJjmPHjsWhQ4eQnJyMrKws
|
|
||||||
zJw5U+V1Hx8fvPrqq9i7d69J6itNL1NbiYiIiKj8Ss/LK3ZrY1lkVCKm7gd/9TV79mxMmTIFSUlJ
|
|
||||||
CAoKwltvvSW7bOXKlTF58mQMHDgQe/fuxdGjRxEXF4dHjx7B0tISjo6OqFu3Ltq1a4dOnTrhtdde
|
|
||||||
MzrewmrVqoWffvoJS5cuxenTp3H//n3Y2dnB3d0d7777Lrp27YqLFy/i+PHjSEkpO4/KNMTL1FYi
|
|
||||||
IiIiopJmIfgjUEREREREZYLFzq2lHUJxlU33U1QvMtG5l17b87F3REREREREZsZEjIiIiIiIyMyY
|
|
||||||
iBEREREREZkZEzEiIiIiojKihb1DaYdABvC1r6Z3GSZiRERERERlxMC69Uo7BDLAwFq19S7DRIyI
|
|
||||||
iIiIqIz4pJEbWld3Ku0wSA9+r9TAx/Ub612OiRgRERERURlyvH1HfNawCRwrVCrtUEiLGhUq4guX
|
|
||||||
Rjj8r7cNKs/fESMiIiIiIjIzfiJGRERERERkZkzEiIiIiIiIzIyJGBERERERkZkxESMiIiIiIjIz
|
|
||||||
JmJERERERERmxkSMiIiIiIjIzJiIERERERERmRkTMSIiIiIiIjNjIkZERERERGRmTMSIiIiIiIjM
|
|
||||||
jIkYERERERGRmTERIyIiIiIiMjMmYkRERERERGbGRIyIiIiIiMjMmIgRERERERGZGRMxIiIiIiIi
|
|
||||||
M2MiRkREREREZGZMxIiIiIiIiMyMiRgREREREZGZMREjIiIiIiIyMyZiREREREREZsZEjIiIiIiI
|
|
||||||
yMyYiBEREREREZkZEzEiIiIiIiIzYyJGRERERERkZgYnYgMGDECvXr00vh4REYH69evjn3/+0bmv
|
|
||||||
rKwsrF692tBQAAD5+fmYNWsWfH190bx5c2RlZRm1v8ICAwMxZ84co/ZhijaWlq1bt8LPzw9ubm7o
|
|
||||||
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
|
|
||||||
zJlIS0vD/PnzVda3bNkSX375pc7vPZN2nPOJiDQz2a2JRHKEh4fDycmJb7oJAPDkyRPExcVxPFCp
|
|
||||||
8fT0RFRUFH799Vekp6fj7t27mDt3Luzs7Jg4mADnfCIizUx2ayKRLnl5ediyZQs+/PDDEntiJJUv
|
|
||||||
r7zyCubOnVvaYdBLrHv37khLS8NXX32FW7du4ZVXXkGrVq3w3XffST8RQYbhnE9EpB1vTSQiIiIi
|
|
||||||
IjIz3ppIRERERERkZkzEiIiIiIiIzIyJGBERERERkZkxESMiIiIiIjIzJmJERERERERmxkSMiIiI
|
|
||||||
iIjIzJiIERERERERmRkTMSIiIiIiIjNjIkZERERERGRmTMSIiIiIiIjMjIkYERERERGRmTERIyIi
|
|
||||||
IiIiMjMmYkRERERERGbGRIyIiIiIiMjMmIgRERERERGZGRMxIiIiIiIiM2MiRkREREREZGZMxIiI
|
|
||||||
iIiIiMyMiRgREREREZGZMREjIiIiIiIyMyZiREREREREZsZEjIiIiIiIyMyYiBEREREREZkZEzEi
|
|
||||||
IiIiIiIzYyJGRERERERkZhWMKZyRkYkHD+4jIyPToPJVqtjCyckZVarYGhMGERERERFRuWIhhBCG
|
|
||||||
FMzIyER8fDxq134d9vb2BlWelpaG27cTUadOHSZjRERERET00jD41sT79+8blYQBgL29PWrXfh33
|
|
||||||
7983eB9ERERERETljcGJWGZmplFJWAF7e3tkZhp2ayMREREREVF59EI8rCM4OBgNGzbUq0yvXr0w
|
|
||||||
ePBgk8Yxf/58uLi4qPzz8PBAz549sX79emRlZZm0PiIiIiIiKp+MelgHqTdjxgzp08KMjAycPn0a
|
|
||||||
c+bMwY8//oiNGzfC2dm5lCMkIiIiIqLSxESsBHTt2hVOTk7S8qBBg3Dy5El88MEHGDNmDMLDw2Fh
|
|
||||||
YVGKERIRERERUWl6IW5NLA/eeustfPLJJzh9+jSOHj1a2uEQEREREVEpemETse+++w4BAQFwdXXF
|
|
||||||
m2++iRkzZiAlJaXYdocPH0bHjh3h7u6OPn364O+//5Ze69WrF0JDQ7Fr1y74+fmp3UYfffr0gYWF
|
|
||||||
BSIjI/WONSwsDP7+/nBzc0OHDh2watUq5ObmGhQHERERERGVrhfy1sRly5YhNDQUQ4cOhVKpREJC
|
|
||||||
AhYtWoTz58/jhx9+QMWKFQEAsbGxSE1NxYQJE5Cfn4958+Zh1KhROHjwICpUeN41GzduxL/+9S8s
|
|
||||||
X74c+fn5mDp1arFt5HrllVdQp04dXLp0Sa9Y16xZg9WrVyMkJATu7u6Ii4vDrFmzkJaWhuDgYNN1
|
|
||||||
HBERERERmcULl4ilpKRgxYoVeP/99xESEiKtr1OnDgYOHIi9e/eiZ8+eAIBatWph27ZtqFSpEgBA
|
|
||||||
CIExY8bg77//hru7OwDAxcUFK1askL7TNXbsWAwfPlxlG31UrVoVycnJsmNt164dli1bhjlz5qBr
|
|
||||||
164AgMaNGyMnJwfBwcEYO3YsrK2tDegpIiIiIiIqLS/crYl//vknsrKyEBAQoLJeqVTC3t4ev/32
|
|
||||||
m7TO2dlZSsIAoG7dugCApKQkaV3VqlVVHqxRs2bNYtvoIzU1VXqiopxY//zzT6Snp2PSpElwc3OT
|
|
||||||
/gUHB0MIgbS0NIPiICIiIiKi0vNCfCKWk5Mj3W74+PFjAICDg0Ox7RwdHaXX1bG0fJ6X5uXladym
|
|
||||||
ICnTto0mycnJiI+Px7vvvis71oJEa8+ePWjQoIHedRIRERERUdnzQnwidufOHbz++usAnn8PCwCe
|
|
||||||
PHlSbLtHjx6pTXrMJTw8HEIIdOrUCYC8WGvUqAEAuHHjhvkCJSIiIiKiElXuE7ELFy7gzz//RLNm
|
|
||||||
zQAALVq0gJ2dHQ4dOqSyXUxMDNLS0vD222+XRpg4deoUli1bBl9fX7Rt2xaAvFi9vLxgZ2eHbdu2
|
|
||||||
qWwjhFCbwBERERERUdlXLm9NPH/+PH788Uc8efIEUVFRsLKywqhRowAA1apVw5gxYzBv3jxYW1tD
|
|
||||||
qVTi9u3bWLhwIby9vdGlS5cSj2/Pnj3S98AyMzNx6tQp7N+/H40aNcKyZcuk2xvlxGphYYHx48dj
|
|
||||||
5syZGDNmDHr27ImMjAx89913SEtLw969e0u8PUREREREZFrlMhG7evUqDhw4gJSUFDRp0gSTJk1S
|
|
||||||
+f7UsGHDYGtri82bN2Pjxo2oWrUqAgICEBwcrPLgjZIyc+ZM6f+2trZo0KABQkJCMGDAgGJPOJQT
|
|
||||||
65AhQ2Bvb4+1a9di9OjRsLe3h1KpxFdffVXibSEiIiIiItOzEEIIQwrevBmHGjUcpU9+DJWWloaH
|
|
||||||
Dx9Boahn1H6IiIiIiIjKC4O/I+bs7IzbtxONenx6Wloabt9OhLOzs8H7ICIiIiIiKm8M/kQMADIy
|
|
||||||
MnH//n1kZmYaVN7W1hbOzs6oUsXW0BCIiIiIiIjKHaMSMSIiIiIiItJfuX98PRERERERUXnDRIyI
|
|
||||||
iIiIiMjMmIgRERERERGZGRMxIiIiIiIiM2MiRkREREREZGZMxIiIiIiIiMyMiRgREREREZGZMREj
|
|
||||||
IiIiIiIyMyZiREREREREZsZEjIiIiIiIyMyYiBEREREREZkZEzEiIiIiIiIzYyJGRERERERkZhWM
|
|
||||||
KZyRkYkHD+4jIyPToPJVqtjCyckZVarYGhMGERERERFRuWIhhBCGFMzIyER8fDxq134d9vb2BlWe
|
|
||||||
lpaG27cTUadOHSZjRERERET00jD41sT79+8blYQBgL29PWrXfh337983eB9ERERERETljcGJWGZm
|
|
||||||
plFJWAF7e3tkZhp2a2OBqKgouLi4YODAgUbHU9j3338PFxcX3L1716T7NdaDBw8wZ84c+Pn5wc3N
|
|
||||||
DV5eXnjvvfcQGRmpst2kSZPQtm1bpKWlad2f3O2IiIiIiMg0XoiHdezcuROVK1fGb7/9VuaSJlM7
|
|
||||||
ceIE/P39sWnTJjRu3BgjR45Ev379kJGRgdGjR2PBggXStjdu3EBSUhLS09Olda1atcL58+dV9qlu
|
|
||||||
OyIiIiIiKjlGPayjLEhNTcWRI0fw4YcfYt26ddi1axdGjhxZ2mGViPj4eIwcORLVq1fHjh07UL9+
|
|
||||||
fZXXIyIi0KhRI2l506ZNSEtLQ82aNQE8/xQzKSmp2H6LbkdERERERCWr3Cdiu3fvRnZ2Nvr3748b
|
|
||||||
N25gx44dL2wiFhoaivT0dGzbtq1YEgYA3bt3V1m2t7dXuX00ISFB7X6LbkdERERERCWr3N+auHPn
|
|
||||||
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
|
|
||||||
U1WW59ScOKv8hvTcu22TXv/DOzP1j+vmKXtOjPac6lOzZ0TrbkrSjkcXKd8VO+7arKRovfxQvho7
|
|
||||||
h/XGYY9yk2P14z/N1aJUuySptqlf+5oDz36d7hrWv9ae17/vm3wZ4mT9TsXjq9L1D/dky++XXv+k
|
|
||||||
U0cvDOnR5W79R0WerjxPF3mzZkasuvp3qq7+XfDYZotWxQMPqri4OHhu3bp1KilZocWLF8tiseiT
|
|
||||||
jz/Wtm3/rQ8/+lBLCgvV29Oj3e/vliUqSn/+2GPKzs6WJHV0dMjlck0Ys6OjQ1VVVYqOjtGmTZtk
|
|
||||||
s82atwsAAABfIZfC0+c9I+oYGLvqtVmJ0XqsLE1+Q3rwlRM6emFIkvTP6+epYqlLT67O0OM7mse1
|
|
||||||
+d7OZlUdDcxm+fyGKotTtPaPknS8fUjvn+xVmsOmNQWJOtkxrBf2nLvmuq/W77XKdEbrr25L16nO
|
|
||||||
YVW+dlJjPkOS9Ozd2Sq/JVmluY5gUJxJZk2yyM6ep7lZcyVJfb19OnHihLZv36Yhr1dlK1dKktLS
|
|
||||||
0pWWFlgXaxiGUtPSJEldnV2SpJMnT2psbFQ5ubnBECZJbrd7wni9vb166603NTo6ovJvbQz2CwAA
|
|
||||||
AMw0l2Z9jGu49rZ8p6wW6aPWgWAIk6Stn3SqYqlLq/KdE9o0eUa+8O/A0sVMZ/T1lBy2flfNdyrG
|
|
||||||
atGClFj9/snCCa9nJcVcX5HTZNYEscWLFwc365CkxsZGbfn5z/TOrne0tKhICQkJ8nq9qqmp0ZEj
|
|
||||||
h9Xe3i6/L7Ae1Hfx98DgoCTJ6Zj4x3W5V//rFUnSnOTkcbNuAAAAwExzpjsQaLKSYpQcZ5XH++XP
|
|
||||||
RSXHByLA5TNnl46T7FZFWQI7Ll7RxfNRljAv+gux38RYq6TAEskXPxg/G2dIOnbBG47qwm7WPSN2
|
|
||||||
SVZWliTJ7/PJ4/FodHRUP928We+/967S09O1cWOFNlZUjGuTcHEzjt7e3kn7X3fvvcrJzVW3x6Oq
|
|
||||||
qqrw3wAAAAAQJvXNfRoc9ctqkX5wZ+ZVr+0aDAQud8L4OZmUi8c9Q74vD2Em8hvXVsT5/lFJUkq8
|
|
||||||
TR+2Doz7+ah1QP0j/kl6iIxZGcQMw1Bdba0kyWqzye12q6WlRR0d7SpYuEibNlWquLhYGRnj/wjz
|
|
||||||
58+XJSpKra0tam1pCZ4/ffq0vN7xSTkvL1+VlZWKi4/XvrpaHTt2bPpvDAAAAAhB37BfW+oDOwhW
|
|
||||||
Fqfon+6dp7mJgSV+CTFR2rjUpb3fvVm35TlU09inEZ+hZVkJ4zbGeGhZYOfDvY19Ux5/9OJzWc7Y
|
|
||||||
8MWLgRG/+oYDISovObC8MDrKormXLTWsawrcz01pdt21KCl4vsAdqweLJu4DMVPMmqWJ9fX1ajgc
|
|
||||||
2P2wp6dHgwMDkqQ1X1sju92uxMREWSwWtbV9rn11dfIbftXU1IzrIzk5WaWlZdpXV6stW7ZowYIF
|
|
||||||
GvQOqrmp6Ypb0yclzVF5ebl+9dpr2r5tm/76iSfkdE6+rBEAAAAw20s155TnitV9S+bogSKXHihy
|
|
||||||
yWcEvmxZknyGlOqIVm1Tv35Sc15Prc7Q9kcWav+ZfqU7olWYEae+Yb/+5YNr32zjkhMXN9coyXFo
|
|
||||||
x3cWamjUUOWrJ6/7nv7nWLcqlrr08kPzVXO6T2W5Ti1wj9/VsWvQp5f2ntMP7szU5o152tfUL4tF
|
|
||||||
Ks11SAosWfy8Z+RK3UfUrJkR6+vr1dm2Np1ta5NvbEy5eXnaVPlnwefG3G63Nmy4T9E2m3ZV7dJn
|
|
||||||
n36m++8vV0HBwnH9rF+/Xt/85t1KcCTo2PFj6u7u1h13rNbatWuvOG5h4S0qLS3T4OCAtm3bJuMa
|
|
||||||
p0gBAAAAM/kN6XtvNOsvf9OkvY198nh98huGLvSP6e0j3Sr/xfHgFyv/W+15Pb2rRa3dI7o936l5
|
|
||||||
c2JUdbRH5b88rtNdw5OMNNGhc169tPe8eod8ykqM0enOoS/9guepeLa6TTsbPHLF27T+5mTtP9Ov
|
|
||||||
Z3a1TNiUZHPdBf3w7RYdvzCkkpwELc2M176mPj38q1MzMoRJksUIMVk0n2mZ/CKEJDdnXqRLAAAA
|
|
||||||
QJh88cuIceNqfKZoStfPmhkxAAAAALhREMQAAAAAwGQEMQAAAAAwGUEMAAAAAExGEAMAAAAAkxHE
|
|
||||||
AAAAAMBkBDEAAAAAMBlBDAAAAABMRhADAAAAAJMRxAAAAADAZCEHMavVGs46cBHvKwAAwI1laXps
|
|
||||||
pEvANAvlMw45iMXHx4XaFFcRF2ePdAkAAAAIo2/dMifSJWCalRcmTblNyEHMlZys6OjoUJvjCqKj
|
|
||||||
bUpxuSJdBgAAAMLo4ZIM3ZrNJMaNamVOnB5ZkTnldhbDMIzrGbijo1MDg4PX0wUkJcTHy+1OiXQZ
|
|
||||||
AAAAmCYv7mnVKx91yTN0Xf/9xgyRbLfo4T9x6YnV2SG1v+4gBgAAAACYGnZNBAAAAACTEcQAAAAA
|
|
||||||
wGQEMQAAAAAwGUEMAAAAAExGEAMAAAAAkxHEAAAAAMBkBDEAAAAAMBlBDAAAAABMRhADAAAAAJMR
|
|
||||||
xAAAAADAZAQxAAAAADAZQQwAAAAATPZ/wVxNFcH7JnEAAAAASUVORK5CYII=
|
|
||||||
"
|
|
||||||
id="image957"
|
|
||||||
x="13.085088"
|
|
||||||
y="84.769852" />
|
|
||||||
<rect
|
|
||||||
rx="2.1166668"
|
|
||||||
y="78.90316"
|
|
||||||
x="10.57835"
|
|
||||||
height="34.661263"
|
|
||||||
width="88.751862"
|
|
||||||
id="rect960"
|
|
||||||
style="fill:none;fill-opacity:1;stroke:#cccccc;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
|
||||||
<rect
|
|
||||||
transform="translate(0,-1.5875)"
|
|
||||||
clip-path="url(#clipPath987)"
|
|
||||||
style="fill:#cccccc;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
|
||||||
id="rect962"
|
|
||||||
width="88.751862"
|
|
||||||
height="32.924011"
|
|
||||||
x="10.57835"
|
|
||||||
y="80.490662"
|
|
||||||
rx="2.1166668" />
|
|
||||||
<rect
|
|
||||||
rx="0.81605595"
|
|
||||||
y="79.615738"
|
|
||||||
x="95.155502"
|
|
||||||
height="2.4673245"
|
|
||||||
width="2.4673245"
|
|
||||||
id="rect993"
|
|
||||||
style="fill:#c83737;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
|
||||||
<rect
|
|
||||||
style="fill:#d45500;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
|
||||||
id="rect995"
|
|
||||||
width="2.4673245"
|
|
||||||
height="2.4673245"
|
|
||||||
x="91.45134"
|
|
||||||
y="79.615738"
|
|
||||||
rx="0.81605595" />
|
|
||||||
<rect
|
|
||||||
rx="0.81605595"
|
|
||||||
y="79.615738"
|
|
||||||
x="87.747177"
|
|
||||||
height="2.4673245"
|
|
||||||
width="2.4673245"
|
|
||||||
id="rect997"
|
|
||||||
style="fill:#2ca02c;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
|
||||||
<text
|
|
||||||
id="text1001"
|
|
||||||
y="89.363792"
|
|
||||||
x="105.43783"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
|
||||||
xml:space="preserve"><tspan
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
|
|
||||||
y="89.363792"
|
|
||||||
x="105.43783"
|
|
||||||
id="tspan999"
|
|
||||||
sodipodi:role="line">Click ‘Help’ for guidance</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
|
||||||
x="105.43783"
|
|
||||||
y="102.59296"
|
|
||||||
id="text1005"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan1003"
|
|
||||||
x="105.43783"
|
|
||||||
y="102.59296"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332">Follow the instructions</tspan><tspan
|
|
||||||
id="tspan1007"
|
|
||||||
sodipodi:role="line"
|
|
||||||
x="105.43783"
|
|
||||||
y="109.64851"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332">to cast your ballot</tspan></text>
|
|
||||||
<text
|
|
||||||
id="text1013"
|
|
||||||
y="143.86795"
|
|
||||||
x="42.731579"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
|
||||||
xml:space="preserve"><tspan
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
|
|
||||||
y="143.86795"
|
|
||||||
x="42.731579"
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan1011">When you receive a <tspan
|
|
||||||
id="tspan1025"
|
|
||||||
style="font-weight:bold;fill:#005500">green</tspan></tspan><tspan
|
|
||||||
id="tspan1017"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
|
|
||||||
y="150.92351"
|
|
||||||
x="42.731579"
|
|
||||||
sodipodi:role="line">‘smart ballot tracker’,</tspan><tspan
|
|
||||||
id="tspan1023"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
|
|
||||||
y="157.97906"
|
|
||||||
x="42.731579"
|
|
||||||
sodipodi:role="line">you're done!</tspan></text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 66 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
@ -1,187 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="172.86369mm"
|
|
||||||
height="69.114601mm"
|
|
||||||
viewBox="0 0 172.86369 69.114601"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
sodipodi:docname="logo.svg"
|
|
||||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
|
||||||
inkscape:export-filename="/tmp/bitmap.png"
|
|
||||||
inkscape:export-xdpi="300"
|
|
||||||
inkscape:export-ydpi="300">
|
|
||||||
<defs
|
|
||||||
id="defs2">
|
|
||||||
<clipPath
|
|
||||||
clipPathUnits="userSpaceOnUse"
|
|
||||||
id="clipPath4586">
|
|
||||||
<ellipse
|
|
||||||
style="fill:#008000;fill-opacity:1;stroke:none;stroke-width:1.29814279;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
|
||||||
id="ellipse4588"
|
|
||||||
cx="57.005764"
|
|
||||||
cy="92.121689"
|
|
||||||
rx="29.557428"
|
|
||||||
ry="29.557301" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="1"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="0.43289459"
|
|
||||||
inkscape:cx="65.975662"
|
|
||||||
inkscape:cy="27.661024"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:window-height="708"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:snap-global="true"
|
|
||||||
showguides="false"
|
|
||||||
fit-margin-top="5"
|
|
||||||
fit-margin-left="5"
|
|
||||||
fit-margin-right="5"
|
|
||||||
fit-margin-bottom="5" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-22.448336,-359.18952)">
|
|
||||||
<g
|
|
||||||
clip-path="url(#clipPath4586)"
|
|
||||||
id="g4865"
|
|
||||||
transform="translate(0,301.62513)">
|
|
||||||
<rect
|
|
||||||
style="fill:#0b1728;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="rect4859"
|
|
||||||
width="59.285988"
|
|
||||||
height="39.253845"
|
|
||||||
x="27.362764"
|
|
||||||
y="93.292252" />
|
|
||||||
<rect
|
|
||||||
y="49.178513"
|
|
||||||
x="27.362764"
|
|
||||||
height="54.870293"
|
|
||||||
width="59.285988"
|
|
||||||
id="rect4861"
|
|
||||||
style="fill:#1f4272;fill-opacity:1;stroke:none;stroke-width:1.11792147;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(0.21824265,300.61125)"
|
|
||||||
id="g4879">
|
|
||||||
<path
|
|
||||||
style="fill:#f9f9f9;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="m 46.70614,80.20013 h 20.162756 l 4.233337,6.690286 H 42.47281 Z"
|
|
||||||
id="path4867"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
sodipodi:nodetypes="ccccc" />
|
|
||||||
<g
|
|
||||||
id="g4873"
|
|
||||||
style="fill:#e0e0e0;fill-opacity:1"
|
|
||||||
transform="translate(0,-1.5875)">
|
|
||||||
<rect
|
|
||||||
y="88.477921"
|
|
||||||
x="42.472809"
|
|
||||||
height="17.719406"
|
|
||||||
width="28.629425"
|
|
||||||
id="rect4869"
|
|
||||||
style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
<rect
|
|
||||||
ry="3"
|
|
||||||
rx="3"
|
|
||||||
style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="rect4871"
|
|
||||||
width="28.629425"
|
|
||||||
height="15.76951"
|
|
||||||
x="42.472809"
|
|
||||||
y="96.689354" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#0b1728;stroke-width:0.69999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="M 51.385265,83.545273 H 62.189779"
|
|
||||||
id="path4875"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
<rect
|
|
||||||
style="fill:#5fbcd3;fill-opacity:1;stroke:none;stroke-width:0.69999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="rect4877"
|
|
||||||
width="12.071115"
|
|
||||||
height="6.5703545"
|
|
||||||
x="-22.056793"
|
|
||||||
y="91.344513"
|
|
||||||
rx="1"
|
|
||||||
ry="1"
|
|
||||||
transform="rotate(-45.88581)" />
|
|
||||||
</g>
|
|
||||||
<text
|
|
||||||
id="text4883"
|
|
||||||
y="393.78824"
|
|
||||||
x="95.370705"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.57777786px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#0b1728;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
|
||||||
xml:space="preserve"><tspan
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.57777786px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#0b1728;stroke-width:0.26458332"
|
|
||||||
y="393.78824"
|
|
||||||
x="95.370705"
|
|
||||||
id="tspan4881"
|
|
||||||
sodipodi:role="line">Eos Voting</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;line-height:1.25;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#373e48;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
|
||||||
x="96.429039"
|
|
||||||
y="410.19238"
|
|
||||||
id="text4887"><tspan
|
|
||||||
y="410.19238"
|
|
||||||
x="96.429039"
|
|
||||||
id="tspan4885"
|
|
||||||
sodipodi:role="line">Vote with confidence</tspan></text>
|
|
||||||
<g
|
|
||||||
transform="translate(0.51981687,300.63247)"
|
|
||||||
id="g4895">
|
|
||||||
<rect
|
|
||||||
style="fill:none;fill-opacity:1;stroke:#3771c8;stroke-width:1.20000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="rect4889"
|
|
||||||
width="12.425195"
|
|
||||||
height="12.425195"
|
|
||||||
x="50.574924"
|
|
||||||
y="92.730827" />
|
|
||||||
<path
|
|
||||||
sodipodi:nodetypes="ccccc"
|
|
||||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#e0e0e0;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
|
||||||
d="m 64.052734,91.082031 -7.623046,9.347659 1.640433,1.15336 7.531441,-9.237347 z"
|
|
||||||
id="path4891"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#002255;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="m 64.096834,92.609763 -7.590808,9.309147 -3.025264,-3.025268"
|
|
||||||
id="path4893"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
sodipodi:nodetypes="ccc" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 8.5 KiB |
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,37 +19,18 @@
|
|||||||
window = self; // Workaround for libraries
|
window = self; // Workaround for libraries
|
||||||
isLibrariesLoaded = false;
|
isLibrariesLoaded = false;
|
||||||
|
|
||||||
eosjs = null;
|
|
||||||
|
|
||||||
function generateEncryptedVote(election, answers, should_do_fingerprint) {
|
function generateEncryptedVote(election, answers, should_do_fingerprint) {
|
||||||
if (election._name === 'eos.psr.election.PSRElection') {
|
encrypted_answers = [];
|
||||||
encrypted_answers = [];
|
for (var q_num = 0; q_num < answers.length; q_num++) {
|
||||||
for (var q_num = 0; q_num < answers.length; q_num++) {
|
answer_json = answers[q_num];
|
||||||
answer_json = answers[q_num];
|
answer = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(answer_json, null);
|
||||||
answer = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null);
|
encrypted_answer = eosjs.eos.psr.election.__all__.BlockEncryptedAnswer.encrypt(election.public_key, answer, election.questions.__getitem__(q_num).max_bits() + 32); // +32 bits for the length
|
||||||
encrypted_answer = eosjs.eos.psr.election.BlockEncryptedAnswer.encrypt(election.public_key, answer, election.questions.__getitem__(q_num).max_bits() + 32); // +32 bits for the length
|
encrypted_answers.push(eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(encrypted_answer, null));
|
||||||
encrypted_answers.push(eosjs.eos.core.objects.EosObject.serialise_and_wrap(encrypted_answer, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
postMessage({
|
|
||||||
encrypted_answers: encrypted_answers
|
|
||||||
});
|
|
||||||
} else if (election._name === 'eos.base.election.Election') {
|
|
||||||
encrypted_answers = [];
|
|
||||||
for (var q_num = 0; q_num < answers.length; q_num++) {
|
|
||||||
answer_json = answers[q_num];
|
|
||||||
answer = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null);
|
|
||||||
encrypted_answer = eosjs.eos.base.election.NullEncryptedAnswer();
|
|
||||||
encrypted_answer.answer = answer;
|
|
||||||
encrypted_answers.push(eosjs.eos.core.objects.EosObject.serialise_and_wrap(encrypted_answer, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
postMessage({
|
|
||||||
encrypted_answers: encrypted_answers
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw "Don't know how to encrypt ballots in election of type " + election._name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postMessage({
|
||||||
|
encrypted_answers: encrypted_answers
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onmessage = function(msg) {
|
onmessage = function(msg) {
|
||||||
@ -59,11 +40,10 @@ onmessage = function(msg) {
|
|||||||
msg.data.static_base_url + "js/eosjs.js"
|
msg.data.static_base_url + "js/eosjs.js"
|
||||||
);
|
);
|
||||||
isLibrariesLoaded = true;
|
isLibrariesLoaded = true;
|
||||||
eosjs = require("eosjs");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.data.action === "generateEncryptedVote") {
|
if (msg.data.action === "generateEncryptedVote") {
|
||||||
msg.data.election = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(msg.data.election, null);
|
msg.data.election = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(msg.data.election, null);
|
||||||
|
|
||||||
generateEncryptedVote(msg.data.election, msg.data.answers);
|
generateEncryptedVote(msg.data.election, msg.data.answers);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>The following is your ballot with fingerprint <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>, decrypted and ready for auditing.</p>
|
<p>The following is your ballot with fingerprint <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>, decrypted and ready for auditing.</p>
|
||||||
|
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
{# For some reason nunjucks doesn't like calling this the normal way #}
|
{# For some reason nunjucks doesn't like calling this the normal way #}
|
||||||
@ -41,11 +41,3 @@
|
|||||||
{# Dirty trick to go back to the encryption step #}
|
{# Dirty trick to go back to the encryption step #}
|
||||||
<button class="ui right floated primary button" onclick="nextTemplate(-2);">Continue</button>
|
<button class="ui right floated primary button" onclick="nextTemplate(-2);">Continue</button>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block help %}
|
|
||||||
<p>Eos is now ready to audit your ballot. This is an optional step that you can take to ensure that your ballot was prepared correctly by the voting booth.</p>
|
|
||||||
<p>To complete the audit, provide the data shown to a trusted third-party, or <a href="/auditor?electionUrl={{ election_base_url }}" target="_blank">click here to open the Eos auditor</a> and copy-paste in the data shown.</p>
|
|
||||||
<p>If you are satisfied with the result of the audit, click the blue ‘Continue’ button to proceed to cast your ballot.</p>
|
|
||||||
<p>Click the ‘OK’ button below to close this help screen.</p>
|
|
||||||
<p>If you require further assistance, contact your election administrator.</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,80 +18,39 @@
|
|||||||
|
|
||||||
<h1>{{ election.name }}</h1>
|
<h1>{{ election.name }}</h1>
|
||||||
|
|
||||||
<p><small><b>{{ election.kind|title }} fingerprint:</b> <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(election).hash_as_b64() }}</span></small></p>
|
<p><small><b>{{ election.kind|title }} fingerprint:</b> <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(election).hash_as_b64() }}</span></small></p>
|
||||||
|
|
||||||
{# Convert the template name to a numerical index for comparison #}
|
{# Convert the template name to a numerical index for comparison #}
|
||||||
{% if template == 'booth/welcome.html' %}
|
{% if template == 'booth/welcome.html' %}
|
||||||
{% set menuindex = 1 %}
|
{% set menuindex = 1 %}
|
||||||
{% elif template == 'booth/selections.html' %}
|
{% elif template == 'booth/selections.html' %}
|
||||||
{% set menuindex = 2 %}
|
{% set menuindex = 2 %}
|
||||||
{% elif template == 'booth/encrypt.html' %}
|
|
||||||
{% set menuindex = 2.5 %}
|
|
||||||
{% elif template == 'booth/review.html' %}
|
{% elif template == 'booth/review.html' %}
|
||||||
{% set menuindex = 3 %}
|
{% set menuindex = 3 %}
|
||||||
{% elif template == 'booth/audit.html' %}
|
{% elif template == 'booth/audit.html' %}
|
||||||
{% set menuindex = 4 %}
|
{% set menuindex = 4 %}
|
||||||
{% elif template == 'booth/cast.html' %}
|
{% elif template == 'booth/cast.html' %}
|
||||||
{% if election.can_audit() %}
|
{% set menuindex = 5 %}
|
||||||
{% set menuindex = 5 %}
|
|
||||||
{% else %}
|
|
||||||
{% set menuindex = 4 %}
|
|
||||||
{% endif %}
|
|
||||||
{% elif template == 'booth/complete.html' %}
|
{% elif template == 'booth/complete.html' %}
|
||||||
{% if election.can_audit() %}
|
{% set menuindex = 6 %}
|
||||||
{% set menuindex = 6 %}
|
|
||||||
{% else %}
|
|
||||||
{% set menuindex = 5 %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% macro menuitem(index, text) %}
|
<div class="ui secondary pointing menu" id="election-tab-menu">
|
||||||
<li class="progress-step{% if menuindex > index %} is-complete{% elif menuindex == index %} is-active{% endif %}">
|
{# oh dear god #}
|
||||||
<span class="progress-marker">{% if menuindex > index or menuindex == 6 %}<span style="font-family: Icons;"></span>{% else %}{{ index }}{% endif %}</span>
|
<span class="ui{% if menuindex >= 1 %} active{% endif %} item"{% if menuindex != 1 %} style="color: #767676;{% if menuindex > 1 %} font-weight: 400;{% endif %}"{% endif %}>1. Welcome</span>
|
||||||
<span class="progress-text">
|
<span class="ui{% if menuindex >= 2 %} active{% endif %} item"{% if menuindex != 2 %} style="color: #767676;{% if menuindex > 2 %} font-weight: 400;{% endif %}"{% endif %}>2. Make selections</span>
|
||||||
{{ text }}
|
<span class="ui{% if menuindex >= 3 %} active{% endif %} item"{% if menuindex != 3 %} style="color: #767676;{% if menuindex > 3 %} font-weight: 400;{% endif %}"{% endif %}>3. Review selections</span>
|
||||||
</span>
|
<span class="ui{% if menuindex >= 4 %} active{% endif %} item"{% if menuindex != 4 %} style="color: #767676;{% if menuindex > 4 %} font-weight: 400;{% endif %}"{% endif %}>4. Audit ballot</span>
|
||||||
</li>
|
<span class="ui{% if menuindex >= 5 %} active{% endif %} item"{% if menuindex != 5 %} style="color: #767676;{% if menuindex > 5 %} font-weight: 400;{% endif %}"{% endif %}>5. Cast ballot</span>
|
||||||
{% endmacro %}
|
</div>
|
||||||
|
|
||||||
<ul class="progress-tracker progress-tracker--word progress-tracker--word-center" id="election-tab-menu" style="margin-bottom: 20px;">
|
|
||||||
{{ menuitem(1, "Welcome") }}
|
|
||||||
{{ menuitem(2, "Select") }}
|
|
||||||
{{ menuitem(3, "Review") }}
|
|
||||||
{% if election.can_audit() %}
|
|
||||||
{{ menuitem(4, "Audit") }}
|
|
||||||
{{ menuitem(5, "Cast") }}
|
|
||||||
{{ menuitem(6, "Finish") }}
|
|
||||||
{% else %}
|
|
||||||
{{ menuitem(4, "Cast") }}
|
|
||||||
{{ menuitem(5, "Finish") }}
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
{% block helpbtn %}
|
|
||||||
<button class="tiny ui right floated labeled icon teal button" onclick="$('#modal-help').modal('show');" style="margin-bottom: 1em;"><i class="help circle icon"></i> Help</button>
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui container" style="margin-top: 1em;">
|
<div class="ui container" style="margin-top: 1em;">
|
||||||
<div style="clear: both;"></div>
|
|
||||||
{% block buttons %}{% endblock %}
|
{% block buttons %}{% endblock %}
|
||||||
<div style="clear: both;"></div>
|
<div style="clear: both;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui modal" id="modal-help">
|
|
||||||
<i class="close icon"></i>
|
|
||||||
<div class="header">
|
|
||||||
Help
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
{% block help %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
<div class="actions">
|
|
||||||
<button class="ui approve button">OK</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% block after %}{% endblock %}
|
{% block after %}{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="cast_prompt">
|
<div id="cast_prompt">
|
||||||
<p>Your vote has <span class="superem">not</span> yet been cast.{% if election.can_audit() %} Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.{% endif %}</p>
|
<p>Your vote has <span class="superem">not</span> yet been cast. If you have not already done so, please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
||||||
|
|
||||||
<div class="ui negative message">
|
<div class="ui negative message">
|
||||||
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
||||||
@ -53,125 +53,53 @@
|
|||||||
<p id="error_unknown_tech"></p>
|
<p id="error_unknown_tech"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui basic segment" id="casting" style="display: none; min-height: 4em;">
|
<div id="casting" style="display: none;">
|
||||||
<div class="ui active text loader">Casting your ballot. Please wait.</div>
|
<div class="ui active text loader">Casting your ballot. Please wait.</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
{% if is_cast %}
|
<button class="ui left floated button" onclick="prevTemplate(2);">Back</button>
|
||||||
<a class="ui left floated button" href="{{ election_base_url }}booth">Reset</a>
|
<button class="ui right floated primary button" id="cast_button" onclick="castBallot();"{% if not username %} style="display: none;"{% endif %}>Cast ballot</button>
|
||||||
<button class="ui right floated primary button" id="cast_button" onclick="castBallot();"{% if not username %} style="display: none;"{% endif %}>Cast ballot</button>
|
|
||||||
{% else %}
|
|
||||||
<button class="ui left floated button" onclick="prevTemplate(2);">Back</button>
|
|
||||||
<button class="ui right floated primary button" id="cast_button" onclick="stageBallot(castBallot);"{% if not username %} style="display: none;"{% endif %}>Cast ballot</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after %}
|
{% block after %}
|
||||||
{% if election.can_audit() %}
|
|
||||||
<div class="ui tiny message" style="margin-top: 3em;">
|
|
||||||
<div class="header">Information for advanced users</div>
|
|
||||||
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(".message .close").on("click", function() {
|
$(".message .close").on("click", function() {
|
||||||
$(this).closest(".message").addClass("hidden");
|
$(this).closest(".message").addClass("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
function login(el) {
|
function login(el) {
|
||||||
// Stage the vote for casting in case we change page
|
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||||
stageBallot(function(data) {
|
|
||||||
// Stage the next page in case we change page
|
|
||||||
stageNext("{{ election_base_url }}booth?cast", function(data) {
|
|
||||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function callback_complete(username) {
|
function callback_complete(username) {
|
||||||
$("#cast_button").show();
|
$("#cast_button").show();
|
||||||
$("#booth_logged_in_as").text("You are currently logged in as " + username + ".");
|
$("#booth_logged_in_as").text("You are currently logged in as " + username + ".");
|
||||||
// Ballot was staged when we clicked the login button
|
|
||||||
castBallot();
|
castBallot();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stageBallot(callback) {
|
|
||||||
// Prepare ballot
|
|
||||||
var deauditedBallot = booth.ballot.deaudit();
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: "{{ election_base_url }}stage_ballot",
|
|
||||||
type: "POST",
|
|
||||||
data: eosjs.eos.core.objects.EosObject.to_json({
|
|
||||||
"ballot": eosjs.eos.core.objects.EosObject.serialise_and_wrap(deauditedBallot, null),
|
|
||||||
"fingerprint": booth.fingerprint || null
|
|
||||||
}),
|
|
||||||
contentType: "application/json",
|
|
||||||
dataType: "text",
|
|
||||||
async: false // so window.open happens in main thread
|
|
||||||
})
|
|
||||||
.done(function(data) {
|
|
||||||
callback(data);
|
|
||||||
})
|
|
||||||
.fail(function(xhr, status, err) {
|
|
||||||
if (xhr.responseText && xhr.responseText.length < 100) {
|
|
||||||
$("#error_unknown_tech").text("Technical details: " + err + " – " + xhr.responseText);
|
|
||||||
} else {
|
|
||||||
$("#error_unknown_tech").text("Technical details: " + err);
|
|
||||||
}
|
|
||||||
$("#error_unknown").removeClass("hidden");
|
|
||||||
|
|
||||||
$("#error_invalid_id").addClass("hidden");
|
|
||||||
|
|
||||||
console.error(xhr);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function stageNext(url, callback) {
|
|
||||||
$.ajax({
|
|
||||||
url: "/auth/stage_next",
|
|
||||||
type: "POST",
|
|
||||||
data: url,
|
|
||||||
contentType: "text/plain",
|
|
||||||
dataType: "text",
|
|
||||||
async: false // so window.open happens in main thread
|
|
||||||
})
|
|
||||||
.done(function(data) {
|
|
||||||
callback(data);
|
|
||||||
})
|
|
||||||
.fail(function(xhr, status, err) {
|
|
||||||
if (xhr.responseText && xhr.responseText.length < 100) {
|
|
||||||
$("#error_unknown_tech").text("Technical details: " + err + " – " + xhr.responseText);
|
|
||||||
} else {
|
|
||||||
$("#error_unknown_tech").text("Technical details: " + err);
|
|
||||||
}
|
|
||||||
$("#error_unknown").removeClass("hidden");
|
|
||||||
|
|
||||||
$("#error_invalid_id").addClass("hidden");
|
|
||||||
|
|
||||||
console.error(xhr);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function castBallot() {
|
function castBallot() {
|
||||||
$("#cast_prompt").hide();
|
$("#cast_prompt").hide();
|
||||||
$("#casting").show();
|
$("#casting").show();
|
||||||
|
|
||||||
|
// Prepare ballot
|
||||||
|
booth.ballot = booth.ballot.deaudit();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{{ election_base_url }}cast_ballot",
|
url: "{{ election_base_url }}cast_ballot",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
|
data: eosjs.eos.core.objects.__all__.EosObject.to_json({
|
||||||
|
"ballot": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(booth.ballot, null),
|
||||||
|
"fingerprint": booth.fingerprint || null
|
||||||
|
}),
|
||||||
|
contentType: "application/json",
|
||||||
dataType: "text"
|
dataType: "text"
|
||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
response = eosjs.eos.core.objects.EosObject.from_json(data);
|
response = eosjs.eos.core.objects.__all__.EosObject.from_json(data);
|
||||||
booth.voter = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(response.voter);
|
booth.voter = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(response.voter);
|
||||||
booth.vote = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(response.vote);
|
booth.vote = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(response.vote);
|
||||||
|
|
||||||
// Clear plaintexts
|
// Clear plaintexts
|
||||||
booth.answers = null;
|
booth.answers = null;
|
||||||
@ -201,16 +129,5 @@
|
|||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{% if is_cast %}
|
|
||||||
castBallot();
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block help %}
|
|
||||||
<p>You are now ready to cast your ballot. If you disconnected your internet connection earlier, you must now reconnect it before proceeding.</p>
|
|
||||||
<p>Click the ‘OK’ button below to close this help screen, then click the blue ‘Cast ballot’ button. If there is no ‘Cast ballot’ button, select your login method from the list shown, and enter your voter details. These details may be found in the email or message instructing you to vote.</p>
|
|
||||||
<p>Once you have logged in, your vote will be automatically cast.</p>
|
|
||||||
<p>If you require further assistance, contact your election administrator.</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,13 +19,13 @@
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="ui negative message" style="clear: both;">
|
<p>Your vote has <span class="superem">not</span> yet been cast.</p>
|
||||||
|
|
||||||
|
<div class="ui negative message">
|
||||||
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if election.can_audit() %}
|
<p>If you have not already done so, please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>. Please retain a copy of your ballot fingerprint – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p>
|
||||||
<p>Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>. Please retain a copy of your ballot fingerprint – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>To continue, copy and paste the ballot below and provide it to the election administrator.</p>
|
<p>To continue, copy and paste the ballot below and provide it to the election administrator.</p>
|
||||||
|
|
||||||
@ -39,19 +39,3 @@
|
|||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<button class="ui left floated button" onclick="prevTemplate();">Back</a>
|
<button class="ui left floated button" onclick="prevTemplate();">Back</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after %}
|
|
||||||
{% if election.can_audit() %}
|
|
||||||
<div class="ui tiny message" style="margin-top: 3em;">
|
|
||||||
<div class="header">Information for advanced users</div>
|
|
||||||
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block help %}
|
|
||||||
<p>You are now ready to cast your ballot. As this is a pre-poll voting booth, you must manually provide your ballot to the election administrator.</p>
|
|
||||||
<p>Copy and paste the entire text of the ballot shown in the textbox into a message to the election administrator.</p>
|
|
||||||
<p>Click the ‘OK’ button below to close this help screen.</p>
|
|
||||||
<p>If you require further assistance, contact your election administrator.</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -21,44 +21,17 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<p>Your vote has been successfully cast. The following is your ‘smart ballot tracker’. Please retain a copy of your smart ballot tracker – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p>
|
<p>Your vote has been successfully cast. The following is your ‘smart ballot tracker’. Please retain a copy of your smart ballot tracker – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p>
|
||||||
|
|
||||||
<div class="ui success icon message">
|
<div class="ui success message">
|
||||||
<i class="check circle outline icon"></i>
|
<div class="header">Smart ballot tracker</div>
|
||||||
<div class="content">
|
<p>This smart ballot tracker confirms that {{ voter.name }} cast a vote in the election {{ election.py_name }} at {{ vote.cast_at }}.</p>
|
||||||
<div class="header">Smart ballot tracker</div>
|
<p>Ballot fingerprint: <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span></p>
|
||||||
<p>This smart ballot tracker confirms that {{ voter.py_name }} cast a vote in the election {{ election.py_name }} at {{ vote.cast_at }}.</p>
|
|
||||||
{% if election.can_audit() %}
|
|
||||||
<p>Ballot fingerprint: <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64(true) }}</span></p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if election.can_audit() %}
|
<p>Please check that the ballot fingerprint above matches the ballot fingerprint you recorded earlier.</p>
|
||||||
<p>Please check that the ballot fingerprint above matches the ballot fingerprint you recorded earlier.</p>
|
|
||||||
<p>To confirm that your ballot was cast correctly, please go to the <a href="{{ election_base_url }}view/ballots">‘Voters and ballots’ page</a> for the {{ election.kind }} or click ‘Finish’, and confirm that the above ballot fingerprint appears next to your name.</p>
|
<p>To confirm that your ballot was cast correctly, please go to the <a href="{{ election_base_url }}view/ballots">‘Voters and ballots’ page</a> for the {{ election.kind }} or click ‘Finish’, and confirm that the above ballot fingerprint appears next to your name.</p>
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
{% if election.is_votes_public %}
|
<a href="{{ election_base_url }}view/ballots" class="ui right floated primary button">Finish</a>
|
||||||
<a href="{{ election_base_url }}view/ballots" class="ui right floated primary button">Finish</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{ election_base_url }}view" class="ui right floated primary button">Finish</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block after %}
|
|
||||||
{% if election.can_audit() %}
|
|
||||||
<div class="ui tiny message" style="margin-top: 3em;">
|
|
||||||
<div class="header">Information for advanced users</div>
|
|
||||||
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span>.</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block help %}
|
|
||||||
<p>You have now successfully cast your ballot in this election!</p>
|
|
||||||
<p>The green ‘smart ballot tracker’ shows the details of your vote. Please retain a copy of your smart ballot tracker, as it can be used as proof that you voted in this election, and can be used to verify that your vote is counted corectly.</p>
|
|
||||||
<p>You may now close this window and exit the voting booth.</p>
|
|
||||||
<p>Click the ‘OK’ button below to close this help screen.</p>
|
|
||||||
<p>If you require further assistance, contact your election administrator.</p>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
{% extends templates['booth/base.html'] %}
|
|
||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,63 +16,62 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
{% block content %}
|
<div class="ui active text loader">Preparing your ballot. Please wait.</div>
|
||||||
<div class="ui basic segment" style="min-height: 4em;">
|
|
||||||
<div class="ui active text loader">Preparing your ballot. Please wait.</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block after %}
|
<script>
|
||||||
<script>
|
boothWorker.onmessage = function(msg) {
|
||||||
boothWorker.onmessage = function(msg) {
|
try {
|
||||||
try {
|
rawAnswers = [];
|
||||||
rawAnswers = [];
|
for (var answer_json of booth.answers) {
|
||||||
for (var answer_json of booth.answers) {
|
rawAnswers.push(eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(answer_json, null));
|
||||||
rawAnswers.push(eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null));
|
}
|
||||||
}
|
|
||||||
|
encryptedAnswers = [];
|
||||||
encryptedAnswers = [];
|
for (var encrypted_answer_json of msg.data.encrypted_answers) {
|
||||||
for (var encrypted_answer_json of msg.data.encrypted_answers) {
|
encryptedAnswers.push(eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(encrypted_answer_json, null));
|
||||||
encryptedAnswers.push(eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(encrypted_answer_json, null));
|
}
|
||||||
}
|
|
||||||
|
booth.ballot = eosjs.eos.base.election.__all__.Ballot();
|
||||||
booth.ballot = eosjs.eos.base.election.Ballot();
|
booth.ballot.answers = rawAnswers;
|
||||||
booth.ballot.answers = rawAnswers;
|
booth.ballot.encrypted_answers = encryptedAnswers;
|
||||||
booth.ballot.encrypted_answers = encryptedAnswers;
|
booth.ballot.election_id = election._id;
|
||||||
booth.ballot.election_id = election._id;
|
booth.ballot.election_hash = eosjs.eos.core.hashing.__all__.SHA256().update_obj(election).hash_as_b64();
|
||||||
booth.ballot.election_hash = eosjs.eos.core.hashing.SHA256().update_obj(election).hash_as_b64();
|
|
||||||
|
if (should_do_fingerprint) {
|
||||||
if (should_do_fingerprint) {
|
// String.prototype.join confuses fingerprintjs2
|
||||||
// String.prototype.join confuses fingerprintjs2
|
var strjoin = String.prototype.join;
|
||||||
var strjoin = String.prototype.join;
|
String.prototype.join = undefined;
|
||||||
String.prototype.join = undefined;
|
try {
|
||||||
new Fingerprint2().get(function(result, components) {
|
new Fingerprint2().get(function(result, components) {
|
||||||
String.prototype.join = strjoin;
|
String.prototype.join = strjoin;
|
||||||
booth.fingerprint = components;
|
booth.fingerprint = components;
|
||||||
nextTemplate();
|
nextTemplate();
|
||||||
});
|
});
|
||||||
} else {
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
nextTemplate();
|
nextTemplate();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} else {
|
||||||
boothError(err);
|
nextTemplate();
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
boothWorker.onerror = function(err) {
|
|
||||||
boothError(err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
boothWorker.postMessage({
|
|
||||||
"action": "generateEncryptedVote",
|
|
||||||
"static_base_url": "{{ static_base_url }}",
|
|
||||||
"election": eosjs.eos.core.objects.EosObject.serialise_and_wrap(election, null),
|
|
||||||
"answers": booth.answers
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
boothError(err);
|
boothError(err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
</script>
|
}
|
||||||
{% endblock %}
|
boothWorker.onerror = function(err) {
|
||||||
|
boothError(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
boothWorker.postMessage({
|
||||||
|
"action": "generateEncryptedVote",
|
||||||
|
"static_base_url": "{{ static_base_url }}",
|
||||||
|
"election": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(election, null),
|
||||||
|
"answers": booth.answers
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
boothError(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -30,12 +30,8 @@
|
|||||||
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if election.can_audit() %}
|
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
||||||
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
|
<p>Click ‘Continue’, and you will be able to log into cast your vote.</p>
|
||||||
<p>Click ‘Continue’, and you will be able to log in to cast your vote.</p>
|
|
||||||
{% else %}
|
|
||||||
<p>If you are happy with your selections, then click ‘Continue’, and you will be able to log in to cast your vote.</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
@ -44,18 +40,6 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after %}
|
{% block after %}
|
||||||
{% if election.can_audit() %}
|
<div style="clear: both; margin-bottom: 1em;"></div>
|
||||||
<div class="ui tiny message" style="margin-top: 3em;">
|
<p><small>If you would like to audit your ballot, <a href="#" onclick="nextTemplate(1);">click here</a>. Auditing your ballot is an <b>optional</b> step you can take to check that your vote has been prepared correctly. You do not need to audit your ballot in order to cast a vote.</small></p>
|
||||||
<div class="header">Information for advanced users</div>
|
|
||||||
<p>Your ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
|
||||||
<p>If you would like to audit your ballot, <a href="#" onclick="nextTemplate(1);">click here</a></p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block help %}
|
|
||||||
<p>This screen shows your selections for each of the questions in this election. Please carefully check your selections and ensure they are as you intended.</p>
|
|
||||||
<p>If there are any problems with your selections, they will be shown in red or orange.</p>
|
|
||||||
<p>Click the ‘OK’ button below to close this help screen, then click the blue ‘Continue’ button if you are satisfied with your selections, otherwise, click the ‘Back’ button.</p>
|
|
||||||
<p>If you require further assistance, contact your election administrator.</p>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -30,12 +30,8 @@
|
|||||||
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if election.can_audit() %}
|
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
||||||
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
|
<p>Click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
|
||||||
<p>Click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
|
|
||||||
{% else %}
|
|
||||||
<p>If you are happy with your selections, then click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -33,12 +33,7 @@
|
|||||||
boothError("Question template unable to save selections");
|
boothError("Question template unable to save selections");
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadHelp() {
|
|
||||||
$("#modal-help .content").html($("#selections-make-help").html());
|
|
||||||
}
|
|
||||||
|
|
||||||
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||||
loadHelp();
|
|
||||||
|
|
||||||
function previousQuestion() {
|
function previousQuestion() {
|
||||||
saveSelections();
|
saveSelections();
|
||||||
@ -47,20 +42,16 @@
|
|||||||
} else {
|
} else {
|
||||||
booth.questionNum--;
|
booth.questionNum--;
|
||||||
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||||
loadHelp();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextQuestion() {
|
function nextQuestion() {
|
||||||
if (!saveSelections()) {
|
saveSelections();
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (booth.questionNum == election.questions.__len__() - 1) {
|
if (booth.questionNum == election.questions.__len__() - 1) {
|
||||||
nextTemplate();
|
nextTemplate();
|
||||||
} else {
|
} else {
|
||||||
booth.questionNum++;
|
booth.questionNum++;
|
||||||
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||||
loadHelp();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,26 +18,12 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
{% block helpbtn %}
|
|
||||||
{# Don't float – we want it to overlap with the image #}
|
|
||||||
<div style="position: relative;">
|
|
||||||
<button class="tiny ui labeled icon teal button" style="position: absolute; right: 0; z-index: 1; margin: 0;" onclick="$('#modal-help').modal('show');"><i class="help circle icon"></i> Help</button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<img src="/static/img/logo.png" style="width: 100%; max-width: 500px; display: block; margin: 0 auto;">
|
|
||||||
|
|
||||||
<img src="/static/img/guide.png" style="width: 100%; max-width: 500px; display: block; margin: 0 auto; margin-top: 1em;">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block help %}
|
|
||||||
<p>Welcome to the {{ election.name }} voting booth.</p>
|
<p>Welcome to the {{ election.name }} voting booth.</p>
|
||||||
<p>Follow the on-screen directions to prepare and cast your ballot in this {{ election.kind }}. The bar at the top of the page will show your progress. Please note that your ballot will <b>not be cast until</b> you complete the final ‘Cast ballot’ stage and receive a <b>‘smart ballot tracker’</b>.</p>
|
<p>Follow the on-screen directions to prepare and cast your ballot in this {{ election.kind }}. The bar above will show your progress. Please note that your ballot will <b>not be cast until</b> you complete the final ‘Cast ballot’ stage and receive a <b>‘smart ballot tracker’</b>.</p>
|
||||||
<p>If at any point you wish to return to a previous screen, click the ‘Back’ button.</p>
|
<p>If at any point you wish to return to a previous screen, click the ‘Back’ button below.</p>
|
||||||
{# <p>If you wish, you may disconnect your internet connection now while preparing your ballot, however you must re-connect your internet connection before logging in to cast your ballot.</p> #}
|
<p>If you wish, you may disconnect your internet connection now while preparing your ballot, however you must re-connect your internet connection before logging in to cast your ballot.</p>
|
||||||
<p>Click the ‘OK’ button below to close this help screen, then click the blue ‘Continue’ button to continue to the next step.</p>
|
<p>Please click the blue ‘Continue’ button below to continue.</p>
|
||||||
<p>If you require further assistance, contact your election administrator.</p>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,55 +18,18 @@
|
|||||||
|
|
||||||
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
||||||
|
|
||||||
{% if election.questions.__getitem__(questionNum).description %}
|
<p><small>Vote for between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }} choices. Click the check-boxes to the left of the choices to make your selection, then click the ‘Continue’ button. If you make a mistake, click the check-boxes again to clear your selection.</small></p>
|
||||||
<p>{{ election.questions.__getitem__(questionNum).description | urlize | replace('<a ', '<a target="_blank" ') | safe }}</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p><small>
|
|
||||||
Vote for
|
|
||||||
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
|
||||||
exactly {{ election.questions.__getitem__(questionNum).min_choices }}
|
|
||||||
{% elif election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices - 1 %}
|
|
||||||
either {{ election.questions.__getitem__(questionNum).min_choices }} or {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
||||||
{% else %}
|
|
||||||
between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
||||||
{% endif %}
|
|
||||||
choices.
|
|
||||||
{% if is_radio %}
|
|
||||||
Click the radio buttons to the left of the choices to make your selection, then click the ‘Continue button’. If you make a mistake, click another radio button to change your selection.
|
|
||||||
{% else %}
|
|
||||||
Click the check-boxes to the left of the choices to make your selection, then click the ‘Continue’ button. If you make a mistake, click the check-boxes again to clear your selection.
|
|
||||||
{% endif %}
|
|
||||||
</small></p>
|
|
||||||
|
|
||||||
{% set is_radio = election.questions.__getitem__(questionNum).max_choices == 1 %}
|
|
||||||
|
|
||||||
<div id="question-choices" class="ui form" style="margin-bottom: 1em;">
|
<div id="question-choices" class="ui form" style="margin-bottom: 1em;">
|
||||||
<div class="grouped fields">
|
<div class="grouped fields">
|
||||||
{% for choice in election.questions.__getitem__(questionNum).randomised_choices().impl %}
|
{% for choice in election.questions.__getitem__(questionNum).randomised_choices().impl %}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui{% if is_radio %} radio{% endif %} checkbox">
|
<div class="ui checkbox">
|
||||||
<input
|
<input type="checkbox" id="question-choice-{{ election.questions.__getitem__(questionNum).choices.impl.indexOf(choice) }}" onchange="choicesChanged();">
|
||||||
{% if is_radio %}
|
|
||||||
name="question-choices"
|
|
||||||
type="radio"
|
|
||||||
{% else %}
|
|
||||||
type="checkbox"
|
|
||||||
{% endif %}
|
|
||||||
id="question-choice-{{ election.questions.__getitem__(questionNum).choices.impl.indexOf(choice) }}" onchange="choicesChanged();">
|
|
||||||
|
|
||||||
<label for="question-choice-{{ election.questions.__getitem__(questionNum).choices.impl.indexOf(choice) }}">{{ choice.name }}{% if choice.party %} – {{ choice.party }}{% endif %}</label>
|
<label for="question-choice-{{ election.questions.__getitem__(questionNum).choices.impl.indexOf(choice) }}">{{ choice.name }}{% if choice.party %} – {{ choice.party }}{% endif %}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if is_radio and election.questions.__getitem__(questionNum).min_choices == 0 %}
|
|
||||||
<div class="field">
|
|
||||||
<div class="ui{% if is_radio %} radio{% endif %} checkbox">
|
|
||||||
<input name="question-choices" type="radio" id="question-choice-none" onchange="choicesChanged();" checked>
|
|
||||||
<label for="question-choice-none"><i>None of the above</i></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -76,17 +39,15 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function choicesChanged() {
|
function choicesChanged() {
|
||||||
{% if not is_radio %}
|
var numChoices = $("#question-choices input:checked").length;
|
||||||
var numChoices = $("#question-choices input:checked").length;
|
if (numChoices >= election.questions.__getitem__(booth.questionNum).max_choices) {
|
||||||
if (numChoices >= election.questions.__getitem__(booth.questionNum).max_choices) {
|
// Prevent making any more selections
|
||||||
// Prevent making any more selections
|
$("#question-choices input:not(:checked)").prop("disabled", true);
|
||||||
$("#question-choices input:not(:checked)").prop("disabled", true);
|
$("#message-max-choices").removeClass("hidden");
|
||||||
$("#message-max-choices").removeClass("hidden");
|
} else {
|
||||||
} else {
|
$("#question-choices input").prop("disabled", false);
|
||||||
$("#question-choices input").prop("disabled", false);
|
$("#message-max-choices").addClass("hidden");
|
||||||
$("#message-max-choices").addClass("hidden");
|
}
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in ballot with previous selections
|
// Fill in ballot with previous selections
|
||||||
@ -100,40 +61,9 @@
|
|||||||
function saveSelections() {
|
function saveSelections() {
|
||||||
selections = [];
|
selections = [];
|
||||||
$("#question-choices input:checked").each(function(i, el) {
|
$("#question-choices input:checked").each(function(i, el) {
|
||||||
if (el.id !== "question-choice-none") {
|
selections.push(parseInt(el.id.substring("question-choice-".length)));
|
||||||
selections.push(parseInt(el.id.substring("question-choice-".length)));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
answer = eosjs.eos.base.election.__all__.ApprovalAnswer(eosjs.__kwargtrans__({choices: selections}));
|
||||||
if (selections.length < election.questions.__getitem__(booth.questionNum).min_choices) {
|
booth.answers[booth.questionNum] = eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(answer);
|
||||||
if (!window.confirm('You have selected fewer than the minimum required number of choices. If you proceed to cast this ballot, it will **NOT** be counted. If this was not your intention, please click the "Cancel" button below now, and correct your selections.')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
answer = eosjs.eos.base.election.ApprovalAnswer(eosjs.__kwargtrans__({choices: selections}));
|
|
||||||
booth.answers[booth.questionNum] = eosjs.eos.core.objects.EosObject.serialise_and_wrap(answer);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="selections-make-help">
|
|
||||||
<p>{% if is_radio %}This is a vote-for-one question.{% else %}This is an approval voting question.{% endif %} You are required to vote for
|
|
||||||
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
|
||||||
exactly {{ election.questions.__getitem__(questionNum).min_choices }}
|
|
||||||
{% elif election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices - 1 %}
|
|
||||||
either {{ election.questions.__getitem__(questionNum).min_choices }} or {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
||||||
{% else %}
|
|
||||||
between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
||||||
{% endif %}
|
|
||||||
choices.</p>
|
|
||||||
{% if is_radio %}
|
|
||||||
<p>Click or tap the radio buttons to the left of the choices to make your selection. If you make a mistake, click or tap another radio button to change your selection.</p>
|
|
||||||
{% else %}
|
|
||||||
<p>Click or tap the check-boxes to the left of the choices to make your selection. If you make a mistake, click or tap the check-boxes again to clear your selection.</p>
|
|
||||||
{% endif %}
|
|
||||||
<p>Once you are satisfied with your selections, click the ‘Continue’ button.</p>
|
|
||||||
<p>Click the ‘OK’ button below to close this help screen.</p>
|
|
||||||
<p>If you require further assistance, contact your election administrator.</p>
|
|
||||||
</div>
|
|
||||||
|
@ -30,11 +30,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if booth.answers[loop.index0].value.choices.length < question.min_choices %}
|
{% if booth.answers[loop.index0].value.choices.length < question.max_choices %}
|
||||||
<div class="ui error message">
|
|
||||||
<p>You have selected fewer than the minimum required number of choices. If you proceed to cast this ballot, it will <span class="superem">not</span> be counted. If this was not your intention, please click the ‘Back’ button below now, and correct your selections.</p>
|
|
||||||
</div>
|
|
||||||
{% elif booth.answers[loop.index0].value.choices.length < question.max_choices %}
|
|
||||||
<div class="ui warning message">
|
<div class="ui warning message">
|
||||||
<p>You have selected fewer than the maximum allowed number of choices. If this was not your intention, please click the ‘Back’ button below now, and correct your selections.</p>
|
<p>You have selected fewer than the maximum allowed number of choices. If this was not your intention, please click the ‘Back’ button below now, and correct your selections.</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,21 +18,7 @@
|
|||||||
|
|
||||||
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
||||||
|
|
||||||
{% if election.questions.__getitem__(questionNum).description %}
|
<p><small>Vote for between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }} choices. Click and drag the choices from the grey box to the blue box in order from most-preferred to least-preferred. It is in your best interests to vote for as many choices as you can.</small></p>
|
||||||
<p>{{ election.questions.__getitem__(questionNum).description | urlize | replace('<a ', '<a target="_blank" ') | safe }}</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p><small>
|
|
||||||
Vote for
|
|
||||||
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
|
||||||
exactly {{ election.questions.__getitem__(questionNum).min_choices }}
|
|
||||||
{% elif election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices - 1 %}
|
|
||||||
either {{ election.questions.__getitem__(questionNum).min_choices }} or {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
||||||
{% else %}
|
|
||||||
between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
||||||
{% endif %}
|
|
||||||
choices. Click and drag the choices from the grey box to the blue box in order from most-preferred to least-preferred. It is in your best interests to vote for as many choices as you can.
|
|
||||||
</small></p>
|
|
||||||
|
|
||||||
<div id="question-choices-selected" class="preferential-choices">
|
<div id="question-choices-selected" class="preferential-choices">
|
||||||
<div style="color: #3465a4;">Options voted for:</div>
|
<div style="color: #3465a4;">Options voted for:</div>
|
||||||
@ -52,14 +38,7 @@
|
|||||||
|
|
||||||
{% macro printchoice(choice, ticket=None) %}
|
{% macro printchoice(choice, ticket=None) %}
|
||||||
<div class="preferential-choice" data-choiceno="{{ flat_choices.indexOf(choice) }}">
|
<div class="preferential-choice" data-choiceno="{{ flat_choices.indexOf(choice) }}">
|
||||||
<div class="number">
|
<div class="number"></div>
|
||||||
<select class="ui dropdown">
|
|
||||||
<option selected></option>
|
|
||||||
{% for i in range(flat_choices|length) %}
|
|
||||||
<option>{{ i + 1 }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="candidate-name">{{ choice.name }}</div>
|
<div class="candidate-name">{{ choice.name }}</div>
|
||||||
{% if choice.party %}
|
{% if choice.party %}
|
||||||
@ -83,14 +62,7 @@
|
|||||||
{% if choice.choices %}
|
{% if choice.choices %}
|
||||||
{# Ticket #}
|
{# Ticket #}
|
||||||
<div class="preferential-choice ticket">
|
<div class="preferential-choice ticket">
|
||||||
<div class="number">
|
<div class="number"></div>
|
||||||
<select class="ui dropdown">
|
|
||||||
<option selected></option>
|
|
||||||
{% for i in range(flat_choices|length) %}
|
|
||||||
<option>{{ i + 1 }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="party-name">{{ choice.name }}</div>
|
<div class="party-name">{{ choice.name }}</div>
|
||||||
<div class="ticket-choices">
|
<div class="ticket-choices">
|
||||||
@ -113,18 +85,18 @@
|
|||||||
|
|
||||||
function choicesChanged() {
|
function choicesChanged() {
|
||||||
// Recalculate numbers
|
// Recalculate numbers
|
||||||
$(".preferential-choices .preferential-choice .number select").val("");
|
$(".preferential-choices .preferential-choice .number").each(function(i, el) {
|
||||||
var selectedChoices = $("#question-choices-selected .dragarea > .preferential-choice > .number select");
|
$(el).text("");
|
||||||
selectedChoices.each(function(i, el) {
|
|
||||||
$(el).val(i + 1);
|
|
||||||
});
|
});
|
||||||
var selectedCandidates = $("#question-choices-selected .preferential-choice:not(.ticket) .number select");
|
var selectedChoices = $("#question-choices-selected .dragarea > .preferential-choice > .number");
|
||||||
|
selectedChoices.each(function(i, el) {
|
||||||
|
$(el).text(i + 1);
|
||||||
|
});
|
||||||
|
var selectedCandidates = $("#question-choices-selected .preferential-choice:not(.ticket) .number");
|
||||||
if (selectedCandidates.length >= election.questions.__getitem__(booth.questionNum).max_choices) {
|
if (selectedCandidates.length >= election.questions.__getitem__(booth.questionNum).max_choices) {
|
||||||
// Prevent making any more selections
|
// Prevent making any more selections
|
||||||
allowAdding = false;
|
allowAdding = false;
|
||||||
|
|
||||||
$("#question-choices-remaining .preferential-choice .number select").prop("disabled", true);
|
|
||||||
|
|
||||||
if (selectedCandidates.length > election.questions.__getitem__(booth.questionNum).max_choices) {
|
if (selectedCandidates.length > election.questions.__getitem__(booth.questionNum).max_choices) {
|
||||||
// Prevent progression
|
// Prevent progression
|
||||||
$(".primary.button").addClass("disabled");
|
$(".primary.button").addClass("disabled");
|
||||||
@ -135,12 +107,9 @@
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
allowAdding = true;
|
allowAdding = true;
|
||||||
|
|
||||||
$("#question-choices-remaining .preferential-choice .number select").prop("disabled", false);
|
|
||||||
|
|
||||||
$(".primary.button").removeClass("disabled");
|
|
||||||
$("#message-max-choices").addClass("hidden");
|
$("#message-max-choices").addClass("hidden");
|
||||||
$("#message-too-many-choices").addClass("hidden");
|
$("#message-too-many-choices").addClass("hidden");
|
||||||
|
$(".primary.button").removeClass("disabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,10 +120,6 @@
|
|||||||
choicesChanged();
|
choicesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============
|
|
||||||
// DRAG AND DROP
|
|
||||||
// =============
|
|
||||||
|
|
||||||
var dragulaChoices = dragula(
|
var dragulaChoices = dragula(
|
||||||
[document.querySelector("#question-choices-selected .dragarea"), document.querySelector("#question-choices-remaining .dragarea")].concat([].slice.apply(document.querySelectorAll(".ticket-choices"))),
|
[document.querySelector("#question-choices-selected .dragarea"), document.querySelector("#question-choices-remaining .dragarea")].concat([].slice.apply(document.querySelectorAll(".ticket-choices"))),
|
||||||
{
|
{
|
||||||
@ -172,111 +137,32 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
function breakTicket(ticket) {
|
function breakTicket(ticket) {
|
||||||
ticket.find(".ticket-choices").first().children(".preferential-choice").detach().insertAfter(ticket);
|
ticket.find(".ticket-choices .preferential-choice").each(function(i, el) {
|
||||||
|
$(el).detach().insertAfter(ticket);
|
||||||
|
});
|
||||||
ticket.remove();
|
ticket.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
dragulaChoices.on("drop", function(el, target, source, sibling) {
|
dragulaChoices.on("drop", function(el, target, source, sibling) {
|
||||||
// If the source or target is a ticket, break the ticket if necessary
|
// If the source or target is a ticket, break the ticket
|
||||||
if ($(source).parents(".ticket").length > 0) {
|
if ($(source).parents(".ticket").length > 0) {
|
||||||
// This is a candidate dragged out of a ticket – break the ticket
|
breakTicket($(source).parents(".ticket").first())
|
||||||
breakTicket($(source).parents(".ticket").first());
|
|
||||||
}
|
}
|
||||||
if ($(target).parents(".ticket").length > 0) {
|
if ($(target).parents(".ticket").length > 0) {
|
||||||
// This is a candidate/ticket dragged into a .ticket-choices
|
breakTicket($(target).parents(".ticket").first())
|
||||||
// Was it dragged to the first or last position?
|
|
||||||
if (sibling == null) {
|
|
||||||
// Dragged to the end – just move it away
|
|
||||||
$(el).detach().insertAfter($(target).parents(".ticket").first());
|
|
||||||
} else if(sibling == $(target).children(".preferential-choice:not(.ticket)").first().get(0)) {
|
|
||||||
// Dragged to the beginning – just move it away
|
|
||||||
$(el).detach().insertBefore($(target).parents(".ticket").first());
|
|
||||||
} else {
|
|
||||||
// Dragged into the middle – break the ticket
|
|
||||||
breakTicket($(target).parents(".ticket").first());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
choicesChanged();
|
choicesChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===============
|
|
||||||
// DROP DOWN BOXES
|
|
||||||
// ===============
|
|
||||||
|
|
||||||
$(".preferential-choices .preferential-choice .number select").change(function(evt) {
|
|
||||||
var index = parseInt($(this).val());
|
|
||||||
|
|
||||||
var choiceEl = $(this).parents(".preferential-choice").first();
|
|
||||||
var ticket = null;
|
|
||||||
|
|
||||||
// If the source is within a ticket, break the ticket afterwards
|
|
||||||
// (It is impossible for the target to be within a ticket)
|
|
||||||
if (choiceEl.parents(".ticket").length > 0) {
|
|
||||||
ticket = choiceEl.parents(".ticket").first();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate drag-and-drop
|
|
||||||
var selectedChoices = $("#question-choices-selected .dragarea > .preferential-choice");
|
|
||||||
if ($(this).val() === "") {
|
|
||||||
// Deselect
|
|
||||||
choiceEl.detach().appendTo($("#question-choices-remaining .dragarea"));
|
|
||||||
} else if (index > selectedChoices.length) {
|
|
||||||
// Append to end
|
|
||||||
choiceEl.detach().appendTo($("#question-choices-selected .dragarea"));
|
|
||||||
} else {
|
|
||||||
// Insert in the middle
|
|
||||||
choiceEl.detach().insertBefore(selectedChoices[index - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now break the ticket
|
|
||||||
if (ticket !== null) {
|
|
||||||
breakTicket(ticket);
|
|
||||||
}
|
|
||||||
|
|
||||||
choicesChanged();
|
|
||||||
});
|
|
||||||
|
|
||||||
// =======
|
|
||||||
// GENERAL
|
|
||||||
// =======
|
|
||||||
|
|
||||||
function saveSelections() {
|
function saveSelections() {
|
||||||
selections = [];
|
selections = [];
|
||||||
$("#question-choices-selected .preferential-choice:not(.ticket)").each(function(i, el) {
|
$("#question-choices-selected .preferential-choice:not(.ticket)").each(function(i, el) {
|
||||||
selections.push(parseInt(el.dataset.choiceno));
|
selections.push(parseInt(el.dataset.choiceno));
|
||||||
});
|
});
|
||||||
|
answer = eosjs.eos.base.election.__all__.PreferentialAnswer(eosjs.__kwargtrans__({choices: selections}));
|
||||||
if (selections.length < election.questions.__getitem__(booth.questionNum).min_choices) {
|
booth.answers[booth.questionNum] = eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(answer);
|
||||||
if (!window.confirm('You have selected fewer than the minimum required number of choices. If you proceed to cast this ballot, it will **NOT** be counted. If this was not your intention, please click the "Cancel" button below now, and correct your selections.')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
answer = eosjs.eos.base.election.PreferentialAnswer(eosjs.__kwargtrans__({choices: selections}));
|
|
||||||
booth.answers[booth.questionNum] = eosjs.eos.core.objects.EosObject.serialise_and_wrap(answer);
|
|
||||||
|
|
||||||
booth.q_state[booth.questionNum] = [$("#question-choices-selected .dragarea").html(), $("#question-choices-remaining .dragarea").html()]; // wew lad
|
booth.q_state[booth.questionNum] = [$("#question-choices-selected .dragarea").html(), $("#question-choices-remaining .dragarea").html()]; // wew lad
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="selections-make-help">
|
|
||||||
<p>This is a preferential voting question. You are required to vote for
|
|
||||||
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
|
||||||
exactly {{ election.questions.__getitem__(questionNum).min_choices }}
|
|
||||||
{% elif election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices - 1 %}
|
|
||||||
either {{ election.questions.__getitem__(questionNum).min_choices }} or {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
||||||
{% else %}
|
|
||||||
between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
||||||
{% endif %}
|
|
||||||
choices.</p>
|
|
||||||
<p>If using a desktop computer or a laptop, use your mouse to click and drag the choices from the grey box to the blue box.</p>
|
|
||||||
<p>If using a smartphone or tablet, use your finger to drag the choices from the grey box to the blue box.</p>
|
|
||||||
<p>You should order your choices inside the blue box in order from most-preferred (at the top) to least-preferred (at the bottom).</p>
|
|
||||||
<p>It is in your best interests to vote for as many choices as you can.</p>
|
|
||||||
<p>Once you are satisfied with your selections, click the ‘Continue’ button.</p>
|
|
||||||
<p>Click the ‘OK’ button below to close this help screen.</p>
|
|
||||||
<p>If you require further assistance, contact your election administrator.</p>
|
|
||||||
</div>
|
|
||||||
|
@ -35,11 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if booth.answers[loop.index0].value.choices.length < question.min_choices %}
|
{% if booth.answers[loop.index0].value.choices.length < question.max_choices %}
|
||||||
<div class="ui error message">
|
|
||||||
<p>You have selected fewer than the minimum required number of choices. If you proceed to cast this ballot, it will <span class="superem">not</span> be counted. If this was not your intention, please click the ‘Back’ button below now, and correct your selections.</p>
|
|
||||||
</div>
|
|
||||||
{% elif booth.answers[loop.index0].value.choices.length < question.max_choices %}
|
|
||||||
<div class="ui warning message">
|
<div class="ui warning message">
|
||||||
<p>You have selected fewer than the maximum allowed number of choices. If this was not your intention, please click the ‘Back’ button below now, and correct your selections.</p>
|
<p>You have selected fewer than the maximum allowed number of choices. If this was not your intention, please click the ‘Back’ button below now, and correct your selections.</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
# Eos - Verifiable elections
|
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from . import emails
|
|
||||||
|
|
||||||
from eos.core.tasks import *
|
|
||||||
from eos.base.election import *
|
|
||||||
|
|
||||||
class WebTask(Task):
|
|
||||||
def error(self):
|
|
||||||
emails.task_email_failure(self)
|
|
||||||
|
|
||||||
class WorkflowTaskEntryWebTask(WorkflowTaskEntryTask, WebTask):
|
|
||||||
pass
|
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -26,26 +26,26 @@
|
|||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="header">Active tasks</div>
|
<div class="header">Active tasks</div>
|
||||||
{% for task in eos.core.tasks.TaskScheduler.active_tasks() %}
|
{% for task in eos.core.tasks.TaskScheduler.active_tasks() %}
|
||||||
<a class="item" href="{{ url_for('task_view', task_id=task._id) }}">
|
<div class="item">
|
||||||
{{ task.label }}
|
{{ task.label }}
|
||||||
<br><small><i class="wait icon"></i> started {{ task.started_at|pretty_date }}</small>
|
<br><small><i class="wait icon"></i> started {{ task.started_at|pretty_date }}</small>
|
||||||
</a>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="header">Pending tasks</div>
|
<div class="header">Pending tasks</div>
|
||||||
{% for task in eos.core.tasks.TaskScheduler.pending_tasks() %}
|
{% for task in eos.core.tasks.TaskScheduler.pending_tasks() %}
|
||||||
<a class="item" href="{{ url_for('task_view', task_id=task._id) }}">
|
<div class="item">
|
||||||
{{ task.label }}
|
{{ task.label }}
|
||||||
<br><small><i class="wait icon"></i> due {{ task.run_at|pretty_date }}</small>
|
<br><small><i class="wait icon"></i> due {{ task.run_at|pretty_date }}</small>
|
||||||
</a>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="header">Recently completed tasks</div>
|
<div class="header">Recently completed tasks</div>
|
||||||
{% for task in eos.core.tasks.TaskScheduler.completed_tasks(3) %}
|
{% for task in eos.core.tasks.TaskScheduler.completed_tasks(3) %}
|
||||||
<a class="item" href="{{ url_for('task_view', task_id=task._id) }}">
|
<div class="item">
|
||||||
{% if task.status.is_error() %}<i class="warning sign icon"></i> {% endif %}{{ task.label }}
|
{% if task.status < 0 %}<i class="warning sign icon"></i> {% endif %}{{ task.label }}
|
||||||
<br><small><i class="wait icon"></i> completed {{ task.completed_at|pretty_date }}</small>
|
<br><small><i class="wait icon"></i> completed {{ task.completed_at|pretty_date }}</small>
|
||||||
</a>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -31,12 +31,6 @@
|
|||||||
<li><a href="/auth/{{ auth_method[0] }}/login" target="_blank" onclick="login(this);return false;">{{ auth_method[1] }}</a></li>
|
<li><a href="/auth/{{ auth_method[0] }}/login" target="_blank" onclick="login(this);return false;">{{ auth_method[1] }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="ui hidden error message">
|
|
||||||
<div class="header">Error logging in</div>
|
|
||||||
<p>Your log in details appear to be correct, however there was an unknown error while logging you in.</p>
|
|
||||||
<p>Please try again. If the problem persists, contact your election administrator.</p>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block basecontent %}
|
{% block basecontent %}
|
||||||
@ -46,8 +40,8 @@
|
|||||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||||
}
|
}
|
||||||
|
|
||||||
function callback_complete() {
|
function callback_complete(name) {
|
||||||
window.location = "{{ url_for('login_callback') }}";
|
window.location = "/";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -23,46 +23,33 @@
|
|||||||
{% block basecontent %}
|
{% block basecontent %}
|
||||||
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
||||||
<div class="column" style="max-width: 400px;">
|
<div class="column" style="max-width: 400px;">
|
||||||
<div class="ui hidden success message" id="success-popup">
|
<div class="ui hidden success message">
|
||||||
<div class="header">Log in successful</div>
|
<div class="header">Log in successful</div>
|
||||||
<p>You have successfully logged in to your account.</p>
|
<p>You have successfully logged in to your account.</p>
|
||||||
<p>You may now close this window and return to your previous page.</p>
|
<p>You may now close this window and return to your previous page.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui hidden success message" id="success-redirect">
|
|
||||||
<div class="header">Log in successful</div>
|
|
||||||
<p>You have successfully logged in to your account.</p>
|
|
||||||
<p>Please wait, you should be returned to your previous page momentarily.</p>
|
|
||||||
</div>
|
|
||||||
<div class="ui hidden error message">
|
<div class="ui hidden error message">
|
||||||
<div class="header">Error logging in</div>
|
<div class="header">Error logging in</div>
|
||||||
<p>Your log in details appear to be correct, however there was an unknown error while logging you in.</p>
|
<p>Your log in details appear to be correct, however there was an unknown error while logging you in.</p>
|
||||||
<p>Please close this window and try again. If the problem persists, contact your election administrator.</p>
|
<p>Please close this window try again. If the problem persists, contact your election administrator.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
if (window.opener && window.opener.callback_complete) {
|
try {
|
||||||
// Normal popup window
|
var result = window.opener.callback_complete("{{ session.user.name }}");
|
||||||
// Redirect happens in the opener
|
if (result) {
|
||||||
try {
|
$(".success.message").removeClass("hidden");
|
||||||
var result = window.opener.callback_complete("{{ session.user.name }}");
|
|
||||||
if (result) {
|
window.setTimeout(function() {
|
||||||
$("#success-popup").removeClass("hidden");
|
window.close();
|
||||||
setTimeout(window.close, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
$(".error.message").removeClass("hidden");
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
$(".error.message").removeClass("hidden");
|
$(".error.message").removeClass("hidden");
|
||||||
console.error(ex);
|
|
||||||
throw ex;
|
|
||||||
}
|
}
|
||||||
} else {
|
} catch (ex) {
|
||||||
// This browser does not support popups
|
$(".error.message").removeClass("hidden");
|
||||||
// Manually redirect in this window
|
|
||||||
$("#success-redirect").removeClass("hidden");
|
|
||||||
window.location = "{{ url_for('login_callback') }}";
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -29,7 +29,7 @@
|
|||||||
<a href="https://github.com/RunasSudo/Eos" class="item">Source Code</a>
|
<a href="https://github.com/RunasSudo/Eos" class="item">Source Code</a>
|
||||||
{% if session.user %}
|
{% if session.user %}
|
||||||
{% if session.user.is_admin() %}
|
{% if session.user.is_admin() %}
|
||||||
{% include 'task/active_tasks_menu.html' %}
|
{% include 'active_tasks_menu.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="ui simple dropdown item right">
|
<div class="ui simple dropdown item right">
|
||||||
<i class="{% if session.user.is_admin() %}legal{% else %}user circle{% endif %} icon"></i> {{ session.user.name }} <i class="dropdown icon"></i>
|
<i class="{% if session.user.is_admin() %}legal{% else %}user circle{% endif %} icon"></i> {{ session.user.name }} <i class="dropdown icon"></i>
|
||||||
@ -42,7 +42,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui main text container" id="main_container" {% block mainContainerOpts %}{% endblock %}>
|
<div class="ui main text container" id="main_container">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{% for task in election.workflow.tasks %}
|
{% for task in election.workflow.tasks %}
|
||||||
{% if task.status == eos.base.workflow.WorkflowTaskStatus.READY %}
|
{% if task.status == eos.base.workflow.WorkflowTask.Status.READY %}
|
||||||
<li><a href="{{ url_for('election_admin_enter_task', election_id=election._id, task_name=task._name) }}" onclick="return window.confirm('Are you sure you want to execute the task \'{{ task.label }}\'? This action is irreversible.');">{{ task.label }}</a></li>
|
<li><a href="{{ url_for('election_admin_enter_task', election_id=election._id, task_name=task._name) }}" onclick="return window.confirm('Are you sure you want to execute the task \'{{ task.label }}\'? This action is irreversible.');">{{ task.label }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -57,7 +57,7 @@
|
|||||||
$("#election-tab-menu .item").removeClass("active");
|
$("#election-tab-menu .item").removeClass("active");
|
||||||
linkEl.addClass("active");
|
linkEl.addClass("active");
|
||||||
|
|
||||||
$("#election-tab-content").html('<div class="ui basic segment" style="min-height: 8em;"><div class="ui active text loader">Loading. Please wait.</div></div>');
|
$("#election-tab-content").html('<div class="ui active text loader">Loading. Please wait.</div>');
|
||||||
|
|
||||||
$("#election-tab-content").load(linkEl.attr("href") + " #election-tab-content", function() {
|
$("#election-tab-content").load(linkEl.attr("href") + " #election-tab-content", function() {
|
||||||
linkEl.find(".loader").removeClass("active");
|
linkEl.find(".loader").removeClass("active");
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,11 +19,5 @@
|
|||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
{{ tab('Overview', 'election_view') }}
|
{{ tab('Overview', 'election_view') }}
|
||||||
{{ tab('Questions', 'election_view_questions') }}
|
{{ tab('Questions', 'election_view_questions') }}
|
||||||
{% if election.is_voters_public or (session.user and session.user.is_admin()) %}
|
{{ tab('Voters and ballots', 'election_view_ballots') }}
|
||||||
{% if election.is_votes_public or (session.user and session.user.is_admin()) %}
|
|
||||||
{{ tab('Voters and ballots', 'election_view_ballots') }}
|
|
||||||
{% else %}
|
|
||||||
{{ tab('Voters', 'election_view_ballots') }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,30 +19,21 @@
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
{% block electioncontent %}
|
{% block electioncontent %}
|
||||||
<table class="ui selectable celled table">
|
<table class="ui celled table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Voter</th>
|
<th>Voter</th>
|
||||||
{% if election.is_votes_public or (session.user and session.user.is_admin()) %}
|
<th>Ballot fingerprint</th>
|
||||||
<th>Ballot fingerprint</th>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for voter in election.voters %}
|
{% for voter in election.voters %}
|
||||||
<tr>
|
<tr>
|
||||||
{% if election.is_votes_public or (session.user and session.user.is_admin()) %}
|
<td>{{ voter.name }}</td>
|
||||||
<td class="selectable"><a href="{{ url_for('election_voter_view', election_id=election._id, voter_id=voter._id) }}">{{ voter.name }}</a></td>
|
{% if voter.votes|length > 0 %}
|
||||||
{% set votes = voter.votes.get_all() %}
|
<td class="hash">{{ SHA256().update_obj(voter.votes[-1].ballot).hash_as_b64() }}</td>
|
||||||
<td class="selectable"><a href="{{ url_for('election_voter_view', election_id=election._id, voter_id=voter._id) }}">
|
|
||||||
{% if votes|length > 0 %}
|
|
||||||
<span class="hash">{{ SHA256().update_obj(votes[-1].ballot).hash_as_b64(True) }}</span>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
</a></td>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ voter.name }}</td>
|
<td class="hash"></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -23,7 +23,6 @@
|
|||||||
{% block head %}
|
{% block head %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='bower_components/dragula.js/dist/dragula.min.css') }}" type="text/css">
|
<link rel="stylesheet" href="{{ url_for('static', filename='bower_components/dragula.js/dist/dragula.min.css') }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='bower_components/progress-tracker/app/styles/progress-tracker.css') }}" type="text/css">
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -44,8 +43,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var eosjs = require("eosjs");
|
|
||||||
|
|
||||||
var templates = {};
|
var templates = {};
|
||||||
var election = null;
|
var election = null;
|
||||||
var booth = null;
|
var booth = null;
|
||||||
@ -69,18 +66,10 @@
|
|||||||
resetBooth();
|
resetBooth();
|
||||||
|
|
||||||
function loadElection() {
|
function loadElection() {
|
||||||
// Verify booth
|
|
||||||
if (should_do_fingerprint) {
|
|
||||||
if (typeof Fingerprint2 === 'undefined') {
|
|
||||||
boothError('Your browser did not load fingerprintjs2 correctly. Please try again after disabling your ad blockers and similar software. If the issue persists, try using a different browser.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({ url: "{{ url_for('election_api_json', election_id=election._id) }}", dataType: "text" })
|
$.ajax({ url: "{{ url_for('election_api_json', election_id=election._id) }}", dataType: "text" })
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
try {
|
try {
|
||||||
election = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.EosObject.from_json(data), null);
|
election = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.__all__.EosObject.from_json(data), null);
|
||||||
|
|
||||||
boothWorker = new Worker("{{ url_for('static', filename='js/booth_worker.js') }}");
|
boothWorker = new Worker("{{ url_for('static', filename='js/booth_worker.js') }}");
|
||||||
|
|
||||||
@ -119,7 +108,7 @@
|
|||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
try {
|
try {
|
||||||
templates[templateUrl] = nunjucks.compile(data, null, templateUrl);
|
templates[templateUrl] = nunjucks.compile(data);
|
||||||
numTemplatesLoaded += 1;
|
numTemplatesLoaded += 1;
|
||||||
if (numTemplatesLoaded == templateUrls.length) {
|
if (numTemplatesLoaded == templateUrls.length) {
|
||||||
// All templates loaded. Show voting booth
|
// All templates loaded. Show voting booth
|
||||||
@ -202,99 +191,70 @@
|
|||||||
// === BOOTH TASKS ===
|
// === BOOTH TASKS ===
|
||||||
// TODO: Make modular
|
// TODO: Make modular
|
||||||
|
|
||||||
templates['booth/base.html'] = null;
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
if (location.search.indexOf('?cast') < 0) {
|
showTemplate('booth/welcome.html');
|
||||||
// Normal booth
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/welcome.html');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/welcome.html'] = null;
|
|
||||||
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/selections.html');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/selections.html'] = null;
|
|
||||||
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
if (fromLeft) {
|
|
||||||
showTemplate('booth/encrypt.html');
|
|
||||||
} else {
|
|
||||||
prevTemplate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/encrypt.html'] = null;
|
|
||||||
|
|
||||||
if (location.search.indexOf('?prepoll') >= 0) {
|
|
||||||
// Pre-poll
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/review_prepoll.html', {ballot: booth.ballot});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/review_prepoll.html'] = null;
|
|
||||||
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/audit.html'] = null;
|
|
||||||
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/cast_prepoll.html', {ballot: booth.ballot});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/cast_prepoll.html'] = null;
|
|
||||||
} else {
|
|
||||||
// Real voting booth
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/review.html', {ballot: booth.ballot});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/review.html'] = null;
|
|
||||||
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/audit.html'] = null;
|
|
||||||
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/cast.html', {ballot: booth.ballot, is_cast: false});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/cast.html'] = null;
|
|
||||||
|
|
||||||
boothTasks.append({
|
|
||||||
activate: function(fromLeft) {
|
|
||||||
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
templates['booth/complete.html'] = null;
|
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
// Cast immediately
|
templates['booth/base.html'] = null;
|
||||||
{% if session.staged_ballot %}
|
templates['booth/welcome.html'] = null;
|
||||||
booth.ballot = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.EosObject.from_json('{{ eos.core.objects.EosObject.to_json(session.staged_ballot.ballot)|safe }}'), null);
|
boothTasks.append({
|
||||||
{% endif %}
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/selections.html');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/selections.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
if (fromLeft) {
|
||||||
|
showTemplate('booth/encrypt.html');
|
||||||
|
} else {
|
||||||
|
prevTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/encrypt.html'] = null;
|
||||||
|
|
||||||
|
if (location.search.indexOf('?prepoll') >= 0) {
|
||||||
|
// Pre-poll
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/cast.html', {ballot: booth.ballot, is_cast: true});
|
showTemplate('booth/review_prepoll.html', {ballot: booth.ballot});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/review_prepoll.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/audit.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/cast_prepoll.html', {ballot: booth.ballot});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/cast_prepoll.html'] = null;
|
||||||
|
} else {
|
||||||
|
// Real voting booth
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/review.html', {ballot: booth.ballot});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/review.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/audit.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/cast.html', {ballot: booth.ballot});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/cast.html'] = null;
|
templates['booth/cast.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -21,9 +21,6 @@
|
|||||||
{% block electioncontent %}
|
{% block electioncontent %}
|
||||||
{% for question in election.questions %}
|
{% for question in election.questions %}
|
||||||
<h2>{{ loop.index }}. {{ question.prompt }}</h2>
|
<h2>{{ loop.index }}. {{ question.prompt }}</h2>
|
||||||
{% if question.description %}
|
|
||||||
<p>{{ question.description | urlize }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% include eosweb.core.main.model_view_map[question.__class__]['view'] %}
|
{% include eosweb.core.main.model_view_map[question.__class__]['view'] %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,7 +18,7 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
{% set Status = eos.base.workflow.WorkflowTaskStatus %}
|
{% set Status = eos.base.workflow.WorkflowTask.Status %}
|
||||||
|
|
||||||
{% block electioncontent %}
|
{% block electioncontent %}
|
||||||
{% if election.workflow.get_task('eos.base.workflow.TaskConfigureElection').status == Status.EXITED %}
|
{% if election.workflow.get_task('eos.base.workflow.TaskConfigureElection').status == Status.EXITED %}
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<p><a href="{{ url_for('election_api_json', election_id=election._id) }}?full" class="mini ui labeled icon button"><i class="download icon"></i> Export as Eos JSON</a></p>
|
<p><a href="{{ url_for('election_api_json', election_id=election._id) }}?full" class="mini ui labeled icon button"><i class="download icon"></i> Export as Eos JSON</a></p>
|
||||||
|
|
||||||
{% if election.workflow.get_task('eos.base.workflow.TaskReleaseResults').status == Status.EXITED %}
|
{% if election.workflow.get_task('eos.base.workflow.TaskReleaseResults').status == Status.EXITED %}
|
||||||
<p>Results were released {{ election.workflow.get_task('eos.base.workflow.TaskReleaseResults').exited_at|pretty_date }}.</p>
|
<p>Results were released at {{ election.workflow.get_task('eos.base.workflow.TaskReleaseResults').exited_at|pretty_date }}.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="ui warning message">
|
<div class="ui warning message">
|
||||||
This is a preview of the election results, shown only to you, the election administrator. To publicly release the results, you must do so from the <a href="{{ url_for('election_admin_summary', election_id=election._id) }}">‘Administrate this election’</a> tab.
|
This is a preview of the election results, shown only to you, the election administrator. To publicly release the results, you must do so from the <a href="{{ url_for('election_admin_summary', election_id=election._id) }}">‘Administrate this election’</a> tab.
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{#
|
|
||||||
Eos - Verifiable elections
|
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#}
|
|
||||||
|
|
||||||
{# Big tables on this page #}
|
|
||||||
{% block mainContainerOpts %}style="max-width: 100% !important;"{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}{{ voter.name }} – {{ election.name }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{{ election.name }}</h1>
|
|
||||||
|
|
||||||
<p><small><b>{{ election.kind|title }} fingerprint:</b> <span class="hash">{{ SHA256().update_obj(election).hash_as_b64() }}</span></small></p>
|
|
||||||
|
|
||||||
<h2>{{ voter.name }}</h2>
|
|
||||||
|
|
||||||
<h3>Votes cast</h3>
|
|
||||||
|
|
||||||
<table class="ui celled table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="two wide column">Cast at</th>
|
|
||||||
<th class="three wide column">Comment</th>
|
|
||||||
{% if session.user and session.user.is_admin() %}
|
|
||||||
<th class="three wide column">Client</th>
|
|
||||||
<th class="eight wide column">Ballot fingerprint (<i class="dropdown icon" style="margin: 0;"></i>Content/Actions )</th>
|
|
||||||
{% else %}
|
|
||||||
<th class="eleven wide column">Ballot fingerprint (<i class="dropdown icon" style="margin: 0;"></i>Content )</th>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for vote in voter.votes.get_all() %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ vote.cast_at|pretty_date }}</td>
|
|
||||||
<td>{% if vote.comment %}{{ vote.comment }}{% endif %}</td>
|
|
||||||
{% if session.user and session.user.is_admin() %}
|
|
||||||
<td>
|
|
||||||
{% if vote.cast_ip %}{{ vote.cast_ip }}{% if vote.cast_fingerprint %}<br>{% endif %}{% endif %}
|
|
||||||
{% if vote.cast_fingerprint %}<span class="hash">{{ eos.core.hashing.SHA256().update_text(eos.core.objects.EosObject.to_json(vote.cast_fingerprint)).hash_as_b64(True) }}</span>{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
<td>
|
|
||||||
<div class="ui accordion">
|
|
||||||
<div class="title">
|
|
||||||
<i class="dropdown icon"></i>
|
|
||||||
<span class="hash">{{ eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<div class="monoout" style="max-height: 10em;">{% if session.user and session.user.is_admin() %}{{ eos.core.objects.EosObject.to_json(eos.core.objects.EosObject.serialise_and_wrap(vote)) }}{% else %}{{ eos.core.objects.EosObject.to_json(eos.core.objects.EosObject.serialise_and_wrap(vote, options=eos.core.objects.SerialiseOptions(should_protect=True))) }}{% endif %}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block basecontent %}
|
|
||||||
{{ super() }}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$('.ui.accordion').accordion();
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@ -1,37 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{#
|
|
||||||
Eos - Verifiable elections
|
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#}
|
|
||||||
|
|
||||||
{% block title %}Elections{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>All elections: Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</h1>
|
|
||||||
|
|
||||||
{% if session.user and session.user.is_admin() %}
|
|
||||||
<div style="text-align: right; font-size: small;"><a href="{{ url_for('elections_batch') }}">Batch operations</a></div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>Please choose an election from the list below:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{% for election in elections %}
|
|
||||||
<li><a href="{{ url_for('election_view', election_id=election._id) }}">{{ election.name }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
@ -1,56 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{#
|
|
||||||
Eos - Verifiable elections
|
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#}
|
|
||||||
|
|
||||||
{% block title %}Perform batch operations{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Perform batch operations</h1>
|
|
||||||
|
|
||||||
<form method="POST">
|
|
||||||
<table class="ui selectable celled table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Next stage</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for election in elections %}
|
|
||||||
<tr>
|
|
||||||
<td><input type="checkbox" name="election_{{ election._id }}" id="election_{{ election._id }}"></td>
|
|
||||||
<td>{{ election.name }}</td>
|
|
||||||
<td>
|
|
||||||
<ul style="padding-left: 1em; margin: 0;">
|
|
||||||
{% for task in election.workflow.tasks %}
|
|
||||||
{% if task.status == eos.base.workflow.WorkflowTaskStatus.READY %}
|
|
||||||
<li>{{ task.label }}</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<input class="ui primary button" type="submit" value="Execute">
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
@ -1,76 +0,0 @@
|
|||||||
{#
|
|
||||||
Eos - Verifiable elections
|
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
|
||||||
|
|
||||||
This file adapted from https://github.com/leemunroe/responsive-html-email-template, licensed under the MIT licence.
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#}
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
<title>{{ title }}</title>
|
|
||||||
<style>
|
|
||||||
{{ css|safe }}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="">
|
|
||||||
<table border="0" cellpadding="0" cellspacing="0" class="body">
|
|
||||||
<tr>
|
|
||||||
<td> </td>
|
|
||||||
<td class="container">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<!-- START CENTERED WHITE CONTAINER -->
|
|
||||||
<table class="main">
|
|
||||||
|
|
||||||
<!-- START MAIN CONTENT AREA -->
|
|
||||||
<tr>
|
|
||||||
<td class="wrapper">
|
|
||||||
<table border="0" cellpadding="0" cellspacing="0">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ text|safe }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- END MAIN CONTENT AREA -->
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- START FOOTER -->
|
|
||||||
<div class="footer">
|
|
||||||
<table border="0" cellpadding="0" cellspacing="0">
|
|
||||||
<tr>
|
|
||||||
<td class="content-block">
|
|
||||||
<a href="{{ eosweb.app.config['BASE_URI'] }}" style="text-decoration: none">Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- END FOOTER -->
|
|
||||||
|
|
||||||
<!-- END CENTERED WHITE CONTAINER -->
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,347 +0,0 @@
|
|||||||
{#
|
|
||||||
Eos - Verifiable elections
|
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
|
||||||
|
|
||||||
This file adapted from https://github.com/leemunroe/responsive-html-email-template, licensed under the MIT licence.
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
GLOBAL RESETS
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: none;
|
|
||||||
-ms-interpolation-mode: bicubic;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
font-family: sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
-ms-text-size-adjust: 100%;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: separate;
|
|
||||||
mso-table-lspace: 0pt;
|
|
||||||
mso-table-rspace: 0pt;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
td {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
BODY & CONTAINER
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
.body {
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
|
||||||
.container {
|
|
||||||
display: block;
|
|
||||||
Margin: 0 auto !important;
|
|
||||||
/* makes it centered */
|
|
||||||
max-width: 580px;
|
|
||||||
padding: 10px;
|
|
||||||
width: 580px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
|
||||||
.content {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
Margin: 0 auto;
|
|
||||||
max-width: 580px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
HEADER, FOOTER, MAIN
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
.main {
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 3px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-block {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
clear: both;
|
|
||||||
Margin-top: 10px;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer td, .footer p, .footer span, .footer a {
|
|
||||||
color: #999999;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
TYPOGRAPHY
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
h1, h2, h3, h4 {
|
|
||||||
color: #000000;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 0;
|
|
||||||
Margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 35px;
|
|
||||||
font-weight: 300;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
p, ul, ol {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0;
|
|
||||||
Margin-bottom: 15px;
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style-position: inside;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #3498db;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
BUTTONS
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
& > tbody > tr > td {
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: auto;
|
|
||||||
|
|
||||||
td {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: solid 1px #3498db;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #3498db;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0;
|
|
||||||
padding: 12px 25px;
|
|
||||||
text-decoration: none;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
table td {
|
|
||||||
background-color: #3498db;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
background-color: #3498db;
|
|
||||||
border-color: #3498db;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
OTHER STYLES THAT MIGHT BE USEFUL
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
.last {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.first {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.align-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.align-left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt0 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
.mb0 {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preheader {
|
|
||||||
color: transparent;
|
|
||||||
display: none;
|
|
||||||
height: 0;
|
|
||||||
max-height: 0;
|
|
||||||
max-width: 0;
|
|
||||||
opacity: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
mso-hide: all;
|
|
||||||
visibility: hidden;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.powered-by a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 0;
|
|
||||||
border-bottom: 1px solid #f6f6f6;
|
|
||||||
Margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
MISCELLANEOUS
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
@media only screen and (max-width: 620px) {
|
|
||||||
table[class=body] {
|
|
||||||
h1 {
|
|
||||||
font-size: 28px !important;
|
|
||||||
margin-bottom: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
p, ul, ol, td, span, a {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper, .article {
|
|
||||||
padding: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 0 !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
border-left-width: 0 !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
border-right-width: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
table {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-responsive {
|
|
||||||
height: auto !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------
|
|
||||||
PRESERVE THESE STYLES IN THE HEAD
|
|
||||||
------------------------------------- */
|
|
||||||
|
|
||||||
@media all {
|
|
||||||
.ExternalClass {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {
|
|
||||||
line-height: 100%;
|
|
||||||
}
|
|
||||||
.apple-link a {
|
|
||||||
color: inherit !important;
|
|
||||||
font-family: inherit !important;
|
|
||||||
font-size: inherit !important;
|
|
||||||
font-weight: inherit !important;
|
|
||||||
line-height: inherit !important;
|
|
||||||
text-decoration: none !important;
|
|
||||||
}
|
|
||||||
.btn-primary table td:hover {
|
|
||||||
background-color: #34495e !important;
|
|
||||||
}
|
|
||||||
.btn-primary a:hover {
|
|
||||||
background-color: #34495e !important;
|
|
||||||
border-color: #34495e !important;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
{#
|
|
||||||
Eos - Verifiable elections
|
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
|
||||||
|
|
||||||
This file adapted from https://github.com/leemunroe/responsive-html-email-template, licensed under the MIT licence.
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#}
|
|
||||||
|
|
||||||
{{ text|safe }}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}
|
|
||||||
{{ eosweb.app.config['BASE_URI'] }}
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -24,33 +24,9 @@
|
|||||||
<h1>Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</h1>
|
<h1>Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</h1>
|
||||||
|
|
||||||
<p>Please choose an election from the list below:</p>
|
<p>Please choose an election from the list below:</p>
|
||||||
|
<ul>
|
||||||
{% if elections_open %}
|
{% for election in eos.base.election.Election.get_all() %}
|
||||||
<h2>Currently open</h2>
|
<li><a href="{{ url_for('election_view', election_id=election._id) }}">{{ election.name }}</a></li>
|
||||||
<ul>
|
{% endfor %}
|
||||||
{% for election in elections_open %}
|
</ul>
|
||||||
<li><a href="{{ url_for('election_view', election_id=election._id) }}">{{ election.name }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if elections_soon %}
|
|
||||||
<h2>Opening soon</h2>
|
|
||||||
<ul>
|
|
||||||
{% for election in elections_soon %}
|
|
||||||
<li><a href="{{ url_for('election_view', election_id=election._id) }}">{{ election.name }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if elections_closed %}
|
|
||||||
<h2>Recently closed</h2>
|
|
||||||
<ul>
|
|
||||||
{% for election in elections_closed %}
|
|
||||||
<li><a href="{{ url_for('election_view', election_id=election._id) }}">{{ election.name }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<a href="{{ url_for('elections') }}">Show all</a>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -16,18 +16,7 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
<p><small>
|
<p><small>Approval voting. Vote for between {{ question.min_choices }} and {{ question.max_choices }} choices.{% if question.randomise_choices %} Order of choices is randomised.{% endif %}</small></p>
|
||||||
Approval voting. Vote for
|
|
||||||
{% if question.min_choices == question.max_choices %}
|
|
||||||
exactly {{ question.min_choices }}
|
|
||||||
{% else %}
|
|
||||||
between {{ question.min_choices }} and {{ question.max_choices }}
|
|
||||||
{% endif %}
|
|
||||||
choices.
|
|
||||||
{% if question.randomise_choices %}
|
|
||||||
Order of choices is randomised.
|
|
||||||
{% endif %}
|
|
||||||
</small></p>
|
|
||||||
|
|
||||||
<ul class="ui list">
|
<ul class="ui list">
|
||||||
{% for choice in question.choices %}
|
{% for choice in question.choices %}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -40,6 +40,8 @@
|
|||||||
Count log
|
Count log
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="monoout" style="max-height: 20em;">{{ result.log }}</div>
|
<div class="ui form">
|
||||||
|
<textarea style="font-family: monospace;" rows="20">{{ result.log }}</textarea>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,18 +16,7 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
<p><small>
|
<p><small>Preferential voting. Vote for between {{ question.min_choices }} and {{ question.max_choices }} choices.{% if question.randomise_choices %} Order of choices is randomised.{% endif %}</small></p>
|
||||||
Preferential voting. Vote for
|
|
||||||
{% if question.min_choices == question.max_choices %}
|
|
||||||
exactly {{ question.min_choices }}
|
|
||||||
{% else %}
|
|
||||||
between {{ question.min_choices }} and {{ question.max_choices }}
|
|
||||||
{% endif %}
|
|
||||||
choices.
|
|
||||||
{% if question.randomise_choices %}
|
|
||||||
Order of choices is randomised.
|
|
||||||
{% endif %}
|
|
||||||
</small></p>
|
|
||||||
|
|
||||||
<ul class="ui list">
|
<ul class="ui list">
|
||||||
{% for choice in question.choices %}
|
{% for choice in question.choices %}
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{#
|
|
||||||
Eos - Verifiable elections
|
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#}
|
|
||||||
|
|
||||||
{% block title %}{{ task.label }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{{ task.label }}</h1>
|
|
||||||
|
|
||||||
<table class="ui celled definition table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Status</td>
|
|
||||||
<td>{{ task.status.name|title }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Strategy</td>
|
|
||||||
<td>{{ task.run_strategy._name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Scheduled</td>
|
|
||||||
<td>{% if task.run_at %}{{ task.run_at|pretty_date }}{% endif %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Started</td>
|
|
||||||
<td>{% if task.started_at %}{{ task.started_at|pretty_date }}{% endif %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Exited</td>
|
|
||||||
<td>{% if task.completed_at %}{{ task.completed_at|pretty_date }}{% endif %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Messages</td>
|
|
||||||
<td>
|
|
||||||
{% if task.messages %}
|
|
||||||
<div class="monoout" style="max-height: 10em;">{{ '\n'.join(task.messages) }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Result</td>
|
|
||||||
<td>
|
|
||||||
{% if task.result %}
|
|
||||||
<div class="monoout" style="max-height: 3em;">{{ eos.core.object.EosObject.to_json(eos.core.object.EosObject.serialise_and_wrap(task.result)) }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
|
@ -1,46 +0,0 @@
|
|||||||
# Eos - Verifiable elections
|
|
||||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import flask
|
|
||||||
|
|
||||||
from eos.nsauth.election import *
|
|
||||||
|
|
||||||
import urllib.request, urllib.parse
|
|
||||||
|
|
||||||
blueprint = flask.Blueprint('eosweb.nsauth', __name__, template_folder='templates')
|
|
||||||
|
|
||||||
app = None
|
|
||||||
|
|
||||||
@blueprint.record
|
|
||||||
def reddit_register(setup_state):
|
|
||||||
global app
|
|
||||||
app = setup_state.app
|
|
||||||
|
|
||||||
@blueprint.route('/auth/nationstates/login')
|
|
||||||
def nationstates_login():
|
|
||||||
return flask.render_template('auth/nationstates/login.html')
|
|
||||||
|
|
||||||
@blueprint.route('/auth/nationstates/authenticate', methods=['POST'])
|
|
||||||
def nationstates_authenticate():
|
|
||||||
username = flask.request.form['username'].lower().strip().replace(' ', '_')
|
|
||||||
|
|
||||||
with urllib.request.urlopen(urllib.request.Request('https://www.nationstates.net/cgi-bin/api.cgi?a=verify&' + urllib.parse.urlencode({'nation': username, 'checksum': flask.request.form['checksum']}), headers={'User-Agent': app.config['NATIONSTATES_USER_AGENT']})) as resp:
|
|
||||||
if resp.read().decode('utf-8').strip() != '1':
|
|
||||||
return flask.render_template('auth/nationstates/login.html', error='The nation name or verification code you entered was invalid. Please check your details and try again. If the issue persists, contact the election administrator.')
|
|
||||||
|
|
||||||
flask.session['user'] = NationStatesUser(username=username)
|
|
||||||
|
|
||||||
return flask.redirect(flask.url_for('login_complete'))
|
|
@ -1,17 +0,0 @@
|
|||||||
# Eos - Verifiable elections
|
|
||||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
NATIONSTATES_USER_AGENT = 'FIXME'
|
|
@ -1,51 +0,0 @@
|
|||||||
{% extends 'semantic_base.html' %}
|
|
||||||
|
|
||||||
{#
|
|
||||||
Eos - Verifiable elections
|
|
||||||
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#}
|
|
||||||
|
|
||||||
{% block title %}Log in{% endblock %}
|
|
||||||
|
|
||||||
{% block basecontent %}
|
|
||||||
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
|
||||||
<div class="column" style="max-width: 400px;">
|
|
||||||
<p>1. Log in to NationStates below if necessary, and copy your <i>Login Verification Code</i>.</p>
|
|
||||||
<iframe src="https://m.nationstates.net/page=verify_login" style="width: 100%; height: 10em;"></iframe>
|
|
||||||
<p>2. Type your nation name and paste your Login Verification Code into the form below.</p>
|
|
||||||
<form class="ui large form" action="{{ url_for('eosweb.nsauth.nationstates_authenticate') }}" method="post">
|
|
||||||
{% if error %}
|
|
||||||
<div class="ui visible error message">{{ error }}</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="ui stacked segment">
|
|
||||||
<div class="field">
|
|
||||||
<div class="ui left icon input">
|
|
||||||
<i class="user icon"></i>
|
|
||||||
<input type="text" name="username" placeholder="Nation name">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<div class="ui left icon input">
|
|
||||||
<i class="linkify icon"></i>
|
|
||||||
<input type="text" name="checksum" placeholder="Login verification code">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="submit" class="ui fluid large teal submit button" value="Log in">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from authlib.integrations.flask_client import OAuth
|
from flask_oauthlib.client import OAuth
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
@ -23,52 +23,44 @@ from eos.redditauth.election import *
|
|||||||
import base64
|
import base64
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
blueprint = flask.Blueprint('eosweb.redditauth', __name__)
|
def main(app):
|
||||||
|
oauth = OAuth()
|
||||||
app = None
|
reddit = oauth.remote_app('Reddit',
|
||||||
oauth = None
|
request_token_url=None,
|
||||||
|
|
||||||
@blueprint.record
|
|
||||||
def reddit_register(setup_state):
|
|
||||||
global app, oauth
|
|
||||||
|
|
||||||
app = setup_state.app
|
|
||||||
|
|
||||||
oauth = OAuth(app)
|
|
||||||
oauth.register('reddit',
|
|
||||||
#request_token_url=None,
|
|
||||||
authorize_url='https://www.reddit.com/api/v1/authorize.compact',
|
authorize_url='https://www.reddit.com/api/v1/authorize.compact',
|
||||||
authorize_params={'duration': 'temporary', 'scope': 'identity'},
|
request_token_params={'duration': 'temporary', 'scope': 'identity'},
|
||||||
access_token_url='https://www.reddit.com/api/v1/access_token',
|
access_token_url='https://www.reddit.com/api/v1/access_token',
|
||||||
access_token_method='POST',
|
access_token_method='POST',
|
||||||
access_token_headers={
|
access_token_headers={
|
||||||
'Authorization': 'Basic ' + base64.b64encode('{}:{}'.format(app.config['REDDIT_OAUTH_CLIENT_ID'], app.config['REDDIT_OAUTH_CLIENT_SECRET']).encode('ascii')).decode('ascii'),
|
'Authorization': 'Basic ' + base64.b64encode('{}:{}'.format(app.config['REDDIT_OAUTH_CLIENT_ID'], app.config['REDDIT_OAUTH_CLIENT_SECRET']).encode('ascii')).decode('ascii'),
|
||||||
'User-Agent': app.config['REDDIT_USER_AGENT']
|
'User-Agent': app.config['REDDIT_USER_AGENT']
|
||||||
},
|
},
|
||||||
client_id=app.config['REDDIT_OAUTH_CLIENT_ID'],
|
consumer_key=app.config['REDDIT_OAUTH_CLIENT_ID'],
|
||||||
client_secret=app.config['REDDIT_OAUTH_CLIENT_SECRET'],
|
consumer_secret=app.config['REDDIT_OAUTH_CLIENT_SECRET']
|
||||||
fetch_token=lambda: flask.session.get('user').oauth_token
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@blueprint.route('/auth/reddit/login')
|
|
||||||
def reddit_login():
|
|
||||||
return oauth.reddit.authorize_redirect(redirect_uri=app.config['BASE_URI'] + flask.url_for('eosweb.redditauth.reddit_oauth_authorized'), state=str(uuid.uuid4()))
|
|
||||||
|
|
||||||
@blueprint.route('/auth/reddit/oauth_callback')
|
|
||||||
def reddit_oauth_authorized():
|
|
||||||
try:
|
|
||||||
token = oauth.reddit.authorize_access_token()
|
|
||||||
except:
|
|
||||||
# Request denied
|
|
||||||
return flask.redirect(flask.url_for('login_cancelled'))
|
|
||||||
|
|
||||||
user = RedditUser()
|
@app.route('/auth/reddit/login')
|
||||||
user.oauth_token = token
|
def reddit_login():
|
||||||
flask.session['user'] = user
|
return reddit.authorize(callback=app.config['BASE_URI'] + flask.url_for('reddit_oauth_authorized'), state=uuid.uuid4())
|
||||||
|
|
||||||
me = oauth.reddit.get('https://oauth.reddit.com/api/v1/me', headers={
|
@reddit.tokengetter
|
||||||
'User-Agent': app.config['REDDIT_USER_AGENT']
|
def get_reddit_oauth_token():
|
||||||
})
|
return (flask.session.get('user').oauth_token, '')
|
||||||
user.username = me.json()['name']
|
|
||||||
|
|
||||||
return flask.redirect(flask.url_for('login_complete'))
|
@app.route('/auth/reddit/oauth_callback')
|
||||||
|
def reddit_oauth_authorized():
|
||||||
|
resp = reddit.authorized_response()
|
||||||
|
if resp is None:
|
||||||
|
# Request denied
|
||||||
|
return flask.redirect(flask.url_for('login_cancelled'))
|
||||||
|
|
||||||
|
user = RedditUser()
|
||||||
|
user.oauth_token = resp['access_token']
|
||||||
|
flask.session['user'] = user
|
||||||
|
|
||||||
|
me = reddit.get('https://oauth.reddit.com/api/v1/me', headers={
|
||||||
|
'User-Agent': app.config['REDDIT_USER_AGENT']
|
||||||
|
})
|
||||||
|
user.username = me.data['name']
|
||||||
|
|
||||||
|
return flask.redirect(flask.url_for('login_complete'))
|
||||||
|
2
js.html
2
js.html
@ -1 +1 @@
|
|||||||
<script src="eosweb/core/static/js/eosjs.js"></script>
|
<script src="eos/__javascript__/eos.js_tests.js"></script>
|
||||||
|
@ -9,11 +9,9 @@ AUTH_METHODS = [
|
|||||||
('reddit', 'Reddit')
|
('reddit', 'Reddit')
|
||||||
]
|
]
|
||||||
|
|
||||||
import eos.base.election
|
|
||||||
import eos.redditauth.election
|
import eos.redditauth.election
|
||||||
ADMINS = [
|
ADMINS = [
|
||||||
#eos.redditauth.election.RedditUser(username='xxxxxxxx'),
|
#eos.redditauth.election.RedditUser(username='xxxxxxxx')
|
||||||
#eos.base.election.EmailUser(email='xxxxx@example.com', password='abc123'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TASK_RUN_STRATEGY = 'eos.core.tasks.direct.DirectRunStrategy'
|
TASK_RUN_STRATEGY = 'eos.core.tasks.direct.DirectRunStrategy'
|
||||||
@ -34,13 +32,11 @@ DB_NAME = 'eos'
|
|||||||
|
|
||||||
# Email
|
# Email
|
||||||
|
|
||||||
MAIL_SERVER, MAIL_PORT = 'localhost', 25
|
SMTP_HOST, SMTP_PORT = 'localhost', 25
|
||||||
MAIL_USERNAME, MAIL_PASSWORD = None, None
|
SMTP_USER, SMTP_PASS = None, None
|
||||||
MAIL_DEFAULT_SENDER = 'eos@localhost'
|
SMTP_FROM = 'eos@localhost'
|
||||||
|
|
||||||
# Reddit
|
# Reddit
|
||||||
# Register a web app at https://www.reddit.com/prefs/apps
|
|
||||||
# The redirect URI will be http(s)://hostname(:port)/auth/reddit/oauth_callback
|
|
||||||
|
|
||||||
REDDIT_OAUTH_CLIENT_ID = 'xxxxxxxxxxxxxx'
|
REDDIT_OAUTH_CLIENT_ID = 'xxxxxxxxxxxxxx'
|
||||||
REDDIT_OAUTH_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
REDDIT_OAUTH_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
|
6839
package-lock.json
generated
6839
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/cli": "^7.15.7",
|
|
||||||
"@babel/core": "^7.15.8",
|
|
||||||
"@babel/preset-env": "^7.15.8",
|
|
||||||
"babelify": "^10.0.0",
|
|
||||||
"browserify": "^17.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +1,27 @@
|
|||||||
Authlib==0.14.3
|
certifi==2017.11.5
|
||||||
|
chardet==3.0.4
|
||||||
|
click==6.7
|
||||||
coverage==4.4.1
|
coverage==4.4.1
|
||||||
Flask==0.12.2
|
Flask==0.12.2
|
||||||
Flask-Mail==0.9.1
|
Flask-OAuthlib==0.9.4
|
||||||
flask-paginate==0.7.0
|
|
||||||
Flask-Session==0.3.1
|
|
||||||
Flask-SQLAlchemy==2.3.2
|
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
libsass==0.13.4
|
idna==2.6
|
||||||
premailer==3.1.1
|
itsdangerous==0.24
|
||||||
psycopg2==2.8.5
|
Jinja2==2.10
|
||||||
|
MarkupSafe==1.0
|
||||||
|
mypy==0.550
|
||||||
|
oauthlib==2.0.6
|
||||||
|
psutil==5.4.1
|
||||||
|
psycopg2==2.7.3.2
|
||||||
PyExecJS==1.4.1
|
PyExecJS==1.4.1
|
||||||
pymongo[srv]==3.10.1
|
pymongo==3.5.1
|
||||||
pyRCV==0.3
|
pyRCV==0.3
|
||||||
pytz==2017.3
|
pytz==2017.3
|
||||||
|
requests==2.18.4
|
||||||
|
requests-oauthlib==0.8.0
|
||||||
|
six==1.10.0
|
||||||
timeago==1.0.8
|
timeago==1.0.8
|
||||||
Transcrypt==3.9.0
|
Transcrypt==3.6.60
|
||||||
|
typed-ast==1.1.0
|
||||||
|
urllib3==1.22
|
||||||
|
Werkzeug==0.12.2
|
||||||
|
@ -1 +1 @@
|
|||||||
python-3.7.2
|
python-3.6.3
|
||||||
|
Loading…
Reference in New Issue
Block a user