шаблон для Docker задач
This commit is contained in:
parent
3997e2d2ab
commit
9fbbfdb697
5
pom.xml
5
pom.xml
|
|
@ -33,6 +33,11 @@
|
||||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-oauth2-jose</artifactId>
|
<artifactId>spring-security-oauth2-jose</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
package ru.oa2.lti.controller;
|
package ru.oa2.lti.controller;
|
||||||
|
|
||||||
import jakarta.servlet.ServletRequest;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
import ru.oa2.lti.ApplicationService;
|
import ru.oa2.lti.ApplicationService;
|
||||||
import ru.oa2.lti.model.LtiLogin;
|
import ru.oa2.lti.model.LtiLogin;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@Controller
|
||||||
@RequestMapping("/tool/lti")
|
@RequestMapping("/tool/lti")
|
||||||
public class LoginController {
|
public class LoginController {
|
||||||
|
|
||||||
|
|
@ -18,14 +20,14 @@ public class LoginController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public String login(
|
public String login(@RequestParam("client_id") String clientId,
|
||||||
@RequestParam("client_id") String clientId,
|
|
||||||
@RequestParam("iss") String iss,
|
@RequestParam("iss") String iss,
|
||||||
@RequestParam("login_hint") String loginHint,
|
@RequestParam("login_hint") String loginHint,
|
||||||
@RequestParam("lti_deployment_id") String ltiDeploymentId,
|
@RequestParam("lti_deployment_id") String ltiDeploymentId,
|
||||||
@RequestParam("lti_message_hint") String ltiMessageHint,
|
@RequestParam("lti_message_hint") String ltiMessageHint,
|
||||||
@RequestParam("target_link_uri") String targetLinkUri,
|
@RequestParam("target_link_uri") String targetLinkUri,
|
||||||
ServletRequest servletRequest) {
|
RedirectAttributes redirectAttributes) {
|
||||||
|
|
||||||
var ltiLogin = LtiLogin.builder()
|
var ltiLogin = LtiLogin.builder()
|
||||||
.clientId(clientId)
|
.clientId(clientId)
|
||||||
.iss(iss)
|
.iss(iss)
|
||||||
|
|
@ -37,6 +39,51 @@ public class LoginController {
|
||||||
|
|
||||||
log.info("BODY: {}", ltiLogin);
|
log.info("BODY: {}", ltiLogin);
|
||||||
|
|
||||||
return service.getTask(ltiLogin);
|
var data = service.getTask(ltiLogin);
|
||||||
|
redirectAttributes.addFlashAttribute("description", data);
|
||||||
|
redirectAttributes.addFlashAttribute("codeTitle", "Dockerfile:");
|
||||||
|
redirectAttributes.addFlashAttribute("inputCode", "FROM maven:3.9.9-eclipse-temurin-21-jammy AS build\n" +
|
||||||
|
"\n" +
|
||||||
|
"COPY . /build\n" +
|
||||||
|
"WORKDIR /build\n" +
|
||||||
|
"\n" +
|
||||||
|
"RUN --mount=type=cache,target=/root/.m2/repository,rw \\\n" +
|
||||||
|
"\tmvn clean package -DskipTests -B\n" +
|
||||||
|
"\n" +
|
||||||
|
"FROM eclipse-temurin:21-jdk AS extract\n" +
|
||||||
|
"\n" +
|
||||||
|
"COPY --from=build /build/target/lti-provider-*.jar app.jar\n" +
|
||||||
|
"RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted\n" +
|
||||||
|
"\n" +
|
||||||
|
"FROM eclipse-temurin:21-jre\n" +
|
||||||
|
"\n" +
|
||||||
|
"LABEL org.opencontainers.image.title=\"LTI Provider\"\n" +
|
||||||
|
"LABEL org.opencontainers.image.description=\"LTI провайдер для лабораторных по Docker и Kubernetes\"\n" +
|
||||||
|
"LABEL org.opencontainers.image.url=\"TODO\"\n" +
|
||||||
|
"LABEL org.opencontainers.image.source=\"TODO\"\n" +
|
||||||
|
"LABEL org.opencontainers.image.documentation=\"#TODO\"\n" +
|
||||||
|
"\n" +
|
||||||
|
"WORKDIR /opt\n" +
|
||||||
|
"\n" +
|
||||||
|
"COPY --from=extract extracted/dependencies/ ./\n" +
|
||||||
|
"COPY --from=extract extracted/spring-boot-loader/ ./\n" +
|
||||||
|
"COPY --from=extract extracted/snapshot-dependencies/ ./\n" +
|
||||||
|
"COPY --from=extract extracted/application/ ./\n" +
|
||||||
|
"\n" +
|
||||||
|
"ENV TZ=\"Europe/Moscow\"\n" +
|
||||||
|
"ENV JAVA_TOOL_OPTIONS=\"-Xmx1g -Xms1g\"\n" +
|
||||||
|
"\n" +
|
||||||
|
"ENTRYPOINT [\"java\", \"-jar\", \"/opt/app.jar\"]");
|
||||||
|
|
||||||
|
return "redirect:/tool/lti/docker-task";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/docker-task")
|
||||||
|
public String showDockerTas(Model model) {
|
||||||
|
|
||||||
|
if (!model.containsAttribute("description")) {
|
||||||
|
return "redirect:/error";
|
||||||
|
}
|
||||||
|
return "docker-task";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package ru.oa2.lti.controller;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/tool/result")
|
||||||
|
public class ResultController {
|
||||||
|
|
||||||
|
@PostMapping("/docker")
|
||||||
|
public String result(@RequestBody String body) {
|
||||||
|
|
||||||
|
log.info("RESULT: {}", body);
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
return "{\n" +
|
||||||
|
" \"success\": true,\n" +
|
||||||
|
" \"message\": \"Результат успешно получен и обработан\",\n" +
|
||||||
|
" \"data\": {\n" +
|
||||||
|
" \"contextId\": \"ctx-12345\",\n" +
|
||||||
|
" \"participantId\": \"usr-67890\",\n" +
|
||||||
|
" \"submittedText\": \"console.log('Hello');\",\n" +
|
||||||
|
" \"timestamp\": \"2025-12-14T17:55:30Z\",\n" +
|
||||||
|
" \"status\": \"processed\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
<!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">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body, html {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.container-fluid {
|
||||||
|
height: 90vh;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.left-section {
|
||||||
|
border-right: 2px solid #dee2e6;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
.right-section {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.right-top {
|
||||||
|
height: 50%;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
.right-bottom {
|
||||||
|
height: 50%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.task-block {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.code-block {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.input-output {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
flex: 1;
|
||||||
|
resize: none;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.output-area {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.run-button {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 10px 30px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.result-title {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #495057;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- Левая секция -->
|
||||||
|
<div class="col-md-6 left-section">
|
||||||
|
<h3>Условие задачи</h3>
|
||||||
|
<div class="task-block" th:utext="${description}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Правая секция -->
|
||||||
|
<div class="col-md-6 right-section">
|
||||||
|
<!-- Верхняя часть правой секции -->
|
||||||
|
<div class="right-top">
|
||||||
|
<h4>Введите решение</h4>
|
||||||
|
<div class="input-output">
|
||||||
|
<div class="code-prompt mb-2" th:utext="${codeTitle}">
|
||||||
|
<!-- Текст подсказки для ввода кода -->
|
||||||
|
</div>
|
||||||
|
<textarea id="codeInput" class="form-control" th:text="${inputCode}">
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Нижняя часть правой секции -->
|
||||||
|
<div class="right-bottom">
|
||||||
|
<h4>Результат выполнения</h4>
|
||||||
|
<div class="input-output">
|
||||||
|
<div class="result-title">Вывод программы:</div>
|
||||||
|
<div id="outputArea" class="output-area">
|
||||||
|
<!-- Здесь будет появляться результат -->
|
||||||
|
Результат выполнения появится здесь...
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<button id="runButton" class="btn btn-primary run-button">
|
||||||
|
<i class="fas fa-play"></i> Запустить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Подключаем FontAwesome для иконок -->
|
||||||
|
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('runButton').addEventListener('click', function() {
|
||||||
|
const codeInput = document.getElementById('codeInput').value;
|
||||||
|
const outputArea = document.getElementById('outputArea');
|
||||||
|
|
||||||
|
// Показываем индикатор загрузки
|
||||||
|
outputArea.innerHTML = '<div class="text-center"><i class="fas fa-spinner fa-spin"></i> Отправка данных...</div>';
|
||||||
|
|
||||||
|
// Получаем данные пользователя и контекста
|
||||||
|
// Предположим, что эти значения хранятся в data-атрибутах или скрытых полях
|
||||||
|
const contextId = document.getElementById('contextId')?.value || 'unknown_context';
|
||||||
|
const participantId = document.getElementById('participantId')?.value || 'unknown_participant';
|
||||||
|
|
||||||
|
// Формируем JSON-объект для отправки
|
||||||
|
const payload = {
|
||||||
|
contextId: contextId,
|
||||||
|
participantId: participantId,
|
||||||
|
text: codeInput
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch('/tool/result/docker', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка сети: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
// Успешный ответ от сервера
|
||||||
|
outputArea.innerHTML = '<div class="text-success">✓ Данные успешно отправлены!</div>';
|
||||||
|
console.log('Ответ сервера:', data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Обработка ошибок сети или сервера
|
||||||
|
outputArea.innerHTML = `
|
||||||
|
<div class="text-danger">
|
||||||
|
<strong>Ошибка отправки:</strong><br>
|
||||||
|
${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
console.error('Ошибка при отправке:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Автоматический рост textarea при вводе
|
||||||
|
document.getElementById('codeInput').addEventListener('input', function() {
|
||||||
|
this.style.height = 'auto';
|
||||||
|
this.style.height = (this.scrollHeight) + 'px';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue