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.

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