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
|
||||
/htmlcov
|
||||
/venv
|
||||
__target__
|
||||
__javascript__
|
||||
__pycache__
|
||||
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
|
||||
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_js.sh
|
||||
|
15
README.md
15
README.md
@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@ -16,9 +16,18 @@ Eye Candy | No | Yes!
|
||||
|
||||
## 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
|
||||
|
||||
|
27
build_js.sh
27
build_js.sh
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
# 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
|
||||
# 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_tests; do
|
||||
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
|
||||
|
||||
# Transcrypt syntax errors
|
||||
perl -0777 -pi -e 's/import \{, /import \{/g' __target__/eos*.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
|
||||
cp eos/__javascript__/eos.js_tests.js eosweb/core/static/js/eosjs.js
|
||||
perl -0777 -pi -e 's/eosjs_tests/eosjs/g' eosweb/core/static/js/eosjs.js
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
@ -29,9 +29,6 @@ class NullEncryptedAnswer(EncryptedAnswer):
|
||||
|
||||
def decrypt(self):
|
||||
return None, self.answer
|
||||
|
||||
def deaudit(self):
|
||||
return self
|
||||
|
||||
class Ballot(EmbeddedObject):
|
||||
#_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)
|
||||
|
||||
class Vote(TopLevelObject):
|
||||
_ver = StringField(default='0.6')
|
||||
|
||||
_id = UUIDField()
|
||||
voter_id = UUIDField()
|
||||
class Vote(EmbeddedObject):
|
||||
_ver = StringField(default='0.5')
|
||||
|
||||
ballot = EmbeddedObjectField()
|
||||
cast_at = DateTimeField()
|
||||
@ -63,10 +57,8 @@ class Vote(TopLevelObject):
|
||||
cast_fingerprint = BlobField(is_protected=True)
|
||||
|
||||
class Voter(EmbeddedObject):
|
||||
_ver = StringField(default='0.6')
|
||||
|
||||
_id = UUIDField()
|
||||
votes = RelatedObjectListField(related_type=Vote, object_type=None, this_field='_id', related_field='voter_id')
|
||||
votes = EmbeddedObjectListField()
|
||||
|
||||
class User(EmbeddedObject):
|
||||
admins = []
|
||||
@ -79,9 +71,6 @@ class User(EmbeddedObject):
|
||||
if admin.matched_by(self):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getstate__(self):
|
||||
return {k: v for k, v in self.__dict__.items() if k != '_instance'}
|
||||
|
||||
def generate_password():
|
||||
if is_python:
|
||||
@ -101,6 +90,18 @@ class EmailUser(User):
|
||||
if not isinstance(other, EmailUser):
|
||||
return False
|
||||
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):
|
||||
user = EmbeddedObjectField()
|
||||
@ -110,15 +111,14 @@ class UserVoter(Voter):
|
||||
return self.user.name
|
||||
|
||||
class Question(EmbeddedObject):
|
||||
_ver = StringField(default='0.7')
|
||||
|
||||
prompt = StringField()
|
||||
description = StringField()
|
||||
|
||||
class Result(EmbeddedObject):
|
||||
pass
|
||||
|
||||
class ListChoiceQuestion(Question):
|
||||
_ver = StringField(default='0.5')
|
||||
|
||||
choices = EmbeddedObjectListField()
|
||||
min_choices = IntField()
|
||||
max_choices = IntField()
|
||||
@ -214,8 +214,6 @@ class STVResult(Result):
|
||||
random = BlobField()
|
||||
|
||||
class Election(TopLevelObject):
|
||||
_ver = StringField(default='0.9')
|
||||
|
||||
_id = UUIDField()
|
||||
workflow = EmbeddedObjectField(Workflow) # Once saved, we don't care what kind of workflow it is
|
||||
name = StringField()
|
||||
@ -224,13 +222,6 @@ class Election(TopLevelObject):
|
||||
questions = EmbeddedObjectListField()
|
||||
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):
|
||||
#__pragma__('skip')
|
||||
from eos.core.hashing import SHA256
|
||||
@ -238,7 +229,7 @@ class Election(TopLevelObject):
|
||||
election_hash = SHA256().update_obj(self).hash_as_b64()
|
||||
|
||||
for voter in self.voters:
|
||||
for vote in voter.votes.get_all():
|
||||
for vote in voter.votes:
|
||||
if vote.ballot.election_id != self._id:
|
||||
raise Exception('Invalid election ID on ballot')
|
||||
if vote.ballot.election_hash != election_hash:
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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()
|
||||
|
||||
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:
|
||||
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()
|
||||
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:
|
||||
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
|
||||
def test_run_election(self):
|
||||
# Set up election
|
||||
election = Election()
|
||||
election.workflow = BaseWorkflow()
|
||||
election.workflow = WorkflowBase()
|
||||
|
||||
# Check _instance
|
||||
self.assertEqual(election.workflow._instance, (election, 'workflow'))
|
||||
|
||||
# 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)
|
||||
|
||||
# Set election details
|
||||
@ -95,10 +95,10 @@ class ElectionTestCase(EosTestCase):
|
||||
answer = ApprovalAnswer(choices=VOTES[i][j])
|
||||
encrypted_answer = NullEncryptedAnswer(answer=answer)
|
||||
ballot.encrypted_answers.append(encrypted_answer)
|
||||
vote = Vote(voter_id=election.voters[i]._id, ballot=ballot, cast_at=DateTimeField.now())
|
||||
vote.save()
|
||||
vote = Vote(ballot=ballot, cast_at=DateTimeField.now())
|
||||
election.voters[i].votes.append(vote)
|
||||
|
||||
#election.save()
|
||||
election.save()
|
||||
|
||||
# Close voting
|
||||
self.do_task_assert(election, 'eos.base.workflow.TaskCloseVoting', 'eos.base.workflow.TaskDecryptVotes')
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Eos - Verifiable elections
|
||||
# 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
|
||||
# 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:
|
||||
if candidate.party:
|
||||
electionLines.append('"{} – {}"'.format(candidate.name, candidate.party))
|
||||
electionLines.append("'{} – {}'".format(candidate.name, candidate.party))
|
||||
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
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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.tasks import *
|
||||
|
||||
class WorkflowTaskStatus(EosEnum):
|
||||
UNKNOWN = 0
|
||||
NOT_READY = 10
|
||||
READY = 20
|
||||
ENTERED = 30
|
||||
#COMPLETE = 40
|
||||
EXITED = 50
|
||||
|
||||
class WorkflowTask(EmbeddedObject):
|
||||
class Status:
|
||||
UNKNOWN = 0
|
||||
NOT_READY = 10
|
||||
READY = 20
|
||||
ENTERED = 30
|
||||
#COMPLETE = 40
|
||||
EXITED = 50
|
||||
|
||||
depends_on = []
|
||||
provides = []
|
||||
|
||||
status = EnumField(WorkflowTaskStatus, is_hashed=False, default=WorkflowTaskStatus.UNKNOWN)
|
||||
status = IntField(default=0, is_hashed=False)
|
||||
exited_at = DateTimeField(is_hashed=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -40,8 +40,8 @@ class WorkflowTask(EmbeddedObject):
|
||||
|
||||
self.workflow = self.recurse_parents(Workflow)
|
||||
|
||||
if self.status == WorkflowTaskStatus.UNKNOWN:
|
||||
self.status = WorkflowTaskStatus.READY if self.are_dependencies_met() else WorkflowTaskStatus.NOT_READY
|
||||
if self.status == WorkflowTask.Status.UNKNOWN:
|
||||
self.status = WorkflowTask.Status.READY if self.are_dependencies_met() else WorkflowTask.Status.NOT_READY
|
||||
|
||||
self.listeners = {
|
||||
'enter': [],
|
||||
@ -51,20 +51,15 @@ class WorkflowTask(EmbeddedObject):
|
||||
# Helpers
|
||||
|
||||
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_task in self.workflow.get_tasks(depends_on_desc):
|
||||
depends_on_task.listeners['exit'].append(on_dependency_exit)
|
||||
|
||||
def are_dependencies_met(self):
|
||||
for depends_on_desc in self.depends_on:
|
||||
depends_on_tasks = list(self.workflow.get_tasks(depends_on_desc))
|
||||
|
||||
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:
|
||||
for depends_on_task in self.workflow.get_tasks(depends_on_desc):
|
||||
if depends_on_task.status is not WorkflowTask.Status.EXITED:
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -76,10 +71,10 @@ class WorkflowTask(EmbeddedObject):
|
||||
self.exit()
|
||||
|
||||
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')
|
||||
|
||||
self.status = WorkflowTaskStatus.ENTERED
|
||||
self.status = WorkflowTask.Status.ENTERED
|
||||
self.fire_event('enter')
|
||||
self.on_enter()
|
||||
|
||||
@ -91,10 +86,10 @@ class WorkflowTask(EmbeddedObject):
|
||||
self.exited_at = DateTimeField.now()
|
||||
|
||||
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')
|
||||
|
||||
self.status = WorkflowTaskStatus.EXITED
|
||||
self.status = WorkflowTask.Status.EXITED
|
||||
self.fire_event('exit')
|
||||
self.on_exit()
|
||||
|
||||
@ -151,7 +146,7 @@ class TaskConfigureElection(WorkflowTask):
|
||||
label = 'Freeze the election'
|
||||
|
||||
#def on_enter(self):
|
||||
# self.status = WorkflowTaskStatus.COMPLETE
|
||||
# self.status = WorkflowTask.Status.COMPLETE
|
||||
|
||||
class TaskOpenVoting(WorkflowTask):
|
||||
label = 'Open voting'
|
||||
@ -172,8 +167,8 @@ class TaskDecryptVotes(WorkflowTask):
|
||||
election.results.append(EosObject.lookup('eos.base.election.RawResult')())
|
||||
|
||||
for voter in election.voters:
|
||||
if len(voter.votes.get_all()) > 0:
|
||||
vote = voter.votes.get_all()[-1]
|
||||
if len(voter.votes) > 0:
|
||||
vote = voter.votes[-1]
|
||||
ballot = vote.ballot
|
||||
for q_num in range(len(ballot.encrypted_answers)):
|
||||
plaintexts, answer = ballot.encrypted_answers[q_num].decrypt()
|
||||
@ -189,9 +184,7 @@ class TaskReleaseResults(WorkflowTask):
|
||||
# Concrete workflows
|
||||
# ==================
|
||||
|
||||
class BaseWorkflow(Workflow):
|
||||
"""Base workflow, with no encryption"""
|
||||
|
||||
class WorkflowBase(Workflow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
# 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.objects import *
|
||||
from eos.core.objects import EosObject
|
||||
|
||||
import random
|
||||
|
||||
@ -125,7 +125,7 @@ class BigInt(EosObject):
|
||||
def nbits(self):
|
||||
return self.impl.bitLength()
|
||||
|
||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
||||
def serialise(self, for_hash=False, should_protect=False):
|
||||
return str(self)
|
||||
|
||||
@classmethod
|
||||
|
@ -14,7 +14,7 @@
|
||||
# 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.objects import *
|
||||
from eos.core.objects import EosObject
|
||||
|
||||
import math
|
||||
|
||||
@ -46,7 +46,7 @@ class BigInt(EosObject):
|
||||
def nbits(self):
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
|
@ -27,18 +27,12 @@ class DBProvider:
|
||||
def get_all(self, collection):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def get_all_by_fields(self, collection, fields):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def get_by_id(self, collection, _id):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def update_by_id(self, collection, _id, value):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def delete_by_id(self, collection, _id):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def reset_db(self):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
@ -49,18 +43,12 @@ class DummyProvider(DBProvider):
|
||||
def get_all(self, collection):
|
||||
pass
|
||||
|
||||
def get_all_by_fields(self, collection, fields):
|
||||
pass
|
||||
|
||||
def get_by_id(self, collection, _id):
|
||||
pass
|
||||
|
||||
def update_by_id(self, collection, _id, value):
|
||||
pass
|
||||
|
||||
def delete_by_id(self, collection, _id):
|
||||
pass
|
||||
|
||||
def reset_db(self):
|
||||
pass
|
||||
|
||||
|
@ -26,25 +26,12 @@ class MongoDBProvider(eos.core.db.DBProvider):
|
||||
def get_all(self, collection):
|
||||
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):
|
||||
return self.db[collection].find_one(_id)
|
||||
|
||||
def update_by_id(self, collection, _id, value):
|
||||
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):
|
||||
self.client.drop_database(self.db_name)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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)))
|
||||
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):
|
||||
self.create_table(table)
|
||||
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.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):
|
||||
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()
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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):
|
||||
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
|
||||
|
||||
def update_obj_raw(self, *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
|
||||
|
||||
def hash_as_b64(self, short=False):
|
||||
def hash_as_b64(self):
|
||||
if is_python:
|
||||
b64 = base64.b64encode(self.impl.digest()).decode('utf-8')
|
||||
return base64.b64encode(self.impl.digest()).decode('utf-8')
|
||||
else:
|
||||
b64 = self.impl.getHash('B64')
|
||||
if short:
|
||||
return b64[:10]
|
||||
return b64
|
||||
return self.impl.getHash('B64')
|
||||
|
||||
def hash_as_hex(self):
|
||||
if is_python:
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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.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
|
||||
|
||||
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):
|
||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
||||
def serialise(self, value, for_hash=False, should_protect=False):
|
||||
return value
|
||||
|
||||
def deserialise(self, value):
|
||||
@ -115,8 +93,8 @@ class EmbeddedObjectField(Field):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.object_type = object_type
|
||||
|
||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
||||
return EosObject.serialise_and_wrap(value, self.object_type, options)
|
||||
def serialise(self, value, for_hash=False, should_protect=False):
|
||||
return EosObject.serialise_and_wrap(value, self.object_type, for_hash, should_protect)
|
||||
|
||||
def deserialise(self, value):
|
||||
return EosObject.deserialise_and_unwrap(value, self.object_type)
|
||||
@ -126,8 +104,8 @@ class ListField(Field):
|
||||
super().__init__(default=EosList, *args, **kwargs)
|
||||
self.element_field = element_field
|
||||
|
||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
||||
return [self.element_field.serialise(x, options) for x in (value.impl if isinstance(value, EosList) else value)]
|
||||
def serialise(self, value, for_hash=False, should_protect=False):
|
||||
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):
|
||||
return EosList([self.element_field.deserialise(x) for x in value])
|
||||
@ -137,68 +115,29 @@ class EmbeddedObjectListField(Field):
|
||||
super().__init__(default=EosList, *args, **kwargs)
|
||||
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
|
||||
if value is 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):
|
||||
if value is None:
|
||||
return None
|
||||
return EosList([EosObject.deserialise_and_unwrap(x, self.object_type) for x in value])
|
||||
|
||||
class RelatedObjectListManager:
|
||||
def __init__(self, field, obj):
|
||||
self.field = field
|
||||
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:
|
||||
if is_python:
|
||||
class UUIDField(Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(default=uuid.uuid4, *args, **kwargs)
|
||||
else:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
||||
return str(value)
|
||||
|
||||
def deserialise(self, value):
|
||||
if is_python:
|
||||
|
||||
def serialise(self, value, for_hash=False, should_protect=False):
|
||||
return str(value)
|
||||
|
||||
def deserialise(self, value):
|
||||
return uuid.UUID(value)
|
||||
else:
|
||||
return value
|
||||
else:
|
||||
UUIDField = PrimitiveField
|
||||
|
||||
class DateTimeField(Field):
|
||||
def pad(self, number):
|
||||
@ -206,7 +145,7 @@ class DateTimeField(Field):
|
||||
return '0' + 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:
|
||||
return None
|
||||
|
||||
@ -276,13 +215,12 @@ class EosObject(metaclass=EosObjectType):
|
||||
return EosObject.objects[name]
|
||||
|
||||
@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 value:
|
||||
return value.serialise(options)
|
||||
if value:
|
||||
return {'type': value._name, 'value': (value.serialise(options) if value else None)}
|
||||
return None
|
||||
return value.serialise(for_hash, should_protect)
|
||||
return None
|
||||
return {'type': value._name, 'value': (value.serialise(for_hash, should_protect) if value else None)}
|
||||
|
||||
@staticmethod
|
||||
def deserialise_and_unwrap(value, object_type=None):
|
||||
@ -362,16 +300,7 @@ class DocumentObjectType(EosObjectType):
|
||||
fields = {}
|
||||
if hasattr(cls, '_fields'):
|
||||
fields = cls._fields.copy() if is_python else Object.create(cls._fields)
|
||||
|
||||
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:
|
||||
for attr in list(dir(cls)):
|
||||
if not is_python:
|
||||
# We must skip things with getters or else they will be called here (too soon)
|
||||
if Object.getOwnPropertyDescriptor(cls, attr).js_get:
|
||||
@ -398,9 +327,14 @@ class DocumentObjectType(EosObjectType):
|
||||
if is_python:
|
||||
def make_property(name, field):
|
||||
def field_getter(self):
|
||||
return field.object_get(self)
|
||||
return self._field_values[name]
|
||||
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)
|
||||
|
||||
for attr, val in fields.items():
|
||||
@ -419,8 +353,6 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
self._json = None
|
||||
|
||||
self._field_values = {}
|
||||
|
||||
# Different to Python
|
||||
@ -430,11 +362,15 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
||||
pass
|
||||
else:
|
||||
def make_property(name, field):
|
||||
# TNYI: Transcrypt doesn't pass self
|
||||
def field_getter():
|
||||
return field.object_get(self)
|
||||
return self._field_values[name]
|
||||
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)
|
||||
prop = make_property(val.real_name, val)
|
||||
# TNYI: No support for property()
|
||||
@ -450,19 +386,15 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
||||
})
|
||||
|
||||
if val.internal_name in kwargs:
|
||||
val.object_init(self, kwargs[val.internal_name])
|
||||
setattr(self, val.real_name, kwargs[val.internal_name])
|
||||
else:
|
||||
default = val.default
|
||||
if default is not None and callable(default):
|
||||
default = default()
|
||||
val.object_init(self, default)
|
||||
setattr(self, val.real_name, default)
|
||||
|
||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
||||
if self._ver != self._fields['_ver'].default:
|
||||
# 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))}
|
||||
def serialise(self, for_hash=False, should_protect=False):
|
||||
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))}
|
||||
|
||||
@classmethod
|
||||
def deserialise(cls, value):
|
||||
@ -473,11 +405,7 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
||||
for attr, val in cls._fields.items():
|
||||
if attr in value:
|
||||
attrs[val.internal_name] = val.deserialise(value[val.real_name])
|
||||
inst = cls(**attrs)
|
||||
|
||||
inst._json = value
|
||||
|
||||
return inst
|
||||
return cls(**attrs)
|
||||
|
||||
class TopLevelObjectType(DocumentObjectType):
|
||||
def __new__(meta, name, bases, attrs):
|
||||
@ -499,26 +427,14 @@ class TopLevelObjectType(DocumentObjectType):
|
||||
|
||||
class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType):
|
||||
def save(self):
|
||||
if self._ver != self._fields['_ver'].default:
|
||||
# Different version, unable to save
|
||||
raise Exception('Attempted to save older vesion object')
|
||||
|
||||
#res = db[self._name].replace_one({'_id': self.serialise()['_id']}, self.serialise(), upsert=True)
|
||||
#res = dbinfo.db[self._db_name].replace_one({'_id': self._fields['_id'].serialise(self._id)}, EosObject.serialise_and_wrap(self), upsert=True)
|
||||
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
|
||||
def get_all(cls):
|
||||
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
|
||||
def get_by_id(cls, _id):
|
||||
if not isinstance(_id, str):
|
||||
@ -527,61 +443,3 @@ class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType):
|
||||
|
||||
class EmbeddedObject(DocumentObject):
|
||||
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
|
||||
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||
# Copyright © 2017 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
|
||||
@ -16,34 +16,26 @@
|
||||
|
||||
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):
|
||||
label = 'Unknown task'
|
||||
_ver = StringField(default='0.8')
|
||||
class Status:
|
||||
UNKNOWN = 0
|
||||
|
||||
READY = 20
|
||||
PROCESSING = 30
|
||||
COMPLETE = 50
|
||||
|
||||
FAILED = -10
|
||||
TIMEOUT = -20
|
||||
|
||||
_id = UUIDField()
|
||||
run_strategy = EmbeddedObjectField()
|
||||
|
||||
run_at = DateTimeField()
|
||||
|
||||
timeout = IntField(default=3600) # seconds
|
||||
|
||||
started_at = DateTimeField()
|
||||
completed_at = DateTimeField()
|
||||
|
||||
status = EnumField(TaskStatus, default=TaskStatus.UNKNOWN)
|
||||
status = IntField(default=0)
|
||||
messages = ListField(StringField())
|
||||
|
||||
def run(self):
|
||||
@ -51,12 +43,6 @@ class Task(TopLevelObject):
|
||||
|
||||
def _run(self):
|
||||
pass
|
||||
|
||||
def complete(self):
|
||||
pass
|
||||
|
||||
def error(self):
|
||||
pass
|
||||
|
||||
class DummyTask(Task):
|
||||
_db_name = Task._db_name
|
||||
@ -80,12 +66,9 @@ class TaskScheduler:
|
||||
tasks = Task.get_all()
|
||||
|
||||
for task in tasks:
|
||||
if task.status == TaskStatus.READY:
|
||||
if task.status == Task.Status.READY:
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
@ -94,7 +77,7 @@ class TaskScheduler:
|
||||
tasks = Task.get_all()
|
||||
|
||||
for task in tasks:
|
||||
if task.status == TaskStatus.PROCESSING:
|
||||
if task.status == Task.Status.PROCESSING:
|
||||
active_tasks.append(task)
|
||||
|
||||
return active_tasks
|
||||
@ -105,7 +88,7 @@ class TaskScheduler:
|
||||
tasks = Task.get_all()
|
||||
|
||||
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)
|
||||
|
||||
if limit:
|
||||
@ -116,16 +99,6 @@ class TaskScheduler:
|
||||
|
||||
@staticmethod
|
||||
def tick():
|
||||
now = DateTimeField.now()
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
||||
# Copyright © 2017 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
|
||||
@ -19,19 +19,17 @@ from eos.core.objects import *
|
||||
|
||||
class DirectRunStrategy(RunStrategy):
|
||||
def run(self, task):
|
||||
task.status = TaskStatus.PROCESSING
|
||||
task.status = Task.Status.PROCESSING
|
||||
task.started_at = DateTimeField.now()
|
||||
task.save()
|
||||
|
||||
try:
|
||||
task._run()
|
||||
task.status = TaskStatus.COMPLETE
|
||||
task.status = Task.Status.COMPLETE
|
||||
task.completed_at = DateTimeField.now()
|
||||
task.save()
|
||||
|
||||
task.complete()
|
||||
except Exception as e:
|
||||
task.status = TaskStatus.FAILED
|
||||
task.status = Task.Status.FAILED
|
||||
task.completed_at = DateTimeField.now()
|
||||
if is_python:
|
||||
#__pragma__('skip')
|
||||
@ -41,5 +39,3 @@ class DirectRunStrategy(RunStrategy):
|
||||
else:
|
||||
task.messages.append(repr(e))
|
||||
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
|
||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||
# Copyright © 2017 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
|
||||
@ -133,7 +133,6 @@ class TaskTestCase(EosTestCase):
|
||||
def setUpClass(cls):
|
||||
cls.db_connect_and_reset()
|
||||
|
||||
@py_only
|
||||
def test_normal(self):
|
||||
class TaskNormal(Task):
|
||||
result = StringField()
|
||||
@ -145,12 +144,11 @@ class TaskTestCase(EosTestCase):
|
||||
task.save()
|
||||
task.run()
|
||||
|
||||
self.assertEqual(task.status, TaskStatus.COMPLETE)
|
||||
self.assertEqual(task.status, Task.Status.COMPLETE)
|
||||
self.assertEqual(len(task.messages), 1)
|
||||
self.assertEqual(task.messages[0], 'Hello World')
|
||||
self.assertEqual(task.result, 'Success')
|
||||
|
||||
@py_only
|
||||
def test_error(self):
|
||||
class TaskError(Task):
|
||||
def _run(self):
|
||||
@ -160,6 +158,6 @@ class TaskTestCase(EosTestCase):
|
||||
task.save()
|
||||
task.run()
|
||||
|
||||
self.assertEqual(task.status, TaskStatus.FAILED)
|
||||
self.assertEqual(task.status, Task.Status.FAILED)
|
||||
self.assertEqual(len(task.messages), 1)
|
||||
self.assertTrue('Test exception' in task.messages[0])
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import eos.core.objects
|
||||
import eos.core.bigint
|
||||
import eos.core.hashing
|
||||
import eos.js
|
||||
|
||||
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.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)
|
||||
return bs
|
||||
|
||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
||||
def serialise(self):
|
||||
return self.impl
|
||||
|
||||
@classmethod
|
||||
@ -173,7 +173,7 @@ class InfiniteHashBitStream(BitStream):
|
||||
# 11000110110
|
||||
# ^----
|
||||
if nbits is None:
|
||||
raise Exception('Cannot read indefinite amount from InfiniteHashBitStream')
|
||||
nbits = self.remaining
|
||||
while nbits > self.remaining:
|
||||
self.ctr += 1
|
||||
self.sha.update_text(str(self.ctr))
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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.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
|
||||
|
||||
@ -175,8 +175,6 @@ class EGCiphertext(EmbeddedObject):
|
||||
return ct.gamma == self.gamma and ct.delta == self.delta
|
||||
|
||||
class EGProvedPlaintext(EmbeddedObject):
|
||||
_ver = StringField(default='0.6')
|
||||
|
||||
message = EmbeddedObjectField(BigInt)
|
||||
|
||||
ciphertext = EmbeddedObjectField()
|
||||
@ -259,8 +257,7 @@ class PedersenVSSPrivateKey(EmbeddedObject):
|
||||
def get_modified_secret(self):
|
||||
mod_s = self.x
|
||||
for j in range(1, threshold + 1): # 1 to threshold
|
||||
# TODO
|
||||
pass
|
||||
...
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if (
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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
|
||||
orig_answers = []
|
||||
for voter in self.recurse_parents(Election).voters:
|
||||
if len(voter.votes.get_all()) > 0:
|
||||
vote = voter.votes.get_all()[-1]
|
||||
if len(voter.votes) > 0:
|
||||
vote = voter.votes[-1]
|
||||
ballot = vote.ballot
|
||||
orig_answers.append(ballot.encrypted_answers[question_num])
|
||||
return orig_answers
|
||||
@ -195,8 +195,8 @@ class InternalMixingTrustee(MixingTrustee):
|
||||
else:
|
||||
orig_answers = []
|
||||
for voter in election.voters:
|
||||
if len(voter.votes.get_all()) > 0:
|
||||
ballot = voter.votes.get_all()[-1].ballot
|
||||
if len(voter.votes) > 0:
|
||||
ballot = voter.votes[-1].ballot
|
||||
orig_answers.append(ballot.encrypted_answers[question])
|
||||
shuffled_answers, commitments = self.mixnets[question].shuffle(orig_answers)
|
||||
self.mixed_questions.append(EosList(shuffled_answers))
|
||||
@ -225,20 +225,12 @@ class InternalMixingTrustee(MixingTrustee):
|
||||
return True
|
||||
|
||||
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
|
||||
|
||||
public_key = EmbeddedObjectField(SEGPublicKey)
|
||||
mixing_trustees = EmbeddedObjectListField()
|
||||
|
||||
def can_audit(self):
|
||||
"""Overrides Election.can_audit"""
|
||||
return True
|
||||
|
||||
def verify(self):
|
||||
"""Overrides Election.verify"""
|
||||
# Verify ballots
|
||||
super().verify()
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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()
|
||||
|
||||
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:
|
||||
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()
|
||||
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:
|
||||
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
|
||||
def test_run_election(self):
|
||||
@ -278,10 +278,10 @@ class ElectionTestCase(EosTestCase):
|
||||
answer = ApprovalAnswer(choices=VOTES[i][j])
|
||||
encrypted_answer = BlockEncryptedAnswer.encrypt(election.sk.public_key, answer)
|
||||
ballot.encrypted_answers.append(encrypted_answer)
|
||||
vote = Vote(voter_id=election.voters[i]._id, ballot=ballot, cast_at=DateTimeField.now())
|
||||
vote.save()
|
||||
vote = Vote(ballot=ballot, cast_at=DateTimeField.now())
|
||||
election.voters[i].votes.append(vote)
|
||||
|
||||
#election.save()
|
||||
election.save()
|
||||
|
||||
# Close voting
|
||||
self.do_task_assert(election, 'eos.base.workflow.TaskCloseVoting', 'eos.psr.workflow.TaskMixVotes')
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
@ -43,9 +43,9 @@ class BasePyTestCase(TestCase):
|
||||
class BaseJSTestCase(TestCase):
|
||||
@classmethod
|
||||
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()
|
||||
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
|
||||
def add_method(cls, method):
|
||||
|
@ -19,7 +19,6 @@
|
||||
"semantic": "semantic-ui#^2.2.13",
|
||||
"nunjucks": "^3.0.1",
|
||||
"dragula.js": "dragula#^3.7.2",
|
||||
"fingerprintjs2": "^1.5.1",
|
||||
"progress-tracker": "^1.4.0"
|
||||
"fingerprintjs2": "^1.5.1"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||
# Copyright © 2017 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
|
||||
@ -16,7 +16,6 @@
|
||||
|
||||
import click
|
||||
import flask
|
||||
import flask_session
|
||||
import timeago
|
||||
|
||||
from eos.core.objects import *
|
||||
@ -29,10 +28,6 @@ from eos.psr.election import *
|
||||
from eos.psr.mixnet import *
|
||||
from eos.psr.workflow import *
|
||||
|
||||
from eosweb.core.tasks import *
|
||||
|
||||
from . import emails
|
||||
|
||||
import eos.core.hashing
|
||||
import eosweb
|
||||
|
||||
@ -45,7 +40,6 @@ import json
|
||||
import os
|
||||
import pytz
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
app = flask.Flask(__name__, static_folder=None)
|
||||
|
||||
@ -63,16 +57,6 @@ if 'EOSWEB_SETTINGS' in os.environ:
|
||||
# Connect to database
|
||||
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
|
||||
User.admins = app.config['ADMINS']
|
||||
|
||||
@ -115,10 +99,49 @@ def run_tests(prefix, lang):
|
||||
import eos.tests
|
||||
eos.tests.run_tests(prefix, lang)
|
||||
|
||||
# Create the session databases (SQL only)
|
||||
@app.cli.command('sessdb')
|
||||
def sessdb():
|
||||
app.session_interface.db.create_all()
|
||||
# TODO: Will remove this once we have a web UI
|
||||
@app.cli.command('drop_db_and_setup')
|
||||
def setup_test_election():
|
||||
# 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')
|
||||
@click.option('--electionid', default=None)
|
||||
@ -135,8 +158,7 @@ def verify_election(electionid):
|
||||
@click.option('--electionid', default=None)
|
||||
@click.option('--qnum', default=0)
|
||||
@click.option('--randfile', default=None)
|
||||
@click.option('--seats', default=1)
|
||||
def tally_stv_election(electionid, qnum, randfile, numseats):
|
||||
def tally_stv_election(electionid, qnum, randfile):
|
||||
election = Election.get_by_id(electionid)
|
||||
|
||||
with open(randfile, 'r') as f:
|
||||
@ -145,22 +167,8 @@ def tally_stv_election(electionid, qnum, randfile, numseats):
|
||||
election_id=election._id,
|
||||
q_num=qnum,
|
||||
random=dat,
|
||||
num_seats=numseats,
|
||||
status=TaskStatus.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,
|
||||
num_seats=7,
|
||||
status=Task.Status.READY,
|
||||
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
||||
)
|
||||
task.save()
|
||||
@ -183,6 +191,10 @@ def tick_scheduler():
|
||||
|
||||
# === Views ===
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return flask.render_template('index.html')
|
||||
|
||||
def using_election(func):
|
||||
@functools.wraps(func)
|
||||
def wrapped(election_id, **kwargs):
|
||||
@ -192,83 +204,17 @@ def using_election(func):
|
||||
|
||||
def election_admin(func):
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
def wrapped(election, **kwargs):
|
||||
if 'user' in flask.session and flask.session['user'].is_admin():
|
||||
return func(*args, **kwargs)
|
||||
return func(election, **kwargs)
|
||||
else:
|
||||
return flask.Response('Administrator credentials required', 403)
|
||||
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>/')
|
||||
@using_election
|
||||
def election_api_json(election):
|
||||
is_full = 'full' in flask.request.args
|
||||
|
||||
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')
|
||||
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')
|
||||
|
||||
@app.route('/election/<election_id>/view')
|
||||
@using_election
|
||||
@ -291,20 +237,7 @@ def election_view_questions(election):
|
||||
@app.route('/election/<election_id>/view/ballots')
|
||||
@using_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.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)
|
||||
return flask.render_template('election/view/ballots.html', election=election)
|
||||
|
||||
@app.route('/election/<election_id>/view/trustees')
|
||||
@using_election
|
||||
@ -322,13 +255,13 @@ def election_admin_summary(election):
|
||||
@election_admin
|
||||
def election_admin_enter_task(election):
|
||||
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)
|
||||
|
||||
task = WorkflowTaskEntryWebTask(
|
||||
task = WorkflowTaskEntryTask(
|
||||
election_id=election._id,
|
||||
workflow_task=workflow_task._name,
|
||||
status=TaskStatus.READY,
|
||||
status=Task.Status.READY,
|
||||
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
||||
)
|
||||
task.run()
|
||||
@ -341,31 +274,25 @@ def election_admin_enter_task(election):
|
||||
def election_admin_schedule_task(election):
|
||||
workflow_task = election.workflow.get_task(flask.request.form['task_name'])
|
||||
|
||||
task = WorkflowTaskEntryWebTask(
|
||||
task = WorkflowTaskEntryTask(
|
||||
election_id=election._id,
|
||||
workflow_task=workflow_task._name,
|
||||
run_at=DateTimeField().deserialise(flask.request.form['datetime']),
|
||||
status=TaskStatus.READY,
|
||||
status=Task.Status.READY,
|
||||
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
||||
)
|
||||
task.save()
|
||||
|
||||
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'])
|
||||
@using_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
|
||||
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:
|
||||
# User is not authenticated
|
||||
@ -383,7 +310,7 @@ def election_api_cast_vote(election):
|
||||
|
||||
# Cast the vote
|
||||
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
|
||||
if app.config['CAST_FINGERPRINT']:
|
||||
@ -394,13 +321,13 @@ def election_api_cast_vote(election):
|
||||
else:
|
||||
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({
|
||||
'voter': EosObject.serialise_and_wrap(voter, None, SerialiseOptions(should_protect=True)),
|
||||
'vote': EosObject.serialise_and_wrap(vote, None, SerialiseOptions(should_protect=True))
|
||||
'voter': EosObject.serialise_and_wrap(voter, should_protect=True),
|
||||
'vote': EosObject.serialise_and_wrap(vote, should_protect=True)
|
||||
}), mimetype='application/json')
|
||||
|
||||
@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'
|
||||
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')
|
||||
def auditor():
|
||||
return flask.render_template('election/auditor.html')
|
||||
@ -427,28 +348,14 @@ def debug():
|
||||
|
||||
@app.route('/auth/login')
|
||||
def login():
|
||||
flask.session['login_next'] = flask.request.referrer
|
||||
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')
|
||||
def logout():
|
||||
flask.session['user'] = None
|
||||
if flask.request.referrer:
|
||||
return flask.redirect(flask.request.referrer)
|
||||
else:
|
||||
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('/')
|
||||
#return flask.redirect(flask.request.args['next'] if 'next' in flask.request.args else '/')
|
||||
# I feel like there's some kind of exploit here, so we'll leave this for now
|
||||
return flask.redirect('/')
|
||||
|
||||
@app.route('/auth/login_complete')
|
||||
def login_complete():
|
||||
@ -466,21 +373,13 @@ def email_login():
|
||||
def email_authenticate():
|
||||
user = None
|
||||
|
||||
for u in app.config['ADMINS']:
|
||||
if isinstance(u, EmailUser):
|
||||
if u.email.lower() == flask.request.form['email'].lower():
|
||||
if u.password == flask.request.form['password']:
|
||||
user = u
|
||||
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
|
||||
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:
|
||||
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']:
|
||||
app_main = importlib.import_module(app_name + '.main')
|
||||
app.register_blueprint(app_main.blueprint)
|
||||
app_main.main(app)
|
||||
|
||||
# === Model-Views ===
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -26,18 +26,6 @@
|
||||
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 {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
@ -61,11 +49,6 @@ time[title] {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
/* Fix nested selectable tables */
|
||||
.ui.table.selectable tr > td.selectable:hover {
|
||||
background: initial !important;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body, html {
|
||||
/* Default height: 100% causes blank pages */
|
||||
@ -135,29 +118,15 @@ time[title] {
|
||||
padding: 0.5em 0 0.5em 0.5em;
|
||||
}
|
||||
|
||||
.ticket-choices .number {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ticket-choices .content {
|
||||
.ticket-choices .number, .ticket-choices .content {
|
||||
padding: 0 0 0 0.5em;
|
||||
}
|
||||
|
||||
.preferential-choice .number {
|
||||
width: 2em;
|
||||
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 {
|
||||
font-style: italic;
|
||||
font-size: small;
|
||||
@ -170,7 +139,3 @@ time[title] {
|
||||
.ticket > .content > .party-name {
|
||||
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,iVBORw0KGgoAAAANSUhEUgAAA2IAAAEVCAYAAACCF1bCAAAABHNCSVQICAgIfAhkiAAAIABJREFU
|
||||
eJzs3XlcVFX/B/APoCgIaiCUZurgBiICCtmjY6mI4O5jj7iUW7lm5opKak/llpqKS2ruCyZZWuKC
|
||||
K0lKmlqSe7khiOLOrqzn94cP98fALHcWBtDP+/Xy9fLeueee7zn33HPny9y5YyGEECAiIiIiIiKz
|
||||
sSztAIiIiIiIiF42TMSIiIiIiIjMjIkYERERERGRmTERIyIiIiIiMjMmYkRERERERGbGRIyIiIiI
|
||||
iMjMmIgRERERERGZGRMxIiIiIiIiM2MiRkREREREZGZMxIiIiIiIiMyMiRgREREREZGZMREjIiIi
|
||||
IiIyswqlHQAREREREanacOMq5l+7givpqaUdClDZprQjKJPcbe0QrGiAQXVdDCpvIYQQJo6JiIiI
|
||||
iIgMNPB0DLYkxJV2GP+PiZhWw2vXxbfNWuhdjrcmEhERERGVEUv/uVy2kjDSafXtW1h+/W+9yzER
|
||||
IyIiIiIqIzbfulXaIZABNt+5rXcZJmJERERERGXE+Yzk0g6BDHAuI03vMkzEiIiIiIjKiOz8vNIO
|
||||
gQyQlZ+vdxkmYkRERERERGbGRIyIiIiIiMjMTPY7Yjk5Odi9ezf27t2LCxcuIDk5GTY2Nqhfvz46
|
||||
dOiA/v37o1q1aqaqTkV+fj5+/fVXHD16FGfOnMH9+/eRkpICe3t7ODk5wdfXFx07doRSqVQp9+DB
|
||||
A7Rs2VJaDgkJwbBhw0okRnW2bt2KJUuWwMbGBjNmzECHDh3MVjcREREREZUek/yOWFxcHD766CNc
|
||||
uXJF4zY1atTAihUr4OPjY2x1Kg4cOICFCxfi2rVrOrf18vLCF198AQ8PDwClm4jdvHkT/v7+yP/f
|
||||
/aQ2NjY4efIk7O3tzVI/EREREZU9Fju3lnYIxfF3xGQRnXvptb3RtyY+fvwY7733ntYkDAAePnyI
|
||||
oUOHIj4+3tgqAQC5ubn44osvMGrUKFlJGADExsYiKCgIv/zyi0liMEZiYqKUhAHA06dP8eDBg1KM
|
||||
iIiIiIiIzMXoWxO/+uor3L17V1ru27cvRowYgZo1a+LmzZtYuHAhDh8+DABITU1FaGgoFi1aZGy1
|
||||
+OyzzxAeHq6y7pVXXoG/vz+8vb1RvXp1PHv2DFeuXMGePXuQmJgIAKhSpQqaNGlidP3Gat68OerW
|
||||
rYtb//utCF9fX9SrV690gyIiIiKiF4JzpcqwsbLCnadPkSP0f6IflTyjErHHjx8jIiJCWu7Rowfm
|
||||
zJkjLTdu3BjLly9HYGAg4uLiAACHDh1CXl4erKysDK73xx9/LJaEvf/++wgODi52a1+PHj0wceJE
|
||||
bN68GT/88AM+/fRTvPrqqwbXbSq2trb46aef8PPPP6Ny5cr497//DUtLPjuFiIiIiAxTtUJFfNLA
|
||||
FX3fqAsH60oAgFwhcCDpDhZfvYxLaSmlHCEVZlQidvr0aWRnZwMALC0t8cknnxTbxtraGq1bt5YS
|
||||
sYyMDDx69AjOzs4G1ZmVlYWFCxeqrBs1ahSCg4M1lqlQoQI++OADfPDBBwbVWVKqV6+OwYMHl3YY
|
||||
RERERFTO1a9ij7A3W6OubRUAwNO8PCRnZ+M1Gxt0qfk6OrxaE5PO/YEdiab5mpAppAf0QBUrK1js
|
||||
26myvnZlGyS074S/M9LhGn1Q1r6SO3ZDUlaW7O3LAqMSsXbt2mHfvn24du0a6tatC4VCoXY7a2tr
|
||||
lWULCwuD69y1axfu3bsnLbu6umLChAkG76+o5ORkrFmzBgcOHEBiYiIqVaoET09PDB8+HK1bt1Zb
|
||||
RgiBgwcPYv/+/Th79iwePnwIAKhXrx66deuGQYMGoXLlysXKtWnTRrpl0t/fH99++630WuEHiTRo
|
||||
0AAHDhwAAISFhSEsLAzx8fGws7PD+vXr4eHhobJ9kyZNEBERgdOnT2PdunX4888/kZWVhSZNmmDS
|
||||
pEnw9fUFAFy4cAHr16/HyZMn8fjxY1StWhU+Pj4YNWqU9EATU7RVXWy3b9/G2rVr8euvv+LevXuy
|
||||
+pmIiIiIirO1rIANPv9CXdsqeJqXh5ALZ7EjMR55QqCebRUs9vRFSwdHLPJsgbiMDPyR/Ki0QyYY
|
||||
mYhZW1vD1dUVrq6uWrf7559/pP9Xr14dNWrUMLjOI0eOqCx/+OGHRt3mWFhsbCzWrFkjJRfA80/g
|
||||
jh07huPHj2PWrFno16+fSpkrV65gwoQJah9WcunSJVy6dAl79+7F5s2bUb16dYPiunbtGu7du4ev
|
||||
vvpK5VbQrKwsODo6qq03ODgYP/30k8r606dPY+DAgdiyZQtu3bqFkJAQ5ObmSq8/fPgQ+/fvx5Ej
|
||||
R7B8+XL4+/ubvK2XLl3C/PnzsXHjRunT1IK2aOtnIiIiIlLv/boKNLB7/vWcZdevYPvtW6hnWwX1
|
||||
bO1w9OE9DD79G075BcK+QkVMc2uKXieiSzliAszwg863bt3CiRMnpOXu3bsb9YnY6dOnVZbfeecd
|
||||
g/dVVGRkJB4+fAhbW1s0bdoUTk5O0mtCCHz55ZcqDybJzs4u9sTIatWqoX79+qhUqZK07sKFC5gx
|
||||
Y4ZRsc2cOVMlCQOef8+sZs2aarf/6aefYGtri549e6J///7SdllZWZgyZQqmT5+O3NxcNG7cGO+/
|
||||
/z7at28vlc3JycHkyZORkZFRIm1dvXo1srOz4evri8GDByMwMFBKptX1MxERERFp1uv1N6T/j6nv
|
||||
ismN3HGoTQds8G2FVyvZICU3G7f+977uLYcaqFXZtrRCNZhDxYoI8/JFasfuSPLrgm+beqNaBfWf
|
||||
KdlZWUF07oWZjZpgkVszPOnYDQ/9u2LYG/XMG7QOJZ6IzZo1S3pMu7W1NYYPH27wvrKzs5GcnCwt
|
||||
V61a1ahP14qysrLC+PHjcerUKURERODkyZMYNWqU9HpWVhZ27dolLVtbW0vfO1MoFFixYgX++OMP
|
||||
HDp0CKdOncJbb70lbbtv3z7pNkRDREZGAgBq166NwMBA9OjRA6NHj9aY1NavXx8HDhzAokWLMGvW
|
||||
LOzbtw8ODg4Anv+GWVZWFjp27Ig9e/bgyy+/xNq1a1USqJSUFOl2SFO31cHBAVu2bMH333+Pzz77
|
||||
DCtWrEBoaKj0etF+JiIiIiLNXKrYSf+3sbLCuIauuJf1DJ2OReFe1lO42lVD46pVpW3qValSGmFq
|
||||
1MC2isq/ejaqiaIlgMMt26Cbc03Mvf43QuOuol+tN7DGo4XW/U5r4Io3bGww4+9LeJydjdUezeFd
|
||||
tVoJtkQ/JZqIrVu3TuVWwilTpqBWrVoG7y8tLU1l2dQ/fvzRRx9hzJgxsLV9fvAtLCwwbtw4VC00
|
||||
cM+dO6dSZvTo0YiOjsahQ4cQGBgoPfnQ3t4ekydPlrYTQuDUqVNGxTd+/HgcPXoUK1aswOLFi1WS
|
||||
xKKCgoLw+uuvS8vVqlVDhw4dVLYZOXKkym2d/fv3V0nszp8/r7K9qdqq7ntgXbp0UfkEsmg/ExER
|
||||
EZF6Ofmi2LqJ587gSnoKetV6A7tbt0VFi/9/25+bX7YeZ3+1bYDKv2P/Ur3jrfurNeFdtTr++88l
|
||||
zL3+N766/g8W3byK3jVfh72V5m9anUp+jN5//o7lt65jwuXn7y0Hvl63RNuiD6N/R0yTXbt2qTzK
|
||||
vmvXrhgyZIhR+yyaeKWnpxu1v6Ls7OyKratYsSLq1KmDCxcuAACePHlSbJs33nij2DoAaNiwocry
|
||||
/fv3DY4tMDAQY8aMMbg8gGKP7a9bV3UgVqpUCQ4ODnj06PkXOAt/+ljAFG3V9ClezZo1pR+1VtfP
|
||||
RERERFTcP+lpaOmg+tyAoNr10NbpNQypWx9VCt3ClycErpn4PbSxev/5u8qyQ0VrfOvhLS172D//
|
||||
FGu+mwe+cm0KALD63/vJuja2uJCeqna/6Xn//yyEi2nPt3GxLTufBpZIIhYZGYng4GAI8Tw7b9Om
|
||||
Db7++muj92ttbQ17e3vpk7HU1FQ8fvxYuuWupFQoPHjz8nRu//TpU6SmpuLp06cq63NycgyOwdvb
|
||||
W/dGOhT9nTJ1DzkpnCSZu62F45NTNxEREREB2xJuFkvEll//Gzcz0tHp1VqoWrGitP7Qvbt4nJNl
|
||||
7hC1+jFJ9SsttSvbqCxb/u/96ScXY3H00QNpvQBwNUNeUlnpf+8zs8vQj1ubPBGLjIzE2LFjpafx
|
||||
vfnmm1i1alWxR9gbysvLC8eOHQPw/Ba4mJgYdOvWzST7NlRWVhb27NmDI0eO4Pz587hz546UhL5o
|
||||
Xqa2EhEREZUHOxLj0e+Nemjp8P/PTrC1rIBKlpZSEgMAabk5+PJy+fv6x8knjwEAzeyrYVX8TQCA
|
||||
BYBXKlaEtrTKCv/f9jb/65vzqWXnR61NmogVTcJatGiBdevWwcbGRkdJ+ZRKpZSIAcD69etLNRE7
|
||||
ePAgpk+frvLI+xfVy9RWIiIiovIiTwgM/eMk1rX4F9783ydjh972U9nmUVYWhv15EnGZGep2UaYd
|
||||
eHgPvz5+iJF1XfBKRWtczUxHF+fX4GRdCfV/2Y8cDR8KtHV0wvpmLXD32TOMqVcfGXl5+PZ/iVxZ
|
||||
YLKHdRRNwry8vLB+/XpUMfFTWd59912VxO6vv/7C2rVrZZUtevucsU6cOIGPPvpISkw8PT0xa9Ys
|
||||
7N27F7GxsSq/n1bevUxtJSIiIipvHmVn4d2T0Zh87k/88eQx0nNzkCPycTMjHcuv/Q2/Y4dx8nH5
|
||||
/WN6tzO/YW3CTQQ6v4opLo2QnZ+PD8/9oTEJA4Dzaal4vXJljFM0wK2nmWh/8lfcyy47t2Wa5BOx
|
||||
okmYh4cHNm3aJPuphjdu3EBSUhJatGih8ptU6jg4OGDQoEFYtWqVtG7u3Ll4+vQpRo4ciYqF7oEt
|
||||
kJmZif/+97+IiIjAkiVLEBgYqEfrNFu3bp30aH5fX19s27at2PewXhQvU1uJiIiIyqM8IRCWcBNh
|
||||
CWXnUx9t7A6o/7mi28+ewmLfTpV1qbm5GH7+LIafP6u2TPWDu4utu5/9DAGnYowPtIQYnYgVTcLc
|
||||
3NwwY8YMnD17Fs+ePSu2fdWqVdGyZUvpoRCbN2/GF198ASEEXF1dsWPHDp23Mo4bNw6//fab9Ihz
|
||||
IQQWL16MnTt3omvXrmjUqBGqVKmClJQUXLhwAbt27cLjx8/vLR0/fjxq1qwJT09PY5suPeEPeP77
|
||||
XkUTk8KP7i/vXqa2EhERERGVNKMSsRMnTqgkYQBw7do1BAUFaS03d+5c9OnTBwDw7bffSg97uHLl
|
||||
Co4ePYpOnTppLW9tbY21a9di+PDhiI2NldbfunUL33zzjdayrVu3hpubm9Zt5HJ1dZV+a2vfvn1o
|
||||
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
|
||||
3Lkz9uzZY/YYmjZtiq1bt2rdJiMjA0OHDoWXl5fB9bRp0wbLly83uHyB3r17Y+jQoUbvxxhCCERE
|
||||
ROC9996Dl5cX3Nzc4O/vjyVLliAzM1PabvDgwRg/fry0rE/s+pwbms4BU/W5HCVdlyFzhSnmF3My
|
||||
1VxmrvFQdHzrou5aYqo267pOvUjnSGnMgWX5OlveznNz0Pfc1EbTsTf3OCwL57AcL9N1v6wzOBHr
|
||||
378/YmNjcfbsWbWvb9iwAT4+PmjUqJHOfV2/fh0rVqwwNBQAwM6dOxEeHo4lS5YgOjoalSpVMmp/
|
||||
pmaKNpaGNWvWYNasWXjvvfewZcsWBAQEYPz48YiIiCjt0FTcu3cPffv2xfHjx0s7FACAlZUVLC1L
|
||||
7wPnvLw8jBkzBuPHj0e1atXw6aefYsGCBejcuTPCw8Mxffp0jWVLKvbyeg6QKlMdx7I6HtRdS0wV
|
||||
q67rVFntE0OUxhz4IvUf6UfTsTf3OCyvY5DX/dJTwdCC/v7+cHZ2xrZt2+Dt7a3y2pUrV/DXX38h
|
||||
NDRU1r7i4+MNDUNy+fJlNGnSBK1atTJ6XyXBFG00t7S0NCxZsgQTJ07EBx98AADw8fFBWloa5syZ
|
||||
g86dO6NCBYOHkMncuHEDAwYMgJubGwIDA7FmzZrSDgnh4eGlWv/KlSuxb98+LFmyBN26dVN5bdiw
|
||||
YcjOztZYtqRiL4/nABVnquNYVseDumuJqWLVdZ0qq31iiNKYA1+k/iP9aDr25h6H5XUM8rpfegxO
|
||||
fytUqICgoCDs2bMHqampKq999913cHBwQGBgILKzs7FgwQK0bNkSbm5u6NmzJ86cOSNt++WXX2LM
|
||||
mDFITU2Fi4sLXFxccP36dQDPP9IsuI3Dy8sLw4YNQ0JCQrFYPvjgA2zYsAFnzpyR9gFAZ90A8Pbb
|
||||
b+PgwYP44osv4OHhgQEDBqhtrxACy5cvh6+vL5o1a4Y5c+YgNzdXel1brJraOGDAAEycOFGlnh49
|
||||
emDcuHHF1i1evFhWn+h6/e2330ZERASmTZsGLy8vvPXWW/jmm2/UtjkmJgaZmZn497//rbK+Z8+e
|
||||
uH//Pv76669iZRISEjB27Fh4e3ujRYsW+PTTT/H06VO96s/IyMCkSZPg4eGBNm3a4Ntvv1UbXwFH
|
||||
R0cMHToUq1evhq2trdZti/aRm5sb+vXrhytXrkivZ2dnY+bMmRrjkzOuin7sr61OXcds//79CAgI
|
||||
QNOmTdG9e3ccOnRIa/tycnKwdu1aBAYGFkvCAMDOzg4ODg4ay+sTe1Hh4eHw8fHBxYsXVdZrO88B
|
||||
3X0udy6QE6+xdZmiP4rSNb/oGnN79uyBq6srzp8/L637+eef4eXlhTt37shql5xxpu04yjkv5Oyn
|
||||
YF+mGg9FaSur7lqiK9bCMWtrv6brVGn0yYs4B5pqbALPr1Pbt2/HkCFD0KRJE/Tu3RsPHz7EwoUL
|
||||
4evrC19fX8ydOxdCCKmMrmtfUUXnBrnHb+TIkejTp0+x9T169MCCBQtktbegj6KiolT2UfQrAHLe
|
||||
H2k7Nvqep4bOvdqOfdFxKPd9odz3SYWV9Dmsb1z6XKvK+3W/XBNGSExMFA0aNBCbNm2S1mVmZgpP
|
||||
T08xd+5cIYQQo0ePFu3btxfHjx8X169fF0uXLhUNGjQQ586dk8qsX79eeHp6Ftv/iBEjRFBQkLh6
|
||||
9ap4/PixmD9/vvDz8xNZWVnFtv3ss89E9+7dVdbJqVupVIp27dqJUaNGifDwcHH69Oli+w4ICBCe
|
||||
np5i3bp14saNG+LHH38Urq6uYuXKlbJjVdfGTZs2CU9PT5GTkyOEECIuLk64urqKpk2bSuVu374t
|
||||
FAqFuHDhgqx6dL2uVCqFj4+P2L9/v8jKyhJRUVHCxcVFHD9+vFi7ly1bJry9vYutz8rKEgqFQnz/
|
||||
/ffFXuvXr5/4+OOPRWxsrIiKihIeHh4iNDRUpb911T9o0CDRvn17ceLECXHjxg2xcOFCoVAoRFhY
|
||||
WLH6itI0lgobOXKkeOedd0R0dLRISEgQ27ZtEzt27JDi8/DwEBs2bBA3b94UW7ZsEQqFQiU+OeNq
|
||||
0KBBYty4cbLq1HbMbt26JRo2bCh27NghkpOTRUxMjIiIiNDavtjYWKFQKERkZKTO/lIXqz6xBwQE
|
||||
iNmzZwshhPjpp5+Ep6enOHv2rNp6NB0bOX2uz1xg7PHVVZep+qOAnPlFzpj76KOPRJcuXUROTo64
|
||||
f/++8PLyEj/88IOsdukzzjQdRzkxytmPqcdD0fGsq6y6a4mceUVO+9XtuzT65EWdA001NpVKpVAq
|
||||
leL8+fMiLS1NDB48WLRv315MnjxZpKeni9OnT4vGjRurxKHr2qdrbpB7/I4dO6bynkAIIc6ePSvq
|
||||
168v4uPjZbU3JSVFKBQKceTIEZV9u7u7q1xndb0/0nVsdLVJ33NT2xjSdOyL1iH3faHc90lFleQ5
|
||||
rG9c2vrrRbvul2dGJWJCCDF06FARGBgoLW/fvl24uLiIuLg4ce7cOaFQKMTJkydVygwaNEgMHjxY
|
||||
WlZ3oC5evCgUCoW4efOmtC4/P1+8+eabIiYmplgcRS9wcutWKpViyJAhWtsYEBAgZsyYobJuypQp
|
||||
ws/PT3as6tp4584d4eLiIn7//XchhBDffvutGDNmjFAqlSIqKkoIIcTmzZuFUqmUVY+cOJRKpVi6
|
||||
dKlKHJ06dRLz588v1u558+aJ1q1bq+2Thg0bivXr1xdb/+zZM5Xl4OBgERQUJC3rqr+gDUWPcdEL
|
||||
hCa63jBdvnxZ7f4Lx7do0SKVdR07dpTikzuuCk9q2urUdcxOnjwpFAqFuHr1qvaGFxIVFSUUCoXa
|
||||
Pyqoo21C1tVfBRPy3r17RbNmzcSpU6c01qNtQtbW5/rMBcYeX111mbI/CpfRNr/IHXMPHz4UPj4+
|
||||
YtWqVWLEiBFi2LBh0mumHGfqjqPcGHXtRwjTjoeCGArGs5yyhiRicttvTCJWVs6RsjwHmmpsKpVK
|
||||
sWzZMml59+7dQqFQiNu3b0vr+vbtKz799FNpWde1T9vcoM/xy8/PF+3btxfTpk2T1k2dOlVqi5z2
|
||||
6pOIaXt/pO3YyGmTPuemrnErJxHT532h3PdJcuMwxTmsT1y6+utFuu6Xd0Z/wad///748MMP8ccf
|
||||
f6BFixbYtm0blEol6tati61bt8LCwqLYd8i8vb2xadMmrfu9dOkSLCwsij3FRQiB5ORknXGdO3dO
|
||||
dt3NmzfXub/KlSurLLu5uWHXrl0QQhgca82aNeHh4YGjR4/izTffxMGDBzFkyBA4OTnh0KFDaNeu
|
||||
HX755RcEBAQA0N0nd+7ckRWHhYWFyut2dnZISUkpFp+tra3K0/UKZGdnIzc3V+1tgEW/fO7s7Ixz
|
||||
586prNNW/6VLlwDIOyaGuHz5MiwsLODj46Nxm4oVK6osV6tWTYpPn3Elp05dx7RDhw7w9fVF9+7d
|
||||
4efnh27dusHf379YHxaNFwAePXqkcRu55PTX5s2bsXbtWgwcOBC+vr4G1aOtz/U5v4w9vrrqunfv
|
||||
nsH9cebMGQQFBUnLQ4YMwYwZMwBon1/kjjlHR0d8/vnnmDhxIipXrowjR45Ir5XEOCvMkPNCG1ON
|
||||
h6KMva5oYur2q1NWzpHyMAcWZopjU3Cts7e3l9ZVqVIFT548kZblXPs0zQ36HD8LCwu89957CA0N
|
||||
RUhICABg7969WLhwocnaW5i2a7G3t7fGY6PvuWaKuVcXffpG7vskfZjiHJYbl5zzXJPydt0v74xO
|
||||
xN555x3Url0b27Ztg52dHWJjY7Fy5UoAULl/Wl/5+fmwsrLC/v37DXoghDF1y5Gfn48KFSrAwsLC
|
||||
qFg7duyIPXv2YPDgwbh8+TLatm2LGjVq4JNPPkFmZiZOnDiBkSNHSnVqq2f79u1G9VlR9evXR3Jy
|
||||
Mh49egRHR0dp/dWrV6XXi0pMTERYWBhOnTqFBw8eIDU1Fa+99prsOnNycmBhYVFiDwF59uwZAMPH
|
||||
hyHltNUpZ+xs27YNv/32G3bv3o2xY8eiS5cu+PrrrzXW5+7uDhsbG+nefWPI6a+BAwfC29sbY8eO
|
||||
RdeuXY26UKqjz/ll7PHVVde2bdt07l9Tf7i7uyMyMlLaTtv39ArPL/q0JTExEfb29khOTkZcXJx0
|
||||
3pbEOCuspOfbwoyZb429rmhizvarY85zpDzMgcbGawg51z5Nc4O+4/I///kPvv76a0RERMDS0hL2
|
||||
9vZo3749APOORWtra43HRt82mWLu1aW0z1NtTD03GXOel7frfnln9LMqLS0t0a9fP0RGRmLr1q14
|
||||
7bXX4OfnBwDw8PCQ/qJbWGxsLJo2bap1v56ensjNzcWpU6cMisuYuuX4448/4OrqanSs/v7+uHLl
|
||||
CrZu3QqlUokqVarAx8cH+fn5WLVqFezs7NCiRQtZ9RjbZ0UplUrY2NgUe1R9REQEnJyciv1e16NH
|
||||
j9C5c2dYWlpi8eLFiI6O1vjwE03eeOMNCCEQFxcnrcvPz0d+fr7B7SjM3d0dQgiNP7ugiyHjSlud
|
||||
co6ZpaUllEol5s2bh5kzZ2Lfvn1aY6xUqRL69euHXbt24eDBg8Vef/DggUr/aiO3vzp16oR+/fph
|
||||
3LhxJv9rlT7j2tjjq6suY/rDxsYGjRs3lv45OTlpLF94fpE75m7cuIGBAZVeAAAgAElEQVTFixdj
|
||||
9uzZePfddzF16lTpd6pKYpwVVtLzbWHGzHOmniMLmLP96pjzHCkPc6Cx8epLn2ufurlB33FZtWpV
|
||||
dOvWDdu3b8eOHTsQFBQEKysrAPLaa2trCysrK2RkZBjaZImmY6Nvm0w192pT2uepNqaem4zpr/J2
|
||||
3S/vTPKjAUFBQcjNzcX27dvRp08fKXv19PSEv78/pk2bht9//x3x8fFYuXIljh07pvJ0FkdHR6Sn
|
||||
pyM6OhoRERHIyspC48aN0aNHD0yZMgW//PILEhIScPjwYQwcOFB6Cpg2cuuWKzIyEtHR0UhISMD6
|
||||
9etx4MABjBgxAgBkxaqujQDQsGFDKBQKbNy4EYGBgQCe/56Dn58fNm7cCD8/P2mC1VWPsX1WlL29
|
||||
PT7++GN8/fXXCAsLw19//YVVq1Zhw4YNmDJlSrG/UsTHxyMtLQ29e/dGnTp1kJSUhJMnT+pVZ8uW
|
||||
LfH666/jq6++wv3795GUlISQkBCdiVhGRgZSU1Px7NkzCCGQmpqK1NTUYn/RadasGdq2bSuNizt3
|
||||
7mDz5s3S06Z0MWRcaatT1zG7dOkSQkNDkZSUhLS0NJw4cUJKgOPi4tC2bVu1v+k2adIk+Pj4YNSo
|
||||
UZg4cSIiIiIQGRmJhQsXIjAwUPrUWhd9+mvatGmoXr06pkyZonZfms4BXfQZ18YeX111mbI/CtM2
|
||||
v8gZc/n5+Zg8eTICAgLQsWNHhISEICUlBUuWLJHVLm3jrCh1x9GQ88Ic48FUZXXFaqrrTXk4R8rS
|
||||
HCin/0z9XkAdfa99RecGQ8blgAEDcO7cOfz1118qT1GU094KFSqgefPm+P7776W7XiZNmiR7vBXQ
|
||||
dmz0bZOxc6+cc8dUYyEpKQl9+/ZFTExMsddKY15Tx5jzvLxd98s7k3ze5+joiICAAERGRhZ7rOqS
|
||||
JUuwaNEi6RGWjRo1QlhYmMo9uh07dkSrVq0wevRouLu7w8fHB7Vq1cK8efOwePFiTJkyBSkpKahb
|
||||
ty7ef/991KpVS1ZccuqWa8CAAdiyZQtiYmJQo0YNLFiwAB06dJBe1xWrpjYCQEBAgJR0FejUqRO2
|
||||
b9+Ojh07qsShqx5j+6yoUaNGwcbGBuvWrcPdu3dRt25dzJ8/Hz179iy2raenJ/r374+goCA4OTmh
|
||||
UaNG6N27NzZu3Ci7vooVK2Lt2rWYPn062rZtizp16uCzzz7DzZs3tZbr37+/yqO7Cy4GMTExqFmz
|
||||
psq2S5cuxezZszF8+HDk5eWhRYsW0r32chgyrrTVqe2YWVpaIi4uDgEBAbCwsEDLli2lW3Kys7OR
|
||||
nJys9nt8lStXRlhYGDZt2oSff/4ZkZGRsLS0RIMGDTBhwgT07dtXdnvl9pe1tTWWLVuGXr16YdOm
|
||||
TRg0aJDK69rOAV30GdfGHl9ddZmqPwrTNb/oGnPr169HYmKi9Bt61apVw/Tp0xEcHIxOnTrBw8PD
|
||||
4HFWlKbjqO95Ya7xYIqycmI1xfWmvJwjZWUOLMpUY1Nf+l771M0N+o7Lpk2bwtXVFXXq1Cl2C6Sc
|
||||
9s6bNw+TJ09Gq1atUKtWLUyePFnv33xycHDQemz0bZMxc6/cc8cUY+HZs2e4ePEiYmJi0Lp1a5XX
|
||||
SmteU8eY87y8XffLMwtRlm+aJSIiIiIVqampaNWqFVauXIk2bdqUdjgvnRUrVqBZs2ZQKpWlHQqV
|
||||
cya5NZGIiIiIzCM8PBxOTk5MBErBkydPEBcXx74nk+AnYkRERETlRF5eHtq2bYsPP/wQgwcPLu1w
|
||||
Xkp5eXnS9/eJjMFEjIiIiIiIyMx4ayIREREREZGZMREjIiIiIiIyMyZiREREREREZsZEjIiIiIiI
|
||||
yMyYiBEREREREZkZEzEiIiIiIiIzYyJGRERERERkZkzEiIiIiIiIzIyJGBERERERkZkxESMiIiIi
|
||||
IjIzJmJERERERERmxkSMiIiIiIjIzJiIERERERERmZlRidjMmTNx584dU8VSrq1btw7Xrl0r7TCI
|
||||
iIiIiKgcMDgRi46Oxj///INatWpJ64QQiIiIwHvvvQcvLy+4ubnB398fS5YsQWZmpkkCLiwrKwur
|
||||
V68utr53794YOnSoyevTJioqCkePHi22PjAwEHPmzDF6/5raqml9Sfv+++/h4uKCu3fvSuvy8/Mx
|
||||
a9Ys+Pr6onnz5sjKyjJ7XOZgqmNqDvqcC4aOpdLoj8GDB2P8+PHSsj7t1CdeTX3Spk0bLF++XF6w
|
||||
Rirpugw5fuXpHDAHY6855pjHi54zJJ8p+64svW8hotJncCL2xx9/QKFQSMt5eXkYM2YMxo8fj2rV
|
||||
quHTTz/FggUL0LlzZ4SHh2P69OkmCbiw69evY8WKFcXWW1lZwdLSvHdd1qhRAw4ODiW2f01t1bS+
|
||||
NOzcuRPh4eFYsmQJoqOjUalSpdIO6aWnz7lQlsaSvkrqnC/PfULmY+z44zh7eZSl9y1EVPoqGFqw
|
||||
Tp06OHPmjLS8cuVK7Nu3D0uWLEG3bt1Uth02bBiys7MNj1KD+Ph4tevDw8NNXpc2CQkJuH37Njp3
|
||||
7lxidWhqq6b1peHy5cto0qQJWrVqVdqh0P/ocy6UpbGkr5I658tzn5D5GDv+OM5eHmXlfQsRlQ0G
|
||||
//mlZ8+eCAsLAwDk5ORg7dq1CAwMLJaEAYCdnZ3Kp0XZ2dlYsGABWrZsCTc3N/Ts2VMlqQOAt99+
|
||||
GxEREZg2bRq8vLzw1ltv4ZtvvpFe//LLLzFmzBikpqbCxcUFLi4uuH79OoDitxHo2lfBPqKiolRi
|
||||
aNq0KbZu3SotZ2VlSbfeeXl5YdiwYUhISMBrr72G1atXo3Llymr7SgiB5cuXw9fXF82aNcOcOXOQ
|
||||
m5sru25NbdXWB6boY3188MEH2LBhA86cOSPFUtjIkSPRp0+fYuV69OiBBQsWyIpZ7nEq2sbt27dj
|
||||
yJAhaNKkCXr37o2HDx9i4cKF8PX1ha+vL+bOnQshhFQmISEBY8eOhbe3N1q0aIFPP/0UT58+1dj2
|
||||
8PBw+Pj44OLFiwA0jxN19KnLkLYUPRf279+PgIAANG3aFN27d8ehQ4cAaD+fzNkfhRUu5+bmhn79
|
||||
+uHKlStqty3aTn3KFo23gLY+AZ6P15kzZ2o8d/Rpt654ja3LFP1RlLZ5rSBmbefznj174OrqivPn
|
||||
z0vrfv75Z3h5eUnfPdbVLk3juShdY/j8+fPo06cP3N3dpWPt4uKCadOmySqv7zWnMGPn8cL0Oc66
|
||||
+lZXm99++20cPHgQX3zxBTw8PDBgwACV/ZtzztcVC6B9rOg7Rxl6vunzvsXc13AiKiXCBGJjY4VC
|
||||
oRCRkZGyth89erRo3769OH78uLh+/bpYunSpaNCggTh37py0jVKpFD4+PmL//v0iKytLREVFCRcX
|
||||
F3H8+HFpm/Xr1wtPT89i+x80aJAYN26c7H2lpKQIhUIhjhw5orIfd3d3ERYWJi2PGDFCBAUFiatX
|
||||
r4rHjx+L+fPnCz8/P5GVlaWxrQEBAcLT01OsW7dO3LhxQ/z444/C1dVVrFy5Uq+6NbVV03pT9bEm
|
||||
4eHhQqFQiDt37kjrPvvsM9G9e3e12x87dkwoFApx4cIFad3Zs2dF/fr1RXx8vKyY5fZVYUqlUiiV
|
||||
SnH+/HmRlpYmBg8eLNq3by8mT54s0tPTxenTp0Xjxo1FRESEVKZfv37i448/FrGxsSIqKkp4eHiI
|
||||
0NBQ6fWAgAAxe/ZsIYQQP/30k/D09BRnz56VXtdnnOiqy9i2FD4Xbt26JRo2bCh27NghkpOTRUxM
|
||||
jMq2msaSOfujsJEjR4p33nlHREdHi4SEBLFt2zaxY8eOYu1St6ytrK54C9PUJ0qlUnh4eIgNGzaI
|
||||
mzdvii1btgiFQqFy7ujTbm3xmqIuU/VHAV3zmhDy5qCPPvpIdOnSReTk5Ij79+8LLy8v8cMPP8hq
|
||||
l67xXJi2MZyVlSV8fX1FaGioyMrKEteuXROtW7cWH3/8sbh3757O8kLof80pyph5vDB9zhldY0ZX
|
||||
m5VKpWjXrp0YNWqUCA8PF6dPn1aJxZxzvq5YdI0VXX2hb99pOw5y37eU9DWciMoGkyRiUVFRQqFQ
|
||||
FJv81Dl37pxQKBTi5MmTKusHDRokBg8eLC0rlUqxdOlSlW06deok5s+fLy3rk4hp25ecyf7ixYtC
|
||||
oVCImzdvSq/n5+eLN998U8TExGhsb0BAgJgxY4bKuilTpgg/Pz/ZdWtrq7r1puxjTfRNxPLz80X7
|
||||
9u3FtGnTpHVTp06V4pETs6GJ2LJly6Tl3bt3C4VCIW7fvi2t69u3r/j000+l5WfPnqnsIzg4WAQF
|
||||
BUnLBW9c9+7dK5o1ayZOnTolvabvONFVl7FtKXwunDx5UigUCnH16lW1+9c0xszZHwUuX74sFAqF
|
||||
xm20JWK6ymqLtyhtidiiRYtU1nXs2FE6d/Rpt654ja3LlP1RuIy2eU3uHPTw4UPh4+MjVq1aJUaM
|
||||
GCGGDRsmvaarXbrGc2HaxvDVq1eFQqEQDx8+lF6fOnWqmDJliqzyBe3S55pTlDHzeAF9zhk541NX
|
||||
m5VKpRgyZIjaugr2Z645X1cs2saKnL7Qp+90HQc571vMcQ0norLB4O+IFVatWjUAwKNHj3Rue+7c
|
||||
OVhYWMDb21tlvbe3NzZt2qSyzsLCQmXZzs4OKSkpBsVo7L4uXboECwuLYk81EkIgOTlZa9mityy6
|
||||
ublh165dKreQmVJp9bE2FhYWeO+99xAaGoqQkBAAwN69e7Fw4UK9YzaGra0tAMDe3l5aV6VKFTx5
|
||||
8kRaLvqQEWdnZ5w7d05l3ebNm7F27VoMHDgQvr6+0np9x4mcuoxpS2He3t7w9fVF9+7d4efnh27d
|
||||
usHf37/YGDAkRlP1R4HLly/DwsICPj4+WmMztKymePVRsWJFleVq1apJ544+7ZYTrzF13bt3z+D+
|
||||
OHPmDIKCgqTlIUOGYMaMGQC0z2tyz2dHR0d8/vnnmDhxIipXrowjR45Ir+lqV4cOHWSPZ21juHbt
|
||||
2nBycsIPP/yAYcOG4e7du4iJiVG5TcyQ89TYuVXfOVGfc0bO+JTT5ubNm2usw9xzvrZYtM19+s5R
|
||||
pjjfdCmL13AiKhkmScTc3d1hY2Mj3YOtTUklHyUtPz8fVlZW2L9/PypUMK7b8vPzUaFCBZ1vgA1V
|
||||
Vvv4P//5D77++mtERETA0tIS9vb2aN++PYCyFXNiYiLCwsJw6tQpPHjwAKmpqXjttddUthk4cCC8
|
||||
vb0xduxYdO3aVbro6jtO5NRlKtbW1ti2bRt+++037N69G2PHjkWXLl3w9ddfGx2jqfqjwLNnzwAY
|
||||
Ni7klNUUr6no025j2iqnrm3btuncv6b+cHd3R2RkpLSdtifDFp7X9GlLYmIi7O3tkZycjLi4ODg6
|
||||
OspqV0Hb5IxnbWO4cuXKmDt3LiZMmIANGzagatWqGDRoEP7973/LKl9S9B0P+owjOX1rijaXlTlf
|
||||
29yn7xxlivNNl7J0PSSikmWSZ6VWqlQJ/fr1w65du3Dw4MFirz948ABxcXEAAA8PD+kvpoXFxsai
|
||||
adOmpghHb7a2trCyskJGRobGbTw9PZGbm4tTp04ZXd8ff/wBV1dX2XXrqyz2MQBUrVoV3bp1w/bt
|
||||
27Fjxw4EBQXBysoKgLyYS6Kvinr06BE6d+4MS0tLLF68GNHR0Wq/+A0AnTp1Qr9+/TBu3DjpL6f6
|
||||
jBN96jIVS0tLKJVKzJs3DzNnzsS+fftMFqOx/VGYu7s7hBA4e/asXuX0KasuXlPRp93GtFVOXcb0
|
||||
h42NDRo3biz9c3Jy0li+8Lwmdw66ceMGFi9ejNmzZ+Pdd9/F1KlTpd8flNOHcsaznDE8a9YshISE
|
||||
4Pfff8ehQ4fw4Ycf6lW+JOg7j+szjnT1ranaXJbmfE1jRd85ylTnmzZl9RpORKZnsh+tmDRpEnx8
|
||||
fDBq1ChMnDgRERERiIyMxMKFCxEYGIiVK1cCeD6J+fv7Y9q0afj9998RHx+PlStX4tixY3r/YKKj
|
||||
oyPS09MRHR2NiIgIg39AuEKFCmjevDm+//57JCcn49GjR5g0aZLK/ho3bowePXpgypQp+OWXX5CQ
|
||||
kIDDhw9j4MCB0hO+NImMjER0dDQSEhKwfv16HDhwACNGjJBdt7a2qltvqj6Oi4tD27ZtERERIbuM
|
||||
LgMGDMC5c+fw119/qTxRS07McvvKGPHx8UhLS0Pv3r1Rp04dJCUl4eTJkxq3nzZtGqpXr44pU6YA
|
||||
0G+c6FuXsS5duoTQ0FAkJSUhLS0NJ06cgJeXl/S6urFU0v2RlJSEvn37IiYmRmU/zZo1Q9u2baXx
|
||||
cOfOHWzevFl62po2+pQtGm9Rhs4x+owDY9oqpy5T9kdh2uY1Oedzfn4+Jk+ejICAAHTs2BEhISFI
|
||||
SUnBkiVLZLVL13guIGcMe3t7IyQkBO7u7vDy8kLbtm0xf/585Ofnm+U8NcU8rs9x1tW3pmxzWZjz
|
||||
tY0Vfa/txp5vcuaUsnwNJyLTMsmticDz2zvCwsKwadMm/Pzzz4iMjISlpSUaNGiACRMmoG/fvtK2
|
||||
S5YswaJFi6THuDZq1AhhYWHF7ofWpWPHjmjVqhVGjx4Nd3d3+Pj4oFatWgbFP2/ePEyePBmtWrVC
|
||||
rVq1MHny5GK/9zFv3jwsXrwYU6ZMQUpKCurWrYv3339fZ50DBgzAli1bEBMTgxo1amDBggXo0KGD
|
||||
XnVraqum9abo4+zsbCQnJyMzM1N2GV2aNm0KV1dX1KlTp9htLnJiltNXxvD09ET//v0RFBQEJycn
|
||||
NGrUCL1798bGjRvVbm9tbY1ly5ahV69e2LRpEwYNGiR7nOhbl7EcHBwQFxeHgIAAWFhYoGXLliq3
|
||||
cakbSyXdH3Fxcbh48SJiYmLQunVrlX0tXboUs2fPxvDhw5GXl4cWLVpI3zXRRW5ZdfEWZswco898
|
||||
YUxb5dRlqv4oTNe8put8Xr9+PRITE7FmzRoAz7/3Nn36dAQHB6NTp07w8PDQ2i5LS0ut47mArjF8
|
||||
/vx5xMbGYteuXXB0dMSzZ89w+vRpTJ8+HT4+Pmjbtm2Jn6emmsf1GUfa+va1114zWZvLwpyva+7T
|
||||
99puzPkmd04pq9dwIjItC8GbkcmMUlNT0apVK6xcuRJt2rQp7XCoDFixYgWaNWsGpVJZ2qHQS2jm
|
||||
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
|
||||
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
|
||||
jGvXrumdBOlr1qxZyMzMxOzZs1XWy20rERERERFpVi5vTczMzMT+/fvRqVMneHh4FPv3/vvvIycn
|
||||
B7t375a1P4VCAQA4e/asyvoHDx7IKu/l5QU7Ozts27ZNZb0QAk+ePJGWmzZtipo1ayIsLAxJSUlo
|
||||
27YtgOeJ2Pnz57F79260aNECDg4Oauv55JNPYGVlhdGjR+POnTvF6vr2229x+PBhjXFaWj4/3Pn5
|
||||
+Vrbc+jQIXz//ff4+uuvpSRV37YSEREREZFm5fITsf379yMjIwNdunRR+3q9evXQpEkT7Ny5E4MH
|
||||
D9a5vzp16qB169ZYtWoVatWqhYYNGyIqKgo//PCDrHhsbW0xfvx4zJw5E2PGjEHPnj2RkZGB7777
|
||||
Dmlpadi7d6+0rZ+fH7Zv34633npL+t5XgwYNUK9ePezdu1f6lEwdNzc3zJs3D8HBwejYsSMCAgJQ
|
||||
r149pKen49dff8XVq1fxxRdfaCxf8ICOrVu34urVq7C0tESvXr1Utnn48CFCQkLwzjvvwNbWViU5
|
||||
9fb21qutRERERESkXrlMxHbu3AmFQgFfX1+N2wQFBeHzzz+X/btVK1asQEhICCZPngw7OzsEBQXh
|
||||
m2++QY8ePWSVHzJkCOzt7bF27VqMHj0a9vb2UCqV+Oqrr1S269ChA8LCwoo9GCQgIACrVq1Chw4d
|
||||
tNbTo0cPuLq6YtWqVYiJicGePXtQrVo1+Pj4YO7cufDy8tJYVqFQYMSIEfjuu+9w8uRJjB49utg2
|
||||
hw8fxuPHj3H06FEcPXpU5bUbN27o1VYiIiIiIlLPQgghDCl48eIluLvL+2Fhc+6LiIiIiIiorDP4
|
||||
O2K2trYGP9yisLS0NLU/LExERERERPSiMjgRc3Z2xu3biUYlY2lpabh9O1HWY+OJiIiIiIheFAbf
|
||||
mggAGRmZuH//PjIzMw0qb2trC2dnZ1Spwk/EiIiIiIjo5WFUIkZERERERET6K5e/I0ZERERERFSe
|
||||
MREjIiIiIiIyMyZiREREREREZsZEjIiIiIiIyMyYiBEREREREZkZEzEiIiIiIiIzYyJGRERERERk
|
||||
ZkzEiIiIiIiIzIyJGBERERERkZkxESMiIiIiIjIzJmJERERERERmxkSMiIiIiIjIzJiIERERERER
|
||||
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
|
||||
"
|
||||
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
|
||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||
Copyright © 2017 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
|
||||
@ -19,37 +19,18 @@
|
||||
window = self; // Workaround for libraries
|
||||
isLibrariesLoaded = false;
|
||||
|
||||
eosjs = null;
|
||||
|
||||
function generateEncryptedVote(election, answers, should_do_fingerprint) {
|
||||
if (election._name === 'eos.psr.election.PSRElection') {
|
||||
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.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.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;
|
||||
encrypted_answers = [];
|
||||
for (var q_num = 0; q_num < answers.length; q_num++) {
|
||||
answer_json = answers[q_num];
|
||||
answer = eosjs.eos.core.objects.__all__.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_answers.push(eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(encrypted_answer, null));
|
||||
}
|
||||
|
||||
postMessage({
|
||||
encrypted_answers: encrypted_answers
|
||||
});
|
||||
}
|
||||
|
||||
onmessage = function(msg) {
|
||||
@ -59,11 +40,10 @@ onmessage = function(msg) {
|
||||
msg.data.static_base_url + "js/eosjs.js"
|
||||
);
|
||||
isLibrariesLoaded = true;
|
||||
eosjs = require("eosjs");
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
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>
|
||||
</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">
|
||||
{# 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 #}
|
||||
<button class="ui right floated primary button" onclick="nextTemplate(-2);">Continue</button>
|
||||
{% 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
|
||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||
Copyright © 2017 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
|
||||
@ -18,80 +18,39 @@
|
||||
|
||||
<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 #}
|
||||
{% if template == 'booth/welcome.html' %}
|
||||
{% set menuindex = 1 %}
|
||||
{% elif template == 'booth/selections.html' %}
|
||||
{% set menuindex = 2 %}
|
||||
{% elif template == 'booth/encrypt.html' %}
|
||||
{% set menuindex = 2.5 %}
|
||||
{% elif template == 'booth/review.html' %}
|
||||
{% set menuindex = 3 %}
|
||||
{% elif template == 'booth/audit.html' %}
|
||||
{% set menuindex = 4 %}
|
||||
{% elif template == 'booth/cast.html' %}
|
||||
{% if election.can_audit() %}
|
||||
{% set menuindex = 5 %}
|
||||
{% else %}
|
||||
{% set menuindex = 4 %}
|
||||
{% endif %}
|
||||
{% set menuindex = 5 %}
|
||||
{% elif template == 'booth/complete.html' %}
|
||||
{% if election.can_audit() %}
|
||||
{% set menuindex = 6 %}
|
||||
{% else %}
|
||||
{% set menuindex = 5 %}
|
||||
{% endif %}
|
||||
{% set menuindex = 6 %}
|
||||
{% endif %}
|
||||
|
||||
{% macro menuitem(index, text) %}
|
||||
<li class="progress-step{% if menuindex > index %} is-complete{% elif menuindex == index %} is-active{% endif %}">
|
||||
<span class="progress-marker">{% if menuindex > index or menuindex == 6 %}<span style="font-family: Icons;"></span>{% else %}{{ index }}{% endif %}</span>
|
||||
<span class="progress-text">
|
||||
{{ text }}
|
||||
</span>
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
||||
<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 secondary pointing menu" id="election-tab-menu">
|
||||
{# oh dear god #}
|
||||
<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="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>
|
||||
<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 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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<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 %}
|
||||
</div>
|
||||
|
||||
<div class="ui container" style="margin-top: 1em;">
|
||||
<div style="clear: both;"></div>
|
||||
{% block buttons %}{% endblock %}
|
||||
<div style="clear: both;"></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 %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
{% block content %}
|
||||
<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">
|
||||
<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>
|
||||
</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>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
{% if is_cast %}
|
||||
<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>
|
||||
{% 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 %}
|
||||
<button class="ui left floated button" onclick="prevTemplate(2);">Back</button>
|
||||
<button class="ui right floated primary button" id="cast_button" onclick="castBallot();"{% if not username %} style="display: none;"{% endif %}>Cast ballot</button>
|
||||
{% 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 %}
|
||||
|
||||
<script>
|
||||
$(".message .close").on("click", function() {
|
||||
$(this).closest(".message").addClass("hidden");
|
||||
});
|
||||
|
||||
function login(el) {
|
||||
// Stage the vote for casting in case we change page
|
||||
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");
|
||||
});
|
||||
});
|
||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||
}
|
||||
|
||||
function callback_complete(username) {
|
||||
$("#cast_button").show();
|
||||
$("#booth_logged_in_as").text("You are currently logged in as " + username + ".");
|
||||
// Ballot was staged when we clicked the login button
|
||||
castBallot();
|
||||
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() {
|
||||
$("#cast_prompt").hide();
|
||||
$("#casting").show();
|
||||
|
||||
// Prepare ballot
|
||||
booth.ballot = booth.ballot.deaudit();
|
||||
|
||||
$.ajax({
|
||||
url: "{{ election_base_url }}cast_ballot",
|
||||
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"
|
||||
})
|
||||
.done(function(data) {
|
||||
response = eosjs.eos.core.objects.EosObject.from_json(data);
|
||||
booth.voter = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(response.voter);
|
||||
booth.vote = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(response.vote);
|
||||
response = eosjs.eos.core.objects.__all__.EosObject.from_json(data);
|
||||
booth.voter = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(response.voter);
|
||||
booth.vote = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(response.vote);
|
||||
|
||||
// Clear plaintexts
|
||||
booth.answers = null;
|
||||
@ -201,16 +129,5 @@
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
{% if is_cast %}
|
||||
castBallot();
|
||||
{% endif %}
|
||||
</script>
|
||||
{% 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
|
||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||
Copyright © 2017 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
|
||||
@ -19,13 +19,13 @@
|
||||
#}
|
||||
|
||||
{% 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>
|
||||
</div>
|
||||
|
||||
{% if election.can_audit() %}
|
||||
<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>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>To continue, copy and paste the ballot below and provide it to the election administrator.</p>
|
||||
|
||||
@ -39,19 +39,3 @@
|
||||
{% block buttons %}
|
||||
<button class="ui left floated button" onclick="prevTemplate();">Back</a>
|
||||
{% 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
|
||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||
Copyright © 2017 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
|
||||
@ -21,44 +21,17 @@
|
||||
{% 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>
|
||||
|
||||
<div class="ui success icon message">
|
||||
<i class="check circle outline icon"></i>
|
||||
<div class="content">
|
||||
<div class="header">Smart ballot tracker</div>
|
||||
<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 class="ui success message">
|
||||
<div class="header">Smart ballot tracker</div>
|
||||
<p>This smart ballot tracker confirms that {{ voter.name }} cast a vote in the election {{ election.py_name }} at {{ vote.cast_at }}.</p>
|
||||
<p>Ballot fingerprint: <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span></p>
|
||||
</div>
|
||||
|
||||
{% if election.can_audit() %}
|
||||
<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>
|
||||
{% endif %}
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
{% if election.is_votes_public %}
|
||||
<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>
|
||||
<a href="{{ election_base_url }}view/ballots" class="ui right floated primary button">Finish</a>
|
||||
{% endblock %}
|
||||
|
@ -1,8 +1,6 @@
|
||||
{% extends templates['booth/base.html'] %}
|
||||
|
||||
{#
|
||||
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
|
||||
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/>.
|
||||
#}
|
||||
|
||||
{% block content %}
|
||||
<div class="ui basic segment" style="min-height: 4em;">
|
||||
<div class="ui active text loader">Preparing your ballot. Please wait.</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<div class="ui active text loader">Preparing your ballot. Please wait.</div>
|
||||
|
||||
{% block after %}
|
||||
<script>
|
||||
boothWorker.onmessage = function(msg) {
|
||||
try {
|
||||
rawAnswers = [];
|
||||
for (var answer_json of booth.answers) {
|
||||
rawAnswers.push(eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null));
|
||||
}
|
||||
|
||||
encryptedAnswers = [];
|
||||
for (var encrypted_answer_json of msg.data.encrypted_answers) {
|
||||
encryptedAnswers.push(eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(encrypted_answer_json, null));
|
||||
}
|
||||
|
||||
booth.ballot = eosjs.eos.base.election.Ballot();
|
||||
booth.ballot.answers = rawAnswers;
|
||||
booth.ballot.encrypted_answers = encryptedAnswers;
|
||||
booth.ballot.election_id = election._id;
|
||||
booth.ballot.election_hash = eosjs.eos.core.hashing.SHA256().update_obj(election).hash_as_b64();
|
||||
|
||||
if (should_do_fingerprint) {
|
||||
// String.prototype.join confuses fingerprintjs2
|
||||
var strjoin = String.prototype.join;
|
||||
String.prototype.join = undefined;
|
||||
<script>
|
||||
boothWorker.onmessage = function(msg) {
|
||||
try {
|
||||
rawAnswers = [];
|
||||
for (var answer_json of booth.answers) {
|
||||
rawAnswers.push(eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(answer_json, null));
|
||||
}
|
||||
|
||||
encryptedAnswers = [];
|
||||
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));
|
||||
}
|
||||
|
||||
booth.ballot = eosjs.eos.base.election.__all__.Ballot();
|
||||
booth.ballot.answers = rawAnswers;
|
||||
booth.ballot.encrypted_answers = encryptedAnswers;
|
||||
booth.ballot.election_id = election._id;
|
||||
booth.ballot.election_hash = eosjs.eos.core.hashing.__all__.SHA256().update_obj(election).hash_as_b64();
|
||||
|
||||
if (should_do_fingerprint) {
|
||||
// String.prototype.join confuses fingerprintjs2
|
||||
var strjoin = String.prototype.join;
|
||||
String.prototype.join = undefined;
|
||||
try {
|
||||
new Fingerprint2().get(function(result, components) {
|
||||
String.prototype.join = strjoin;
|
||||
booth.fingerprint = components;
|
||||
nextTemplate();
|
||||
});
|
||||
} else {
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
nextTemplate();
|
||||
}
|
||||
} catch (err) {
|
||||
boothError(err);
|
||||
throw err;
|
||||
} else {
|
||||
nextTemplate();
|
||||
}
|
||||
}
|
||||
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) {
|
||||
boothError(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
|
||||
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||
Copyright © 2017 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
|
||||
@ -30,12 +30,8 @@
|
||||
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
||||
{% 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.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</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 %}
|
||||
<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>Click ‘Continue’, and you will be able to log into cast your vote.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
@ -44,18 +40,6 @@
|
||||
{% 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 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>
|
||||
<div style="clear: both; margin-bottom: 1em;"></div>
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
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"]] %}
|
||||
{% 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.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>
|
||||
{% 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 %}
|
||||
<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>Click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
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");
|
||||
}
|
||||
|
||||
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");
|
||||
loadHelp();
|
||||
|
||||
function previousQuestion() {
|
||||
saveSelections();
|
||||
@ -47,20 +42,16 @@
|
||||
} else {
|
||||
booth.questionNum--;
|
||||
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||
loadHelp();
|
||||
}
|
||||
}
|
||||
|
||||
function nextQuestion() {
|
||||
if (!saveSelections()) {
|
||||
return;
|
||||
}
|
||||
saveSelections();
|
||||
if (booth.questionNum == election.questions.__len__() - 1) {
|
||||
nextTemplate();
|
||||
} else {
|
||||
booth.questionNum++;
|
||||
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||
loadHelp();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
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/>.
|
||||
#}
|
||||
|
||||
{% 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 %}
|
||||
<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>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>If at any point you wish to return to a previous screen, click the ‘Back’ button.</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>If you require further assistance, contact your election administrator.</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 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>Please click the blue ‘Continue’ button below to continue.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{#
|
||||
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
|
||||
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>
|
||||
|
||||
{% if election.questions.__getitem__(questionNum).description %}
|
||||
<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 %}
|
||||
<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>
|
||||
|
||||
<div id="question-choices" class="ui form" style="margin-bottom: 1em;">
|
||||
<div class="grouped fields">
|
||||
{% for choice in election.questions.__getitem__(questionNum).randomised_choices().impl %}
|
||||
<div class="field">
|
||||
<div class="ui{% if is_radio %} radio{% endif %} checkbox">
|
||||
<input
|
||||
{% 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();">
|
||||
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" 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>
|
||||
</div>
|
||||
</div>
|
||||
{% 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>
|
||||
|
||||
@ -76,17 +39,15 @@
|
||||
|
||||
<script>
|
||||
function choicesChanged() {
|
||||
{% if not is_radio %}
|
||||
var numChoices = $("#question-choices input:checked").length;
|
||||
if (numChoices >= election.questions.__getitem__(booth.questionNum).max_choices) {
|
||||
// Prevent making any more selections
|
||||
$("#question-choices input:not(:checked)").prop("disabled", true);
|
||||
$("#message-max-choices").removeClass("hidden");
|
||||
} else {
|
||||
$("#question-choices input").prop("disabled", false);
|
||||
$("#message-max-choices").addClass("hidden");
|
||||
}
|
||||
{% endif %}
|
||||
var numChoices = $("#question-choices input:checked").length;
|
||||
if (numChoices >= election.questions.__getitem__(booth.questionNum).max_choices) {
|
||||
// Prevent making any more selections
|
||||
$("#question-choices input:not(:checked)").prop("disabled", true);
|
||||
$("#message-max-choices").removeClass("hidden");
|
||||
} else {
|
||||
$("#question-choices input").prop("disabled", false);
|
||||
$("#message-max-choices").addClass("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in ballot with previous selections
|
||||
@ -100,40 +61,9 @@
|
||||
function saveSelections() {
|
||||
selections = [];
|
||||
$("#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)));
|
||||
});
|
||||
|
||||
if (selections.length < election.questions.__getitem__(booth.questionNum).min_choices) {
|
||||
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;
|
||||
answer = eosjs.eos.base.election.__all__.ApprovalAnswer(eosjs.__kwargtrans__({choices: selections}));
|
||||
booth.answers[booth.questionNum] = eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(answer);
|
||||
}
|
||||
</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 %}
|
||||
</div>
|
||||
|
||||
{% if booth.answers[loop.index0].value.choices.length < question.min_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 %}
|
||||
{% if booth.answers[loop.index0].value.choices.length < question.max_choices %}
|
||||
<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>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{#
|
||||
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
|
||||
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>
|
||||
|
||||
{% if election.questions.__getitem__(questionNum).description %}
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<div id="question-choices-selected" class="preferential-choices">
|
||||
<div style="color: #3465a4;">Options voted for:</div>
|
||||
@ -52,14 +38,7 @@
|
||||
|
||||
{% macro printchoice(choice, ticket=None) %}
|
||||
<div class="preferential-choice" data-choiceno="{{ flat_choices.indexOf(choice) }}">
|
||||
<div class="number">
|
||||
<select class="ui dropdown">
|
||||
<option selected></option>
|
||||
{% for i in range(flat_choices|length) %}
|
||||
<option>{{ i + 1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="number"></div>
|
||||
<div class="content">
|
||||
<div class="candidate-name">{{ choice.name }}</div>
|
||||
{% if choice.party %}
|
||||
@ -83,14 +62,7 @@
|
||||
{% if choice.choices %}
|
||||
{# Ticket #}
|
||||
<div class="preferential-choice ticket">
|
||||
<div class="number">
|
||||
<select class="ui dropdown">
|
||||
<option selected></option>
|
||||
{% for i in range(flat_choices|length) %}
|
||||
<option>{{ i + 1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="number"></div>
|
||||
<div class="content">
|
||||
<div class="party-name">{{ choice.name }}</div>
|
||||
<div class="ticket-choices">
|
||||
@ -113,18 +85,18 @@
|
||||
|
||||
function choicesChanged() {
|
||||
// Recalculate numbers
|
||||
$(".preferential-choices .preferential-choice .number select").val("");
|
||||
var selectedChoices = $("#question-choices-selected .dragarea > .preferential-choice > .number select");
|
||||
selectedChoices.each(function(i, el) {
|
||||
$(el).val(i + 1);
|
||||
$(".preferential-choices .preferential-choice .number").each(function(i, el) {
|
||||
$(el).text("");
|
||||
});
|
||||
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) {
|
||||
// Prevent making any more selections
|
||||
allowAdding = false;
|
||||
|
||||
$("#question-choices-remaining .preferential-choice .number select").prop("disabled", true);
|
||||
|
||||
if (selectedCandidates.length > election.questions.__getitem__(booth.questionNum).max_choices) {
|
||||
// Prevent progression
|
||||
$(".primary.button").addClass("disabled");
|
||||
@ -135,12 +107,9 @@
|
||||
}
|
||||
} else {
|
||||
allowAdding = true;
|
||||
|
||||
$("#question-choices-remaining .preferential-choice .number select").prop("disabled", false);
|
||||
|
||||
$(".primary.button").removeClass("disabled");
|
||||
$("#message-max-choices").addClass("hidden");
|
||||
$("#message-too-many-choices").addClass("hidden");
|
||||
$(".primary.button").removeClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,10 +120,6 @@
|
||||
choicesChanged();
|
||||
}
|
||||
|
||||
// =============
|
||||
// DRAG AND DROP
|
||||
// =============
|
||||
|
||||
var dragulaChoices = dragula(
|
||||
[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) {
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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) {
|
||||
// This is a candidate/ticket dragged into a .ticket-choices
|
||||
// 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());
|
||||
}
|
||||
breakTicket($(target).parents(".ticket").first())
|
||||
}
|
||||
|
||||
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() {
|
||||
selections = [];
|
||||
$("#question-choices-selected .preferential-choice:not(.ticket)").each(function(i, el) {
|
||||
selections.push(parseInt(el.dataset.choiceno));
|
||||
});
|
||||
|
||||
if (selections.length < election.questions.__getitem__(booth.questionNum).min_choices) {
|
||||
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);
|
||||
answer = eosjs.eos.base.election.__all__.PreferentialAnswer(eosjs.__kwargtrans__({choices: selections}));
|
||||
booth.answers[booth.questionNum] = eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(answer);
|
||||
|
||||
booth.q_state[booth.questionNum] = [$("#question-choices-selected .dragarea").html(), $("#question-choices-remaining .dragarea").html()]; // wew lad
|
||||
|
||||
return true;
|
||||
}
|
||||
</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>
|
||||
{% endif %}
|
||||
|
||||
{% if booth.answers[loop.index0].value.choices.length < question.min_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 %}
|
||||
{% if booth.answers[loop.index0].value.choices.length < question.max_choices %}
|
||||
<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>
|
||||
</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
|
||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
||||
Copyright © 2017 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
|
||||
@ -26,26 +26,26 @@
|
||||
<div class="menu">
|
||||
<div class="header">Active tasks</div>
|
||||
{% 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 }}
|
||||
<br><small><i class="wait icon"></i> started {{ task.started_at|pretty_date }}</small>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="divider"></div>
|
||||
<div class="header">Pending tasks</div>
|
||||
{% 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 }}
|
||||
<br><small><i class="wait icon"></i> due {{ task.run_at|pretty_date }}</small>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="divider"></div>
|
||||
<div class="header">Recently completed tasks</div>
|
||||
{% for task in eos.core.tasks.TaskScheduler.completed_tasks(3) %}
|
||||
<a class="item" href="{{ url_for('task_view', task_id=task._id) }}">
|
||||
{% if task.status.is_error() %}<i class="warning sign icon"></i> {% endif %}{{ task.label }}
|
||||
<div class="item">
|
||||
{% 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>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</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>
|
||||
{% endfor %}
|
||||
</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 %}
|
||||
|
||||
{% block basecontent %}
|
||||
@ -46,8 +40,8 @@
|
||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||
}
|
||||
|
||||
function callback_complete() {
|
||||
window.location = "{{ url_for('login_callback') }}";
|
||||
function callback_complete(name) {
|
||||
window.location = "/";
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
@ -23,46 +23,33 @@
|
||||
{% block basecontent %}
|
||||
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
||||
<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>
|
||||
<p>You have successfully logged in to your account.</p>
|
||||
<p>You may now close this window and return to your previous page.</p>
|
||||
</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="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 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>
|
||||
|
||||
<script>
|
||||
if (window.opener && window.opener.callback_complete) {
|
||||
// Normal popup window
|
||||
// Redirect happens in the opener
|
||||
try {
|
||||
var result = window.opener.callback_complete("{{ session.user.name }}");
|
||||
if (result) {
|
||||
$("#success-popup").removeClass("hidden");
|
||||
setTimeout(window.close, 1000);
|
||||
} else {
|
||||
$(".error.message").removeClass("hidden");
|
||||
}
|
||||
} catch (ex) {
|
||||
try {
|
||||
var result = window.opener.callback_complete("{{ session.user.name }}");
|
||||
if (result) {
|
||||
$(".success.message").removeClass("hidden");
|
||||
|
||||
window.setTimeout(function() {
|
||||
window.close();
|
||||
}, 1000);
|
||||
} else {
|
||||
$(".error.message").removeClass("hidden");
|
||||
console.error(ex);
|
||||
throw ex;
|
||||
}
|
||||
} else {
|
||||
// This browser does not support popups
|
||||
// Manually redirect in this window
|
||||
$("#success-redirect").removeClass("hidden");
|
||||
window.location = "{{ url_for('login_callback') }}";
|
||||
} catch (ex) {
|
||||
$(".error.message").removeClass("hidden");
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
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>
|
||||
{% if session.user %}
|
||||
{% if session.user.is_admin() %}
|
||||
{% include 'task/active_tasks_menu.html' %}
|
||||
{% include 'active_tasks_menu.html' %}
|
||||
{% endif %}
|
||||
<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>
|
||||
@ -42,7 +42,7 @@
|
||||
{% endif %}
|
||||
</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 %}
|
||||
{% endblock content %}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<ul>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -57,7 +57,7 @@
|
||||
$("#election-tab-menu .item").removeClass("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() {
|
||||
linkEl.find(".loader").removeClass("active");
|
||||
|
@ -1,6 +1,6 @@
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -19,11 +19,5 @@
|
||||
{% block tabs %}
|
||||
{{ tab('Overview', 'election_view') }}
|
||||
{{ tab('Questions', 'election_view_questions') }}
|
||||
{% if election.is_voters_public or (session.user and session.user.is_admin()) %}
|
||||
{% 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 %}
|
||||
{{ tab('Voters and ballots', 'election_view_ballots') }}
|
||||
{% endblock %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -19,30 +19,21 @@
|
||||
#}
|
||||
|
||||
{% block electioncontent %}
|
||||
<table class="ui selectable celled table">
|
||||
<table class="ui celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Voter</th>
|
||||
{% if election.is_votes_public or (session.user and session.user.is_admin()) %}
|
||||
<th>Ballot fingerprint</th>
|
||||
{% endif %}
|
||||
<th>Ballot fingerprint</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for voter in election.voters %}
|
||||
<tr>
|
||||
{% if election.is_votes_public or (session.user and session.user.is_admin()) %}
|
||||
<td class="selectable"><a href="{{ url_for('election_voter_view', election_id=election._id, voter_id=voter._id) }}">{{ voter.name }}</a></td>
|
||||
{% set votes = voter.votes.get_all() %}
|
||||
<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>
|
||||
<td>{{ voter.name }}</td>
|
||||
{% if voter.votes|length > 0 %}
|
||||
<td class="hash">{{ SHA256().update_obj(voter.votes[-1].ballot).hash_as_b64() }}</td>
|
||||
{% else %}
|
||||
<td>{{ voter.name }}</td>
|
||||
<td class="hash"></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -23,7 +23,6 @@
|
||||
{% block head %}
|
||||
{{ 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/progress-tracker/app/styles/progress-tracker.css') }}" type="text/css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@ -44,8 +43,6 @@
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
var eosjs = require("eosjs");
|
||||
|
||||
var templates = {};
|
||||
var election = null;
|
||||
var booth = null;
|
||||
@ -69,18 +66,10 @@
|
||||
resetBooth();
|
||||
|
||||
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" })
|
||||
.done(function(data) {
|
||||
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') }}");
|
||||
|
||||
@ -119,7 +108,7 @@
|
||||
})
|
||||
.done(function(data) {
|
||||
try {
|
||||
templates[templateUrl] = nunjucks.compile(data, null, templateUrl);
|
||||
templates[templateUrl] = nunjucks.compile(data);
|
||||
numTemplatesLoaded += 1;
|
||||
if (numTemplatesLoaded == templateUrls.length) {
|
||||
// All templates loaded. Show voting booth
|
||||
@ -202,99 +191,70 @@
|
||||
// === BOOTH TASKS ===
|
||||
// TODO: Make modular
|
||||
|
||||
templates['booth/base.html'] = null;
|
||||
|
||||
if (location.search.indexOf('?cast') < 0) {
|
||||
// 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;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/welcome.html');
|
||||
}
|
||||
} else {
|
||||
// Cast immediately
|
||||
{% if session.staged_ballot %}
|
||||
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);
|
||||
{% endif %}
|
||||
});
|
||||
templates['booth/base.html'] = null;
|
||||
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/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;
|
||||
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -21,9 +21,6 @@
|
||||
{% block electioncontent %}
|
||||
{% for question in election.questions %}
|
||||
<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'] %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{#
|
||||
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
|
||||
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/>.
|
||||
#}
|
||||
|
||||
{% set Status = eos.base.workflow.WorkflowTaskStatus %}
|
||||
{% set Status = eos.base.workflow.WorkflowTask.Status %}
|
||||
|
||||
{% block electioncontent %}
|
||||
{% 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>
|
||||
|
||||
{% 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 %}
|
||||
<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.
|
||||
|
@ -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
|
||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
||||
Copyright © 2017 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
|
||||
@ -24,33 +24,9 @@
|
||||
<h1>Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</h1>
|
||||
|
||||
<p>Please choose an election from the list below:</p>
|
||||
|
||||
{% if elections_open %}
|
||||
<h2>Currently open</h2>
|
||||
<ul>
|
||||
{% for election in elections_open %}
|
||||
<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>
|
||||
<ul>
|
||||
{% for election in eos.base.election.Election.get_all() %}
|
||||
<li><a href="{{ url_for('election_view', election_id=election._id) }}">{{ election.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
@ -16,18 +16,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
|
||||
<p><small>
|
||||
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>
|
||||
<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>
|
||||
|
||||
<ul class="ui list">
|
||||
{% for choice in question.choices %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{#
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -40,6 +40,8 @@
|
||||
Count log
|
||||
</div>
|
||||
<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>
|
||||
|
@ -16,18 +16,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
|
||||
<p><small>
|
||||
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>
|
||||
<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>
|
||||
|
||||
<ul class="ui list">
|
||||
{% 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
|
||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
||||
# Copyright © 2017 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
|
||||
@ -14,7 +14,7 @@
|
||||
# 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 authlib.integrations.flask_client import OAuth
|
||||
from flask_oauthlib.client import OAuth
|
||||
|
||||
import flask
|
||||
|
||||
@ -23,52 +23,44 @@ from eos.redditauth.election import *
|
||||
import base64
|
||||
import uuid
|
||||
|
||||
blueprint = flask.Blueprint('eosweb.redditauth', __name__)
|
||||
|
||||
app = None
|
||||
oauth = 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,
|
||||
def main(app):
|
||||
oauth = OAuth()
|
||||
reddit = oauth.remote_app('Reddit',
|
||||
request_token_url=None,
|
||||
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_method='POST',
|
||||
access_token_headers={
|
||||
'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']
|
||||
},
|
||||
client_id=app.config['REDDIT_OAUTH_CLIENT_ID'],
|
||||
client_secret=app.config['REDDIT_OAUTH_CLIENT_SECRET'],
|
||||
fetch_token=lambda: flask.session.get('user').oauth_token
|
||||
consumer_key=app.config['REDDIT_OAUTH_CLIENT_ID'],
|
||||
consumer_secret=app.config['REDDIT_OAUTH_CLIENT_SECRET']
|
||||
)
|
||||
|
||||
@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()
|
||||
user.oauth_token = token
|
||||
flask.session['user'] = user
|
||||
@app.route('/auth/reddit/login')
|
||||
def reddit_login():
|
||||
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={
|
||||
'User-Agent': app.config['REDDIT_USER_AGENT']
|
||||
})
|
||||
user.username = me.json()['name']
|
||||
@reddit.tokengetter
|
||||
def get_reddit_oauth_token():
|
||||
return (flask.session.get('user').oauth_token, '')
|
||||
|
||||
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')
|
||||
]
|
||||
|
||||
import eos.base.election
|
||||
import eos.redditauth.election
|
||||
ADMINS = [
|
||||
#eos.redditauth.election.RedditUser(username='xxxxxxxx'),
|
||||
#eos.base.election.EmailUser(email='xxxxx@example.com', password='abc123'),
|
||||
#eos.redditauth.election.RedditUser(username='xxxxxxxx')
|
||||
]
|
||||
|
||||
TASK_RUN_STRATEGY = 'eos.core.tasks.direct.DirectRunStrategy'
|
||||
@ -34,13 +32,11 @@ DB_NAME = 'eos'
|
||||
|
||||
# Email
|
||||
|
||||
MAIL_SERVER, MAIL_PORT = 'localhost', 25
|
||||
MAIL_USERNAME, MAIL_PASSWORD = None, None
|
||||
MAIL_DEFAULT_SENDER = 'eos@localhost'
|
||||
SMTP_HOST, SMTP_PORT = 'localhost', 25
|
||||
SMTP_USER, SMTP_PASS = None, None
|
||||
SMTP_FROM = 'eos@localhost'
|
||||
|
||||
# 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_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
|
||||
Flask==0.12.2
|
||||
Flask-Mail==0.9.1
|
||||
flask-paginate==0.7.0
|
||||
Flask-Session==0.3.1
|
||||
Flask-SQLAlchemy==2.3.2
|
||||
Flask-OAuthlib==0.9.4
|
||||
gunicorn==19.7.1
|
||||
libsass==0.13.4
|
||||
premailer==3.1.1
|
||||
psycopg2==2.8.5
|
||||
idna==2.6
|
||||
itsdangerous==0.24
|
||||
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
|
||||
pymongo[srv]==3.10.1
|
||||
pymongo==3.5.1
|
||||
pyRCV==0.3
|
||||
pytz==2017.3
|
||||
requests==2.18.4
|
||||
requests-oauthlib==0.8.0
|
||||
six==1.10.0
|
||||
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