Skip to content

Commit b7a0c2d

Browse files
committed
Add edit UI
Move to keyed structure instead of list
1 parent 1629dee commit b7a0c2d

File tree

7 files changed

+181
-69
lines changed

7 files changed

+181
-69
lines changed

backend/backend.py

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,36 @@ def _jinja2_filter_datetimestamp(timestamp, format="%Y-%m-%d %H:%M:%S"):
7979
def main_page():
8080
global messages
8181

82-
# Show messages but once.
83-
# maybe if the change happened more than a few days ago.. add a class
82+
# Sort by last_changed and add the uuid which is usually the key..
83+
sorted_watches=[]
84+
for uuid, watch in datastore.data['watching'].items():
85+
watch['uuid']=uuid
86+
sorted_watches.append(watch)
87+
88+
sorted_watches.sort(key=lambda x: x['last_changed'], reverse=True)
8489

85-
# Sort by last_changed
86-
datastore.data['watching'].sort(key=lambda x: x['last_changed'], reverse=True)
87-
output = render_template("watch-overview.html", watches=datastore.data['watching'], messages=messages)
90+
output = render_template("watch-overview.html", watches=sorted_watches, messages=messages)
91+
92+
# Show messages but once.
8893
messages = []
8994
return output
9095

9196

97+
@app.route("/edit", methods=['GET'])
98+
def edit_page():
99+
global messages
100+
101+
uuid = request.args.get('uuid')
102+
103+
output = render_template("edit.html", uuid=uuid, watch=datastore.data['watching'][uuid], messages=messages)
104+
return output
105+
106+
92107
@app.route("/favicon.ico", methods=['GET'])
93108
def favicon():
94109
return send_from_directory("/app/static/images", filename="favicon.ico")
95110

111+
96112
@app.route("/static/<string:group>/<string:filename>", methods=['GET'])
97113
def static_content(group, filename):
98114
try:
@@ -112,38 +128,63 @@ def api_watch_add():
112128
return redirect(url_for('main_page'))
113129

114130

131+
@app.route("/api/delete", methods=['GET'])
132+
def api_delete():
133+
global messages
134+
uuid = request.args.get('uuid')
135+
datastore.delete(uuid)
136+
messages.append({'class': 'ok', 'message': 'Deleted.'})
137+
138+
return redirect(url_for('main_page'))
139+
140+
141+
@app.route("/api/update", methods=['POST'])
142+
def api_update():
143+
global messages
144+
import validators
145+
146+
uuid = request.args.get('uuid')
147+
148+
url = request.form.get('url').strip()
149+
tag = request.form.get('tag').strip()
150+
151+
validators.url(url) #@todo switch to prop/attr/observer
152+
datastore.data['watching'][uuid].update({'url': url,
153+
'tag': tag})
154+
155+
#@todo switch to prop/attr/observer
156+
datastore.sync_to_json()
157+
158+
messages.append({'class': 'ok', 'message': 'Updated.'})
159+
160+
return redirect(url_for('main_page'))
161+
115162
@app.route("/api/checknow", methods=['GET'])
116163
def api_watch_checknow():
117164
global messages
118165

119166
uuid = request.args.get('uuid')
120167

121-
# dict would be better, this is a simple safety catch.
122-
for watch in datastore.data['watching']:
123-
if watch['uuid'] == uuid:
124-
# @todo cancel if already running?
125-
running_update_threads[uuid] = fetch_site_status.perform_site_check(uuid=uuid,
126-
datastore=datastore)
127-
running_update_threads[uuid].start()
168+
running_update_threads[uuid] = fetch_site_status.perform_site_check(uuid=uuid,
169+
datastore=datastore)
170+
running_update_threads[uuid].start()
128171

129172
return redirect(url_for('main_page'))
130173

131174

132175
@app.route("/api/recheckall", methods=['GET'])
133176
def api_watch_recheckall():
134-
135177
import fetch_site_status
136178

137179
global running_update_threads
138-
i=0
139-
for watch in datastore.data['watching']:
140-
i=i+1
180+
i = 0
181+
for uuid, watch in datastore.data['watching']:
182+
i = i + 1
141183

