]> git.ipfire.org Git - ipfire.org.git/blame - src/web/fireinfo.py
fireinfo: Redesign profile page
[ipfire.org.git] / src / web / fireinfo.py
CommitLineData
66862195
MT
1#!/usr/bin/python
2
66862195
MT
3import datetime
4import hwdata
5import logging
6import re
a95c2f97 7import json
66862195
MT
8import tornado.web
9
a95c2f97 10from .. import fireinfo
66862195 11
124a8404 12from . import base
96c9bb79 13from . import ui_modules
66862195 14
96c9bb79 15class BaseHandler(base.BaseHandler):
66862195
MT
16 @property
17 def when(self):
18 return self.get_argument_date("when", None)
19
20
21MIN_PROFILE_VERSION = 0
22MAX_PROFILE_VERSION = 0
23
24class Profile(dict):
25 def __getattr__(self, key):
26 try:
27 return self[key]
28 except KeyError:
11347e46 29 raise AttributeError(key)
66862195
MT
30
31 def __setattr__(self, key, val):
32 self[key] = val
33
34
96c9bb79 35class ProfileSendHandler(BaseHandler):
66862195
MT
36 def check_xsrf_cookie(self):
37 # This cookie is not required here.
38 pass
39
40 def prepare(self):
41 # Create an empty profile.
42 self.profile = Profile()
43
44 def __check_attributes(self, profile):
45 """
46 Check for attributes that must be provided,
47 """
48 attributes = (
49 "private_id",
50 "profile_version",
51 "public_id",
52 )
53 for attr in attributes:
11347e46 54 if attr not in profile:
66862195
MT
55 raise tornado.web.HTTPError(400, "Profile lacks '%s' attribute: %s" % (attr, profile))
56
57 def __check_valid_ids(self, profile):
58 """
59 Check if IDs contain valid data.
60 """
61 for id in ("public_id", "private_id"):
62 if re.match(r"^([a-f0-9]{40})$", "%s" % profile[id]) is None:
63 raise tornado.web.HTTPError(400, "ID '%s' has wrong format: %s" % (id, profile))
64
65 def __check_equal_ids(self, profile):
66 """
67 Check if public_id and private_id are equal.
68 """
69 if profile.public_id == profile.private_id:
70 raise tornado.web.HTTPError(400, "Public and private IDs are equal: %s" % profile)
71
72 def __check_matching_ids(self, profile):
73 """
74 Check if a profile with the given public_id is already in the
75 database. If so we need to check if the private_id matches.
76 """
77 p = self.profiles.find_one({ "public_id" : profile["public_id"]})
78 if not p:
79 return
80
81 p = Profile(p)
82 if p.private_id != profile.private_id:
83 raise tornado.web.HTTPError(400, "Mismatch of private_id: %s" % profile)
84
85 def __check_profile_version(self, profile):
86 """
87 Check if this version of the server software does support the
88 received profile.
89 """
90 version = profile.profile_version
91
92 if version < MIN_PROFILE_VERSION or version > MAX_PROFILE_VERSION:
93 raise tornado.web.HTTPError(400,
94 "Profile version is not supported: %s" % version)
95
96 def check_profile_blob(self, profile):
97 """
98 This method checks if the blob is sane.
99 """
100 checks = (
101 self.__check_attributes,
102 self.__check_valid_ids,
103 self.__check_equal_ids,
104 self.__check_profile_version,
105 # These checks require at least one database query and should be done
106 # at last.
107 self.__check_matching_ids,
108 )
109
110 for check in checks:
111 check(profile)
112
113 # If we got here, everything is okay and we can go on...
114
115 def get_profile_blob(self):
116 profile = self.get_argument("profile", None)
117
118 # Send "400 bad request" if no profile was provided
119 if not profile:
120 raise tornado.web.HTTPError(400, "No profile received")
121
122 # Try to decode the profile.
123 try:
a95c2f97 124 return json.loads(profile)
11347e46 125 except json.decoder.JSONDecodeError as e:
66862195
MT
126 raise tornado.web.HTTPError(400, "Profile could not be decoded: %s" % e)
127
128 # The GET method is only allowed in debugging mode.
129 def get(self, public_id):
130 if not self.application.settings["debug"]:
fe947d95 131 raise tornado.web.HTTPError(405)
66862195
MT
132
133 return self.post(public_id)
134
135 def post(self, public_id):
136 profile_blob = self.get_profile_blob()
137 #self.check_profile_blob(profile_blob)
138
139 # Get location
140 location = self.get_remote_location()
141 if location:
142 location = location.country
143
144 # Handle the profile.
a69e87a1
MT
145 with self.db.transaction():
146 try:
147 self.fireinfo.handle_profile(public_id, profile_blob, location=location)
66862195 148
a69e87a1
MT
149 except fireinfo.ProfileParserError:
150 raise tornado.web.HTTPError(400)
66862195
MT
151
152 self.finish("Your profile was successfully saved to the database.")
153
154
96c9bb79 155class IndexHandler(BaseHandler):
66862195
MT
156 def get(self):
157 self.render("fireinfo/index.html")
158
66862195 159
b84b407f 160class ProfileHandler(BaseHandler):
66862195
MT
161 def get(self, profile_id):
162 profile = self.fireinfo.get_profile(profile_id, when=self.when)
163
164 if not profile or not profile.is_showable():
b84b407f 165 raise tornado.web.HTTPError(404)
66862195 166
b84b407f 167 self.render("fireinfo/profile.html", profile=profile)
66862195
MT
168
169
96c9bb79 170class RandomProfileHandler(BaseHandler):
66862195
MT
171 def get(self):
172 profile_id = self.fireinfo.get_random_profile(when=self.when)
173 if profile_id is None:
174 raise tornado.web.HTTPError(404)
175
176 self.redirect("/profile/%s" % profile_id)
177
178
96c9bb79 179class DeviceDriverDetail(BaseHandler):
66862195
MT
180 def get(self, driver):
181 self.render("fireinfo/driver.html", driver=driver,
182 driver_map=self.fireinfo.get_driver_map(driver, when=self.when))
183
184
96c9bb79 185class DeviceVendorsHandler(BaseHandler):
66862195
MT
186 def get(self):
187 vendors = self.fireinfo.get_vendor_list(when=self.when)
188
189 self.render("fireinfo/vendors.html", vendors=vendors)
190
191
96c9bb79 192class StatsHandler(BaseHandler):
66862195
MT
193 def get(self):
194 self.render("fireinfo/stats.html")
195
196
96c9bb79 197class StatsProcessorsHandler(BaseHandler):
66862195
MT
198 def get(self):
199 avg, stddev, min, max = self.fireinfo.get_cpu_clock_speeds(when=self.when)
200 arch_map = self.fireinfo.get_arch_map(when=self.when)
201
202 data = {
203 "arch_map" : arch_map,
204 "cpu_vendors" : self.fireinfo.get_cpu_vendors_map(when=self.when),
205 "clock_speed_avg" : avg,
206 "clock_speed_stddev" : stddev,
207 "clock_speed_min" : min,
208 "clock_speed_max" : max,
209 }
210
211 return self.render("fireinfo/stats-cpus.html", **data)
212
213
96c9bb79 214class StatsProcessorDetailHandler(BaseHandler):
66862195
MT
215 def get(self, platform):
216 assert platform in ("arm", "x86")
217
218 flags = self.fireinfo.get_common_cpu_flags_by_platform(platform, when=self.when)
219
220 return self.render("fireinfo/stats-cpus-detail.html",
221 platform=platform, flags=flags)
222
223
96c9bb79 224class StatsMemoryHandler(BaseHandler):
66862195
MT
225 def get(self):
226 avg, stddev, min, max = self.fireinfo.get_memory_amounts(when=self.when)
227 amounts = self.fireinfo.get_common_memory_amounts(when=self.when)
228
229 data = {
230 "mem_avg" : avg,
231 "mem_stddev" : stddev,
232 "mem_min" : min,
233 "mem_max" : max,
234 "amounts" : amounts,
235 }
236
237 return self.render("fireinfo/stats-memory.html", **data)
238
239
96c9bb79 240class StatsReleasesHandler(BaseHandler):
66862195
MT
241 def get(self):
242 data = {
243 "releases" : self.fireinfo.get_releases_map(when=self.when),
244 "kernels" : self.fireinfo.get_kernels_map(when=self.when),
245 }
246 return self.render("fireinfo/stats-oses.html", **data)
247
248
96c9bb79 249class StatsVirtualHandler(BaseHandler):
66862195
MT
250 def get(self):
251 data = {
252 "hypervisors" : self.fireinfo.get_hypervisor_map(when=self.when),
253 "virtual" : self.fireinfo.get_virtual_ratio(when=self.when),
254 }
255
256 return self.render("fireinfo/stats-virtual.html", **data)
257
258
96c9bb79 259class StatsGeoHandler(BaseHandler):
66862195
MT
260 def get(self):
261 return self.render("fireinfo/stats-geo.html",
262 geo_locations = self.fireinfo.get_geo_location_map(when=self.when))
263
264
96c9bb79 265class StatsLanguagesHandler(BaseHandler):
66862195
MT
266 def get(self):
267 return self.render("fireinfo/stats-languages.html",
268 languages = self.fireinfo.get_language_map(when=self.when))
269
270
96c9bb79 271class StatsNetworkingHandler(BaseHandler):
66862195
MT
272 def get(self):
273 network=self.fireinfo.get_network_zones_map(when=self.when)
274
275 return self.render("fireinfo/stats-network.html", network=network)
276
277
96c9bb79 278class DeviceVendorHandler(BaseHandler):
66862195
MT
279 def get(self, subsystem, vendor_id):
280 devices = self.fireinfo.get_devices_by_vendor(subsystem, vendor_id,
281 when=self.when)
282
283 vendor_name = self.fireinfo.get_vendor_string(subsystem, vendor_id)
284
285 self.render("fireinfo/vendor-detail.html", vendor_name=vendor_name,
286 devices=devices)
287
288
96c9bb79 289class DeviceVendorCompatHandler(BaseHandler):
66862195
MT
290 def get(self, subsystem, vendor_id):
291 self.redirect("/device/%s/%s" % (subsystem, vendor_id))
292
293
96c9bb79 294class DeviceModelHandler(BaseHandler):
66862195
MT
295 def get(self, subsystem, vendor, model):
296 percentage = self.fireinfo.get_device_percentage(subsystem, vendor,
297 model, when=self.when)
298 percentage *= 100
299
300 profiles = self.fireinfo.get_device_in_profile(subsystem, vendor,
301 model, when=self.when)
302
303 vendor_name = self.fireinfo.get_vendor_string(subsystem, vendor)
304 model_name = self.fireinfo.get_model_string(subsystem, vendor, model)
305
306 self.render("fireinfo/model-detail.html", profiles=profiles,
307 vendor_id=vendor, vendor_name=vendor_name,
308 model_id=model, model_name=model_name,
309 percentage=percentage, subsystem=subsystem)
310
311
96c9bb79 312class DeviceModelCompatHandler(BaseHandler):
66862195
MT
313 def get(self, subsystem, vendor_id, model_id):
314 self.redirect("/device/%s/%s/%s" % (subsystem, vendor_id, model_id))
315
316
96c9bb79 317class AdminFireinfoHandler(BaseHandler):
66862195
MT
318 def get(self):
319 profiles_with_data, profiles_all = self.fireinfo.get_active_profiles(when=self.when)
320
321 data = {
322 "archive_size" : self.fireinfo.get_archive_size(when=self.when),
de14b297 323 "total_updates" : self.fireinfo.get_total_updates_count(when=self.when),
66862195
MT
324 "profiles_with_data" : profiles_with_data,
325 "profiles_all" : profiles_all,
326 }
327
328 self.render("fireinfo/stats-admin.html", **data)
96c9bb79
MT
329
330
331class DeviceTableModule(ui_modules.UIModule):
332 def render(self, devices):
333 return self.render_string("fireinfo/modules/table-devices.html",
334 devices=devices)
335
336
337class DeviceAndGroupsTableModule(ui_modules.UIModule):
338 def render(self, devices):
339 _ = self.locale.translate
340
341 groups = {}
342
343 for device in devices:
344 if device.cls not in groups:
345 groups[device.cls] = []
346
347 groups[device.cls].append(device)
348
349 # Sort all devices
350 for key in list(groups.keys()):
351 groups[key].sort()
352
353 # Order the groups by their name
354 groups = list(groups.items())
355 groups.sort()
356
357 return self.render_string("fireinfo/modules/table-devices-and-groups.html",
358 groups=groups)
359
360
361class GeoTableModule(ui_modules.UIModule):
362 def render(self, items):
363 countries = []
364 other_countries = []
365 for code, value in items:
366 # Skip the satellite providers in this ranking
367 if code in (None, "A1", "A2"):
368 continue
369
370 name = self.backend.geoip.get_country_name(code)
371
372 # Don't add countries with a small share on the list
373 if value < 0.01:
374 other_countries.append(name)
375 continue
376
377 country = database.Row({
378 "code" : code,
379 "name" : name,
380 "value" : value,
381 })
382 countries.append(country)
383
384 return self.render_string("fireinfo/modules/table-geo.html",
385 countries=countries, other_countries=other_countries)