From 48617debcdcbe097f514ac548a59947c0e8f78b1 Mon Sep 17 00:00:00 2001
From: Ashaba
Date: Thu, 5 Apr 2018 11:30:47 +0300
Subject: [PATCH 1/9] feat(schedule): schedule collection checks
---
application/.DS_Store | Bin 6148 -> 6148 bytes
application/base_model.py | 3 +-
application/helpers.py | 8 ++++-
application/models.py | 6 ++--
application/static/.DS_Store | Bin 6148 -> 6148 bytes
application/templates/collection_details.html | 26 ++++++++++++++++
application/views.py | 7 +++--
config.py | 1 +
main.py | 29 +++++++++++++++++-
migrations/versions/5da1e973b109_.py | 28 +++++++++++++++++
requirements.txt | 1 +
test/application/__init__.py | 0
test/application/test_auth.py | 2 +-
test/application/test_auth_models.py | 2 +-
test/application/test_collection_model.py | 2 +-
test/application/test_request_model.py | 4 +--
test/application/test_response_model.py | 2 +-
test/application/test_team_model.py | 2 +-
test/application/test_views.py | 2 +-
test/base.py | 3 +-
20 files changed, 107 insertions(+), 21 deletions(-)
create mode 100644 migrations/versions/5da1e973b109_.py
delete mode 100644 test/application/__init__.py
diff --git a/application/.DS_Store b/application/.DS_Store
index e9cfff9c0503a42c119b38dae2e68b87802bd4b8..c6b4c1d6e37a4644bbfdb460ded087b25f3a2105 100644
GIT binary patch
delta 98
zcmZoMXfc@J&&abeU^g=(&tx8!>7u+0Neqb$#SEzo@eH{P`3xyQR?g%HETXKf3=9l2
yCeL88=H+6@0Esdb0Lh}s_gJJEXKX&f!pg{DW^SOPU~Fcx`6}xX#?9;;fB6B#2N{z9
delta 48
zcmZoMXfc@J&&ahgU^g=(*JK`+>67oW7)*9#{l#HqVxgm8WNf>Pjj
E0dnCFXaE2J
diff --git a/application/base_model.py b/application/base_model.py
index 1afc056..ce7ca6e 100644
--- a/application/base_model.py
+++ b/application/base_model.py
@@ -1,7 +1,6 @@
-from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.sql import func
from sqlalchemy.exc import SQLAlchemyError
-
+from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
diff --git a/application/helpers.py b/application/helpers.py
index 6a9feec..cb6398f 100644
--- a/application/helpers.py
+++ b/application/helpers.py
@@ -1,7 +1,13 @@
import requests
import json
import re
-from application.models import Request, Response, ResponseSummary, ResponseAssertion
+from application.models import Request, Response, ResponseSummary, ResponseAssertion, Collection
+
+
+def collection_scheduler():
+ collections = Collection.fetch_all()
+ for collection in collections:
+ run_collection_checks(collection.id)
def valid_url(url):
diff --git a/application/models.py b/application/models.py
index 556d765..49f579c 100644
--- a/application/models.py
+++ b/application/models.py
@@ -1,5 +1,4 @@
from application.base_model import Base, db
-from sqlalchemy.orm import backref
# many to many relationship between users and teams
team_members = db.Table(
@@ -32,12 +31,11 @@ class Collection(Base):
name = db.Column(db.String(128), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('User.id'), nullable=False)
team_id = db.Column(db.Integer, db.ForeignKey('Team.id'))
- requests = db.relationship('Request',
- backref='collection', cascade='all, delete-orphan')
+ requests = db.relationship('Request', backref='collection', cascade='all, delete-orphan')
response_summary = db.relationship(
'ResponseSummary', backref='collection', lazy=True
)
- requests = db.relationship('Request', backref='collection', lazy=True)
+ schedule = db.Column(db.Time)
def __str__(self):
return self.name
diff --git a/application/static/.DS_Store b/application/static/.DS_Store
index a84805a192b6c17fa565ce563182b800239256bf..1711bbffbc10dcfc1620312ceef407b47128c7fe 100644
GIT binary patch
delta 146
zcmZoMXfc@J&&azmU^g=(?_@g`vB^p-2RY2l4RjQY%}gf0X6X#(W=La5WGG|EWJqSn
zXUJpFgRnev@{^Nt@{<@C7z7v?7{!6K#(yvXvKScHfU0tV`qCLv8H&-=woXoEJ&tUN
PShow
+<<<<<<< HEAD
+=======
+
{{ context["collection"]["name"] }}
+>>>>>>> feat(schedule): schedule collection checks
@@ -46,6 +51,27 @@
Results
+
diff --git a/application/views.py b/application/views.py
index c08f9a5..4b0044c 100644
--- a/application/views.py
+++ b/application/views.py
@@ -1,9 +1,7 @@
-from datetime import datetime
-
from flask import Blueprint, render_template, request, jsonify
from application.auth.helpers import (current_user, authentication_required)
-from application.models import Team, Collection, RequestAssertion, ResponseAssertion, ResponseSummary, Request, Header
+from application.models import Team, Collection, RequestAssertion, ResponseSummary, Request, Header
from application.helpers import run_collection_checks
app_view = Blueprint('app_view', __name__, template_folder='templates')
@@ -124,6 +122,9 @@ def collection_details(collection_id=None):
results.append(responseSet)
context['results'] = results
context['collection_name'] = Collection.get(collection_id).name
+ collection = Collection.get(collection_id)
+ context["collection"] = collection.serialize()
+
return render_template('collection_details.html', context=context)
diff --git a/config.py b/config.py
index 1b342ed..fb3db55 100644
--- a/config.py
+++ b/config.py
@@ -44,6 +44,7 @@ class DevelopmentConfiguration(ConfigWithCustomDBEngineParams):
DEBUG = True
SQLALCHEMY_ECHO = True
SQLALCHEMY_TRACK_MODIFICATIONS = True
+ ALLOWED_HOSTS = "http://localhost:5000"
class TestingConfiguration(Config):
diff --git a/main.py b/main.py
index 0883c1b..7fff0ff 100644
--- a/main.py
+++ b/main.py
@@ -1,13 +1,17 @@
import os
+import logging
from flask import Flask
from flask_migrate import Migrate
+import threading
+import time
from config import app_configuration
from application import models
from application.views import app_view
from application.auth.views import auth
+from application.helpers import collection_scheduler
os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -31,13 +35,36 @@ def create_application(environment):
# initilize migration commands
Migrate(app, models.db)
-
return app
app = create_application(os.getenv("FLASK_CONFIG") or "development")
+
+@app.before_first_request
+def active_job():
+ def run_job():
+ while True:
+ time.sleep(3)
+ collection_scheduler()
+ thread = threading.Thread(target=run_job)
+ thread.start()
+
+
+def start_runner():
+ def start_loop():
+ while True:
+ logging.info('In start loop')
+ # collection_scheduler()
+ time.sleep(3)
+
+ logging.info('Started runner')
+ thread = threading.Thread(target=start_loop)
+ thread.start()
+
+
if __name__ == "__main__":
environment = os.getenv("FLASK_CONFIG")
app = create_application(environment)
+ start_runner()
app.run()
diff --git a/migrations/versions/5da1e973b109_.py b/migrations/versions/5da1e973b109_.py
new file mode 100644
index 0000000..4c12adf
--- /dev/null
+++ b/migrations/versions/5da1e973b109_.py
@@ -0,0 +1,28 @@
+"""empty message
+
+Revision ID: 5da1e973b109
+Revises: fac4670c01e6
+Create Date: 2018-04-04 14:06:43.047609
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '5da1e973b109'
+down_revision = 'fac4670c01e6'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('Collection', sa.Column('schedule', sa.Time(), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column('Collection', 'schedule')
+ # ### end Alembic commands ###
diff --git a/requirements.txt b/requirements.txt
index 38e2f08..2a4874a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -51,6 +51,7 @@ PyYAML==3.12
query-string==0.0.28
requests==2.18.4
rsa==3.4.2
+schedule==0.5.0
six==1.11.0
SQLAlchemy==1.2.2
uritemplate==3.0.0
diff --git a/test/application/__init__.py b/test/application/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/test/application/test_auth.py b/test/application/test_auth.py
index f2a50c2..498f0ce 100644
--- a/test/application/test_auth.py
+++ b/test/application/test_auth.py
@@ -1,4 +1,4 @@
-from ..base import BaseTestCase, user_payload
+from test.base import BaseTestCase, user_payload
import json
diff --git a/test/application/test_auth_models.py b/test/application/test_auth_models.py
index 6d115bd..2a65ddc 100644
--- a/test/application/test_auth_models.py
+++ b/test/application/test_auth_models.py
@@ -1,4 +1,4 @@
-from ..base import BaseTestCase, db
+from test.base import BaseTestCase
from application.auth.models import User
diff --git a/test/application/test_collection_model.py b/test/application/test_collection_model.py
index cd62351..847aad9 100644
--- a/test/application/test_collection_model.py
+++ b/test/application/test_collection_model.py
@@ -1,4 +1,4 @@
-from ..base import BaseTestCase, db
+from test.base import BaseTestCase
from application.models import Collection, Team
from application.auth.models import User
diff --git a/test/application/test_request_model.py b/test/application/test_request_model.py
index 0448763..c232e0c 100644
--- a/test/application/test_request_model.py
+++ b/test/application/test_request_model.py
@@ -1,5 +1,5 @@
-from ..base import BaseTestCase, db
-from application.models import Header, Request
+from test.base import BaseTestCase
+from application.models import Request
class TestRequestModel(BaseTestCase):
diff --git a/test/application/test_response_model.py b/test/application/test_response_model.py
index 7ba98db..47375f1 100644
--- a/test/application/test_response_model.py
+++ b/test/application/test_response_model.py
@@ -1,4 +1,4 @@
-from ..base import BaseTestCase
+from test.base import BaseTestCase
from application.models import Response
diff --git a/test/application/test_team_model.py b/test/application/test_team_model.py
index 0ccd7a8..e317c2c 100644
--- a/test/application/test_team_model.py
+++ b/test/application/test_team_model.py
@@ -1,4 +1,4 @@
-from ..base import BaseTestCase, db
+from test.base import BaseTestCase
from application.models import Team
from application.auth.models import User
diff --git a/test/application/test_views.py b/test/application/test_views.py
index c1860aa..93dcc7c 100644
--- a/test/application/test_views.py
+++ b/test/application/test_views.py
@@ -1,4 +1,4 @@
-from ..base import BaseTestCase, user_payload
+from test.base import BaseTestCase, user_payload
import json
from application.models import db, Collection, Header, Request, RequestAssertion, Team
from application.auth.models import User
diff --git a/test/base.py b/test/base.py
index 7906a64..ae65fbe 100644
--- a/test/base.py
+++ b/test/base.py
@@ -1,6 +1,6 @@
from flask_testing import TestCase
-from application.models import db
+from application.base_model import db
from main import create_application
user_payload = {
@@ -11,7 +11,6 @@
class BaseTestCase(TestCase):
-
def create_app(self):
self.app = create_application('testing')
self.app_context = self.app.app_context()
From 6d7ef45ab7e8c6cbb4086b9b58aeb7462185ad0d Mon Sep 17 00:00:00 2001
From: Ashaba
Date: Thu, 5 Apr 2018 11:30:47 +0300
Subject: [PATCH 2/9] feat(schedule): schedule collection checks
---
application/helpers.py | 18 ++++++++++++++++++
application/models.py | 4 ++--
application/templates/collection_details.html | 4 ++++
application/views.py | 2 +-
4 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/application/helpers.py b/application/helpers.py
index cb6398f..744b8b4 100644
--- a/application/helpers.py
+++ b/application/helpers.py
@@ -2,6 +2,7 @@
import json
import re
from application.models import Request, Response, ResponseSummary, ResponseAssertion, Collection
+from application.models import Request, Collection
def collection_scheduler():
@@ -84,3 +85,20 @@ def run_collection_checks(collection_id, run_from):
response_summary.responses.append(response)
response_summary.save()
+
+
+def make_get_request(url, headers=None):
+ request = requests.get(url, headers=headers)
+ try:
+ response_object = request.json()
+ except Exception:
+ response_object = request.content.decode("utf-8")
+
+ response = {
+ "status_code": request.status_code,
+ "data": response_object,
+ "url": request.url,
+ "headers": json.dumps(dict(request.headers)),
+ "response_time": request.elapsed.total_seconds()
+ }
+ return response
diff --git a/application/models.py b/application/models.py
index 49f579c..e10e77a 100644
--- a/application/models.py
+++ b/application/models.py
@@ -31,11 +31,11 @@ class Collection(Base):
name = db.Column(db.String(128), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('User.id'), nullable=False)
team_id = db.Column(db.Integer, db.ForeignKey('Team.id'))
- requests = db.relationship('Request', backref='collection', cascade='all, delete-orphan')
+ schedule = db.Column(db.Time)
response_summary = db.relationship(
'ResponseSummary', backref='collection', lazy=True
)
- schedule = db.Column(db.Time)
+ requests = db.relationship('Request', backref='collection', cascade='all, delete-orphan')
def __str__(self):
return self.name
diff --git a/application/templates/collection_details.html b/application/templates/collection_details.html
index 543ff6b..a6db1e8 100644
--- a/application/templates/collection_details.html
+++ b/application/templates/collection_details.html
@@ -19,6 +19,7 @@ Results
+<<<<<<< HEAD
<<<<<<< HEAD
+=======
+
{{ context["collection"]["name"] }}
+>>>>>>> feat(schedule): schedule collection checks
=======
{{ context["collection"]["name"] }}
>>>>>>> feat(schedule): schedule collection checks
diff --git a/application/views.py b/application/views.py
index 4b0044c..361e316 100644
--- a/application/views.py
+++ b/application/views.py
@@ -72,7 +72,7 @@ def collections():
return render_template('collections.html', context=context)
-@app_view.route('/collection-details/
', methods=['GET'])
+@app_view.route('/collection-details/', methods=['GET', 'PUT'])
@authentication_required
def collection_details(collection_id=None):
context = {}
From 8b0fb9cedd920eba594e822c559118914c4b7a93 Mon Sep 17 00:00:00 2001
From: Muhwezi Allan
Date: Tue, 10 Apr 2018 17:55:23 +0300
Subject: [PATCH 3/9] feat (scheduler): add the scheduler slider
---
application/static/css/nouislider.min.css | 4 +
application/static/css/nouislider.pips.css | 98 +++++++++++++++++++
application/static/css/scheduler.css | 62 ++++++++++++
application/static/js/nouislider.min.js | 3 +
application/static/js/scheduler.js | 94 ++++++++++++++++++
application/templates/base.html | 7 +-
application/templates/collection_details.html | 56 ++++++-----
application/templates/settings.html | 54 ++++++++++
application/views.py | 25 ++++-
9 files changed, 371 insertions(+), 32 deletions(-)
create mode 100644 application/static/css/nouislider.min.css
create mode 100644 application/static/css/nouislider.pips.css
create mode 100644 application/static/css/scheduler.css
create mode 100644 application/static/js/nouislider.min.js
create mode 100644 application/static/js/scheduler.js
create mode 100644 application/templates/settings.html
diff --git a/application/static/css/nouislider.min.css b/application/static/css/nouislider.min.css
new file mode 100644
index 0000000..d877717
--- /dev/null
+++ b/application/static/css/nouislider.min.css
@@ -0,0 +1,4 @@
+/*! nouislider - 8.0.2 - 2015-07-06 13:22:09 */
+
+
+.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-user-select:none;-ms-touch-action:none;-ms-user-select:none;-moz-user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative;direction:ltr}.noUi-base{width:100%;height:100%;position:relative;z-index:1}.noUi-origin{position:absolute;right:0;top:0;left:0;bottom:0}.noUi-handle{position:relative;z-index:1}.noUi-stacking .noUi-handle{z-index:10}.noUi-state-tap .noUi-origin{-webkit-transition:left .3s,top .3s;transition:left .3s,top .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-base{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}.noUi-background{background:#FAFAFA;box-shadow:inset 0 1px 1px #f0f0f0}.noUi-connect{background:#3FB8AF;box-shadow:inset 0 0 3px rgba(51,51,51,.45);-webkit-transition:background 450ms;transition:background 450ms}.noUi-origin{border-radius:2px}.noUi-target{border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-target.noUi-connect{box-shadow:inset 0 0 3px rgba(51,51,51,.45),0 3px 6px -5px #BBB}.noUi-dragable{cursor:w-resize}.noUi-vertical .noUi-dragable{cursor:n-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect,[disabled].noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-origin{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;font:400 12px Arial;color:#999}.noUi-value{width:40px;position:absolute;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:50px;top:100%;left:0;width:100%}.noUi-value-horizontal{margin-left:-20px;padding-top:20px}.noUi-value-horizontal.noUi-value-sub{padding-top:15px}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{width:15px;margin-left:20px;margin-top:-5px}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}
\ No newline at end of file
diff --git a/application/static/css/nouislider.pips.css b/application/static/css/nouislider.pips.css
new file mode 100644
index 0000000..f4f97bf
--- /dev/null
+++ b/application/static/css/nouislider.pips.css
@@ -0,0 +1,98 @@
+
+/* Base;
+ *
+ */
+.noUi-pips,
+.noUi-pips * {
+-moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.noUi-pips {
+ position: absolute;
+ font: 400 12px Arial;
+ color: #999;
+}
+
+/* Values;
+ *
+ */
+.noUi-value {
+ width: 40px;
+ position: absolute;
+ text-align: center;
+}
+.noUi-value-sub {
+ color: #ccc;
+ font-size: 10px;
+}
+
+/* Markings;
+ *
+ */
+.noUi-marker {
+ position: absolute;
+ background: #CCC;
+}
+.noUi-marker-sub {
+ background: #AAA;
+}
+.noUi-marker-large {
+ background: #AAA;
+}
+
+/* Horizontal layout;
+ *
+ */
+.noUi-pips-horizontal {
+ padding: 10px 0;
+ height: 50px;
+ top: 100%;
+ left: 0;
+ width: 100%;
+}
+.noUi-value-horizontal {
+ margin-left: -20px;
+ padding-top: 20px;
+}
+.noUi-value-horizontal.noUi-value-sub {
+ padding-top: 15px;
+}
+
+.noUi-marker-horizontal.noUi-marker {
+ margin-left: -1px;
+ width: 2px;
+ height: 5px;
+}
+.noUi-marker-horizontal.noUi-marker-sub {
+ height: 10px;
+}
+.noUi-marker-horizontal.noUi-marker-large {
+ height: 15px;
+}
+
+/* Vertical layout;
+ *
+ */
+.noUi-pips-vertical {
+ padding: 0 10px;
+ height: 100%;
+ top: 0;
+ left: 100%;
+}
+.noUi-value-vertical {
+ width: 15px;
+ margin-left: 20px;
+ margin-top: -5px;
+}
+
+.noUi-marker-vertical.noUi-marker {
+ width: 5px;
+ height: 2px;
+ margin-top: -1px;
+}
+.noUi-marker-vertical.noUi-marker-sub {
+ width: 10px;
+}
+.noUi-marker-vertical.noUi-marker-large {
+ width: 15px;
+}
diff --git a/application/static/css/scheduler.css b/application/static/css/scheduler.css
new file mode 100644
index 0000000..b84b522
--- /dev/null
+++ b/application/static/css/scheduler.css
@@ -0,0 +1,62 @@
+.schedule-box {
+ text-align: center;
+}
+
+.schedule-title {
+ margin-bottom: 20px;
+ color: #83878c;
+ font-size: 2rem;
+}
+
+button {
+ font-size: 2rem !important;
+}
+
+.update-timeout-info {
+ line-height: 22px;
+}
+
+.update-timeout-label {
+ position: relative;
+ right: 3px;
+ display: inline-block;
+ text-align: right;
+ width: 100px;
+}
+
+.update-timeout-value {
+ font-size: 22px;
+ display: inline-block;
+ width: 100px;
+ text-align: left;
+ white-space: nowrap;
+
+}
+
+#period-slider {
+ margin: 20px 50px 80px 50px;
+}
+
+#period-slider.noUi-connect {
+ background: #22bc66;
+}
+
+#period-slider .noUi-value {
+ width: 60px;
+ margin-left: -30px;
+}
+
+.label-tag {
+ background-color: #eee;
+ color: #555;
+ font-style: normal;
+}
+
+
+#show-usage-modal .modal-dialog {
+ width: 1600px;
+ }
+
+ #show-usage-modal .tab-content {
+ margin-top: 15px;
+ }
diff --git a/application/static/js/nouislider.min.js b/application/static/js/nouislider.min.js
new file mode 100644
index 0000000..5d41dbd
--- /dev/null
+++ b/application/static/js/nouislider.min.js
@@ -0,0 +1,3 @@
+/*! nouislider - 8.0.2 - 2015-07-06 13:22:09 */
+
+!function(a){if("function"==typeof define&&define.amd)define([],a);else if("object"==typeof exports){var b=require("fs");module.exports=a(),module.exports.css=function(){return b.readFileSync(__dirname+"/nouislider.min.css","utf8")}}else window.noUiSlider=a()}(function(){"use strict";function a(a){return a.filter(function(a){return this[a]?!1:this[a]=!0},{})}function b(a,b){return Math.round(a/b)*b}function c(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.defaultView||c.parentWindow,e=c.documentElement,f=d.pageXOffset;return/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)&&(f=0),{top:b.top+d.pageYOffset-e.clientTop,left:b.left+f-e.clientLeft}}function d(a){return"number"==typeof a&&!isNaN(a)&&isFinite(a)}function e(a){var b=Math.pow(10,7);return Number((Math.round(a*b)/b).toFixed(7))}function f(a,b,c){j(a,b),setTimeout(function(){k(a,b)},c)}function g(a){return Math.max(Math.min(a,100),0)}function h(a){return Array.isArray(a)?a:[a]}function i(a){var b=a.split(".");return b.length>1?b[1].length:0}function j(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function k(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function l(a,b){a.classList?a.classList.contains(b):new RegExp("(^| )"+b+"( |$)","gi").test(a.className)}function m(a,b){return 100/(b-a)}function n(a,b){return 100*b/(a[1]-a[0])}function o(a,b){return n(a,a[0]<0?b+Math.abs(a[0]):b-a[0])}function p(a,b){return b*(a[1]-a[0])/100+a[0]}function q(a,b){for(var c=1;a>=b[c];)c+=1;return c}function r(a,b,c){if(c>=a.slice(-1)[0])return 100;var d,e,f,g,h=q(c,a);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],f+o([d,e],c)/m(f,g)}function s(a,b,c){if(c>=100)return a.slice(-1)[0];var d,e,f,g,h=q(c,b);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],p([d,e],(c-f)*m(f,g))}function t(a,c,d,e){if(100===e)return e;var f,g,h=q(e,a);return d?(f=a[h-1],g=a[h],e-f>(g-f)/2?g:f):c[h-1]?a[h-1]+b(e-a[h-1],c[h-1]):e}function u(a,b,c){var e;if("number"==typeof b&&(b=[b]),"[object Array]"!==Object.prototype.toString.call(b))throw new Error("noUiSlider: 'range' contains invalid value.");if(e="min"===a?0:"max"===a?100:parseFloat(a),!d(e)||!d(b[0]))throw new Error("noUiSlider: 'range' value isn't numeric.");c.xPct.push(e),c.xVal.push(b[0]),e?c.xSteps.push(isNaN(b[1])?!1:b[1]):isNaN(b[1])||(c.xSteps[0]=b[1])}function v(a,b,c){return b?void(c.xSteps[a]=n([c.xVal[a],c.xVal[a+1]],b)/m(c.xPct[a],c.xPct[a+1])):!0}function w(a,b,c,d){this.xPct=[],this.xVal=[],this.xSteps=[d||!1],this.xNumSteps=[!1],this.snap=b,this.direction=c;var e,f=[];for(e in a)a.hasOwnProperty(e)&&f.push([a[e],e]);for(f.sort(function(a,b){return a[0]-b[0]}),e=0;e2)throw new Error("noUiSlider: 'start' option is incorrect.");a.handles=b.length,a.start=b}function A(a,b){if(a.snap=b,"boolean"!=typeof b)throw new Error("noUiSlider: 'snap' option must be a boolean.")}function B(a,b){if(a.animate=b,"boolean"!=typeof b)throw new Error("noUiSlider: 'animate' option must be a boolean.")}function C(a,b){if("lower"===b&&1===a.handles)a.connect=1;else if("upper"===b&&1===a.handles)a.connect=2;else if(b===!0&&2===a.handles)a.connect=3;else{if(b!==!1)throw new Error("noUiSlider: 'connect' option doesn't match handle count.");a.connect=0}}function D(a,b){switch(b){case"horizontal":a.ort=0;break;case"vertical":a.ort=1;break;default:throw new Error("noUiSlider: 'orientation' option is invalid.")}}function E(a,b){if(!d(b))throw new Error("noUiSlider: 'margin' option must be numeric.");if(a.margin=a.spectrum.getMargin(b),!a.margin)throw new Error("noUiSlider: 'margin' option is only supported on linear sliders.")}function F(a,b){if(!d(b))throw new Error("noUiSlider: 'limit' option must be numeric.");if(a.limit=a.spectrum.getMargin(b),!a.limit)throw new Error("noUiSlider: 'limit' option is only supported on linear sliders.")}function G(a,b){switch(b){case"ltr":a.dir=0;break;case"rtl":a.dir=1,a.connect=[0,2,1,3][a.connect];break;default:throw new Error("noUiSlider: 'direction' option was not recognized.")}}function H(a,b){if("string"!=typeof b)throw new Error("noUiSlider: 'behaviour' must be a string containing options.");var c=b.indexOf("tap")>=0,d=b.indexOf("drag")>=0,e=b.indexOf("fixed")>=0,f=b.indexOf("snap")>=0;a.events={tap:c||f,drag:d,fixed:e,snap:f}}function I(a,b){if(a.format=b,"function"==typeof b.to&&"function"==typeof b.from)return!0;throw new Error("noUiSlider: 'format' requires 'to' and 'from' methods.")}function J(a){var b,c={margin:0,limit:0,animate:!0,format:U};b={step:{r:!1,t:x},start:{r:!0,t:z},connect:{r:!0,t:C},direction:{r:!0,t:G},snap:{r:!1,t:A},animate:{r:!1,t:B},range:{r:!0,t:y},orientation:{r:!1,t:D},margin:{r:!1,t:E},limit:{r:!1,t:F},behaviour:{r:!0,t:H},format:{r:!1,t:I}};var d={connect:!1,direction:"ltr",behaviour:"tap",orientation:"horizontal"};return Object.keys(d).forEach(function(b){void 0===a[b]&&(a[b]=d[b])}),Object.keys(b).forEach(function(d){var e=b[d];if(void 0===a[d]){if(e.r)throw new Error("noUiSlider: '"+d+"' is required.");return!0}e.t(c,a[d])}),c.pips=a.pips,c.style=c.ort?"top":"left",c}function K(a,b,c){var d=a+b[0],e=a+b[1];return c?(0>d&&(e+=Math.abs(d)),e>100&&(d-=e-100),[g(d),g(e)]):[d,e]}function L(a){a.preventDefault();var b,c,d=0===a.type.indexOf("touch"),e=0===a.type.indexOf("mouse"),f=0===a.type.indexOf("pointer"),g=a;return 0===a.type.indexOf("MSPointer")&&(f=!0),d&&(b=a.changedTouches[0].pageX,c=a.changedTouches[0].pageY),(e||f)&&(b=a.clientX+window.pageXOffset,c=a.clientY+window.pageYOffset),g.points=[b,c],g.cursor=e||f,g}function M(a,b){var c=document.createElement("div"),d=document.createElement("div"),e=["-lower","-upper"];return a&&e.reverse(),j(d,T[3]),j(d,T[3]+e[b]),j(c,T[2]),c.appendChild(d),c}function N(a,b,c){switch(a){case 1:j(b,T[7]),j(c[0],T[6]);break;case 3:j(c[1],T[6]);case 2:j(c[0],T[7]);case 0:j(b,T[6])}}function O(a,b,c){var d,e=[];for(d=0;a>d;d+=1)e.push(c.appendChild(M(b,d)));return e}function P(a,b,c){j(c,T[0]),j(c,T[8+a]),j(c,T[4+b]);var d=document.createElement("div");return j(d,T[1]),c.appendChild(d),d}function Q(b,d){function e(a,b,c){if("range"===a||"steps"===a)return M.xVal;if("count"===a){var d,e=100/(b-1),f=0;for(b=[];(d=f++*e)<=100;)b.push(d);a="positions"}return"positions"===a?b.map(function(a){return M.fromStepping(c?M.getStep(a):a)}):"values"===a?c?b.map(function(a){return M.fromStepping(M.getStep(M.toStepping(a)))}):b:void 0}function m(b,c,d){var e=M.direction,f={},g=M.xVal[0],h=M.xVal[M.xVal.length-1],i=!1,j=!1,k=0;return M.direction=0,d=a(d.slice().sort(function(a,b){return a-b})),d[0]!==g&&(d.unshift(g),i=!0),d[d.length-1]!==h&&(d.push(h),j=!0),d.forEach(function(a,e){var g,h,l,m,n,o,p,q,r,s,t=a,u=d[e+1];if("steps"===c&&(g=M.xNumSteps[e]),g||(g=u-t),t!==!1&&void 0!==u)for(h=t;u>=h;h+=g){for(m=M.toStepping(h),n=m-k,q=n/b,r=Math.round(q),s=n/r,l=1;r>=l;l+=1)o=k+l*s,f[o.toFixed(5)]=["x",0];p=d.indexOf(h)>-1?1:"steps"===c?2:0,!e&&i&&(p=0),h===u&&j||(f[m.toFixed(5)]=[h,p]),k=m}}),M.direction=e,f}function n(a,b,c){function e(a){return["-normal","-large","-sub"][a]}function f(a,b,c){return'class="'+b+" "+b+"-"+h+" "+b+e(c[1])+'" style="'+d.style+": "+a+'%"'}function g(a,d){M.direction&&(a=100-a),d[1]=d[1]&&b?b(d[0],d[1]):d[1],i.innerHTML+="",d[1]&&(i.innerHTML+=""+c.to(d[0])+"
")}var h=["horizontal","vertical"][d.ort],i=document.createElement("div");return j(i,"noUi-pips"),j(i,"noUi-pips-"+h),Object.keys(a).forEach(function(b){g(b,a[b])}),i}function o(a){var b=a.mode,c=a.density||1,d=a.filter||!1,f=a.values||!1,g=a.stepped||!1,h=e(b,f,g),i=m(c,b,h),j=a.format||{to:Math.round};return I.appendChild(n(i,d,j))}function p(){return G["offset"+["Width","Height"][d.ort]]}function q(a,b){void 0!==b&&(b=Math.abs(b-d.dir)),Object.keys(R).forEach(function(c){var d=c.split(".")[0];a===d&&R[c].forEach(function(a){a(h(B()),b,r(Array.prototype.slice.call(Q)))})})}function r(a){return 1===a.length?a[0]:d.dir?a.reverse():a}function s(a,b,c,e){var f=function(b){return I.hasAttribute("disabled")?!1:l(I,T[14])?!1:(b=L(b),a===S.start&&void 0!==b.buttons&&b.buttons>1?!1:(b.calcPoint=b.points[d.ort],void c(b,e)))},g=[];return a.split(" ").forEach(function(a){b.addEventListener(a,f,!1),g.push([a,f])}),g}function t(a,b){var c,d,e=b.handles||H,f=!1,g=100*(a.calcPoint-b.start)/p(),h=e[0]===H[0]?0:1;if(c=K(g,b.positions,e.length>1),f=y(e[0],c[h],1===e.length),e.length>1){if(f=y(e[1],c[h?0:1],!1)||f)for(d=0;d1&&j(I,T[12]);var f=function(){return!1};document.body.noUiListener=f,document.body.addEventListener("selectstart",f,!1)}}function w(a){var b,e,g=a.calcPoint,h=0;return a.stopPropagation(),H.forEach(function(a){h+=c(a)[d.style]}),b=h/2>g||1===H.length?0:1,g-=c(G)[d.style],e=100*g/p(),d.events.snap||f(I,T[14],300),H[b].hasAttribute("disabled")?!1:(y(H[b],e),q("slide",b),q("set",b),q("change",b),void(d.events.snap&&v(a,{handles:[H[h]]})))}function x(a){var b,c;if(!a.fixed)for(b=0;b1&&(b=e?Math.max(b,f):Math.min(b,h)),c!==!1&&d.limit&&H.length>1&&(b=e?Math.min(b,i):Math.max(b,l)),b=M.getStep(b),b=g(parseFloat(b.toFixed(7))),b===J[e]?!1:(a.style[d.style]=b+"%",a.previousSibling||(k(a,T[17]),b>50&&j(a,T[17])),J[e]=b,Q[e]=M.fromStepping(b),q("update",e),!0)}function z(a,b){var c,e,f;for(d.limit&&(a+=1),c=0;a>c;c+=1)e=c%2,f=b[e],null!==f&&f!==!1&&("number"==typeof f&&(f=String(f)),f=d.format.from(f),(f===!1||isNaN(f)||y(H[e],M.toStepping(f),c===3-d.dir)===!1)&&q("update",e))}function A(a){var b,c,e=h(a);for(d.dir&&d.handles>1&&e.reverse(),d.animate&&-1!==J[0]&&f(I,T[14],300),b=H.length>1?3:1,1===e.length&&(b=1),z(b,e),c=0;c=c[1]?c[2]:c[0]||!1;return[h,f]});return r(a)}function E(a,b){R[a]=R[a]||[],R[a].push(b),"update"===a.split(".")[0]&&H.forEach(function(a,b){q("update",b)})}function F(a){var b=a.split(".")[0],c=a.substring(b.length);Object.keys(R).forEach(function(a){var d=a.split(".")[0],e=a.substring(d.length);b&&b!==d||c&&c!==e||delete R[a]})}var G,H,I=b,J=[-1,-1],M=d.spectrum,Q=[],R={};if(I.noUiSlider)throw new Error("Slider was already initialized.");return G=P(d.dir,d.ort,I),H=O(d.handles,d.dir,G),N(d.connect,I,H),x(d.events),d.pips&&o(d.pips),{destroy:C,steps:D,on:E,off:F,get:B,set:A}}function R(a,b){if(!a.nodeName)throw new Error("noUiSlider.create requires a single element.");var c=J(b,a),d=Q(a,c);d.set(c.start),a.noUiSlider=d}var S=window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",end:"mouseup touchend"},T=["noUi-target","noUi-base","noUi-origin","noUi-handle","noUi-horizontal","noUi-vertical","noUi-background","noUi-connect","noUi-ltr","noUi-rtl","noUi-dragable","","noUi-state-drag","","noUi-state-tap","noUi-active","","noUi-stacking"];w.prototype.getMargin=function(a){return 2===this.xPct.length?n(this.xVal,a):!1},w.prototype.toStepping=function(a){return a=r(this.xVal,this.xPct,a),this.direction&&(a=100-a),a},w.prototype.fromStepping=function(a){return this.direction&&(a=100-a),e(s(this.xVal,this.xPct,a))},w.prototype.getStep=function(a){return this.direction&&(a=100-a),a=t(this.xPct,this.xSteps,this.snap,a),this.direction&&(a=100-a),a},w.prototype.getApplicableStep=function(a){var b=q(a,this.xPct),c=100===a?2:1;return[this.xNumSteps[b-2],this.xVal[b-c],this.xNumSteps[b-c]]},w.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var U={to:function(a){return a.toFixed(2)},from:Number};return{create:R}});
\ No newline at end of file
diff --git a/application/static/js/scheduler.js b/application/static/js/scheduler.js
new file mode 100644
index 0000000..f3d21f7
--- /dev/null
+++ b/application/static/js/scheduler.js
@@ -0,0 +1,94 @@
+$(function () {
+
+ var MINUTE = {name: "min", nsecs: 60};
+ var HOUR = {name: "hour", nsecs: MINUTE.nsecs * 60};
+ var DAY = {name: "day", nsecs: HOUR.nsecs * 24};
+ var WEEK = {name: "week", nsecs: DAY.nsecs * 7};
+ var UNITS = [WEEK, DAY, HOUR, MINUTE];
+
+ var secsToText = function(total) {
+ var remainingSeconds = Math.floor(total);
+ var result = "";
+ for (var i=0, unit; unit=UNITS[i]; i++) {
+ if (unit === WEEK && remainingSeconds % unit.nsecs != 0) {
+ // Say "8 days" instead of "1 week 1 day"
+ continue
+ }
+
+ var count = Math.floor(remainingSeconds / unit.nsecs);
+ remainingSeconds = remainingSeconds % unit.nsecs;
+
+ if (count == 1) {
+ result += "1 " + unit.name + " ";
+ }
+
+ if (count > 1) {
+ result += count + " " + unit.name + "s ";
+ }
+ }
+
+ return result;
+ }
+
+ var periodSlider = document.getElementById("period-slider");
+ noUiSlider.create(periodSlider, {
+ start: [20],
+ connect: "lower",
+ range: {
+ 'min': [60, 60],
+ '20%': [3600, 3600],
+ '40%': [86400, 86400],
+ '60%': [604800, 604800],
+ '80%': [2592000, 864000],
+ 'max': 5184000,
+ },
+ pips: {
+ mode: 'values',
+ values: [60, 1800, 3600, 43200, 86400, 604800, 2592000, 5184000],
+ density: 4,
+ format: {
+ to: secsToText,
+ from: function() {}
+ }
+ }
+ });
+
+ periodSlider.noUiSlider.on("update", function(a, b, value) {
+ var rounded = Math.round(value);
+ $("#period-slider-value").text(secsToText(rounded));
+ $("#update-timeout-timeout").val(rounded);
+ });
+
+ $('[data-toggle="tooltip"]').tooltip();
+
+ $(".check-menu-remove").click(function() {
+ var $this = $(this);
+
+ $("#remove-check-form").attr("action", $this.data("url"));
+ $(".remove-check-name").text($this.data("name"));
+ $('#remove-check-modal').modal("show");
+
+ return false;
+ });
+
+});
+
+$(".update-timeout-form").submit(updateScheduler);
+
+function updateScheduler (event) {
+ var form = $(this);
+ var id = form.attr('id');
+ console.log(id);
+
+ var form_data = $(this).serializeArray();
+ console.log(form_data);
+ $.ajax({
+ type: 'PUT',
+ contentType: 'application/json',
+ url: '/collection-details/' + id,
+ data: form_data,
+ success: function(data) {
+ console.log('Put was performed.');
+ }
+ });
+}
diff --git a/application/templates/base.html b/application/templates/base.html
index 6cf7d30..b124a6b 100644
--- a/application/templates/base.html
+++ b/application/templates/base.html
@@ -27,6 +27,11 @@
+
+
+
+
+
@@ -53,7 +58,7 @@
Team
- Settings
+ Settings
{% if session.name %}
diff --git a/application/templates/collection_details.html b/application/templates/collection_details.html
index a6db1e8..0574261 100644
--- a/application/templates/collection_details.html
+++ b/application/templates/collection_details.html
@@ -12,15 +12,13 @@ Show
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
{{ context["collection"]["name"] }}
->>>>>>> feat(schedule): schedule collection checks
-=======
-
{{ context["collection"]["name"] }}
->>>>>>> feat(schedule): schedule collection checks
@@ -55,25 +48,33 @@
{{ context["collection"]["name"] }}
-
-
-
-
@@ -83,7 +84,8 @@
{{ context["collection"]["name"] }}
var context = {{ context|tojson }};
-
-
+
+
+
{% endblock %}
+{% block body %}
+
+ {{ banner() }}
+
+
+
+
+
+ Set the time between the pings to the requests in the collection
+
+
+
+
+
+
+
+
+
+{% endblock %}
+