2018-03-18 10:22:58 +00:00
|
|
|
|
import hashlib
|
|
|
|
|
import json
|
2017-12-23 06:16:39 +00:00
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import shutil
|
2018-03-18 10:22:58 +00:00
|
|
|
|
import smtplib
|
2018-05-05 14:42:03 +00:00
|
|
|
|
import time
|
2018-01-04 03:42:20 +00:00
|
|
|
|
from datetime import datetime
|
2016-11-20 11:18:25 +00:00
|
|
|
|
|
2018-03-18 10:22:58 +00:00
|
|
|
|
import pytz
|
|
|
|
|
import requests
|
2017-12-23 06:16:39 +00:00
|
|
|
|
from django.conf import settings
|
2018-03-18 10:22:58 +00:00
|
|
|
|
from django.utils import timezone
|
|
|
|
|
from requests.exceptions import RequestException
|
2016-09-25 06:07:45 +00:00
|
|
|
|
|
2016-11-19 05:47:59 +00:00
|
|
|
|
from account.decorators import super_admin_required
|
2018-01-04 03:42:20 +00:00
|
|
|
|
from account.models import User
|
|
|
|
|
from contest.models import Contest
|
2017-05-10 11:40:26 +00:00
|
|
|
|
from judge.dispatcher import process_pending_task
|
2017-10-01 19:54:34 +00:00
|
|
|
|
from options.options import SysOptions
|
2018-03-18 10:22:58 +00:00
|
|
|
|
from problem.models import Problem
|
|
|
|
|
from submission.models import Submission
|
2016-11-20 11:18:25 +00:00
|
|
|
|
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
2018-01-04 03:42:20 +00:00
|
|
|
|
from utils.shortcuts import send_email, get_env
|
2017-12-24 07:34:22 +00:00
|
|
|
|
from utils.xss_filter import XSSHtml
|
2017-10-01 19:54:34 +00:00
|
|
|
|
from .models import JudgeServer
|
2017-01-23 08:48:04 +00:00
|
|
|
|
from .serializers import (CreateEditWebsiteConfigSerializer,
|
2016-11-19 05:47:59 +00:00
|
|
|
|
CreateSMTPConfigSerializer, EditSMTPConfigSerializer,
|
2017-01-23 08:48:04 +00:00
|
|
|
|
JudgeServerHeartbeatSerializer,
|
2017-12-24 04:10:02 +00:00
|
|
|
|
JudgeServerSerializer, TestSMTPConfigSerializer, EditJudgeServerSerializer)
|
2016-11-19 05:47:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SMTPAPI(APIView):
|
|
|
|
|
@super_admin_required
|
|
|
|
|
def get(self, request):
|
2017-10-01 19:54:34 +00:00
|
|
|
|
smtp = SysOptions.smtp_config
|
2016-11-19 05:47:59 +00:00
|
|
|
|
if not smtp:
|
|
|
|
|
return self.success(None)
|
2017-10-01 19:54:34 +00:00
|
|
|
|
smtp.pop("password")
|
|
|
|
|
return self.success(smtp)
|
2016-11-19 05:47:59 +00:00
|
|
|
|
|
2017-01-23 08:01:56 +00:00
|
|
|
|
@super_admin_required
|
2018-01-04 11:27:41 +00:00
|
|
|
|
@validate_serializer(CreateSMTPConfigSerializer)
|
2016-11-19 05:47:59 +00:00
|
|
|
|
def post(self, request):
|
2017-10-01 19:54:34 +00:00
|
|
|
|
SysOptions.smtp_config = request.data
|
|
|
|
|
return self.success()
|
2016-11-19 05:47:59 +00:00
|
|
|
|
|
2017-01-23 08:01:56 +00:00
|
|
|
|
@super_admin_required
|
2018-01-04 11:27:41 +00:00
|
|
|
|
@validate_serializer(EditSMTPConfigSerializer)
|
2016-11-19 05:47:59 +00:00
|
|
|
|
def put(self, request):
|
2017-10-01 19:54:34 +00:00
|
|
|
|
smtp = SysOptions.smtp_config
|
2016-11-19 05:47:59 +00:00
|
|
|
|
data = request.data
|
2017-10-01 19:54:34 +00:00
|
|
|
|
for item in ["server", "port", "email", "tls"]:
|
|
|
|
|
smtp[item] = data[item]
|
|
|
|
|
if "password" in data:
|
|
|
|
|
smtp["password"] = data["password"]
|
|
|
|
|
SysOptions.smtp_config = smtp
|
|
|
|
|
return self.success()
|
2016-11-19 05:47:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SMTPTestAPI(APIView):
|
|
|
|
|
@super_admin_required
|
|
|
|
|
@validate_serializer(TestSMTPConfigSerializer)
|
|
|
|
|
def post(self, request):
|
2017-12-24 03:34:40 +00:00
|
|
|
|
if not SysOptions.smtp_config:
|
|
|
|
|
return self.error("Please setup SMTP config at first")
|
|
|
|
|
try:
|
|
|
|
|
send_email(smtp_config=SysOptions.smtp_config,
|
|
|
|
|
from_name=SysOptions.website_name_shortcut,
|
|
|
|
|
to_name=request.user.username,
|
|
|
|
|
to_email=request.data["email"],
|
|
|
|
|
subject="You have successfully configured SMTP",
|
|
|
|
|
content="You have successfully configured SMTP")
|
2018-04-23 23:06:33 +00:00
|
|
|
|
except smtplib.SMTPResponseException as e:
|
2017-12-24 03:34:40 +00:00
|
|
|
|
# guess error message encoding
|
2018-03-18 10:22:58 +00:00
|
|
|
|
msg = b"Failed to send email"
|
2017-12-24 03:34:40 +00:00
|
|
|
|
try:
|
2018-03-18 10:22:58 +00:00
|
|
|
|
msg = e.smtp_error
|
2017-12-24 03:34:40 +00:00
|
|
|
|
# qq mail
|
|
|
|
|
msg = msg.decode("gbk")
|
|
|
|
|
except Exception:
|
|
|
|
|
msg = msg.decode("utf-8", "ignore")
|
|
|
|
|
return self.error(msg)
|
2018-03-18 10:22:58 +00:00
|
|
|
|
except Exception as e:
|
|
|
|
|
msg = str(e)
|
|
|
|
|
return self.error(msg)
|
2017-12-24 03:34:40 +00:00
|
|
|
|
return self.success()
|
2016-11-19 05:47:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WebsiteConfigAPI(APIView):
|
|
|
|
|
def get(self, request):
|
2017-10-01 19:54:34 +00:00
|
|
|
|
ret = {key: getattr(SysOptions, key) for key in
|
|
|
|
|
["website_base_url", "website_name", "website_name_shortcut",
|
|
|
|
|
"website_footer", "allow_register", "submission_list_show_all"]}
|
|
|
|
|
return self.success(ret)
|
2016-11-19 05:47:59 +00:00
|
|
|
|
|
|
|
|
|
@super_admin_required
|
2018-01-04 11:27:41 +00:00
|
|
|
|
@validate_serializer(CreateEditWebsiteConfigSerializer)
|
2016-11-19 05:47:59 +00:00
|
|
|
|
def post(self, request):
|
2017-10-01 19:54:34 +00:00
|
|
|
|
for k, v in request.data.items():
|
2017-12-24 07:34:22 +00:00
|
|
|
|
if k == "website_footer":
|
|
|
|
|
with XSSHtml() as parser:
|
|
|
|
|
v = parser.clean(v)
|
2017-10-01 19:54:34 +00:00
|
|
|
|
setattr(SysOptions, k, v)
|
|
|
|
|
return self.success()
|
2016-11-20 11:18:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JudgeServerAPI(APIView):
|
|
|
|
|
@super_admin_required
|
|
|
|
|
def get(self, request):
|
|
|
|
|
servers = JudgeServer.objects.all().order_by("-last_heartbeat")
|
2017-10-01 19:54:34 +00:00
|
|
|
|
return self.success({"token": SysOptions.judge_server_token,
|
2016-11-20 11:18:25 +00:00
|
|
|
|
"servers": JudgeServerSerializer(servers, many=True).data})
|
|
|
|
|
|
|
|
|
|
@super_admin_required
|
|
|
|
|
def delete(self, request):
|
2017-01-24 08:48:39 +00:00
|
|
|
|
hostname = request.GET.get("hostname")
|
|
|
|
|
if hostname:
|
|
|
|
|
JudgeServer.objects.filter(hostname=hostname).delete()
|
2017-01-24 05:22:40 +00:00
|
|
|
|
return self.success()
|
2016-11-20 11:18:25 +00:00
|
|
|
|
|
2017-12-24 04:10:02 +00:00
|
|
|
|
@validate_serializer(EditJudgeServerSerializer)
|
|
|
|
|
@super_admin_required
|
|
|
|
|
def put(self, request):
|
2018-08-21 07:21:31 +00:00
|
|
|
|
is_disabled = request.data.get("is_disabled", False)
|
|
|
|
|
JudgeServer.objects.filter(id=request.data["id"]).update(is_disabled=is_disabled)
|
|
|
|
|
if not is_disabled:
|
|
|
|
|
process_pending_task()
|
2017-12-24 04:10:02 +00:00
|
|
|
|
return self.success()
|
|
|
|
|
|
2016-11-20 11:18:25 +00:00
|
|
|
|
|
|
|
|
|
class JudgeServerHeartbeatAPI(CSRFExemptAPIView):
|
|
|
|
|
@validate_serializer(JudgeServerHeartbeatSerializer)
|
|
|
|
|
def post(self, request):
|
|
|
|
|
data = request.data
|
2017-01-26 05:55:31 +00:00
|
|
|
|
client_token = request.META.get("HTTP_X_JUDGE_SERVER_TOKEN")
|
2017-10-01 19:54:34 +00:00
|
|
|
|
if hashlib.sha256(SysOptions.judge_server_token.encode("utf-8")).hexdigest() != client_token:
|
2016-11-20 11:18:25 +00:00
|
|
|
|
return self.error("Invalid token")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
server = JudgeServer.objects.get(hostname=data["hostname"])
|
|
|
|
|
server.judger_version = data["judger_version"]
|
|
|
|
|
server.cpu_core = data["cpu_core"]
|
|
|
|
|
server.memory_usage = data["memory"]
|
|
|
|
|
server.cpu_usage = data["cpu"]
|
2017-12-24 04:08:56 +00:00
|
|
|
|
server.service_url = data["service_url"]
|
2018-01-06 15:53:41 +00:00
|
|
|
|
server.ip = request.ip
|
2016-11-20 11:18:25 +00:00
|
|
|
|
server.last_heartbeat = timezone.now()
|
2019-04-03 04:07:59 +00:00
|
|
|
|
server.save(update_fields=["judger_version", "cpu_core", "memory_usage", "service_url", "ip", "last_heartbeat"])
|
2016-11-20 11:18:25 +00:00
|
|
|
|
except JudgeServer.DoesNotExist:
|
|
|
|
|
JudgeServer.objects.create(hostname=data["hostname"],
|
|
|
|
|
judger_version=data["judger_version"],
|
|
|
|
|
cpu_core=data["cpu_core"],
|
|
|
|
|
memory_usage=data["memory"],
|
|
|
|
|
cpu_usage=data["cpu"],
|
2017-01-24 08:22:22 +00:00
|
|
|
|
ip=request.META["REMOTE_ADDR"],
|
2017-12-24 04:08:56 +00:00
|
|
|
|
service_url=data["service_url"],
|
2016-11-20 11:18:25 +00:00
|
|
|
|
last_heartbeat=timezone.now(),
|
|
|
|
|
)
|
2019-04-03 04:07:59 +00:00
|
|
|
|
# 新server上线 处理队列中的,防止没有新的提交而导致一直waiting
|
|
|
|
|
process_pending_task()
|
2017-05-10 09:20:52 +00:00
|
|
|
|
|
2016-11-20 11:18:25 +00:00
|
|
|
|
return self.success()
|
2017-01-24 05:22:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LanguagesAPI(APIView):
|
|
|
|
|
def get(self, request):
|
2019-01-05 06:00:59 +00:00
|
|
|
|
return self.success({"languages": SysOptions.languages, "spj_languages": SysOptions.spj_languages})
|
2017-12-23 06:16:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestCasePruneAPI(APIView):
|
|
|
|
|
@super_admin_required
|
|
|
|
|
def get(self, request):
|
|
|
|
|
"""
|
2018-01-02 12:05:33 +00:00
|
|
|
|
return orphan test_case list
|
2017-12-23 06:16:39 +00:00
|
|
|
|
"""
|
|
|
|
|
ret_data = []
|
|
|
|
|
dir_to_be_removed = self.get_orphan_ids()
|
|
|
|
|
|
|
|
|
|
# return an iterator
|
|
|
|
|
for d in os.scandir(settings.TEST_CASE_DIR):
|
|
|
|
|
if d.name in dir_to_be_removed:
|
2017-12-27 12:12:38 +00:00
|
|
|
|
ret_data.append({"id": d.name, "create_time": d.stat().st_mtime})
|
2017-12-23 06:16:39 +00:00
|
|
|
|
return self.success(ret_data)
|
|
|
|
|
|
|
|
|
|
@super_admin_required
|
|
|
|
|
def delete(self, request):
|
|
|
|
|
test_case_id = request.GET.get("id")
|
|
|
|
|
if test_case_id:
|
|
|
|
|
self.delete_one(test_case_id)
|
|
|
|
|
return self.success()
|
|
|
|
|
for id in self.get_orphan_ids():
|
|
|
|
|
self.delete_one(id)
|
|
|
|
|
return self.success()
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_orphan_ids():
|
|
|
|
|
db_ids = Problem.objects.all().values_list("test_case_id", flat=True)
|
|
|
|
|
disk_ids = os.listdir(settings.TEST_CASE_DIR)
|
|
|
|
|
test_case_re = re.compile(r"^[a-zA-Z0-9]{32}$")
|
|
|
|
|
disk_ids = filter(lambda f: test_case_re.match(f), disk_ids)
|
|
|
|
|
return list(set(disk_ids) - set(db_ids))
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def delete_one(id):
|
|
|
|
|
test_case_dir = os.path.join(settings.TEST_CASE_DIR, id)
|
|
|
|
|
if os.path.isdir(test_case_dir):
|
|
|
|
|
shutil.rmtree(test_case_dir, ignore_errors=True)
|
2018-01-02 12:05:33 +00:00
|
|
|
|
|
|
|
|
|
|
2018-01-04 03:42:20 +00:00
|
|
|
|
class ReleaseNotesAPI(APIView):
|
2018-01-02 12:05:33 +00:00
|
|
|
|
def get(self, request):
|
|
|
|
|
try:
|
2018-05-05 14:42:03 +00:00
|
|
|
|
resp = requests.get("https://raw.githubusercontent.com/QingdaoU/OnlineJudge/master/docs/data.json?_=" + str(time.time()),
|
2018-01-02 12:05:33 +00:00
|
|
|
|
timeout=3)
|
2018-01-04 03:42:20 +00:00
|
|
|
|
releases = resp.json()
|
2018-01-02 12:05:33 +00:00
|
|
|
|
except (RequestException, ValueError):
|
|
|
|
|
return self.success()
|
|
|
|
|
with open("docs/data.json", "r") as f:
|
2018-01-04 03:42:20 +00:00
|
|
|
|
local_version = json.load(f)["update"][0]["version"]
|
|
|
|
|
releases["local_version"] = local_version
|
|
|
|
|
return self.success(releases)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DashboardInfoAPI(APIView):
|
|
|
|
|
def get(self, request):
|
|
|
|
|
today = datetime.today()
|
|
|
|
|
today_submission_count = Submission.objects.filter(
|
|
|
|
|
create_time__gte=datetime(today.year, today.month, today.day, 0, 0, tzinfo=pytz.UTC)).count()
|
|
|
|
|
recent_contest_count = Contest.objects.exclude(end_time__lt=timezone.now()).count()
|
|
|
|
|
judge_server_count = len(list(filter(lambda x: x.status == "normal", JudgeServer.objects.all())))
|
|
|
|
|
return self.success({
|
|
|
|
|
"user_count": User.objects.count(),
|
|
|
|
|
"recent_contest_count": recent_contest_count,
|
|
|
|
|
"today_submission_count": today_submission_count,
|
|
|
|
|
"judge_server_count": judge_server_count,
|
|
|
|
|
"env": {
|
|
|
|
|
"FORCE_HTTPS": get_env("FORCE_HTTPS", default=False),
|
|
|
|
|
"STATIC_CDN_HOST": get_env("STATIC_CDN_HOST", default="")
|
|
|
|
|
}
|
|
|
|
|
})
|