142-
running_update_threads[watch['uuid']] = fetch_site_status.perform_site_check(uuid=watch['uuid'],
184+
running_update_threads[watch['uuid']] = fetch_site_status.perform_site_check(uuid=uuid,
143185
datastore=datastore)
144186
running_update_threads[watch['uuid']].start()
145187

146-
147188
return "{} rechecked of {} watches.".format(i, len(datastore.data['watching']))
148189

149190

@@ -152,9 +193,9 @@ def launch_checks():
152193
import fetch_site_status
153194
global running_update_threads
154195

155-
for watch in datastore.data['watching']:
196+
for uuid,watch in datastore.data['watching'].items():
156197
if watch['last_checked'] <= time.time() - 3 * 60 * 60:
157-
running_update_threads[watch['uuid']] = fetch_site_status.perform_site_check(uuid=watch['uuid'],
198+
running_update_threads[watch['uuid']] = fetch_site_status.perform_site_check(uuid=uuid,
158199
datastore=datastore)
159200
running_update_threads[watch['uuid']].start()
160201

backend/fetch_site_status.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ def run(self):
8787
self.datastore.update_watch(self.uuid, 'last_error', str(e))
8888
print(str(e))
8989

90+
except requests.exceptions.MissingSchema:
91+
print ("Skipping {} due to missing schema/bad url".format(self.uuid))
92+
9093
# Usually from html2text level
9194
except UnicodeDecodeError as e:
9295
self.datastore.update_watch(self.uuid, 'last_error', str(e))

backend/static/css/styles.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,40 @@ body:after, body:before {
120120
max-width: 400px;
121121
display: block;
122122
}
123+
124+
.edit-form {
125+
background: #fff;
126+
padding: 2em;
127+
border-radius: 5px;
128+
}
129+
.button-secondary {
130+
color: white;
131+
border-radius: 4px;
132+
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
133+
}
134+
135+
.button-success {
136+
background: rgb(28, 184, 65);
137+
/* this is a green */
138+
}
139+
140+
.button-error {
141+
background: rgb(202, 60, 60);
142+
/* this is a maroon */
143+
}
144+
145+
.button-warning {
146+
background: rgb(223, 117, 20);
147+
/* this is an orange */
148+
}
149+
150+
.button-secondary {
151+
background: rgb(66, 184, 221);
152+
/* this is a light blue */
153+
}
154+
155+
156+
.button-cancel {
157+
background: rgb(200, 200, 200);
158+
/* this is a green */
159+
}

backend/store.py

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
import uuid
2+
import uuid as uuid_builder
33
import validators
44

55

@@ -9,6 +9,10 @@
99
class ChangeDetectionStore:
1010

1111
def __init__(self):
12+
self.data = {
13+
'watching': {}
14+
}
15+
1216

1317
# Base definition for all watchers
1418
self.generic_definition = {
@@ -17,60 +21,44 @@ def __init__(self):
1721
'last_checked': 0,
1822
'last_changed': 0,
1923
'title': None,
20-
'uuid': str(uuid.uuid4()),
24+
'uuid': str(uuid_builder.uuid4()),
2125
'headers' : {}, # Extra headers to send
2226
'history' : {} # Dict of timestamp and output stripped filename
2327
}
2428

2529
try:
2630
with open('/datastore/url-watches.json') as json_file:
27-
self.data = json.load(json_file)
31+
32+
self.data.update(json.load(json_file))
33+
2834
# Reinitialise each `watching` with our generic_definition in the case that we add a new var in the future.
35+
# @todo pretty sure theres a python we todo this with an abstracted(?) object!
2936
i = 0
30-
while i < len(self.data['watching']):
37+
for uuid, watch in self.data['watching'].items():
3138
_blank = self.generic_definition.copy()
32-
_blank.update(self.data['watching'][i])
33-
self.data['watching'][i] = _blank
34-
35-
print("Watching:", self.data['watching'][i]['url'])
36-
i += 1
39+
_blank.update(watch)
40+
self.data['watching'].update({uuid: _blank})
41+
print("Watching:", uuid, _blank['url'])
3742

3843
# First time ran, doesnt exist.
3944
except (FileNotFoundError, json.decoder.JSONDecodeError):
40-
print("Resetting JSON store")
45+
print("Creating JSON store")
4146

42-
self.data = {}
43-
self.data['watching'] = []
44-
self._init_blank_data()
45-
self.sync_to_json()
47+
self.add_watch(url='https://changedetection.io', tag='general')
48+
self.add_watch(url='http://www.quotationspage.com/random.php', tag='test')
4649

47-
def _init_blank_data(self):
50+
def update_watch(self, uuid, val, var):
4851

49-
# Test site
50-
_blank = self.generic_definition.copy()
51-
_blank.update({
52-
'url': 'https://changedetection.io',
53-
'tag': 'general',
54-
'uuid': str(uuid.uuid4())
55-
})
56-
self.data['watching'].append(_blank)
52+
self.data['watching'][uuid].update({val: var})
53+
self.sync_to_json()
5754

58-
# Test site
59-
_blank = self.generic_definition.copy()
60-
_blank.update({
61-
'url': 'http://www.quotationspage.com/random.php',
62-
'tag': 'test',
63-
'uuid': str(uuid.uuid4())
64-
})
65-
self.data['watching'].append(_blank)
6655

67-
def update_watch(self, uuid, val, var):
56+
57+
def delete(self, uuid):
6858
# Probably their should be dict...
69-
for watch in self.data['watching']:
70-
if watch['uuid'] == uuid:
71-
watch[val] = var
72-
# print("Updated..", val)
73-
self.sync_to_json()
59+
del(self.data['watching'][uuid])
60+
self.sync_to_json()
61+
7462

7563
def url_exists(self, url):
7664

@@ -83,13 +71,11 @@ def url_exists(self, url):
8371

8472
def get_val(self, uuid, val):
8573
# Probably their should be dict...
86-
for watch in self.data['watching']:
87-
if watch['uuid'] == uuid:
88-
return watch.get(val)
89-
90-
return None
74+
return self.data['watching'][uuid].get(val)
9175

9276
def add_watch(self, url, tag):
77+
78+
# @todo deal with exception
9379
validators.url(url)
9480

9581
# @todo use a common generic version of this
@@ -98,12 +84,12 @@ def add_watch(self, url, tag):
9884
_blank.update({
9985
'url': url,
10086
'tag': tag,
101-
'uuid': str(uuid.uuid4())
87+
'uuid': str(uuid_builder.uuid4())
10288
})
103-
self.data['watching'].append(_blank)
89+
90+
self.data['watching'].update({_blank['uuid']: _blank})
10491

10592
self.sync_to_json()
106-
# @todo throw custom exception
10793

10894
def sync_to_json(self):
10995
with open('/datastore/url-watches.json', 'w') as json_file:

backend/templates/base.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@
1515
<a class="pure-menu-heading" href=""><strong>Change</strong>Detection.io</a>
1616

1717
<ul class="pure-menu-list">
18-
<li class="pure-menu-item pure-menu-selected"><a class="github-link " href="https://github.com/dgtlmoon/changedetection.io"
19-
data-hotkey="g d" aria-label="Homepage "
20-
data-ga-click="Header, go to dashboard, icon:logo">
18+
19+
<li class="pure-menu-item">
20+
<a href="/import" class="pure-menu-link">IMPORT</a>
21+
</li>
22+
<li class="pure-menu-item">
23+
<a href="/settings" class="pure-menu-link">SETTINGS</a>
24+
</li>
25+
<li class="pure-menu-item"><a class="github-link" href="https://github.com/dgtlmoon/changedetection.io">
2126
<svg class="octicon octicon-mark-github v-align-middle" height="32" viewBox="0 0 16 16" version="1.1"
2227
width="32" aria-hidden="true">
2328
<path fill-rule="evenodd"

backend/templates/edit.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{% extends 'base.html' %}
2+
3+
{% block content %}
4+
<div class="edit-form">
5+
6+
7+
<form class="pure-form pure-form-aligned" action="/api/update?uuid={{uuid}}" method="POST">
8+
<fieldset>
9+
<div class="pure-control-group">
10+
<label for="url">URL</label>
11+
<input type="url" id="url" required="" placeholder="https://..." name="url" value="{{ watch.url}}"
12+
size="50"/>
13+
<span class="pure-form-message-inline">This is a required field.</span>
14+
</div>
15+
<div class="pure-control-group">
16+
<label for="tag">Tag</label>
17+
<input type="text" placeholder="tag" size="10" id="tag" name="tag" value="{{ watch.tag}}"/>
18+
</div>
19+
20+
<div class="pure-controls">
21+
<button type="submit" class="pure-button pure-button-primary">Submit</button>
22+
</div>
23+
24+
<div class="pure-controls">
25+
26+
27+
<a href="/" class="pure-button button-small button-cancel">Cancel</a>
28+
<a href="/api/delete?uuid={{uuid}}"
29+
class="pure-button button-small button-error ">Delete</a>
30+
31+
</div>
32+
33+
34+
</fieldset>
35+
</form>
36+
37+
38+
</div>
39+
40+
{% endblock %}

backend/templates/watch-overview.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
</td>
4646
<td>{{watch.last_changed|format_timestamp_timeago}}</td>
4747
<td><a href="/api/checknow?uuid={{ watch.uuid}}" class="pure-button button-small pure-button-primary">Recheck</a>
48-
<button type="submit" class="pure-button button-small pure-button-primary">Delete</button>
48+
<a href="/edit?uuid={{ watch.uuid}}" class="pure-button button-small pure-button-primary">Edit</a>
4949
</td>
5050
</tr>
5151
{% endfor %}

0 commit comments

Comments
 (0)