Smush the WorkflowTasks and Tasks together; liberally apply superglue and duct tape
This commit is contained in:
parent
f538c33d19
commit
788c5c006c
@ -15,6 +15,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from eos.core.objects import *
|
from eos.core.objects import *
|
||||||
|
from eos.core.tasks import *
|
||||||
|
|
||||||
class WorkflowTask(EmbeddedObject):
|
class WorkflowTask(EmbeddedObject):
|
||||||
class Status:
|
class Status:
|
||||||
@ -64,7 +65,7 @@ class WorkflowTask(EmbeddedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def satisfies(cls, descriptor):
|
def satisfies(cls, descriptor):
|
||||||
return cls._name == descriptor or descriptor in cls.provides or (descriptor in EosObject.objects and issubclass(cls, EosObject.objects[descriptor]))
|
return cls._name == descriptor or descriptor in cls.provides or (descriptor in EosObject.objects and issubclass(cls, EosObject.lookup(descriptor)))
|
||||||
|
|
||||||
def on_enter(self):
|
def on_enter(self):
|
||||||
self.exit()
|
self.exit()
|
||||||
@ -113,6 +114,22 @@ class Workflow(EmbeddedObject):
|
|||||||
except StopIteration:
|
except StopIteration:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
class WorkflowTaskEntryTask(Task):
|
||||||
|
election_id = UUIDField()
|
||||||
|
workflow_task = StringField()
|
||||||
|
|
||||||
|
def _run(self):
|
||||||
|
election = EosObject.lookup('eos.base.election.Election').get_by_id(self.election_id)
|
||||||
|
task = election.workflow.get_task(self.workflow_task)
|
||||||
|
task.enter()
|
||||||
|
election.save()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self):
|
||||||
|
election = EosObject.lookup('eos.base.election.Election').get_by_id(self.election_id)
|
||||||
|
task = election.workflow.get_task(self.workflow_task)
|
||||||
|
return task.label + ' – ' + election.name
|
||||||
|
|
||||||
# Concrete tasks
|
# Concrete tasks
|
||||||
# ==============
|
# ==============
|
||||||
|
|
||||||
@ -138,7 +155,7 @@ class TaskDecryptVotes(WorkflowTask):
|
|||||||
election = self.recurse_parents('eos.base.election.Election')
|
election = self.recurse_parents('eos.base.election.Election')
|
||||||
|
|
||||||
for _ in range(len(election.questions)):
|
for _ in range(len(election.questions)):
|
||||||
election.results.append(EosObject.objects['eos.base.election.RawResult']())
|
election.results.append(EosObject.lookup('eos.base.election.RawResult')())
|
||||||
|
|
||||||
for voter in election.voters:
|
for voter in election.voters:
|
||||||
if len(voter.votes) > 0:
|
if len(voter.votes) > 0:
|
||||||
|
@ -37,6 +37,7 @@ if is_python:
|
|||||||
import base64
|
import base64
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import importlib
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
__pragma__('noskip')
|
__pragma__('noskip')
|
||||||
@ -177,9 +178,6 @@ class EosObjectType(type):
|
|||||||
cls._name = (cls.__module__ + '.' + cls.__name__).replace('.js.', '.').replace('.python.', '.') #TNYI: qualname
|
cls._name = (cls.__module__ + '.' + cls.__name__).replace('.js.', '.').replace('.python.', '.') #TNYI: qualname
|
||||||
if name != 'EosObject':
|
if name != 'EosObject':
|
||||||
EosObject.objects[cls._name] = cls
|
EosObject.objects[cls._name] = cls
|
||||||
if '_db_name' not in attrs:
|
|
||||||
# Don't inherit _db_name, use only if explicitly given
|
|
||||||
cls._db_name = cls._name
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
class EosObject(metaclass=EosObjectType):
|
class EosObject(metaclass=EosObjectType):
|
||||||
@ -195,7 +193,7 @@ class EosObject(metaclass=EosObjectType):
|
|||||||
def recurse_parents(self, cls):
|
def recurse_parents(self, cls):
|
||||||
#if not isinstance(cls, type):
|
#if not isinstance(cls, type):
|
||||||
if isinstance(cls, str):
|
if isinstance(cls, str):
|
||||||
cls = EosObject.objects[cls]
|
cls = EosObject.lookup(cls)
|
||||||
|
|
||||||
if isinstance(self, cls):
|
if isinstance(self, cls):
|
||||||
return self
|
return self
|
||||||
@ -208,6 +206,13 @@ class EosObject(metaclass=EosObjectType):
|
|||||||
return False
|
return False
|
||||||
return EosObject.serialise_and_wrap(self) == EosObject.serialise_and_wrap(other)
|
return EosObject.serialise_and_wrap(self) == EosObject.serialise_and_wrap(other)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def lookup(name):
|
||||||
|
if name in EosObject.objects:
|
||||||
|
return EosObject.objects[name]
|
||||||
|
importlib.import_module(name[:name.rindex('.')])
|
||||||
|
return EosObject.objects[name]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def serialise_and_wrap(value, object_type=None, for_hash=False, should_protect=False):
|
def serialise_and_wrap(value, object_type=None, for_hash=False, should_protect=False):
|
||||||
if object_type:
|
if object_type:
|
||||||
@ -220,7 +225,9 @@ class EosObject(metaclass=EosObjectType):
|
|||||||
def deserialise_and_unwrap(value, object_type=None):
|
def deserialise_and_unwrap(value, object_type=None):
|
||||||
if object_type:
|
if object_type:
|
||||||
return object_type.deserialise(value)
|
return object_type.deserialise(value)
|
||||||
return EosObject.objects[value['type']].deserialise(value['value'])
|
if value:
|
||||||
|
return EosObject.lookup(value['type']).deserialise(value['value'])
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_json(value):
|
def to_json(value):
|
||||||
@ -399,7 +406,25 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
attrs[val.internal_name] = val.deserialise(value[val.real_name])
|
attrs[val.internal_name] = val.deserialise(value[val.real_name])
|
||||||
return cls(**attrs)
|
return cls(**attrs)
|
||||||
|
|
||||||
class TopLevelObject(DocumentObject):
|
class TopLevelObjectType(DocumentObjectType):
|
||||||
|
def __new__(meta, name, bases, attrs):
|
||||||
|
cls = DocumentObjectType.__new__(meta, name, bases, attrs)
|
||||||
|
|
||||||
|
# TopLevelObject obviously has no _db_name
|
||||||
|
if cls._name == 'eos.core.objects.TopLevelObject':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if '_db_name' not in attrs:
|
||||||
|
cls._db_name = cls._name
|
||||||
|
# If _db_name is False, then explicitly use the _name. Otherwise, inherit.
|
||||||
|
if cls._db_name is not False:
|
||||||
|
for base in bases:
|
||||||
|
if hasattr(base, '_db_name'):
|
||||||
|
cls._db_name = base._db_name
|
||||||
|
break
|
||||||
|
return cls
|
||||||
|
|
||||||
|
class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType):
|
||||||
def save(self):
|
def save(self):
|
||||||
#res = db[self._name].replace_one({'_id': self.serialise()['_id']}, self.serialise(), upsert=True)
|
#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)
|
#res = dbinfo.db[self._db_name].replace_one({'_id': self._fields['_id'].serialise(self._id)}, EosObject.serialise_and_wrap(self), upsert=True)
|
||||||
@ -411,6 +436,8 @@ class TopLevelObject(DocumentObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_id(cls, _id):
|
def get_by_id(cls, _id):
|
||||||
|
if not isinstance(_id, str):
|
||||||
|
_id = str(_id)
|
||||||
return EosObject.deserialise_and_unwrap(dbinfo.provider.get_by_id(cls._db_name, _id))
|
return EosObject.deserialise_and_unwrap(dbinfo.provider.get_by_id(cls._db_name, _id))
|
||||||
|
|
||||||
class EmbeddedObject(DocumentObject):
|
class EmbeddedObject(DocumentObject):
|
||||||
|
@ -28,8 +28,14 @@ class Task(TopLevelObject):
|
|||||||
TIMEOUT = -20
|
TIMEOUT = -20
|
||||||
|
|
||||||
_id = UUIDField()
|
_id = UUIDField()
|
||||||
status = IntField(default=0)
|
|
||||||
run_strategy = EmbeddedObjectField()
|
run_strategy = EmbeddedObjectField()
|
||||||
|
|
||||||
|
run_at = DateTimeField()
|
||||||
|
|
||||||
|
started_at = DateTimeField()
|
||||||
|
completed_at = DateTimeField()
|
||||||
|
|
||||||
|
status = IntField(default=0)
|
||||||
messages = ListField(StringField())
|
messages = ListField(StringField())
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -38,26 +44,60 @@ class Task(TopLevelObject):
|
|||||||
def _run(self):
|
def _run(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class DummyTask(Task):
|
||||||
|
_db_name = Task._db_name
|
||||||
|
label = 'A dummy task'
|
||||||
|
|
||||||
|
def _run(self):
|
||||||
|
if is_python:
|
||||||
|
#__pragma__('skip')
|
||||||
|
import time
|
||||||
|
#__pragma__('noskip')
|
||||||
|
time.sleep(15)
|
||||||
|
|
||||||
class RunStrategy(DocumentObject):
|
class RunStrategy(DocumentObject):
|
||||||
def run(self, task):
|
def run(self, task):
|
||||||
raise Exception('Not implemented')
|
raise Exception('Not implemented')
|
||||||
|
|
||||||
class DirectRunStrategy(RunStrategy):
|
class TaskScheduler:
|
||||||
def run(self, task):
|
@staticmethod
|
||||||
task.status = Task.Status.PROCESSING
|
def pending_tasks():
|
||||||
task.save()
|
pending_tasks = []
|
||||||
|
tasks = Task.get_all()
|
||||||
|
|
||||||
try:
|
for task in tasks:
|
||||||
task._run()
|
if task.status == Task.Status.READY and task.run_at and task.run_at < DateTimeField.now():
|
||||||
task.status = Task.Status.COMPLETE
|
pending_tasks.append(task)
|
||||||
task.save()
|
|
||||||
except Exception as e:
|
return pending_tasks
|
||||||
task.status = Task.Status.FAILED
|
|
||||||
if is_python:
|
@staticmethod
|
||||||
#__pragma__('skip')
|
def active_tasks():
|
||||||
import traceback
|
active_tasks = []
|
||||||
#__pragma__('noskip')
|
tasks = Task.get_all()
|
||||||
task.messages.append(traceback.format_exc())
|
|
||||||
else:
|
for task in tasks:
|
||||||
task.messages.append(repr(e))
|
if task.status == Task.Status.PROCESSING:
|
||||||
task.save()
|
active_tasks.append(task)
|
||||||
|
|
||||||
|
return active_tasks
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def completed_tasks(limit=None):
|
||||||
|
completed_tasks = []
|
||||||
|
tasks = Task.get_all()
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
if task.status == Task.Status.COMPLETE or task.status < 0:
|
||||||
|
completed_tasks.append(task)
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
completed_tasks.sort(key=lambda x: x.completed_at)
|
||||||
|
completed_tasks = completed_tasks[-limit:]
|
||||||
|
|
||||||
|
return completed_tasks
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def tick():
|
||||||
|
for task in TaskScheduler.pending_tasks():
|
||||||
|
task.run()
|
||||||
|
@ -15,5 +15,27 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from eos.core.tasks import *
|
from eos.core.tasks import *
|
||||||
|
from eos.core.objects import *
|
||||||
|
|
||||||
|
class DirectRunStrategy(RunStrategy):
|
||||||
|
def run(self, task):
|
||||||
|
task.status = Task.Status.PROCESSING
|
||||||
|
task.started_at = DateTimeField.now()
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
try:
|
||||||
|
task._run()
|
||||||
|
task.status = Task.Status.COMPLETE
|
||||||
|
task.completed_at = DateTimeField.now()
|
||||||
|
task.save()
|
||||||
|
except Exception as e:
|
||||||
|
task.status = Task.Status.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()
|
||||||
|
@ -18,6 +18,7 @@ from eos.core.bigint import *
|
|||||||
from eos.core.objects import *
|
from eos.core.objects import *
|
||||||
from eos.core.hashing import *
|
from eos.core.hashing import *
|
||||||
from eos.core.tasks import *
|
from eos.core.tasks import *
|
||||||
|
from eos.core.tasks.direct import *
|
||||||
|
|
||||||
# Common library things
|
# Common library things
|
||||||
# ===================
|
# ===================
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
import eos.core.objects
|
import eos.core.objects
|
||||||
import eos.core.bigint
|
import eos.core.bigint
|
||||||
import eos.core.hashing
|
import eos.core.hashing
|
||||||
|
import eos.core.tests
|
||||||
|
import eos.core.tasks
|
||||||
|
import eos.core.tasks.direct
|
||||||
|
|
||||||
import eos.base.election
|
import eos.base.election
|
||||||
import eos.base.workflow
|
import eos.base.workflow
|
||||||
|
@ -225,8 +225,6 @@ class InternalMixingTrustee(MixingTrustee):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
class PSRElection(Election):
|
class PSRElection(Election):
|
||||||
_db_name = Election._name
|
|
||||||
|
|
||||||
sk = EmbeddedObjectField(SEGPrivateKey, is_protected=True) # TODO: Threshold
|
sk = EmbeddedObjectField(SEGPrivateKey, is_protected=True) # TODO: Threshold
|
||||||
|
|
||||||
public_key = EmbeddedObjectField(SEGPublicKey)
|
public_key = EmbeddedObjectField(SEGPublicKey)
|
||||||
|
@ -66,7 +66,7 @@ class TaskDecryptVotes(eos.base.workflow.TaskDecryptVotes):
|
|||||||
election = self.recurse_parents('eos.base.election.Election')
|
election = self.recurse_parents('eos.base.election.Election')
|
||||||
|
|
||||||
for _ in range(len(election.questions)):
|
for _ in range(len(election.questions)):
|
||||||
election.results.append(EosObject.objects['eos.base.election.RawResult']())
|
election.results.append(EosObject.lookup('eos.base.election.RawResult')())
|
||||||
|
|
||||||
for i in range(len(election.mixing_trustees[-1].mixed_questions)):
|
for i in range(len(election.mixing_trustees[-1].mixed_questions)):
|
||||||
for encrypted_answer in election.mixing_trustees[-1].mixed_questions[i]:
|
for encrypted_answer in election.mixing_trustees[-1].mixed_questions[i]:
|
||||||
|
@ -18,7 +18,9 @@ import click
|
|||||||
import flask
|
import flask
|
||||||
|
|
||||||
from eos.core.objects import *
|
from eos.core.objects import *
|
||||||
|
from eos.core.tasks import *
|
||||||
from eos.base.election import *
|
from eos.base.election import *
|
||||||
|
from eos.base.workflow import *
|
||||||
from eos.psr.crypto import *
|
from eos.psr.crypto import *
|
||||||
from eos.psr.election import *
|
from eos.psr.election import *
|
||||||
from eos.psr.mixnet import *
|
from eos.psr.mixnet import *
|
||||||
@ -152,6 +154,12 @@ def verify_election(electionid):
|
|||||||
def inject_globals():
|
def inject_globals():
|
||||||
return {'eos': eos, 'eosweb': eosweb, 'SHA256': eos.core.hashing.SHA256}
|
return {'eos': eos, 'eosweb': eosweb, 'SHA256': eos.core.hashing.SHA256}
|
||||||
|
|
||||||
|
# Tickle the plumbus every request
|
||||||
|
@app.before_request
|
||||||
|
def tick_scheduler():
|
||||||
|
# Process pending tasks
|
||||||
|
TaskScheduler.tick()
|
||||||
|
|
||||||
# === Views ===
|
# === Views ===
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@ -217,12 +225,12 @@ def election_admin_summary(election):
|
|||||||
@using_election
|
@using_election
|
||||||
@election_admin
|
@election_admin
|
||||||
def election_admin_enter_task(election):
|
def election_admin_enter_task(election):
|
||||||
task = election.workflow.get_task(flask.request.args['task_name'])
|
workflow_task = election.workflow.get_task(flask.request.args['task_name'])
|
||||||
if task.status != WorkflowTask.Status.READY:
|
if workflow_task.status != WorkflowTask.Status.READY:
|
||||||
return flask.Response('Task is not yet ready or has already exited', 409)
|
return flask.Response('Task is not yet ready or has already exited', 409)
|
||||||
|
|
||||||
task.enter()
|
task = WorkflowTaskEntryTask(election_id=election._id, workflow_task=workflow_task._name, status=Task.Status.READY, run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])())
|
||||||
election.save()
|
task.run()
|
||||||
|
|
||||||
return flask.redirect(flask.url_for('election_admin_summary', election_id=election._id))
|
return flask.redirect(flask.url_for('election_admin_summary', election_id=election._id))
|
||||||
|
|
||||||
|
@ -40,6 +40,11 @@
|
|||||||
margin-left: -1.75rem;
|
margin-left: -1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fix display of multiple right-aligned menu items */
|
||||||
|
.ui.menu:not(.vertical) .right.item ~ .right.item {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
body, html {
|
body, html {
|
||||||
/* Default height: 100% causes blank pages */
|
/* Default height: 100% causes blank pages */
|
||||||
|
42
eosweb/core/templates/active_tasks_menu.html
Normal file
42
eosweb/core/templates/active_tasks_menu.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{#
|
||||||
|
Eos - Verifiable elections
|
||||||
|
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
|
||||||
|
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/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
<div class="ui simple dropdown item right" id="active_tasks_menu">
|
||||||
|
<div class="ui{% if eos.core.tasks.TaskScheduler.active_tasks()|length > 0 %} active{% endif %} mini inline loader" style="margin-right: 1em;"></div>
|
||||||
|
{{ eos.core.tasks.TaskScheduler.active_tasks()|length }} active tasks
|
||||||
|
{% if eos.core.tasks.TaskScheduler.pending_tasks()|length > 0 %}
|
||||||
|
({{ eos.core.tasks.TaskScheduler.pending_tasks()|length }} pending)
|
||||||
|
{% endif %}
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
<div class="menu">
|
||||||
|
<div class="header">Active tasks</div>
|
||||||
|
{% for task in eos.core.tasks.TaskScheduler.active_tasks() %}
|
||||||
|
<div class="item">{{ task.label }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="header">Pending tasks</div>
|
||||||
|
{% for task in eos.core.tasks.TaskScheduler.pending_tasks() %}
|
||||||
|
<div class="item">{{ task.label }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="header">Recently completed tasks</div>
|
||||||
|
{% for task in eos.core.tasks.TaskScheduler.completed_tasks(3) %}
|
||||||
|
<div class="item">{% if task.status < 0 %}<i class="warning sign icon"></i> {% endif %}{{ task.label }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -28,6 +28,9 @@
|
|||||||
<a href="/" class="header item">Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</a>
|
<a href="/" class="header item">Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</a>
|
||||||
<a href="https://github.com/RunasSudo/Eos" class="item">Source Code</a>
|
<a href="https://github.com/RunasSudo/Eos" class="item">Source Code</a>
|
||||||
{% if session.user %}
|
{% if session.user %}
|
||||||
|
{% if session.user.is_admin() %}
|
||||||
|
{% include 'active_tasks_menu.html' %}
|
||||||
|
{% endif %}
|
||||||
<div class="ui simple dropdown item right">
|
<div class="ui simple dropdown item right">
|
||||||
<i class="{% if session.user.is_admin() %}legal{% else %}user circle{% endif %} icon"></i> {{ session.user.name }} <i class="dropdown icon"></i>
|
<i class="{% if session.user.is_admin() %}legal{% else %}user circle{% endif %} icon"></i> {{ session.user.name }} <i class="dropdown icon"></i>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
@ -24,14 +24,8 @@
|
|||||||
<ul>
|
<ul>
|
||||||
{% for task in election.workflow.tasks %}
|
{% for task in election.workflow.tasks %}
|
||||||
{% if task.status == eos.base.workflow.WorkflowTask.Status.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 confirmTask(this);">{{ task.label }}</a></li>
|
<li><a href="{{ url_for('election_admin_enter_task', election_id=election._id, task_name=task._name) }}" onclick="return window.confirm('Are you sure you want to execute the task \'{{ task.label }}\'? This action is irreversible.');">{{ task.label }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<script>
|
|
||||||
function confirmTask(taskLink) {
|
|
||||||
return window.confirm("Are you sure you want to execute the task \"" + taskLink.innerText + "\"? This action is irreversible.");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -14,6 +14,8 @@ ADMINS = [
|
|||||||
#eos.redditauth.election.RedditUser(username='xxxxxxxx')
|
#eos.redditauth.election.RedditUser(username='xxxxxxxx')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TASK_RUN_STRATEGY = 'eos.core.tasks.direct.DirectRunStrategy'
|
||||||
|
|
||||||
# MongoDB
|
# MongoDB
|
||||||
|
|
||||||
DB_TYPE = 'mongodb'
|
DB_TYPE = 'mongodb'
|
||||||
|
Loading…
Reference in New Issue
Block a user