Compare commits

..

No commits in common. "main" and "fix_runs_and_ui" have entirely different histories.

7 changed files with 62 additions and 62 deletions

View file

@ -23,8 +23,9 @@ RUN --mount=type=cache,target=/root/.cache/uv \
COPY src ./src
RUN uv pip install --system --no-deps --no-build-isolation .
# Expose Web UI port
# Expose Web UI port and MLflow port
EXPOSE 8000
EXPOSE 5000
# Start Web UI using the system entry point
CMD ["yolo-train-webui", "--host", "0.0.0.0", "--port", "8000"]

View file

@ -3,19 +3,19 @@ services:
build:
context: .
image: yolo-train-webui:latest
network_mode: bridge
ports:
- "8000:8000"
# The training API has no built-in user accounts, so expose it locally only.
- "127.0.0.1:8000:8000"
volumes:
- ./datasets:/workspace/datasets
- ./runs:/workspace/runs
- ./models:/workspace/models
- ./models/.config:/root/.config/Ultralytics
environment:
# Tracking URI for external MLflow server. Change host/IP if running MLflow on another machine/container.
YOLO_WEBUI_MLFLOW_TRACKING_URI: http://host.docker.internal:5000
extra_hosts:
- "host.docker.internal:host-gateway"
# Use the Compose MLflow service so metadata and artifacts survive container recreation.
YOLO_WEBUI_MLFLOW_TRACKING_URI: http://mlflow:5000
depends_on:
- mlflow
# Uncomment the block below on Linux with NVIDIA GPU to pass the graphics card into the container:
# deploy:
# resources:
@ -26,3 +26,28 @@ services:
# capabilities: [gpu]
ipc: host
restart: unless-stopped
mlflow:
build:
context: .
image: yolo-train-webui:latest
command:
- mlflow
- server
- --backend-store-uri
- sqlite:////mlflow/mlflow.db
- --artifacts-destination
- /mlflow/artifacts
- --host
- 0.0.0.0
- --port
- "5000"
- --workers
- "1"
- --allowed-hosts
- localhost:5000,127.0.0.1:5000,mlflow:5000
ports:
- "127.0.0.1:5000:5000"
volumes:
- ./mlflow:/mlflow
restart: unless-stopped

View file

@ -24,7 +24,6 @@ document.addEventListener('DOMContentLoaded', () => {
const mlflowInputs = document.querySelectorAll('.mlflow-fields input');
const trackingUriInput = document.getElementById('tracking-uri');
const mlflowHeaderLink = document.getElementById('mlflow-header-link');
const taskSelect = document.getElementById('task');
// --- Dynamic Model Selection ---
const standardModels = {
@ -35,7 +34,6 @@ document.addEventListener('DOMContentLoaded', () => {
obb: ['yolo11n-obb.pt', 'yolo11s-obb.pt', 'yolo11m-obb.pt', 'yolo11l-obb.pt', 'yolo11x-obb.pt']
};
let discoveredModels = [];
let discoveredDatasets = [];
function getDiscoveredModelValue(model) {
return model.path || model.name;
@ -101,6 +99,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
const taskSelect = document.getElementById('task');
// Session Controls
const sessionSelect = document.getElementById('session-select');
@ -250,11 +249,6 @@ document.addEventListener('DOMContentLoaded', () => {
mlflowEnabled.addEventListener('change', updateMlflowFields);
// --- Logger ---
function scrollToLogBottom(container) {
if (!container) return;
container.scrollTop = container.scrollHeight;
}
function addLogLine(message, level = 'info') {
const line = document.createElement('div');
line.className = `log-line log-level-${level.toLowerCase()}`;
@ -262,7 +256,7 @@ document.addEventListener('DOMContentLoaded', () => {
logContainer.appendChild(line);
if (autoscrollCheck.checked) {
scrollToLogBottom(logContainer);
logContainer.scrollTop = logContainer.scrollHeight;
}
}
@ -386,7 +380,7 @@ document.addEventListener('DOMContentLoaded', () => {
addLogLine(data.message, data.level);
}
if (autoscrollCheck && autoscrollCheck.checked) {
scrollToLogBottom(logContainer);
logContainer.scrollTop = logContainer.scrollHeight;
}
} else if (data.type === 'progress') {
updateProgress(data.epoch, data.total_epochs, data.message);
@ -707,7 +701,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
// --- Datasets Auto-Discovery ---
let discoveredDatasets = [];
async function loadDatasetsList() {
try {
@ -938,7 +932,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
if (configForm) {
configForm.addEventListener('submit', (e) => e.preventDefault());
configForm.addEventListener('input', () => {
const config = getFormConfig();
localStorage.setItem('draft_config', JSON.stringify(config));
@ -1029,7 +1022,7 @@ document.addEventListener('DOMContentLoaded', () => {
exportLogContainer.appendChild(line);
if (exportAutoscrollCheck.checked) {
scrollToLogBottom(exportLogContainer);
exportLogContainer.scrollTop = exportLogContainer.scrollHeight;
}
}
@ -1256,8 +1249,5 @@ document.addEventListener('DOMContentLoaded', () => {
loadSessionsList();
connectWebSocket();
connectExportWebSocket();
}).catch(err => {
console.error('Initial load error:', err);
addLogLine('Ошибка загрузки конфигурации: ' + err.message, 'warning');
});
});

View file

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#f97316" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
<line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>

Before

Width:  |  Height:  |  Size: 397 B

View file

@ -7,8 +7,8 @@
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/style.css?v=6">
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<header class="app-header">
@ -536,6 +536,6 @@
</section>
</div> <!-- End export-view -->
</main>
<script src="/static/app.js?v=6"></script>
<script src="/static/app.js"></script>
</body>
</html>

View file

@ -44,11 +44,10 @@ body {
background-color: var(--bg-primary);
color: var(--text-main);
font-family: var(--font-sans);
height: 100vh;
max-height: 100vh;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
overflow-x: hidden;
}
/* Header Styles */
@ -137,18 +136,14 @@ body {
width: 100%;
margin: 0 auto;
height: calc(100vh - 57px);
min-height: 0;
overflow: hidden;
}
.workspace-view {
display: none;
grid-template-columns: 420px 1fr;
grid-template-columns: 380px 1fr;
gap: 1.5rem;
flex: 1;
height: 100%;
min-height: 0;
overflow: hidden;
}
.workspace-view.active {
@ -174,7 +169,6 @@ body {
flex-direction: column;
overflow: hidden;
height: 100%;
min-height: 0;
}
#config-pane,
@ -189,9 +183,8 @@ body {
display: flex;
flex-direction: column;
gap: 1.25rem;
height: 100%;
max-height: 100%;
overflow: hidden;
overflow-y: auto;
}
/* Custom Scrollbars */
@ -350,26 +343,20 @@ body {
border-radius: 8px;
padding: 0.25rem;
margin-bottom: 1.25rem;
gap: 0.2rem;
overflow-x: auto;
gap: 0.25rem;
}
.tab-btn {
flex: 1 1 0;
min-width: 0;
flex: 1;
background: none;
border: none;
border-radius: 6px;
color: var(--text-muted);
font-family: var(--font-sans);
font-size: 0.82rem;
font-size: 0.9rem;
font-weight: 600;
padding: 0.55rem 0.25rem;
padding: 0.6rem;
cursor: pointer;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: var(--transition-fast);
}
@ -775,7 +762,7 @@ body {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
min-height: 480px;
overflow: hidden;
}
@ -822,18 +809,16 @@ body {
}
.log-body {
flex: 1 1 0;
min-height: 0;
flex: 1;
overflow-x: auto;
overflow-y: auto;
padding: 0.6rem 0.85rem;
padding: 0.75rem 1rem;
font-family: var(--font-mono);
font-size: 0.75rem;
line-height: 1.35;
letter-spacing: -0.015em;
font-size: 0.85rem;
line-height: 1.5;
display: flex;
flex-direction: column;
gap: 0.15rem;
gap: 0.25rem;
color: #e4e4e7;
}

View file

@ -12,12 +12,16 @@ def test_compose_is_local_and_does_not_require_nvidia() -> None:
compose = yaml.safe_load(
(PROJECT_ROOT / "docker-compose.yml").read_text(encoding="utf-8")
)
assert set(compose["services"].keys()) == {"webui"}
webui = compose["services"]["webui"]
assert webui["ports"] == ["8000:8000"]
assert webui["ports"] == ["127.0.0.1:8000:8000"]
assert "deploy" not in webui
assert "YOLO_WEBUI_MLFLOW_TRACKING_URI" in webui["environment"]
mlflow = compose["services"]["mlflow"]
assert mlflow["ports"] == ["127.0.0.1:5000:5000"]
assert mlflow["volumes"] == ["./mlflow:/mlflow"]
assert "server" in mlflow["command"]
assert webui["environment"]["YOLO_WEBUI_MLFLOW_TRACKING_URI"] == "http://mlflow:5000"
def test_docker_dependencies_are_exported_from_lock_file() -> None: