{% 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 %}