Добавление режима для учителя
This commit is contained in:
parent
05c04c49ed
commit
23ea2ed09f
|
|
@ -0,0 +1,287 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для создания группы, репозитория и копирования кода в GitLab
|
||||||
|
# с назначением прав пользователю из Keycloak
|
||||||
|
|
||||||
|
set -e # Прерывать выполнение при ошибках
|
||||||
|
|
||||||
|
# === КОНФИГУРАЦИЯ ===
|
||||||
|
|
||||||
|
# GitLab Private Token с правами администратора
|
||||||
|
PRIVATE_TOKEN=$1
|
||||||
|
# Пользователь из Keycloak (должен существовать в GitLab)
|
||||||
|
KEYCLOAK_USERNAME=$2
|
||||||
|
USER_ID=${KEYCLOAK_USERNAME}
|
||||||
|
|
||||||
|
GITLAB_URL="https://gitlab.oa2.ru" # URL вашего GitLab
|
||||||
|
|
||||||
|
# Данные для новой группы и репозитория
|
||||||
|
GROUP_NAME="group-${USER_ID}"
|
||||||
|
GROUP_PATH="group-${USER_ID}" # Может отличаться от имени
|
||||||
|
GROUP_DESCRIPTION="Группа для пользователя ${USER_ID}"
|
||||||
|
|
||||||
|
# Данные для нового репозитория
|
||||||
|
PROJECT_NAME="task1"
|
||||||
|
PROJECT_DESCRIPTION="Репозиторий с кодом task1"
|
||||||
|
|
||||||
|
# Внешний репозиторий для копирования
|
||||||
|
SOURCE_REPO_URL="https://git.oa2.ru/dzyk/task1" # Замените на реальный URL
|
||||||
|
|
||||||
|
# Уровень доступа для пользователя (owner = 50)
|
||||||
|
ACCESS_LEVEL=50
|
||||||
|
|
||||||
|
# Временная директория для клонирования
|
||||||
|
TEMP_DIR="/tmp/gitlab-migration-$(date +%s)"
|
||||||
|
|
||||||
|
# === ФУНКЦИИ ===
|
||||||
|
|
||||||
|
# Функция для логирования
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Функция для выполнения HTTP запросов к GitLab API
|
||||||
|
gitlab_api() {
|
||||||
|
local method=$1
|
||||||
|
local endpoint=$2
|
||||||
|
local data=$3
|
||||||
|
|
||||||
|
local curl_cmd="curl -s -X $method \
|
||||||
|
'$GITLAB_URL/api/v4$endpoint' \
|
||||||
|
-H 'Private-Token: $PRIVATE_TOKEN' \
|
||||||
|
-H 'Content-Type: application/json'"
|
||||||
|
|
||||||
|
if [ ! -z "$data" ]; then
|
||||||
|
curl_cmd="$curl_cmd -d '$data'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval $curl_cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
# Функция для проверки ошибок API
|
||||||
|
check_api_error() {
|
||||||
|
local response=$1
|
||||||
|
local operation=$2
|
||||||
|
|
||||||
|
if echo "$response" | jq -e '.error' > /dev/null 2>&1; then
|
||||||
|
error_msg=$(echo "$response" | jq -r '.error')
|
||||||
|
log "Ошибка при $operation: $error_msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$response" | jq -e '.message' > /dev/null 2>&1; then
|
||||||
|
error_msg=$(echo "$response" | jq -r '.message')
|
||||||
|
if [ "$error_msg" != "null" ] && [ ! -z "$error_msg" ]; then
|
||||||
|
log "Ошибка при $operation: $error_msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# === ОСНОВНОЙ СКРИПТ ===
|
||||||
|
|
||||||
|
log "Начало выполнения скрипта"
|
||||||
|
|
||||||
|
# 1. Проверка зависимостей
|
||||||
|
log "Проверка зависимостей..."
|
||||||
|
for cmd in curl jq git; do
|
||||||
|
if ! command -v $cmd &> /dev/null; then
|
||||||
|
log "Ошибка: $cmd не установлен"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
log "Все зависимости установлены"
|
||||||
|
|
||||||
|
# 2. Поиск пользователя из Keycloak
|
||||||
|
log "Поиск пользователя '$KEYCLOAK_USERNAME' в GitLab..."
|
||||||
|
user_search=$(gitlab_api "GET" "/users?username=$KEYCLOAK_USERNAME")
|
||||||
|
if ! check_api_error "$user_search" "поиске пользователя"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
user_id=$(echo "$user_search" | jq -r '.[0].id')
|
||||||
|
user_username=$(echo "$user_search" | jq -r '.[0].username')
|
||||||
|
|
||||||
|
if [ "$user_id" = "null" ] || [ -z "$user_id" ]; then
|
||||||
|
log "Ошибка: Пользователь '$KEYCLOAK_USERNAME' не найден в GitLab"
|
||||||
|
log "Убедитесь, что пользователь вошел через Keycloak хотя бы один раз"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Найден пользователь: $user_username (ID: $user_id)"
|
||||||
|
|
||||||
|
# 4. Создание группы
|
||||||
|
log "Проверка существования группы '$GROUP_NAME'..."
|
||||||
|
group_check=$(gitlab_api "GET" "/groups/$GROUP_PATH")
|
||||||
|
if echo "$group_check" | jq -e '.id' > /dev/null 2>&1; then
|
||||||
|
group_id=$(echo "$group_check" | jq -r '.id')
|
||||||
|
log "Группа '$GROUP_NAME' уже существует (ID: $group_id)"
|
||||||
|
else
|
||||||
|
log "Создание группы '$GROUP_NAME'..."
|
||||||
|
group_data=$(jq -n \
|
||||||
|
--arg name "$GROUP_NAME" \
|
||||||
|
--arg path "$GROUP_PATH" \
|
||||||
|
--arg desc "$GROUP_DESCRIPTION" \
|
||||||
|
--arg visibility "private" \
|
||||||
|
'{
|
||||||
|
name: $name,
|
||||||
|
path: $path,
|
||||||
|
description: $desc,
|
||||||
|
visibility: $visibility,
|
||||||
|
lfs_enabled: true,
|
||||||
|
request_access_enabled: true
|
||||||
|
}')
|
||||||
|
|
||||||
|
create_group=$(gitlab_api "POST" "/groups" "$group_data")
|
||||||
|
if ! check_api_error "$create_group" "создании группы"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
group_id=$(echo "$create_group" | jq -r '.id')
|
||||||
|
log "Группа создана успешно (ID: $group_id)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Создание проекта в группе
|
||||||
|
log "Проверка существования проекта '$PROJECT_NAME' в группе..."
|
||||||
|
project_check=$(gitlab_api "GET" "/projects/$GROUP_PATH%2F$PROJECT_NAME")
|
||||||
|
if echo "$project_check" | jq -e '.id' > /dev/null 2>&1; then
|
||||||
|
project_id=$(echo "$project_check" | jq -r '.id')
|
||||||
|
log "Проект '$PROJECT_NAME' уже существует (ID: $project_id)"
|
||||||
|
else
|
||||||
|
log "Создание проекта '$PROJECT_NAME' в группе..."
|
||||||
|
project_data=$(jq -n \
|
||||||
|
--arg name "$PROJECT_NAME" \
|
||||||
|
--arg path "$PROJECT_NAME" \
|
||||||
|
--arg desc "$PROJECT_DESCRIPTION" \
|
||||||
|
--arg namespace_id "$group_id" \
|
||||||
|
--arg visibility "private" \
|
||||||
|
'{
|
||||||
|
name: $name,
|
||||||
|
path: $path,
|
||||||
|
description: $desc,
|
||||||
|
namespace_id: $namespace_id,
|
||||||
|
visibility: $visibility,
|
||||||
|
initialize_with_readme: false,
|
||||||
|
lfs_enabled: true,
|
||||||
|
container_registry_enabled: true
|
||||||
|
}')
|
||||||
|
|
||||||
|
create_project=$(gitlab_api "POST" "/projects" "$project_data")
|
||||||
|
if ! check_api_error "$create_project" "создании проекта"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
project_id=$(echo "$create_project" | jq -r '.id')
|
||||||
|
project_ssh_url=$(echo "$create_project" | jq -r '.ssh_url_to_repo')
|
||||||
|
project_http_url=$(echo "$create_project" | jq -r '.http_url_to_repo')
|
||||||
|
log "Проект создан успешно (ID: $project_id)"
|
||||||
|
log "URL проекта: $project_http_url"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Назначение прав пользователю как owner группы
|
||||||
|
log "Проверка прав пользователя $user_username в группе..."
|
||||||
|
group_members=$(gitlab_api "GET" "/groups/$group_id/members")
|
||||||
|
user_in_group=$(echo "$group_members" | jq --arg user_id "$user_id" '.[] | select(.id == ($user_id|tonumber))')
|
||||||
|
|
||||||
|
if [ -z "$user_in_group" ]; then
|
||||||
|
log "Назначение прав owner пользователю $user_username в группе..."
|
||||||
|
member_data=$(jq -n \
|
||||||
|
--arg user_id "$user_id" \
|
||||||
|
--arg access_level "$ACCESS_LEVEL" \
|
||||||
|
'{
|
||||||
|
user_id: $user_id,
|
||||||
|
access_level: ($access_level|tonumber)
|
||||||
|
}')
|
||||||
|
|
||||||
|
add_member=$(gitlab_api "POST" "/groups/$group_id/members" "$member_data")
|
||||||
|
if ! check_api_error "$add_member" "добавлении пользователя в группу"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Пользователю $user_username назначены права owner в группе"
|
||||||
|
else
|
||||||
|
current_access=$(echo "$user_in_group" | jq -r '.access_level')
|
||||||
|
if [ "$current_access" != "$ACCESS_LEVEL" ]; then
|
||||||
|
log "Обновление прав пользователя $user_username с уровня $current_access на $ACCESS_LEVEL..."
|
||||||
|
member_data=$(jq -n \
|
||||||
|
--arg access_level "$ACCESS_LEVEL" \
|
||||||
|
'{
|
||||||
|
access_level: ($access_level|tonumber)
|
||||||
|
}')
|
||||||
|
|
||||||
|
update_member=$(gitlab_api "PUT" "/groups/$group_id/members/$user_id" "$member_data")
|
||||||
|
if ! check_api_error "$update_member" "обновлении прав пользователя"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "Права пользователя обновлены"
|
||||||
|
else
|
||||||
|
log "Пользователь $user_username уже имеет права owner в группе"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7. Клонирование и копирование кода
|
||||||
|
log "Создание временной директории: $TEMP_DIR"
|
||||||
|
mkdir -p "$TEMP_DIR"
|
||||||
|
cd "$TEMP_DIR"
|
||||||
|
|
||||||
|
log "Клонирование исходного репозитория: $SOURCE_REPO_URL"
|
||||||
|
if ! git clone --bare "$SOURCE_REPO_URL" source-repo.git; then
|
||||||
|
log "Ошибка при клонировании исходного репозитория"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Клонирование целевого репозитория GitLab"
|
||||||
|
# Получаем URL репозитория с токеном для записи
|
||||||
|
repo_url_with_token=$(echo "$project_http_url" | sed "s|https://|https://gitlab-ci-token:$PRIVATE_TOKEN@|")
|
||||||
|
if ! git clone "$repo_url_with_token" target-repo; then
|
||||||
|
log "Ошибка при клонировании целевого репозитория"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd target-repo
|
||||||
|
|
||||||
|
log "Добавление remote исходного репозитория"
|
||||||
|
git remote add source ../source-repo.git
|
||||||
|
|
||||||
|
log "Получение данных из исходного репозитория"
|
||||||
|
git fetch source
|
||||||
|
|
||||||
|
log "Объединение всех веток из исходного репозитория"
|
||||||
|
for branch in $(git branch -r | grep 'source/' | sed 's|source/||'); do
|
||||||
|
if [ "$branch" != "HEAD" ] && [ "$branch" != "->" ]; then
|
||||||
|
log " Копирование ветки: $branch"
|
||||||
|
git checkout -b "$branch" "source/$branch" 2>/dev/null || git branch "$branch" "source/$branch"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Возвращаемся в основную ветку (если есть)
|
||||||
|
if git show-ref --verify --quiet refs/heads/main; then
|
||||||
|
git checkout main
|
||||||
|
elif git show-ref --verify --quiet refs/heads/master; then
|
||||||
|
git checkout master
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Отправка всех веток в GitLab"
|
||||||
|
git push --all origin
|
||||||
|
git push --tags origin
|
||||||
|
|
||||||
|
log "Очистка временных файлов"
|
||||||
|
cd /
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
|
||||||
|
# 8. Проверка результата
|
||||||
|
log "Проверка созданной структуры..."
|
||||||
|
final_check=$(gitlab_api "GET" "/projects/$project_id")
|
||||||
|
project_name=$(echo "$final_check" | jq -r '.name_with_namespace')
|
||||||
|
web_url=$(echo "$final_check" | jq -r '.web_url')
|
||||||
|
|
||||||
|
log "=== ОТЧЕТ ==="
|
||||||
|
log "Группа: $GROUP_NAME (ID: $group_id)"
|
||||||
|
log "Проект: $project_name (ID: $project_id)"
|
||||||
|
log "URL проекта: $web_url"
|
||||||
|
log "Пользователь с правами owner: $user_username"
|
||||||
|
log "Код скопирован из: $SOURCE_REPO_URL"
|
||||||
|
log ""
|
||||||
|
log "Скрипт успешно выполнен!"
|
||||||
|
|
@ -10,6 +10,7 @@ import ru.oa2.lti.repository.entities.LMSContent;
|
||||||
import ru.oa2.lti.repository.entities.Task;
|
import ru.oa2.lti.repository.entities.Task;
|
||||||
import ru.oa2.lti.service.jwt.JwtService;
|
import ru.oa2.lti.service.jwt.JwtService;
|
||||||
import ru.oa2.lti.service.jwt.Payload;
|
import ru.oa2.lti.service.jwt.Payload;
|
||||||
|
import ru.oa2.lti.service.results.dto.ResultResponse;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -55,18 +56,13 @@ public class ApplicationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String saveResult(String body) {
|
public ResultResponse updateTask(String body) {
|
||||||
|
return new ResultResponse("success");
|
||||||
|
}
|
||||||
|
|
||||||
return "{\n" +
|
public ResultResponse saveResult(String body) {
|
||||||
" \"success\": true,\n" +
|
|
||||||
" \"message\": \"Результат успешно получен и обработан\",\n" +
|
//TODO заполнять из результатов проверок check скрипта
|
||||||
" \"data\": {\n" +
|
return new ResultResponse("success");
|
||||||
" \"contextId\": \"ctx-12345\",\n" +
|
|
||||||
" \"participantId\": \"usr-67890\",\n" +
|
|
||||||
" \"submittedText\": \"console.log('Hello');\",\n" +
|
|
||||||
" \"timestamp\": \"2025-12-14T17:55:30Z\",\n" +
|
|
||||||
" \"status\": \"processed\"\n" +
|
|
||||||
" }\n" +
|
|
||||||
"}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package ru.oa2.lti.controller;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
@ -11,6 +12,8 @@ import ru.oa2.lti.infrastructure.LMSService;
|
||||||
import ru.oa2.lti.model.LtiLogin;
|
import ru.oa2.lti.model.LtiLogin;
|
||||||
import ru.oa2.lti.service.jwt.Payload;
|
import ru.oa2.lti.service.jwt.Payload;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/tool/lti")
|
@RequestMapping("/tool/lti")
|
||||||
|
|
@ -63,6 +66,11 @@ public class LaunchController {
|
||||||
return "redirect:/tool/lti/select";
|
return "redirect:/tool/lti/select";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/task")
|
||||||
|
public ResponseEntity updateTask() {
|
||||||
|
|
||||||
|
return service.updateTask("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/task")
|
@GetMapping("/task")
|
||||||
public String showDockerTas(Model model,
|
public String showDockerTas(Model model,
|
||||||
|
|
@ -81,6 +89,9 @@ public class LaunchController {
|
||||||
model.addAttribute("description", data);
|
model.addAttribute("description", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Objects.requireNonNull(payload).getCoach()) {
|
||||||
|
return "task-editor";
|
||||||
|
}
|
||||||
return "task";
|
return "task";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ public class ResultController {
|
||||||
jwtService.getTokenPayload(idToken)
|
jwtService.getTokenPayload(idToken)
|
||||||
);
|
);
|
||||||
|
|
||||||
//TODO возвращать json
|
|
||||||
return ResponseEntity
|
return ResponseEntity
|
||||||
.accepted()
|
.accepted()
|
||||||
.body(service.saveResult(body));
|
.body(service.saveResult(body));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Редактор задачи LTI</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body, html {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
.container-fluid {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.header-section {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-bottom: 1px solid #e0e4e8;
|
||||||
|
}
|
||||||
|
.content-section {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.form-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #495057;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: vertical;
|
||||||
|
background-color: #fdfefe;
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #80bdff;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
padding: 15px 20px;
|
||||||
|
background-color: #f1f3f5;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.status-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
.status-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.status-success {
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
.status-failed {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
.status-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.submit-btn {
|
||||||
|
padding: 10px 24px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.code-area {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background-color: #f0f4f8;
|
||||||
|
border: 1px solid #d0d7de;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<!-- Заголовок -->
|
||||||
|
<div class="header-section">
|
||||||
|
<h4>Редактор задачи</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Основное содержимое -->
|
||||||
|
<div class="content-section">
|
||||||
|
|
||||||
|
<!-- Поле: Описание задачи (редактируемое) -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Описание задачи (HTML)</label>
|
||||||
|
<textarea id="description" class="code-area" placeholder="Введите HTML-описание задачи..."><th:block th:utext="${description ?: ''}"/></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Поле: initScript -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">initScript (JavaScript)</label>
|
||||||
|
<textarea id="initScript" class="code-area" placeholder="JavaScript-код для инициализации среды..."><th:block th:text="${initScript ?: ''}"/></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Поле: checkScript -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">checkScript (JavaScript)</label>
|
||||||
|
<textarea id="checkScript" class="code-area" placeholder="JavaScript-код для проверки результата..."><th:block th:text="${checkScript ?: ''}"/></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Футер: статус + кнопки -->
|
||||||
|
<div class="footer">
|
||||||
|
<!-- Статус -->
|
||||||
|
<div id="statusContainer" class="status-container" style="display: none;">
|
||||||
|
<div id="statusIcon" class="status-icon">
|
||||||
|
<i id="icon" class="fa-solid"></i>
|
||||||
|
</div>
|
||||||
|
<span id="statusLabel" class="status-label"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Кнопки -->
|
||||||
|
<div>
|
||||||
|
<button id="saveBtn" class="btn btn-success submit-btn me-2">
|
||||||
|
<i class="fas fa-save"></i> Сохранить задачу
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Скрытые поля -->
|
||||||
|
<input type="hidden" id="contextId" th:value="${contextId}" />
|
||||||
|
<input type="hidden" id="participantId" th:value="${participantId}" />
|
||||||
|
|
||||||
|
<!-- Скрипт отправки -->
|
||||||
|
<script th:src="@{/tool/lti/js/main.js}"></script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('saveBtn').addEventListener('click', async () => {
|
||||||
|
const contextId = document.getElementById('contextId').value;
|
||||||
|
const description = document.getElementById('description').value;
|
||||||
|
const initScript = document.getElementById('initScript').value;
|
||||||
|
const checkScript = document.getElementById('checkScript').value;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
contextId,
|
||||||
|
description,
|
||||||
|
initScript,
|
||||||
|
checkScript
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/tool/lti/task', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
const statusContainer = document.getElementById('statusContainer');
|
||||||
|
const statusIcon = document.getElementById('icon');
|
||||||
|
const statusLabel = document.getElementById('statusLabel');
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
statusIcon.className = 'fa-solid fa-check-circle';
|
||||||
|
statusIcon.classList.add('status-success');
|
||||||
|
statusLabel.textContent = 'Задача успешно сохранена';
|
||||||
|
statusContainer.style.display = 'flex';
|
||||||
|
statusContainer.classList.remove('status-failed');
|
||||||
|
statusContainer.classList.add('status-success');
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || 'Ошибка сохранения');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const statusContainer = document.getElementById('statusContainer');
|
||||||
|
const statusIcon = document.getElementById('icon');
|
||||||
|
const statusLabel = document.getElementById('statusLabel');
|
||||||
|
|
||||||
|
statusIcon.className = 'fa-solid fa-times-circle';
|
||||||
|
statusIcon.classList.add('status-failed');
|
||||||
|
statusLabel.textContent = 'Ошибка: ' + error.message;
|
||||||
|
statusContainer.style.display = 'flex';
|
||||||
|
statusContainer.classList.remove('status-success');
|
||||||
|
statusContainer.classList.add('status-failed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue