DevPlace CI / test (push) Failing after 1h0m46s
The API key block and the workspace editor password each carried their own copy of the same monospace, break-all, boxed styling. Both now use a single secret-value class in base.css, so a new secret surface inherits the presentation instead of duplicating it. ARCHITECTURE.md is a structural overview of the application derived from the source tree, the live schema and the imported app object, linked from the README.
129 lines
5.4 KiB
HTML
129 lines
5.4 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block extra_head %}
|
|
<link rel="stylesheet" href="{{ static_url('css/containers.css') }}">
|
|
<link rel="stylesheet" href="{{ static_url('css/workspace.css') }}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="workspace-page" data-workspace-root
|
|
data-slug="{{ project.slug or project.uid }}"
|
|
data-has-workspace="{{ 1 if has_workspace else 0 }}">
|
|
|
|
<h1 class="workspace-title">Workspace: {{ project.title }}</h1>
|
|
|
|
{% if not has_workspace %}
|
|
<div class="card workspace-empty">
|
|
<p>No workspace yet for this project. Opening one starts a container with your
|
|
project files, a browser editor, and a terminal.</p>
|
|
<p class="workspace-muted">You are using {{ workspace_count }} of {{ max_workspaces }} workspaces.</p>
|
|
<form method="post" action="/projects/{{ project.slug or project.uid }}/workspace">
|
|
<button type="submit" class="btn btn-primary">Open workspace</button>
|
|
</form>
|
|
</div>
|
|
{% else %}
|
|
|
|
{% if workspace.suspended %}
|
|
<div class="card workspace-suspended">
|
|
<strong>This workspace is suspended.</strong>
|
|
<p>{{ workspace.flag_reason or "Contact an administrator." }}</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% for flag in workspace.flags %}
|
|
<div class="card workspace-flag workspace-flag-{{ flag.severity }}">
|
|
<strong>{{ flag.kind }}</strong>
|
|
<span>{{ flag.detail }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<div class="card workspace-summary">
|
|
<div class="workspace-state" data-workspace-status>{{ workspace.status }}</div>
|
|
<div class="workspace-meters">
|
|
<div class="workspace-meter">
|
|
<span>Disk</span>
|
|
<progress max="100" value="{{ workspace.disk_percent }}"></progress>
|
|
<span>{{ workspace.disk_percent }}% of {{ workspace.disk_quota_mb }} MB</span>
|
|
</div>
|
|
<div class="workspace-meter">
|
|
<span>Egress</span>
|
|
<progress max="100" value="{{ workspace.egress_percent }}"></progress>
|
|
<span>{{ workspace.egress_percent }}% of {{ workspace.egress_quota_mb }} MB</span>
|
|
</div>
|
|
</div>
|
|
<p class="workspace-muted">
|
|
Stops after {{ workspace.idle_stop_minutes }} minutes idle. Removed after
|
|
{{ workspace.retention_days }} days idle.
|
|
{% if workspace.last_active_at %}
|
|
Last active {{ dt_ago(workspace.last_active_at) }}.
|
|
{% endif %}
|
|
</p>
|
|
<div class="workspace-actions">
|
|
{% if workspace.status == "running" %}
|
|
<a class="btn btn-primary" href="{{ editor_url }}" target="_blank" rel="noopener">Open editor</a>
|
|
<form method="post" action="/projects/{{ project.slug or project.uid }}/workspace/stop">
|
|
<button type="submit" class="btn">Stop</button>
|
|
</form>
|
|
{% else %}
|
|
<form method="post" action="/projects/{{ project.slug or project.uid }}/workspace">
|
|
<button type="submit" class="btn btn-primary">Start</button>
|
|
</form>
|
|
{% endif %}
|
|
{% if editor_password %}
|
|
<code class="secret-value" id="editor-password">{{ editor_password }}</code>
|
|
<button type="button" class="btn btn-sm" data-copy="editor-password">Copy password</button>
|
|
{% endif %}
|
|
<form method="post" action="/projects/{{ project.slug or project.uid }}/workspace/delete">
|
|
<button type="submit" data-confirm="Delete this workspace?" data-confirm-danger class="btn btn-danger">Delete</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card workspace-tunnels">
|
|
<h2>Public tunnels</h2>
|
|
<p class="workspace-muted">
|
|
A tunnel publishes a port from inside your container on a public HTTPS address.
|
|
Anyone with the link can reach it. You may have up to {{ workspace.max_tunnels }}.
|
|
</p>
|
|
<ul class="workspace-tunnel-list" data-tunnel-list>
|
|
{% for tunnel in workspace.tunnels %}
|
|
<li class="workspace-tunnel">
|
|
<a href="https://{{ tunnel.hostname }}" rel="noopener" target="_blank">{{ tunnel.hostname }}</a>
|
|
<span class="workspace-badge">port {{ tunnel.container_port }}</span>
|
|
<span class="workspace-badge workspace-badge-{{ tunnel.status }}">{{ tunnel.status }}</span>
|
|
{% if tunnel.last_error %}<span class="workspace-error">{{ tunnel.last_error }}</span>{% endif %}
|
|
<form method="post"
|
|
action="/projects/{{ project.slug or project.uid }}/workspace/tunnels/{{ tunnel.uid }}/delete">
|
|
<button type="submit" data-confirm="Remove this tunnel?" data-confirm-danger class="btn btn-sm">Remove</button>
|
|
</form>
|
|
</li>
|
|
{% else %}
|
|
<li class="workspace-muted">No tunnels yet.</li>
|
|
{% endfor %}
|
|
</ul>
|
|
<form class="workspace-tunnel-form" method="post"
|
|
action="/projects/{{ project.slug or project.uid }}/workspace/tunnels">
|
|
<input type="text" name="label" placeholder="Label (optional)" maxlength="64">
|
|
<input type="number" name="container_port" placeholder="Port" min="1" max="65535" required>
|
|
<button type="submit" class="btn">Add tunnel</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="card workspace-help">
|
|
<h2>Inside the container</h2>
|
|
<ul>
|
|
<li><code>sudo</code> and <code>apt install</code> work with no extra setup.</li>
|
|
<li>Python, Rust, Nim and Swift toolchains are preinstalled.</li>
|
|
<li>Ports below 1024 cannot bind. Use a high port and a tunnel.</li>
|
|
<li>Your public URLs are also in <code>/app/.devplace/tunnels.json</code>.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script type="module" src="{{ static_url('js/WorkspaceManager.js') }}"></script>
|
|
{% endblock %}
|