mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-11-07 13:56:01 +01:00
1027 lines
52 KiB
HTML
1027 lines
52 KiB
HTML
{% extends "baseTemplate/index.html" %}
|
|
{% load i18n %}
|
|
{% block title %}{% trans "Docker Sites - CyberPanel" %}{% endblock %}
|
|
{% block content %}
|
|
|
|
{% load static %}
|
|
{% get_current_language as LANGUAGE_CODE %}
|
|
<!-- Current language: {{ LANGUAGE_CODE }} -->
|
|
|
|
<style>
|
|
.info-box {
|
|
background: #fff;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.info-box:hover {
|
|
box-shadow: 0 3px 6px rgba(0,0,0,0.16);
|
|
}
|
|
|
|
.info-box h4 {
|
|
margin-top: 0;
|
|
margin-bottom: 15px;
|
|
color: #333;
|
|
font-weight: 600;
|
|
border-bottom: 2px solid #f5f5f5;
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.progress {
|
|
margin-bottom: 15px;
|
|
height: 20px;
|
|
background-color: #f5f5f5;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-bar {
|
|
background-color: #2196F3;
|
|
color: white;
|
|
text-align: center;
|
|
line-height: 20px;
|
|
transition: width 0.6s ease;
|
|
}
|
|
|
|
.progress-bar.high {
|
|
background-color: #f44336;
|
|
}
|
|
|
|
.label {
|
|
padding: 5px 10px;
|
|
border-radius: 3px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.label-success {
|
|
background-color: #4CAF50;
|
|
}
|
|
|
|
.label-danger {
|
|
background-color: #F44336;
|
|
}
|
|
|
|
.label-warning {
|
|
background-color: #FF9800;
|
|
}
|
|
|
|
.table-striped > tbody > tr:nth-of-type(odd) {
|
|
background-color: #f9f9f9;
|
|
}
|
|
|
|
.table {
|
|
margin-bottom: 0;
|
|
background-color: white;
|
|
}
|
|
|
|
.table > thead > tr > th {
|
|
border-bottom: 2px solid #ddd;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.table > tbody > tr > td {
|
|
vertical-align: middle;
|
|
padding: 12px 8px;
|
|
}
|
|
|
|
.table-responsive {
|
|
border: none;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* Resource usage colors */
|
|
.progress-bar[aria-valuenow^="8"],
|
|
.progress-bar[aria-valuenow^="9"] {
|
|
background-color: #f44336;
|
|
}
|
|
|
|
.progress-bar[aria-valuenow^="7"] {
|
|
background-color: #ff9800;
|
|
}
|
|
|
|
/* Container status colors */
|
|
.status-indicator {
|
|
display: inline-block;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
margin-right: 5px;
|
|
}
|
|
|
|
.status-running {
|
|
background-color: #4CAF50;
|
|
}
|
|
|
|
.status-stopped {
|
|
background-color: #F44336;
|
|
}
|
|
|
|
/* Tooltips */
|
|
[data-toggle="tooltip"] {
|
|
cursor: help;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
.info-box {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.table-responsive {
|
|
border: 1px solid #ddd;
|
|
}
|
|
}
|
|
|
|
/* Button Group Styles */
|
|
.btn-group {
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
border: none;
|
|
padding: 8px 16px;
|
|
font-weight: 500;
|
|
transition: all 0.2s ease;
|
|
margin: 0;
|
|
position: relative;
|
|
}
|
|
|
|
.btn-group .btn:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.btn-group .btn:active {
|
|
transform: translateY(0);
|
|
box-shadow: none;
|
|
}
|
|
|
|
.btn-group .btn i {
|
|
margin-right: 5px;
|
|
}
|
|
|
|
.btn-group .btn-sm {
|
|
font-size: 0.875rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Button Colors */
|
|
.btn-success {
|
|
background-color: #2ecc71;
|
|
}
|
|
|
|
.btn-warning {
|
|
background-color: #f1c40f;
|
|
}
|
|
|
|
.btn-danger {
|
|
background-color: #e74c3c;
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: #3498db;
|
|
}
|
|
|
|
.btn-info {
|
|
background-color: #2980b9;
|
|
}
|
|
|
|
/* Button Hover States */
|
|
.btn-success:hover {
|
|
background-color: #27ae60;
|
|
}
|
|
|
|
.btn-warning:hover {
|
|
background-color: #f39c12;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background-color: #c0392b;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background-color: #2980b9;
|
|
}
|
|
|
|
.btn-info:hover {
|
|
background-color: #2472a4;
|
|
}
|
|
|
|
/* Tooltip enhancement */
|
|
[title] {
|
|
position: relative;
|
|
}
|
|
|
|
[title]:hover:after {
|
|
content: attr(title);
|
|
position: absolute;
|
|
bottom: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(0,0,0,0.8);
|
|
color: white;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
white-space: nowrap;
|
|
z-index: 10;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
/* n8n specific styles */
|
|
.n8n-container {
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
border-left: 4px solid #2ecc71;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.n8n-metrics {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.metric-card {
|
|
background: white;
|
|
padding: 15px;
|
|
border-radius: 4px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.metric-card h5 {
|
|
color: #666;
|
|
margin-bottom: 10px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.metric-card .value {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
.quick-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.quick-action-btn {
|
|
padding: 8px 15px;
|
|
border-radius: 4px;
|
|
border: none;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.quick-action-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.workflow-status {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 3px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.workflow-status.active {
|
|
background: #e3fcef;
|
|
color: #00a854;
|
|
}
|
|
|
|
.workflow-status.error {
|
|
background: #fff1f0;
|
|
color: #f5222d;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
$(document).ready(function () {
|
|
$('[data-toggle="tooltip"]').tooltip();
|
|
});
|
|
</script>
|
|
|
|
|
|
<div class="container" ng-controller="ListDockersitecontainer">
|
|
|
|
<div id="page-title">
|
|
<h2 id="domainNamePage">{% trans "Containers" %} <img id="cyberpanelLoading" ng-hide="cyberpanelLoading"
|
|
src="{% static 'images/loading.gif' %}">
|
|
<a class="pull-right btn btn-primary" href="{% url "CreateDockersite" %}">Create</a>
|
|
</h2>
|
|
<p>{% trans "Manage containers on server" %}</p>
|
|
</div>
|
|
|
|
<div class="panel" ng-hide="true">
|
|
<div class="panel-body">
|
|
<h3 class="content-box-header">
|
|
{% trans "Containers" %} {{ dockerSite.SiteName }}<img id="imageLoading"
|
|
src="/static/images/loading.gif"
|
|
style="display: none;">
|
|
</h3>
|
|
<span style="display: none" id="sitename">{{ dockerSite.SiteName }}</span>
|
|
<div class="example-box-wrapper">
|
|
<div ng-repeat="web in ContainerList track by $index" ng-init="Lunchcontainer(web.id)"></div>
|
|
</div>
|
|
|
|
<div id="listFail" class="alert alert-danger">
|
|
<p>{% trans "Error message:" %} {$ errorMessage $}</p>
|
|
</div>
|
|
|
|
<div class="row text-center">
|
|
|
|
<div class="col-sm-4 col-sm-offset-8">
|
|
|
|
<nav aria-label="Page navigation">
|
|
<ul class="pagination">
|
|
|
|
|
|
{% for items in pagination %}
|
|
|
|
<li ng-click="getFurtherContainersFromDB({{ forloop.counter }})" id="webPages">
|
|
<a href="">{{ forloop.counter }}</a></li>
|
|
|
|
{% endfor %}
|
|
|
|
</ul>
|
|
</nav>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
{% if showUnlistedContainer %}
|
|
<h3 class="title-hero">
|
|
{% trans "Unlisted Containers" %} <i class="fa fa-question-circle"
|
|
title="{% trans "Containers listed below were either not created through panel or were not saved to database properly" %}"></i>
|
|
</h3>
|
|
|
|
<table cellpadding="0" cellspacing="0" border="0" class="table table-striped table-bordered"
|
|
id="datatable-example">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
|
|
{% for container in unlistedContainers %}
|
|
<tr>
|
|
<td>{{ container.name }}</td>
|
|
<td>{{ container.status }}</td>
|
|
<td>
|
|
<button class="btn btn-primary"
|
|
ng-click="delContainer('{{ container.name }}', true)"><i
|
|
class="fa fa-trash"></i></button>
|
|
<button class="btn btn-primary" ng-click="showLog('{{ container.name }}')"><i
|
|
class="fa fa-file"></i></button>
|
|
<button class="btn btn-primary"
|
|
ng-click="assignContainer('{{ container.name }}')"><i
|
|
class="fa fa-user"></i></button>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
|
|
</tbody>
|
|
</table>
|
|
|
|
{% endif %}
|
|
|
|
<div id="logs" class="modal fade" role="dialog">
|
|
<div class="modal-dialog">
|
|
|
|
<!-- Modal content-->
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
<h4 class="modal-title">Container logs</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<textarea name="logs" class="form-control" id="" cols="30"
|
|
rows="10">{$ logs $}</textarea>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-primary" ng-click="showLog('', true)">Refresh
|
|
</button>
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="assign" class="modal fade" role="dialog">
|
|
<div class="modal-dialog">
|
|
|
|
<!-- Modal content-->
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
<h4 class="modal-title">Assign Container to user</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form action="/" class="form-horizontal">
|
|
<div ng-hide="installationDetailsForm" class="form-group">
|
|
<label class="col-sm-3 control-label">{% trans "Select Owner" %}</label>
|
|
<div class="col-sm-6">
|
|
<select ng-model="dockerOwner" class="form-control">
|
|
{% for user in adminNames %}
|
|
<option>{{ user }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-primary" ng-click="submitAssignContainer()">
|
|
Submit
|
|
</button>
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
<div ng-hide="conatinerview">
|
|
<div>
|
|
|
|
<div ng-show="ContainerList.length > 0">
|
|
<div ng-repeat="web in ContainerList">
|
|
<!-- n8n Container Section -->
|
|
<div ng-if="web.environment && (web.environment | filter:'n8n').length > 0" class="n8n-container">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h3 class="m-0">
|
|
<i class="fa fa-cube"></i> n8n Container: {$ web.name $}
|
|
<span class="workflow-status" ng-class="{'active': web.status === 'running', 'error': web.status !== 'running'}">
|
|
{$ web.status $}
|
|
</span>
|
|
</h3>
|
|
<div class="quick-actions">
|
|
<button class="quick-action-btn btn-success" ng-click="handleAction('start', web)" ng-if="web.status !== 'running'">
|
|
<i class="fa fa-play"></i> Start
|
|
</button>
|
|
<button class="quick-action-btn btn-warning" ng-click="handleAction('restart', web)" ng-if="web.status === 'running'">
|
|
<i class="fa fa-refresh"></i> Restart
|
|
</button>
|
|
<button class="quick-action-btn btn-danger" ng-click="handleAction('stop', web)" ng-if="web.status === 'running'">
|
|
<i class="fa fa-stop"></i> Stop
|
|
</button>
|
|
<button class="quick-action-btn btn-primary" ng-click="openSettings(web)">
|
|
<i class="fa fa-cog"></i> Settings
|
|
</button>
|
|
<a class="quick-action-btn btn-info" href="http://{$ location.hostname $}:{$ web.ports['5678/tcp'][0].HostPort $}" target="_blank" ng-if="web.status === 'running'">
|
|
<i class="fa fa-external-link"></i> Open n8n
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container-fluid p-0 mt-4">
|
|
<div class="row g-4">
|
|
<div class="col-md-6">
|
|
<div class="info-box shadow-sm">
|
|
<h4 class="border-bottom pb-2 mb-3">Resource Usage</h4>
|
|
<div class="n8n-metrics">
|
|
<div class="metric-card">
|
|
<h5>Memory Usage</h5>
|
|
<div class="value">{$ web.memoryUsage $}</div>
|
|
<div class="progress mt-2">
|
|
<div class="progress-bar" role="progressbar"
|
|
ng-style="{'width': web.memoryUsagePercent + '%'}"
|
|
aria-valuenow="{$ web.memoryUsagePercent $}"
|
|
aria-valuemin="0"
|
|
aria-valuemax="100">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="metric-card">
|
|
<h5>CPU Usage</h5>
|
|
<div class="value">{$ web.cpuUsagePercent | number:1 $}%</div>
|
|
<div class="progress mt-2">
|
|
<div class="progress-bar" role="progressbar"
|
|
ng-style="{'width': web.cpuUsagePercent + '%'}"
|
|
aria-valuenow="{$ web.cpuUsagePercent $}"
|
|
aria-valuemin="0"
|
|
aria-valuemax="100">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="metric-card">
|
|
<h5>Container Uptime</h5>
|
|
<div class="value">{$ web.uptime $}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-box shadow-sm mt-4">
|
|
<h4 class="border-bottom pb-2 mb-3">Network & Ports</h4>
|
|
<div ng-if="web.ports">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Container Port</th>
|
|
<th>Host Binding</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="(containerPort, hostBindings) in web.ports">
|
|
<td><code>{$ containerPort $}</code></td>
|
|
<td>
|
|
<span ng-repeat="binding in hostBindings" class="badge bg-light text-dark me-2">
|
|
{$ binding.HostIp || '0.0.0.0' $}:{$ binding.HostPort $}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="info-box shadow-sm">
|
|
<h4 class="border-bottom pb-2 mb-3">Volumes</h4>
|
|
<div ng-if="web.volumes && web.volumes.length > 0">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Source</th>
|
|
<th>Destination</th>
|
|
<th>Mode</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="volume in web.volumes">
|
|
<td><code>{$ volume.Source $}</code></td>
|
|
<td><code>{$ volume.Destination $}</code></td>
|
|
<td><span class="badge bg-secondary">{$ volume.Mode $}</span></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div ng-if="!web.volumes || web.volumes.length === 0" class="text-muted">
|
|
<p class="mb-0">No volumes mounted</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-box shadow-sm mt-4">
|
|
<h4 class="border-bottom pb-2 mb-3">Environment Variables</h4>
|
|
<div ng-if="web.environment && web.environment.length > 0">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Variable</th>
|
|
<th>Value</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="env in web.environment">
|
|
<td><code>{$ env.split('=')[0] $}</code></td>
|
|
<td>
|
|
<code ng-if="env.split('=')[1] === '********'">{$ env.split('=')[1] $}</code>
|
|
<code ng-if="env.split('=')[1] !== '********'">{$ env.split('=')[1] $}</code>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div ng-if="!web.environment || web.environment.length === 0" class="text-muted">
|
|
<p class="mb-0">No environment variables set</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-box shadow-sm mt-4">
|
|
<h4 class="border-bottom pb-2 mb-3">
|
|
{% trans "Logs" %}
|
|
<span style="cursor:pointer;" class="float-end" ng-click="getcontainerlog(web.id)">
|
|
<i class="fa fa-refresh btn-icon"></i>
|
|
</span>
|
|
</h4>
|
|
<div class="content-box-wrapper">
|
|
<textarea name="logs" class="form-control" cols="30" rows="10">{$ web.logs $}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Regular Container Section -->
|
|
<div ng-if="!web.environment || (web.environment | filter:'n8n').length === 0" class="mb-5">
|
|
<div id="page-title" class="mb-4">
|
|
<h2 id="domainNamePage" class="d-flex justify-content-between align-items-center">
|
|
<span>{% trans "Currently managing: " %} {$ web.name $}</span>
|
|
<div class="btn-group" role="group" aria-label="Container Actions">
|
|
<button class="btn btn-success btn-sm" ng-click="handleAction('start', web)" ng-if="web.status !== 'running'" title="Start Container">
|
|
<i class="fa fa-play"></i> Start
|
|
</button>
|
|
<button class="btn btn-warning btn-sm" ng-click="handleAction('restart', web)" ng-if="web.status === 'running'" title="Restart Container">
|
|
<i class="fa fa-refresh"></i> Restart
|
|
</button>
|
|
<button class="btn btn-danger btn-sm" ng-click="handleAction('stop', web)" ng-if="web.status === 'running'" title="Stop Container">
|
|
<i class="fa fa-stop"></i> Stop
|
|
</button>
|
|
<button class="btn btn-primary btn-sm" ng-click="openSettings(web)" title="Container Settings">
|
|
<i class="fa fa-cog"></i> Settings
|
|
</button>
|
|
<a class="btn btn-info btn-sm" href="http://{$ location.hostname $}:{$ web.ports['5678/tcp'][0].HostPort $}" target="_blank" ng-if="web.status === 'running'" title="Open n8n Interface">
|
|
<i class="fa fa-external-link"></i> Open n8n
|
|
</a>
|
|
</div>
|
|
</h2>
|
|
<p class="text-muted">
|
|
{% trans "Container ID" %}: <code>{$ web.id $}</code>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="container-fluid p-0">
|
|
<div class="panel panel-body">
|
|
<h3 class="content-box-header d-flex justify-content-between align-items-center mb-4">
|
|
{% trans "Container Information" %}
|
|
<img id="infoLoading" src="/static/images/loading.gif" style="display: none;">
|
|
</h3>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-md-6">
|
|
<div class="info-box shadow-sm">
|
|
<h4 class="border-bottom pb-2 mb-3">Basic Information</h4>
|
|
<div class="info-list">
|
|
<p class="mb-2"><strong>Container ID:</strong> <code>{$ web.id $}</code></p>
|
|
<p class="mb-2"><strong>Status:</strong>
|
|
<span class="label px-3 py-1 rounded" ng-class="{'label-success': web.status === 'running', 'label-danger': web.status === 'stopped', 'label-warning': web.status !== 'running' && web.status !== 'stopped'}">
|
|
{$ web.status $}
|
|
</span>
|
|
</p>
|
|
<p class="mb-2"><strong>Created:</strong> {$ web.created | date:'medium' $}</p>
|
|
<p class="mb-2"><strong>Uptime:</strong> {$ web.uptime $}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-box shadow-sm mt-4">
|
|
<h4 class="border-bottom pb-2 mb-3">Resource Usage</h4>
|
|
<div class="mb-3">
|
|
<label class="mb-2">Memory Usage</label>
|
|
<div class="progress" style="height: 20px;">
|
|
<div class="progress-bar" role="progressbar"
|
|
ng-style="{'width': web.memoryUsagePercent + '%'}"
|
|
aria-valuenow="{$ web.memoryUsagePercent $}"
|
|
aria-valuemin="0"
|
|
aria-valuemax="100">
|
|
{$ web.memoryUsage $}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="mb-2">CPU Usage</label>
|
|
<div class="progress" style="height: 20px;">
|
|
<div class="progress-bar" role="progressbar"
|
|
ng-style="{'width': web.cpuUsagePercent + '%'}"
|
|
aria-valuenow="{$ web.cpuUsagePercent $}"
|
|
aria-valuemin="0"
|
|
aria-valuemax="100">
|
|
{$ web.cpuUsagePercent | number:1 $}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="info-box shadow-sm">
|
|
<h4 class="border-bottom pb-2 mb-3">Network & Ports</h4>
|
|
<div ng-if="web.ports">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Container Port</th>
|
|
<th>Host Binding</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="(containerPort, hostBindings) in web.ports">
|
|
<td><code>{$ containerPort $}</code></td>
|
|
<td>
|
|
<span ng-repeat="binding in hostBindings" class="badge bg-light text-dark me-2">
|
|
{$ binding.HostIp || '0.0.0.0' $}:{$ binding.HostPort $}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div ng-if="!web.ports" class="text-muted">
|
|
<p class="mb-0">No ports exposed</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-box shadow-sm mt-4">
|
|
<h4 class="border-bottom pb-2 mb-3">Volumes</h4>
|
|
<div ng-if="web.volumes && web.volumes.length > 0">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Source</th>
|
|
<th>Destination</th>
|
|
<th>Mode</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="volume in web.volumes">
|
|
<td><code>{$ volume.Source $}</code></td>
|
|
<td><code>{$ volume.Destination $}</code></td>
|
|
<td><span class="badge bg-secondary">{$ volume.Mode $}</span></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div ng-if="!web.volumes || web.volumes.length === 0" class="text-muted">
|
|
<p class="mb-0">No volumes mounted</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-box shadow-sm mt-4">
|
|
<h4 class="border-bottom pb-2 mb-3">Environment Variables</h4>
|
|
<div ng-if="web.environment && web.environment.length > 0">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Variable</th>
|
|
<th>Value</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="env in web.environment">
|
|
<td><code>{$ env.split('=')[0] $}</code></td>
|
|
<td>
|
|
<code ng-if="env.split('=')[1] === '********'">{$ env.split('=')[1] $}</code>
|
|
<code ng-if="env.split('=')[1] !== '********'">{$ env.split('=')[1] $}</code>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div ng-if="!web.environment || web.environment.length === 0" class="text-muted">
|
|
<p class="mb-0">No environment variables set</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="example-box-wrapper">
|
|
<div class="content-box panel-body">
|
|
<h3 class="content-box-header">
|
|
{% trans "Logs" %}
|
|
<span style="cursor:pointer;" class="pull-right" ng-click="getcontainerlog(web.id)"><i
|
|
class="fa fa-refresh btn-icon"></i></span>
|
|
</h3>
|
|
<div class="content-box-wrapper">
|
|
<div class="row">
|
|
<textarea name="logs" class="form-control" cols="30"
|
|
rows="10">{$ web.logs $}</textarea><br>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="settings" class="modal fade" role="dialog">
|
|
<div class="modal-dialog">
|
|
|
|
<!-- Modal content-->
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
<h4 class="modal-title">Container Settings
|
|
<img id="containerSettingLoading" src="/static/images/loading.gif"
|
|
style="display: none;">
|
|
</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
|
|
<form name="containerSettingsForm" action="/" class="form-horizontal">
|
|
<div ng-hide="installationDetailsForm" class="form-group">
|
|
<label class="col-sm-3 control-label">{% trans "Memory limit" %}</label>
|
|
<div class="col-sm-6">
|
|
<input name="memory" type="number" class="form-control"
|
|
ng-model="web.memoryLimit" required>
|
|
</div>
|
|
<div class="current-pack ng-binding">MB</div>
|
|
</div>
|
|
|
|
<div ng-hide="installationDetailsForm" class="form-group">
|
|
<label class="col-sm-3 control-label">Start on reboot</label>
|
|
<div class="col-sm-9">
|
|
<div class="checkbox">
|
|
<label>
|
|
<input ng-model="web.startOnReboot" type="checkbox">
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
<div ng-hide="installationDetailsForm" class="form-group">
|
|
<label class="col-sm-3 control-label">{% trans "Confirmation" %}</label>
|
|
<div class="col-sm-9">
|
|
<div class="checkbox">
|
|
<label>
|
|
<input ng-model="envConfirmation" type="checkbox">
|
|
Editing ENV or Volume will recreate container.
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
<span ng-init="envList = {}"></span>
|
|
{% for env, value in envList.items %}
|
|
|
|
<span ng-init="envList[{{ forloop.counter0 }}] = {'name':'{{ env }}' , 'value':'{{ value }}'} "></span>
|
|
|
|
{% endfor %}
|
|
|
|
<div ng-repeat="env in envList track by $index">
|
|
|
|
<div ng-hide="installationDetailsForm" class="form-group">
|
|
<label class="col-sm-3 control-label">
|
|
<div ng-show="$first">
|
|
{% trans "ENV" %}
|
|
</div>
|
|
</label>
|
|
<div class="col-sm-2">
|
|
<input name="$index" ng-disabled="!envConfirmation" type="text"
|
|
class="form-control" ng-model="envList[$index].name"
|
|
required>
|
|
</div>
|
|
<div class="col-sm-4">
|
|
<input name="$index" ng-disabled="!envConfirmation" type="text"
|
|
class="form-control" ng-model="envList[$index].value"
|
|
required>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="col-md-offset-3">
|
|
<button type="button" ng-disabled="!envConfirmation" class="btn btn-info"
|
|
ng-click="addEnvField()">Add more
|
|
</button>
|
|
</div>
|
|
<br>
|
|
|
|
<span ng-init="volList = {}"></span>
|
|
<span ng-init="volListNumber=1"></span>
|
|
{% for key, value in volList.items %}
|
|
<span ng-init="volList[{{ forloop.counter0 }}] = {'dest':'{{ value.bind }}' , 'src':'{{ key }}'}"></span>
|
|
<span ng-init="volListNumber={{ forloop.counter0 }} + 1"></span>
|
|
{% endfor %}
|
|
|
|
<hr>
|
|
|
|
<div ng-hide="installationDetailsForm" class="form-group text-center">
|
|
<label class="control-label">
|
|
{% trans "Map Volumes" %}
|
|
</label>
|
|
</div>
|
|
<div ng-repeat="volume in volList track by $index">
|
|
<div ng-hide="installationDetailsForm" class="form-group">
|
|
<div class="col-sm-5">
|
|
<input type="text" ng-disabled="!envConfirmation"
|
|
class="form-control"
|
|
ng-model="volList[$index].dest" placeholder="Destination"
|
|
required>
|
|
</div>
|
|
<div class="col-sm-5">
|
|
<input type="text" ng-disabled="!envConfirmation"
|
|
class="form-control"
|
|
ng-model="volList[$index].src" placeholder="Source" required>
|
|
</div>
|
|
<div ng-show="$last">
|
|
<div class="col-sm-1">
|
|
<button class="btn btn-primary" ng-disabled="!envConfirmation"
|
|
type="button"
|
|
ng-click="removeVolField()"><i
|
|
style="position: inherit; top: 0px; left: 0px"
|
|
class="fa fa-times"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div ng-hide="installationDetailsForm" class="text-center">
|
|
<button type="button" ng-disabled="!envConfirmation" class="btn btn-info"
|
|
ng-click="addVolField()">{% trans "Add field" %}</button>
|
|
</div>
|
|
<br>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" ng-disabled="savingSettings" class="btn btn-primary"
|
|
ng-click="saveSettings()">Save
|
|
</button>
|
|
<button type="button" ng-disabled="savingSettings" class="btn btn-default"
|
|
data-dismiss="modal">
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="processes" class="modal fade" role="dialog">
|
|
<div class="modal-dialog" style="width: 96%;">
|
|
|
|
<!-- Modal content-->
|
|
<div class="modal-content panel-body">
|
|
<div class="modal-header">
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
<h4 class="modal-title content-box=header">Container Processes</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
|
|
<table cellpadding="0" cellspacing="0" border="0" class="table table-striped"
|
|
id="datatable-example">
|
|
<thead>
|
|
<tr>
|
|
<th ng-repeat="item in topHead track by $index">{$ item $}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
|
|
<tr ng-repeat="process in topProcesses track by $index">
|
|
<th ng-repeat="item in process track by $index">{$ item $}</th>
|
|
</tr>
|
|
|
|
</tbody>
|
|
</table>
|
|
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" ng-disabled="savingSettings" class="btn btn-primary"
|
|
ng-click="showTop()">
|
|
Refresh
|
|
</button>
|
|
<button type="button" ng-disabled="savingSettings" class="btn btn-default"
|
|
data-dismiss="modal">
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{% endblock %}
|