You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

441 lines
12 KiB

9 years ago
9 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. from collections import Counter
  2. from datetime import timedelta as td
  3. from itertools import tee
  4. from django.conf import settings
  5. from django.contrib.auth.decorators import login_required
  6. from django.core.urlresolvers import reverse
  7. from django.http import HttpResponseBadRequest, HttpResponseForbidden
  8. from django.shortcuts import get_object_or_404, redirect, render
  9. from django.utils import timezone
  10. from django.utils.crypto import get_random_string
  11. from django.utils.six.moves.urllib.parse import urlencode
  12. from hc.accounts.models import Profile
  13. from hc.api.decorators import uuid_or_400
  14. from hc.api.models import Channel, Check, Ping
  15. from hc.front.forms import AddChannelForm, NameTagsForm, TimeoutForm
  16. # from itertools recipes:
  17. def pairwise(iterable):
  18. "s -> (s0,s1), (s1,s2), (s2, s3), ..."
  19. a, b = tee(iterable)
  20. next(b, None)
  21. return zip(a, b)
  22. @login_required
  23. def my_checks(request):
  24. checks = Check.objects.filter(user=request.user).order_by("created")
  25. counter = Counter()
  26. down_tags, grace_tags = set(), set()
  27. for check in checks.iterator():
  28. status = check.get_status()
  29. for tag in check.tags_list():
  30. if tag == "":
  31. continue
  32. counter[tag] += 1
  33. if status == "down":
  34. down_tags.add(tag)
  35. elif status == "grace":
  36. grace_tags.add(tag)
  37. ctx = {
  38. "page": "checks",
  39. "checks": checks,
  40. "now": timezone.now(),
  41. "tags": counter.most_common(),
  42. "down_tags": down_tags,
  43. "grace_tags": grace_tags
  44. }
  45. return render(request, "front/my_checks.html", ctx)
  46. def _welcome_check(request):
  47. check = None
  48. if "welcome_code" in request.session:
  49. code = request.session["welcome_code"]
  50. check = Check.objects.filter(code=code).first()
  51. if check is None:
  52. check = Check()
  53. check.save()
  54. request.session["welcome_code"] = str(check.code)
  55. return check
  56. def index(request):
  57. if request.user.is_authenticated():
  58. return redirect("hc-checks")
  59. check = _welcome_check(request)
  60. ctx = {
  61. "page": "welcome",
  62. "check": check,
  63. "ping_url": check.url()
  64. }
  65. return render(request, "front/welcome.html", ctx)
  66. def docs(request):
  67. check = _welcome_check(request)
  68. ctx = {
  69. "page": "docs",
  70. "ping_endpoint": settings.PING_ENDPOINT,
  71. "check": check,
  72. "ping_url": check.url()
  73. }
  74. return render(request, "front/docs.html", ctx)
  75. def about(request):
  76. return render(request, "front/about.html", {"page": "about"})
  77. @login_required
  78. def add_check(request):
  79. assert request.method == "POST"
  80. check = Check(user=request.user)
  81. check.save()
  82. check.assign_all_channels()
  83. return redirect("hc-checks")
  84. @login_required
  85. @uuid_or_400
  86. def update_name(request, code):
  87. assert request.method == "POST"
  88. check = get_object_or_404(Check, code=code)
  89. if check.user_id != request.user.id:
  90. return HttpResponseForbidden()
  91. form = NameTagsForm(request.POST)
  92. if form.is_valid():
  93. check.name = form.cleaned_data["name"]
  94. check.tags = form.cleaned_data["tags"]
  95. check.save()
  96. return redirect("hc-checks")
  97. @login_required
  98. @uuid_or_400
  99. def update_timeout(request, code):
  100. assert request.method == "POST"
  101. check = get_object_or_404(Check, code=code)
  102. if check.user != request.user:
  103. return HttpResponseForbidden()
  104. form = TimeoutForm(request.POST)
  105. if form.is_valid():
  106. check.timeout = td(seconds=form.cleaned_data["timeout"])
  107. check.grace = td(seconds=form.cleaned_data["grace"])
  108. check.save()
  109. return redirect("hc-checks")
  110. @login_required
  111. @uuid_or_400
  112. def email_preview(request, code):
  113. """ A debug view to see how email will look.
  114. Will keep it around until I'm happy with email stying.
  115. """
  116. check = Check.objects.get(code=code)
  117. if check.user != request.user:
  118. return HttpResponseForbidden()
  119. ctx = {
  120. "check": check,
  121. "checks": check.user.check_set.all(),
  122. "now": timezone.now()
  123. }
  124. return render(request, "emails/alert/body.html", ctx)
  125. @login_required
  126. @uuid_or_400
  127. def remove_check(request, code):
  128. assert request.method == "POST"
  129. check = get_object_or_404(Check, code=code)
  130. if check.user != request.user:
  131. return HttpResponseForbidden()
  132. check.delete()
  133. return redirect("hc-checks")
  134. @login_required
  135. @uuid_or_400
  136. def log(request, code):
  137. check = get_object_or_404(Check, code=code)
  138. if check.user != request.user:
  139. return HttpResponseForbidden()
  140. profile = Profile.objects.for_user(request.user)
  141. limit = profile.ping_log_limit
  142. pings = Ping.objects.filter(owner=check).order_by("-id")[:limit]
  143. pings = list(pings.iterator())
  144. # oldest-to-newest order will be more convenient for adding
  145. # "not received" placeholders:
  146. pings.reverse()
  147. # Add a dummy ping object at the end. We iterate over *pairs* of pings
  148. # and don't want to handle a special case of a check with a single ping.
  149. pings.append(Ping(created=timezone.now()))
  150. # Now go through pings, calculate time gaps, and decorate
  151. # the pings list for convenient use in template
  152. wrapped = []
  153. early = False
  154. for older, newer in pairwise(pings):
  155. wrapped.append({"ping": older, "early": early})
  156. # Fill in "missed ping" placeholders:
  157. expected_date = older.created + check.timeout
  158. n_blanks = 0
  159. while expected_date + check.grace < newer.created and n_blanks < 10:
  160. wrapped.append({"placeholder_date": expected_date})
  161. expected_date = expected_date + check.timeout
  162. n_blanks += 1
  163. # Prepare early flag for next ping to come
  164. early = older.created + check.timeout > newer.created + check.grace
  165. reached_limit = len(pings) > limit
  166. wrapped.reverse()
  167. ctx = {
  168. "check": check,
  169. "pings": wrapped,
  170. "num_pings": len(pings),
  171. "limit": limit,
  172. "show_limit_notice": reached_limit and settings.USE_PAYMENTS
  173. }
  174. return render(request, "front/log.html", ctx)
  175. @login_required
  176. def channels(request):
  177. if request.method == "POST":
  178. code = request.POST["channel"]
  179. try:
  180. channel = Channel.objects.get(code=code)
  181. except Channel.DoesNotExist:
  182. return HttpResponseBadRequest()
  183. if channel.user_id != request.user.id:
  184. return HttpResponseForbidden()
  185. new_checks = []
  186. for key in request.POST:
  187. if key.startswith("check-"):
  188. code = key[6:]
  189. try:
  190. check = Check.objects.get(code=code)
  191. except Check.DoesNotExist:
  192. return HttpResponseBadRequest()
  193. if check.user_id != request.user.id:
  194. return HttpResponseForbidden()
  195. new_checks.append(check)
  196. channel.checks = new_checks
  197. return redirect("hc-channels")
  198. channels = Channel.objects.filter(user=request.user).order_by("created")
  199. num_checks = Check.objects.filter(user=request.user).count()
  200. ctx = {
  201. "page": "channels",
  202. "channels": channels,
  203. "num_checks": num_checks,
  204. "enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
  205. }
  206. return render(request, "front/channels.html", ctx)
  207. def do_add_channel(request, data):
  208. form = AddChannelForm(data)
  209. if form.is_valid():
  210. channel = form.save(commit=False)
  211. channel.user = request.user
  212. channel.save()
  213. checks = Check.objects.filter(user=request.user)
  214. channel.checks.add(*checks)
  215. if channel.kind == "email":
  216. channel.send_verify_link()
  217. return redirect("hc-channels")
  218. else:
  219. return HttpResponseBadRequest()
  220. @login_required
  221. def add_channel(request):
  222. assert request.method == "POST"
  223. return do_add_channel(request, request.POST)
  224. @login_required
  225. @uuid_or_400
  226. def channel_checks(request, code):
  227. channel = get_object_or_404(Channel, code=code)
  228. if channel.user_id != request.user.id:
  229. return HttpResponseForbidden()
  230. assigned = set(channel.checks.values_list('code', flat=True).distinct())
  231. checks = Check.objects.filter(user=request.user).order_by("created")
  232. ctx = {
  233. "checks": checks,
  234. "assigned": assigned,
  235. "channel": channel
  236. }
  237. return render(request, "front/channel_checks.html", ctx)
  238. @uuid_or_400
  239. def verify_email(request, code, token):
  240. channel = get_object_or_404(Channel, code=code)
  241. if channel.make_token() == token:
  242. channel.email_verified = True
  243. channel.save()
  244. return render(request, "front/verify_email_success.html")
  245. return render(request, "bad_link.html")
  246. @login_required
  247. @uuid_or_400
  248. def remove_channel(request, code):
  249. assert request.method == "POST"
  250. # user may refresh the page during POST and cause two deletion attempts
  251. channel = Channel.objects.filter(code=code).first()
  252. if channel:
  253. if channel.user != request.user:
  254. return HttpResponseForbidden()
  255. channel.delete()
  256. return redirect("hc-channels")
  257. @login_required
  258. def add_email(request):
  259. ctx = {"page": "channels"}
  260. return render(request, "integrations/add_email.html", ctx)
  261. @login_required
  262. def add_webhook(request):
  263. ctx = {"page": "channels"}
  264. return render(request, "integrations/add_webhook.html", ctx)
  265. @login_required
  266. def add_pd(request):
  267. ctx = {"page": "channels"}
  268. return render(request, "integrations/add_pd.html", ctx)
  269. @login_required
  270. def add_slack(request):
  271. ctx = {"page": "channels"}
  272. return render(request, "integrations/add_slack.html", ctx)
  273. @login_required
  274. def add_hipchat(request):
  275. ctx = {"page": "channels"}
  276. return render(request, "integrations/add_hipchat.html", ctx)
  277. @login_required
  278. def add_pushover(request):
  279. if settings.PUSHOVER_API_TOKEN is None or settings.PUSHOVER_SUBSCRIPTION_URL is None:
  280. return HttpResponseForbidden()
  281. if request.method == "POST":
  282. # Initiate the subscription
  283. nonce = get_random_string()
  284. request.session["po_nonce"] = nonce
  285. failure_url = settings.SITE_ROOT + reverse("hc-channels")
  286. success_url = settings.SITE_ROOT + reverse("hc-add-pushover") + "?" + urlencode({
  287. "nonce": nonce,
  288. "prio": request.POST.get("po_priority", "0"),
  289. })
  290. subscription_url = settings.PUSHOVER_SUBSCRIPTION_URL + "?" + urlencode({
  291. "success": success_url,
  292. "failure": failure_url,
  293. })
  294. return redirect(subscription_url)
  295. # Handle successful subscriptions
  296. if "pushover_user_key" in request.GET:
  297. if "nonce" not in request.GET or "prio" not in request.GET:
  298. return HttpResponseBadRequest()
  299. # Validate nonce
  300. if request.GET["nonce"] != request.session.get("po_nonce"):
  301. return HttpResponseForbidden()
  302. # Validate priority
  303. if request.GET["prio"] not in ("-2", "-1", "0", "1", "2"):
  304. return HttpResponseBadRequest()
  305. # All looks well--
  306. del request.session["po_nonce"]
  307. if request.GET.get("pushover_unsubscribed") == "1":
  308. # Unsubscription: delete all Pushover channels for this user
  309. Channel.objects.filter(user=request.user, kind="po").delete()
  310. return redirect("hc-channels")
  311. else:
  312. # Subscription
  313. user_key = request.GET["pushover_user_key"]
  314. priority = int(request.GET["prio"])
  315. return do_add_channel(request, {
  316. "kind": "po",
  317. "value": "%s|%d" % (user_key, priority),
  318. })
  319. # Show Integration Settings form
  320. ctx = {
  321. "page": "channels",
  322. "po_retry_delay": td(seconds=settings.PUSHOVER_EMERGENCY_RETRY_DELAY),
  323. "po_expiration": td(seconds=settings.PUSHOVER_EMERGENCY_EXPIRATION),
  324. }
  325. return render(request, "integrations/add_pushover.html", ctx)
  326. def privacy(request):
  327. return render(request, "front/privacy.html", {})