mirror of
https://github.com/dockpeek/dockpeek.git
synced 2026-01-07 10:00:10 -06:00
modular py
This commit is contained in:
13
Dockerfile
13
Dockerfile
@@ -1,15 +1,18 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Ustawianie argumentów przekazywanych podczas budowania
|
||||
ARG BUILD_DATE
|
||||
ARG VERSION
|
||||
ARG VCS_REF
|
||||
|
||||
# Ustawianie zmiennych środowiskowych dla Pythona i PIP
|
||||
ENV VERSION=${VERSION} \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
|
||||
# Metadane obrazu zgodne ze standardem Open Containers Initiative
|
||||
LABEL org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
org.opencontainers.image.version="${VERSION}" \
|
||||
org.opencontainers.image.revision="${VCS_REF}" \
|
||||
@@ -19,13 +22,21 @@ LABEL org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
org.opencontainers.image.title="Dockpeek" \
|
||||
org.opencontainers.image.description="Docker container monitoring and management tool"
|
||||
|
||||
# Ustawienie katalogu roboczego w kontenerze
|
||||
WORKDIR /app
|
||||
|
||||
# Kopiowanie pliku z zależnościami
|
||||
COPY requirements.txt .
|
||||
|
||||
# Instalacja zależności (ten krok jest cachowany przez Dockera)
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Kopiowanie reszty kodu aplikacji
|
||||
COPY . .
|
||||
|
||||
# Uwidocznienie portu, na którym działa aplikacja
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
# ZAKTUALIZOWANA KOMENDA URUCHOMIENIOWA
|
||||
# Używamy Gunicorn do uruchomienia aplikacji w trybie produkcyjnym,
|
||||
CMD ["gunicorn", "--workers", "4", "--bind", "0.0.0.0:8000", "run:app"]
|
||||
30
config.py
Normal file
30
config.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
class Config:
|
||||
"""Główna klasa konfiguracyjna."""
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY")
|
||||
if not SECRET_KEY:
|
||||
raise RuntimeError("ERROR: SECRET_KEY environment variable is not set.")
|
||||
|
||||
# Dane logowania
|
||||
ADMIN_USERNAME = os.environ.get("USERNAME")
|
||||
ADMIN_PASSWORD = os.environ.get("PASSWORD")
|
||||
if not ADMIN_USERNAME or not ADMIN_PASSWORD:
|
||||
raise RuntimeError("USERNAME and PASSWORD environment variables must be set.")
|
||||
|
||||
# Ustawienia funkcji aplikacji
|
||||
TRAEFIK_ENABLE = os.environ.get("TRAEFIK_LABELS", "true").lower() == "true"
|
||||
TAGS_ENABLE = os.environ.get("TAGS", "true").lower() == "true"
|
||||
|
||||
# Czas życia sesji
|
||||
PERMANENT_SESSION_LIFETIME = timedelta(days=14)
|
||||
|
||||
# Wersja aplikacji
|
||||
APP_VERSION = os.environ.get('VERSION', 'dev')
|
||||
|
||||
# Konfiguracja Dockera
|
||||
DOCKER_TIMEOUT = 0.5
|
||||
|
||||
# Ustawienia logowania
|
||||
LOG_LEVEL = "INFO"
|
||||
30
dockpeek/__init__.py
Normal file
30
dockpeek/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import os
|
||||
import logging
|
||||
from flask import Flask
|
||||
from config import Config
|
||||
from .extensions import login_manager, cors
|
||||
|
||||
def create_app(config_class=Config):
|
||||
"""
|
||||
Tworzy i konfiguruje instancję aplikacji Flask.
|
||||
"""
|
||||
# Konfiguracja logowania
|
||||
logging.basicConfig(level=config_class.LOG_LEVEL, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logging.getLogger('werkzeug').setLevel(logging.WARNING)
|
||||
|
||||
# Inicjalizacja aplikacji
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config_class)
|
||||
|
||||
# Inicjalizacja rozszerzeń
|
||||
login_manager.init_app(app)
|
||||
cors.init_app(app)
|
||||
|
||||
# Rejestracja Blueprints
|
||||
from . import auth
|
||||
app.register_blueprint(auth.auth_bp)
|
||||
|
||||
from . import main
|
||||
app.register_blueprint(main.main_bp)
|
||||
|
||||
return app
|
||||
54
dockpeek/auth.py
Normal file
54
dockpeek/auth.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from flask import (
|
||||
Blueprint, render_template, request, redirect, url_for, session, current_app
|
||||
)
|
||||
from flask_login import UserMixin, login_user, logout_user, login_required
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from .extensions import login_manager
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
# Przechowywanie użytkowników (teraz pobiera dane z konfiguracji aplikacji)
|
||||
def get_users():
|
||||
return {
|
||||
current_app.config['ADMIN_USERNAME']: {
|
||||
"password": generate_password_hash(current_app.config['ADMIN_PASSWORD'])
|
||||
}
|
||||
}
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
if user_id in get_users():
|
||||
return User(user_id)
|
||||
return None
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized_callback():
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
@auth_bp.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
error = None
|
||||
if request.method == "POST":
|
||||
username = request.form.get("username")
|
||||
password = request.form.get("password")
|
||||
|
||||
users = get_users()
|
||||
user_record = users.get(username)
|
||||
|
||||
if user_record and check_password_hash(user_record["password"], password):
|
||||
login_user(User(username))
|
||||
session.permanent = True
|
||||
return redirect(url_for("main.index"))
|
||||
else:
|
||||
error = "Invalid credentials. Please try again."
|
||||
return render_template("login.html", error=error)
|
||||
|
||||
@auth_bp.route("/logout")
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for("auth.login"))
|
||||
270
dockpeek/docker_utils.py
Normal file
270
dockpeek/docker_utils.py
Normal file
@@ -0,0 +1,270 @@
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Lock
|
||||
from urllib.parse import urlparse
|
||||
import docker
|
||||
from docker.client import DockerClient
|
||||
from packaging import version
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from flask import request
|
||||
|
||||
# Używamy loggera skonfigurowanego w __init__.py
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Globalny executor i klasa UpdateChecker pozostają bez zmian
|
||||
executor = ThreadPoolExecutor(max_workers=4)
|
||||
|
||||
class UpdateChecker:
|
||||
# ... (cała klasa UpdateChecker skopiowana z app.py bez zmian) ...
|
||||
def __init__(self):
|
||||
self.cache = {}
|
||||
self.lock = Lock()
|
||||
self.cache_duration = 300 # 5 minutes
|
||||
|
||||
def get_cache_key(self, server_name, container_name, image_name):
|
||||
return f"{server_name}:{container_name}:{image_name}"
|
||||
|
||||
def is_cache_valid(self, timestamp):
|
||||
return datetime.now() - timestamp < timedelta(seconds=self.cache_duration)
|
||||
|
||||
def get_cached_result(self, cache_key):
|
||||
with self.lock:
|
||||
if cache_key in self.cache:
|
||||
result, timestamp = self.cache[cache_key]
|
||||
if self.is_cache_valid(timestamp):
|
||||
return result, True
|
||||
return None, False
|
||||
|
||||
def set_cache_result(self, cache_key, result):
|
||||
with self.lock:
|
||||
self.cache[cache_key] = (result, datetime.now())
|
||||
|
||||
def check_local_image_updates(self, client, container, server_name):
|
||||
try:
|
||||
container_image_id = container.attrs.get('Image', '')
|
||||
if not container_image_id: return False
|
||||
image_name = container.attrs.get('Config', {}).get('Image', '')
|
||||
if not image_name: return False
|
||||
if ':' in image_name: base_name, current_tag = image_name.rsplit(':', 1)
|
||||
else: base_name, current_tag = image_name, 'latest'
|
||||
try:
|
||||
local_image = client.images.get(f"{base_name}:{current_tag}")
|
||||
return container_image_id != local_image.id
|
||||
except Exception: return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking local image updates for container '{container.name}'")
|
||||
return False
|
||||
|
||||
def check_image_updates_async(self, client, container, server_name):
|
||||
try:
|
||||
container_image_id = container.attrs.get('Image', '')
|
||||
if not container_image_id: return False
|
||||
image_name = container.attrs.get('Config', {}).get('Image', '')
|
||||
if not image_name: return False
|
||||
cache_key = self.get_cache_key(server_name, container.name, image_name)
|
||||
cached_result, is_valid = self.get_cached_result(cache_key)
|
||||
if is_valid:
|
||||
logger.info(f"🔄[ {server_name} ] - Using cached update result for {image_name}: {cached_result}")
|
||||
return cached_result
|
||||
if ':' in image_name: base_name, current_tag = image_name.rsplit(':', 1)
|
||||
else: base_name, current_tag = image_name, 'latest'
|
||||
try:
|
||||
client.images.pull(base_name, tag=current_tag)
|
||||
updated_image = client.images.get(f"{base_name}:{current_tag}")
|
||||
result = container_image_id != updated_image.id
|
||||
self.set_cache_result(cache_key, result)
|
||||
if result: logger.info(f" [ {server_name} ] - Update available - ⬆️{base_name} :{current_tag}")
|
||||
else: logger.info(f" [ {server_name} ] - Image is up to date - ✅{base_name} :{current_tag}")
|
||||
return result
|
||||
except Exception as pull_error:
|
||||
logger.warning(f" [ {server_name} ] - Cannot pull latest version of - ⚠️{base_name} :{current_tag} - it might be a locally built image")
|
||||
self.set_cache_result(cache_key, False)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error checking image updates for '{container.name}'")
|
||||
return False
|
||||
|
||||
# Globalna instancja
|
||||
update_checker = UpdateChecker()
|
||||
|
||||
# ... (wszystkie funkcje pomocnicze _extract_hostname_from_url, _is_likely_internal_hostname, _get_link_hostname skopiowane z app.py bez zmian) ...
|
||||
def _extract_hostname_from_url(url, is_docker_host):
|
||||
if not url: return None
|
||||
if url.startswith("unix://"): return None
|
||||
if url.startswith("tcp://"):
|
||||
try:
|
||||
parsed = urlparse(url)
|
||||
hostname = parsed.hostname
|
||||
if hostname and hostname not in ["127.0.0.1", "0.0.0.0", "localhost"] and not _is_likely_internal_hostname(hostname, is_docker_host):
|
||||
return hostname
|
||||
except Exception: pass
|
||||
try:
|
||||
match = re.search(r"(?:tcp://)?([^:]+)(?::\d+)?", url)
|
||||
if match:
|
||||
hostname = match.group(1)
|
||||
if hostname not in ["127.0.0.1", "0.0.0.0", "localhost"] and not _is_likely_internal_hostname(hostname, is_docker_host):
|
||||
return hostname
|
||||
except Exception: pass
|
||||
return None
|
||||
|
||||
def _is_likely_internal_hostname(hostname, is_docker_host):
|
||||
if not is_docker_host: return False
|
||||
if re.match(r'^(\d{1,3}\.){3}\d{1,3}$', hostname): return False
|
||||
if '.' in hostname: return False
|
||||
return True
|
||||
|
||||
def _get_link_hostname(public_hostname, host_ip, is_docker_host):
|
||||
if public_hostname: return public_hostname
|
||||
if host_ip and host_ip not in ['0.0.0.0', '127.0.0.1']: return host_ip
|
||||
try: return request.host.split(":")[0]
|
||||
except: return "localhost"
|
||||
|
||||
|
||||
def discover_docker_clients():
|
||||
# ... (cała funkcja discover_docker_clients skopiowana z app.py, ale DOCKER_TIMEOUT pobieramy z config.py) ...
|
||||
# Zamiast DOCKER_TIMEOUT użyj current_app.config['DOCKER_TIMEOUT']
|
||||
# Jednak dla prostoty zostawmy stałą wartość, bo ta funkcja nie ma dostępu do kontekstu aplikacji
|
||||
DOCKER_TIMEOUT = 0.5
|
||||
clients = []
|
||||
if "DOCKER_HOST" in os.environ:
|
||||
host_url = os.environ.get("DOCKER_HOST")
|
||||
host_name = os.environ.get("DOCKER_HOST_NAME", "default")
|
||||
public_hostname = os.environ.get("DOCKER_HOST_PUBLIC_HOSTNAME") or _extract_hostname_from_url(host_url, True)
|
||||
try:
|
||||
client = DockerClient(base_url=host_url, timeout=DOCKER_TIMEOUT)
|
||||
client.ping()
|
||||
clients.append({"name": host_name, "client": client, "url": host_url, "public_hostname": public_hostname, "status": "active", "is_docker_host": True, "order": 0})
|
||||
except Exception:
|
||||
logger.error(f"Could not connect to DOCKER_HOST '{host_name}' at '{host_url}'")
|
||||
clients.append({"name": host_name, "client": None, "url": host_url, "public_hostname": public_hostname, "status": "inactive", "is_docker_host": True, "order": 0})
|
||||
host_vars = {k: v for k, v in os.environ.items() if re.match(r"^DOCKER_HOST_\d+_URL$", k)}
|
||||
for key, url in host_vars.items():
|
||||
match = re.match(r"^DOCKER_HOST_(\d+)_URL$", key)
|
||||
if match:
|
||||
num = match.group(1)
|
||||
name = os.environ.get(f"DOCKER_HOST_{num}_NAME", f"server{num}")
|
||||
public_hostname = os.environ.get(f"DOCKER_HOST_{num}_PUBLIC_HOSTNAME") or _extract_hostname_from_url(url, False)
|
||||
try:
|
||||
client = DockerClient(base_url=url, timeout=DOCKER_TIMEOUT)
|
||||
client.ping()
|
||||
logger.info(f"[ {name} ] Docker host is active")
|
||||
clients.append({"name": name, "client": client, "url": url, "public_hostname": public_hostname, "status": "active", "is_docker_host": False, "order": int(num)})
|
||||
except Exception:
|
||||
logger.error(f"[ {name} ] Could not connect to Docker host at {url}")
|
||||
clients.append({"name": name, "client": None, "url": url, "public_hostname": public_hostname, "status": "inactive", "is_docker_host": False, "order": int(num)})
|
||||
if not clients:
|
||||
fallback_name = os.environ.get("DOCKER_NAME", "default")
|
||||
try:
|
||||
client = docker.from_env(timeout=DOCKER_TIMEOUT)
|
||||
client.ping()
|
||||
clients.append({"name": fallback_name, "client": client, "url": "unix:///var/run/docker.sock", "public_hostname": "", "status": "active", "is_docker_host": True, "order": 0})
|
||||
except Exception:
|
||||
clients.append({"name": fallback_name, "client": None, "url": "unix:///var/run/docker.sock", "public_hostname": "", "status": "inactive", "is_docker_host": True, "order": 0})
|
||||
return clients
|
||||
|
||||
def get_container_status_with_exit_code(container):
|
||||
# ... (cała funkcja get_container_status_with_exit_code skopiowana z app.py bez zmian) ...
|
||||
try:
|
||||
base_status = container.status
|
||||
state = container.attrs.get('State', {})
|
||||
exit_code = state.get('ExitCode')
|
||||
if base_status in ['exited', 'dead']: return base_status, exit_code
|
||||
if base_status in ['paused', 'restarting', 'removing', 'created']: return base_status, None
|
||||
if base_status == 'running':
|
||||
health = state.get('Health', {})
|
||||
if health:
|
||||
health_status = health.get('Status', '')
|
||||
if health_status == 'healthy': return 'healthy', None
|
||||
if health_status == 'unhealthy': return 'unhealthy', exit_code
|
||||
if health_status == 'starting': return 'starting', None
|
||||
return 'running', None
|
||||
return base_status, None
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting status for container {container.name}: {e}")
|
||||
return container.status, None
|
||||
|
||||
def get_all_data():
|
||||
# ... (cała funkcja get_all_data skopiowana z app.py, z modyfikacją pobierania konfiguracji) ...
|
||||
# Zamiast TRAEFIK_ENABLE i TAGS_ENABLE użyj current_app.config['TRAEFIK_ENABLE'] itd.
|
||||
from flask import current_app
|
||||
TRAEFIK_ENABLE = current_app.config['TRAEFIK_ENABLE']
|
||||
TAGS_ENABLE = current_app.config['TAGS_ENABLE']
|
||||
|
||||
servers = discover_docker_clients()
|
||||
if not servers: return {"servers": [], "containers": []}
|
||||
all_container_data = []
|
||||
server_list_for_json = [{"name": s["name"], "status": s["status"], "order": s["order"], "url": s["url"]} for s in servers]
|
||||
for host in servers:
|
||||
if host['status'] == 'inactive': continue
|
||||
try:
|
||||
server_name, client, public_hostname, is_docker_host = host["name"], host["client"], host["public_hostname"], host["is_docker_host"]
|
||||
containers = client.containers.list(all=True)
|
||||
for container in containers:
|
||||
try:
|
||||
image_name = container.attrs.get('Config', {}).get('Image', 'unknown')
|
||||
cache_key = update_checker.get_cache_key(server_name, container.name, image_name)
|
||||
cached_update, is_cache_valid = update_checker.get_cached_result(cache_key)
|
||||
container_status, exit_code = get_container_status_with_exit_code(container)
|
||||
labels = container.attrs.get('Config', {}).get('Labels', {}) or {}
|
||||
stack_name = labels.get('com.docker.compose.project', '')
|
||||
source_url = labels.get('org.opencontainers.image.source') or labels.get('org.opencontainers.image.url', '')
|
||||
https_ports = labels.get('dockpeek.https', '')
|
||||
custom_url = labels.get('dockpeek.link', '')
|
||||
custom_ports = labels.get('dockpeek.ports', '') or labels.get('dockpeek.port', '')
|
||||
custom_tags = labels.get('dockpeek.tags', '') or labels.get('dockpeek.tag', '')
|
||||
tags = [tag.strip() for tag in custom_tags.split(',') if tag.strip()] if TAGS_ENABLE and custom_tags else []
|
||||
traefik_routes = []
|
||||
if TRAEFIK_ENABLE and labels.get('traefik.enable', '').lower() != 'false':
|
||||
for key, value in labels.items():
|
||||
if key.startswith('traefik.http.routers.') and key.endswith('.rule'):
|
||||
router_name = key.split('.')[3]
|
||||
host_matches = re.findall(r'Host\(`([^`]+)`\)', value)
|
||||
for host_match in host_matches:
|
||||
tls_key = f'traefik.http.routers.{router_name}.tls'
|
||||
is_tls = labels.get(tls_key, '').lower() == 'true'
|
||||
entrypoints_key = f'traefik.http.routers.{router_name}.entrypoints'
|
||||
entrypoints_str = labels.get(entrypoints_key, '')
|
||||
is_https_entrypoint = any('https' in ep or '443' in ep for ep in entrypoints_str.split(',')) if entrypoints_str else False
|
||||
protocol = 'https' if is_tls or is_https_entrypoint else 'http'
|
||||
url = f"{protocol}://{host_match}"
|
||||
path_match = re.search(r'PathPrefix\(`([^`]+)`\)', value)
|
||||
if path_match: url += path_match.group(1)
|
||||
traefik_routes.append({'router': router_name, 'url': url, 'rule': value, 'host': host_match})
|
||||
https_ports_list = [str(p.strip()) for p in https_ports.split(',') if p.strip()] if https_ports else []
|
||||
port_map = []
|
||||
custom_ports_list = [str(p.strip()) for p in custom_ports.split(',') if p.strip()] if custom_ports else []
|
||||
ports = container.attrs['NetworkSettings']['Ports']
|
||||
if ports:
|
||||
for container_port, mappings in ports.items():
|
||||
if mappings:
|
||||
m = mappings[0]
|
||||
host_port, host_ip = m['HostPort'], m.get('HostIp', '0.0.0.0')
|
||||
link_hostname = _get_link_hostname(public_hostname, host_ip, is_docker_host)
|
||||
is_https = "443" in container_port or host_port == "443" or str(host_port) in https_ports_list
|
||||
protocol = "https" if is_https else "http"
|
||||
link = f"{protocol}://{link_hostname}" + (f":{host_port}" if host_port != "443" else "")
|
||||
port_map.append({'container_port': container_port, 'host_port': host_port, 'link': link, 'is_custom': False})
|
||||
if custom_ports_list:
|
||||
link_hostname = _get_link_hostname(public_hostname, None, is_docker_host)
|
||||
for port in custom_ports_list:
|
||||
is_https = port == "443" or str(port) in https_ports_list
|
||||
protocol = "https" if is_https else "http"
|
||||
link = f"{protocol}://{link_hostname}" + (f":{port}" if port != "443" else "")
|
||||
port_map.append({'container_port': '', 'host_port': port, 'link': link, 'is_custom': True})
|
||||
|
||||
container_info = {'server': server_name, 'name': container.name, 'status': container_status, 'exit_code': exit_code, 'image': image_name, 'stack': stack_name, 'source_url': source_url, 'custom_url': custom_url, 'ports': port_map, 'traefik_routes': traefik_routes, 'tags': tags if TAGS_ENABLE else []}
|
||||
if cached_update is not None and is_cache_valid:
|
||||
container_info['update_available'] = cached_update
|
||||
else:
|
||||
container_info['update_available'] = update_checker.check_local_image_updates(client, container, server_name)
|
||||
all_container_data.append(container_info)
|
||||
except Exception as container_error:
|
||||
logger.error(f"Error processing container {getattr(container, 'name', 'unknown')}: {container_error}")
|
||||
all_container_data.append({'server': server_name, 'name': getattr(container, 'name', 'unknown'), 'status': 'error', 'image': 'error-loading', 'ports': []})
|
||||
except Exception as host_error:
|
||||
logger.error(f"Error connecting to host {host['name']}: {host_error}")
|
||||
for s in server_list_for_json:
|
||||
if s["name"] == host["name"]: s["status"] = "inactive"
|
||||
return {"servers": server_list_for_json, "containers": all_container_data, "traefik_enabled": TRAEFIK_ENABLE}
|
||||
7
dockpeek/extensions.py
Normal file
7
dockpeek/extensions.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from flask_cors import CORS
|
||||
from flask_login import LoginManager
|
||||
|
||||
# Inicjalizujemy rozszerzenia tutaj, aby uniknąć cyklicznych importów
|
||||
cors = CORS()
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = 'auth.login' # 'auth' to nazwa blueprint, 'login' to nazwa funkcji widoku
|
||||
97
dockpeek/main.py
Normal file
97
dockpeek/main.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from flask import (
|
||||
Blueprint, render_template, jsonify, request, current_app, make_response
|
||||
)
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
# Importujemy logikę Dockera z dedykowanego modułu
|
||||
from .docker_utils import get_all_data, discover_docker_clients, update_checker
|
||||
|
||||
main_bp = Blueprint('main', __name__)
|
||||
|
||||
@main_bp.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
version = current_app.config['APP_VERSION']
|
||||
return render_template("index.html", version=version)
|
||||
|
||||
@main_bp.route("/data")
|
||||
@login_required
|
||||
def data():
|
||||
return jsonify(get_all_data())
|
||||
|
||||
@main_bp.route("/check-updates", methods=["POST"])
|
||||
@login_required
|
||||
def check_updates():
|
||||
request_data = request.get_json() or {}
|
||||
server_filter = request_data.get('server_filter', 'all')
|
||||
|
||||
servers = discover_docker_clients()
|
||||
active_servers = [s for s in servers if s['status'] == 'active']
|
||||
|
||||
if server_filter != 'all':
|
||||
active_servers = [s for s in active_servers if s['name'] == server_filter]
|
||||
|
||||
updates = {}
|
||||
|
||||
def check_container_update(args):
|
||||
server, container = args
|
||||
try:
|
||||
update_available = update_checker.check_image_updates_async(
|
||||
server['client'], container, server['name']
|
||||
)
|
||||
return f"{server['name']}:{container.name}", update_available
|
||||
except Exception:
|
||||
return f"{server['name']}:{container.name}", False
|
||||
|
||||
check_args = []
|
||||
for server in active_servers:
|
||||
try:
|
||||
for container in server['client'].containers.list(all=True):
|
||||
check_args.append((server, container))
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"❌ Error accessing containers on {server['name']}: {e}")
|
||||
|
||||
with ThreadPoolExecutor(max_workers=4) as executor:
|
||||
results = executor.map(check_container_update, check_args)
|
||||
for key, result in results:
|
||||
updates[key] = result
|
||||
|
||||
return jsonify({"updates": updates})
|
||||
|
||||
|
||||
@main_bp.route("/export/json")
|
||||
@login_required
|
||||
def export_json():
|
||||
server_filter = request.args.get('server', 'all')
|
||||
data = get_all_data()
|
||||
|
||||
filtered_containers = data.get("containers", [])
|
||||
if server_filter != 'all':
|
||||
filtered_containers = [c for c in filtered_containers if c.get("server") == server_filter]
|
||||
|
||||
# ... (logika eksportu, która była w app.py, bez zmian) ...
|
||||
export_data = {
|
||||
"export_info": {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"dockpeek_version": current_app.config['APP_VERSION'],
|
||||
"server_filter": server_filter,
|
||||
"total_containers": len(filtered_containers),
|
||||
},
|
||||
"containers": []
|
||||
}
|
||||
for c in filtered_containers:
|
||||
export_container = {k: v for k, v in c.items() if k in ['name', 'server', 'stack', 'image', 'status', 'exit_code', 'custom_url']}
|
||||
if c.get("ports"): export_container["ports"] = c["ports"]
|
||||
if c.get("traefik_routes"): export_container["traefik_routes"] = [{"router": r["router"], "url": r["url"]} for r in c["traefik_routes"]]
|
||||
export_data["containers"].append(export_container)
|
||||
|
||||
formatted_json = json.dumps(export_data, indent=2, ensure_ascii=False)
|
||||
filename = f'dockpeek-export-{server_filter}-{datetime.now().strftime("%Y%m%d-%H%M%S")}.json'
|
||||
|
||||
response = make_response(formatted_json)
|
||||
response.headers['Content-Disposition'] = f'attachment; filename={filename}'
|
||||
response.headers['Content-Type'] = 'application/json'
|
||||
return response
|
||||
144
dockpeek/static/fonts/unused/fonts.css
Normal file
144
dockpeek/static/fonts/unused/fonts.css
Normal file
@@ -0,0 +1,144 @@
|
||||
/* inter-100 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-100.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-100italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-100italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-200 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-200.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-200italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-200italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-300 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-300italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-300italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-regular - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-500 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-500italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-600 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-600italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-600italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-700 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-700italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-800 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-800italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-800italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-900 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-900.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-900italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-900italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
BIN
dockpeek/static/fonts/unused/inter-v19-latin-100.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-100.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-100italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-100italic.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-200.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-200.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-200italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-200italic.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-300.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-300.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-300italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-300italic.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-500italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-500italic.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-600italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-600italic.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-700italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-700italic.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-800italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-800italic.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-900.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-900.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-900italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-900italic.woff2
Normal file
Binary file not shown.
BIN
dockpeek/static/fonts/unused/inter-v19-latin-italic.woff2
Normal file
BIN
dockpeek/static/fonts/unused/inter-v19-latin-italic.woff2
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 710 B After Width: | Height: | Size: 710 B |
|
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 611 B |
@@ -1,6 +1,7 @@
|
||||
flask
|
||||
flask-cors
|
||||
flask-login
|
||||
docker
|
||||
Flask
|
||||
Flask-Cors
|
||||
Flask-Login
|
||||
werkzeug
|
||||
packaging
|
||||
docker
|
||||
packaging
|
||||
gunicorn # (opcjonalnie, do wdrożeń produkcyjnych)
|
||||
8
run.py
Normal file
8
run.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import os
|
||||
from dockpeek import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
|
||||
app.run(host="0.0.0.0", port=8000, debug=debug)
|
||||
41
static_old/css/fonts.css
Normal file
41
static_old/css/fonts.css
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
/* inter-regular - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-500 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-600 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-700 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-800 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
1477
static_old/css/styles.css
Normal file
1477
static_old/css/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
1986
static_old/css/tailwindcss.css
Normal file
1986
static_old/css/tailwindcss.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
static_old/fonts/inter/inter-v19-latin-500.woff2
Normal file
BIN
static_old/fonts/inter/inter-v19-latin-500.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/inter/inter-v19-latin-600.woff2
Normal file
BIN
static_old/fonts/inter/inter-v19-latin-600.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/inter/inter-v19-latin-700.woff2
Normal file
BIN
static_old/fonts/inter/inter-v19-latin-700.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/inter/inter-v19-latin-800.woff2
Normal file
BIN
static_old/fonts/inter/inter-v19-latin-800.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/inter/inter-v19-latin-regular.woff2
Normal file
BIN
static_old/fonts/inter/inter-v19-latin-regular.woff2
Normal file
Binary file not shown.
144
static_old/fonts/unused/fonts.css
Normal file
144
static_old/fonts/unused/fonts.css
Normal file
@@ -0,0 +1,144 @@
|
||||
/* inter-100 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-100.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-100italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-100italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-200 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-200.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-200italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-200italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-300 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-300italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-300italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-regular - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-500 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-500italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-600 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-600italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-600italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-700 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-700italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-800 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-800italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-800italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-900 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-900.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-900italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
src: url('/static/fonts/inter/inter-v19-latin-900italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
BIN
static_old/fonts/unused/inter-v19-latin-100.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-100.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-100italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-100italic.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-200.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-200.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-200italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-200italic.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-300.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-300.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-300italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-300italic.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-500italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-500italic.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-600italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-600italic.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-700italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-700italic.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-800italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-800italic.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-900.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-900.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-900italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-900italic.woff2
Normal file
Binary file not shown.
BIN
static_old/fonts/unused/inter-v19-latin-italic.woff2
Normal file
BIN
static_old/fonts/unused/inter-v19-latin-italic.woff2
Normal file
Binary file not shown.
1453
static_old/js/app.js
Normal file
1453
static_old/js/app.js
Normal file
File diff suppressed because it is too large
Load Diff
14
static_old/logo.svg
Normal file
14
static_old/logo.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||
<!-- Tło: ciemny granat/prawie czarny -->
|
||||
<circle cx="50" cy="50" r="50" fill="#1A202C"/>
|
||||
|
||||
<!-- Sylizowana baza przypominająca wieloryba: jasnoszary -->
|
||||
<path d="M15 50 Q30 20 50 30 T85 50 Q70 80 50 70 T15 50 Z" fill="#E2E8F0"/>
|
||||
|
||||
<!-- Uproszczone elementy kontenerów na grzbiecie wieloryba: ciemnoszary/grafitowy -->
|
||||
<rect x="35" y="38" width="10" height="15" rx="2" ry="2" fill="#4A5568"/>
|
||||
<rect x="56" y="44" width="10" height="15" rx="2" ry="2" fill="#4A5568"/>
|
||||
|
||||
<!-- Opcjonalnie: małe "oko" lub detal na wielorybie: ciemniejszy szary -->
|
||||
<circle cx="28" cy="45" r="3" fill="#2D3748"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 710 B |
12
static_old/logo_2.svg
Normal file
12
static_old/logo_2.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||
|
||||
<!-- Sylizowana baza przypominająca wieloryba: jasnoszary -->
|
||||
<path d="M15 50 Q30 20 50 30 T85 50 Q70 80 50 70 T15 50 Z" fill="#E2E8F0"/>
|
||||
|
||||
<!-- Uproszczone elementy kontenerów na grzbiecie wieloryba: ciemnoszary/grafitowy -->
|
||||
<rect x="35" y="38" width="10" height="15" rx="2" ry="2" fill="#4A5568"/>
|
||||
<rect x="56" y="44" width="10" height="15" rx="2" ry="2" fill="#4A5568"/>
|
||||
|
||||
<!-- Opcjonalnie: małe "oko" lub detal na wielorybie: ciemniejszy szary -->
|
||||
<circle cx="28" cy="45" r="3" fill="#2D3748"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 611 B |
367
templates_old/index.html
Normal file
367
templates_old/index.html
Normal file
@@ -0,0 +1,367 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dockpeek</title>
|
||||
<link href="/static/css/tailwindcss.css" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="/static/logo.svg">
|
||||
</head>
|
||||
|
||||
<body class="p-6 dark-mode">
|
||||
<div class="container mx-auto bg-white rounded-lg shadow-xl p-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center space-x-4">
|
||||
<h1 class="logo-title text-3xl font-bold">dockpeek</h1>
|
||||
</div>
|
||||
<div class="flex space-x-4 items-center text-sm controls-container">
|
||||
<div class="status-filter flex items-center space-x-3">
|
||||
<label for="filter-running-checkbox"
|
||||
class="text-sm text-gray-600 dark:text-gray-400 cursor-pointer select-none">
|
||||
Running only
|
||||
</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="filter-running-checkbox" class="hidden">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button id="refresh-button" data-tooltip="Refresh Data"
|
||||
class="p-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-75">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M21 3V8M21 8H16M21 8L18 5.29168C16.4077 3.86656 14.3051 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.2832 21 19.8675 18.008 20.777 14"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="theme-switcher"
|
||||
class="p-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-75">
|
||||
<span id="theme-icon"> </span>
|
||||
</button>
|
||||
<button id="check-updates-button" class="px-4 py-2 rounded-lg font-medium">
|
||||
<svg fill="currentColor" width="20" height="20" viewBox="2 2 52 52" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M28 51.906c13.055 0 23.906-10.828 23.906-23.906 0-13.055-10.875-23.906-23.93-23.906C14.899 4.094 4.095 14.945 4.095 28c0 13.078 10.828 23.906 23.906 23.906m0-3.984C16.937 47.922 8.1 39.062 8.1 28c0-11.04 8.813-19.922 19.876-19.922 11.039 0 19.921 8.883 19.945 19.922.023 11.063-8.883 19.922-19.922 19.922m0-31.969c-.516 0-.89.188-1.36.656l-7.968 7.922c-.305.305-.445.727-.445 1.219 0 .96.726 1.688 1.687 1.688.492 0 .937-.188 1.242-.516l2.906-2.93 2.368-2.812-.188 4.945v12.21c0 1.032.727 1.759 1.758 1.759 1.008 0 1.758-.727 1.758-1.758V26.125l-.188-4.969 2.367 2.836 2.907 2.93c.304.351.726.515 1.242.515.96 0 1.71-.726 1.71-1.687 0-.492-.163-.914-.491-1.219l-7.922-7.922c-.469-.445-.867-.656-1.383-.656" />
|
||||
</svg>
|
||||
Check for updates
|
||||
</button>
|
||||
<a href="/logout" id="logout-button"
|
||||
class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 ease-in-out focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-opacity-75 text-center font-medium">
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 relative flex items-center">
|
||||
<input type="text" id="search-input" placeholder="Search: name, image, stack, #tag, :port"
|
||||
class="w-full p-3 pr-12 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
|
||||
<div class="absolute right-18 flex items-center space-x-3">
|
||||
<button id="clear-search-button" class="mr-3 text-gray-400 hover:text-gray-600 focus:outline-none hidden"
|
||||
data-tooltip="Clear search">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<label for="filter-updates-checkbox"
|
||||
class="hidden md:inline text-sm text-gray-600 dark:text-gray-400 cursor-pointer select-none">
|
||||
Update available
|
||||
</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="filter-updates-checkbox" class="hidden">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="column-menu-button" class="column-menu-container">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
class="column-menu-svg">
|
||||
<path
|
||||
d="M8.97436 2.41026C8.25641 2.20513 7.23077 2 6.41026 2C5.58974 2 4.5641 2.20513 3.84615 2.41026C3.02564 2.51282 2.51282 3.23077 2.41026 3.94872C2.30769 5.48718 2 9.38462 2 12.0513C2 14.7179 2.30769 18.6154 2.41026 20.1538C2.51282 20.9744 3.02564 21.5897 3.74359 21.6923C4.46154 21.8974 5.48718 22 6.30769 22C7.1282 22 8.15385 21.7949 8.87179 21.6923C9.58974 21.4872 10.2051 20.8718 10.2051 20.1538C10.3077 18.6154 10.6154 14.7179 10.6154 12.0513C10.6154 9.38462 10.3077 5.48718 10.2051 3.94872C10.3077 3.23077 9.79487 2.51282 8.97436 2.41026Z"
|
||||
fill="currentcolor"></path>
|
||||
<path
|
||||
d="M21.6923 3.94882C21.5897 3.12831 21.0769 2.51292 20.3589 2.41036C19.641 2.20523 18.6154 2.10266 17.7948 2.10266C16.9743 2.10266 15.8461 2.20523 15.1282 2.41036C14.4102 2.61548 13.7948 3.23087 13.7948 3.94882C13.6923 5.48728 13.3846 9.38472 13.3846 12.0514C13.3846 14.7181 13.6923 18.6155 13.7948 20.1539C13.8974 20.9745 14.4102 21.5898 15.1282 21.6924C15.8461 21.8975 16.8718 22.0001 17.6923 22.0001C18.5128 22.0001 19.5384 21.795 20.2564 21.6924C20.9743 21.4873 21.5897 20.8719 21.5897 20.1539C21.6923 18.6155 22 14.7181 22 12.0514C22 9.38472 21.7948 5.48728 21.6923 3.94882Z"
|
||||
fill="currentcolor"></path>
|
||||
</svg>
|
||||
</svg>
|
||||
<div id="column-menu" class="column-menu hidden">
|
||||
<div id="column-list" class="column-list">
|
||||
<div class="column-menu-item draggable" data-column="name">
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<label for="toggle-name">
|
||||
<span>Name</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-name" checked>
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="column-menu-item draggable" data-column="stack">
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<label for="toggle-stack">
|
||||
<span>Stack</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-stack" checked>
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="column-menu-item draggable" data-column="server">
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<label for="toggle-server">
|
||||
<span>Server</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-server" checked>
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="column-menu-item draggable" data-column="ports">
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<label for="toggle-ports">
|
||||
<span>Ports</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-ports" checked>
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="column-menu-item draggable" data-column="traefik">
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<label for="toggle-traefik">
|
||||
<span>Traefik</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-traefik" checked>
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="column-menu-item draggable" data-column="image">
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<label for="toggle-image">
|
||||
<span>Image</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-image" checked>
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="column-menu-item draggable" data-column="tags">
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<label for="toggle-tags">
|
||||
<span>Tags</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-tags" checked>
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="column-menu-item draggable" data-column="status">
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<label for="toggle-status">
|
||||
<span>Status</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-status" checked>
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column-menu-reset text-sm">
|
||||
<a href="#" id="reset-columns-button" class="reset-columns-btn">
|
||||
Reset all
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="server-filter-container" class="mb-4 ml-2 flex flex-wrap gap-2"></div>
|
||||
<div id="container-table-wrapper" class="overflow-x-auto rounded-lg overflow-hidden">
|
||||
<table id="main-table" class="min-w-full bg-white">
|
||||
<thead class="main-table-header">
|
||||
<tr>
|
||||
<th
|
||||
class="table-cell-name py-3 px-4 text-left text-sm font-semibold text-gray-700 border-b border-gray-200 sortable-header"
|
||||
data-sort-column="name">
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
class=" table-cell-stack py-3 px-4 text-left text-sm font-semibold text-gray-700 border-b border-gray-200 sortable-header"
|
||||
data-sort-column="stack">
|
||||
Stack
|
||||
</th>
|
||||
<th
|
||||
class="server-column table-cell-server py-3 px-4 text-left text-sm font-semibold text-gray-700 border-b border-gray-200 sortable-header server-column"
|
||||
data-sort-column="server">
|
||||
Server
|
||||
</th>
|
||||
<th
|
||||
class="table-cell-ports py-3 px-4 text-left text-sm font-semibold text-gray-700 border-b border-gray-200 sortable-header"
|
||||
data-sort-column="ports">
|
||||
Ports
|
||||
</th>
|
||||
<th
|
||||
class="traefik-column table-cell-traefik py-3 px-4 text-left text-sm font-semibold text-gray-700 border-b border-gray-200 sortable-header traefik-column"
|
||||
data-sort-column="traefik">
|
||||
Traefik
|
||||
</th>
|
||||
<th
|
||||
class="table-cell-image py-3 px-4 text-left text-sm font-semibold text-gray-700 border-b border-gray-200 sortable-header"
|
||||
data-sort-column="image">
|
||||
Image
|
||||
</th>
|
||||
<th
|
||||
class="table-cell-tags py-3 px-4 text-left text-sm font-semibold text-gray-700 border-b border-gray-200 tags-column"
|
||||
data-sort-column="tags">
|
||||
Tags
|
||||
</th>
|
||||
<th
|
||||
class="table-cell-status py-3 px-4 text-left text-sm font-semibold text-gray-700 border-b border-gray-200 sortable-header"
|
||||
data-sort-column="status">
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="container-rows">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer container mx-auto py-3 flex justify-center items-center text-sm text-gray-500 p-4">
|
||||
<div class="footer-section footer-center flex items-center space-x-9">
|
||||
<a href="/export/json" id="export-json-link" class="hover:text-blue-500"
|
||||
data-tooltip="Export container data for currently selected server">
|
||||
Export JSON
|
||||
</a>
|
||||
|
||||
<span>
|
||||
Dockpeek
|
||||
{% if version and version != 'dev' %}
|
||||
<a href="https://github.com/dockpeek/dockpeek/releases/tag/{{ version }}" target="_blank"
|
||||
class="hover:text-blue-500">
|
||||
{{ version }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ version }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<a href="https://github.com/dockpeek/dockpeek" target="_blank"
|
||||
class="flex items-center space-x-1 hover:text-blue-500">
|
||||
<svg width="16" height="16" viewBox="0 0 97 96" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="container-row-template">
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="py-3 px-4 border-b border-gray-200 font-medium container-name-cell" data-content="name">
|
||||
<div class="container-name-wrapper inline-block items-center space-x-2">
|
||||
<div class="name-wrapper">
|
||||
<span data-content="container-name"></span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 border-b border-gray-200 text-gray-600 text-sm table-cell-stack" data-content="stack">
|
||||
</td>
|
||||
<td class="py-3 px-4 border-b border-gray-200 text-gray-400 text-sm server-column" data-content="server">
|
||||
<span data-content="server-name"></span>
|
||||
</td>
|
||||
<td class="py-3 px-4 border-b border-gray-200" data-content="ports">
|
||||
</td>
|
||||
<td class="py-3 px-4 border-b border-gray-200 traefik-column" data-content="traefik-routes">
|
||||
</td>
|
||||
<td class="py-3 px-4 border-b border-gray-200 table-cell-image">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="image-wrapper">
|
||||
<div class="image-wrapper-inline">
|
||||
<code class="bg-gray-100 text-gray-700 px-2 py-1 rounded text-sm" data-content="image"></code>
|
||||
</div>
|
||||
<a href="#" class="source-link hidden text-gray-500 hover:text-blue-600" data-content="source-link"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<span class="update-indicator hidden" data-content="update-indicator" data-tooltip="Update available">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg fill="currentColor" width="20" height="20" viewBox="2 2 52 52" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M28 51.906c13.055 0 23.906-10.828 23.906-23.906 0-13.055-10.875-23.906-23.93-23.906C14.899 4.094 4.095 14.945 4.095 28c0 13.078 10.828 23.906 23.906 23.906m0-3.984C16.937 47.922 8.1 39.062 8.1 28c0-11.04 8.813-19.922 19.876-19.922 11.039 0 19.921 8.883 19.945 19.922.023 11.063-8.883 19.922-19.922 19.922m0-31.969c-.516 0-.89.188-1.36.656l-7.968 7.922c-.305.305-.445.727-.445 1.219 0 .96.726 1.688 1.687 1.688.492 0 .937-.188 1.242-.516l2.906-2.93 2.368-2.812-.188 4.945v12.21c0 1.032.727 1.759 1.758 1.759 1.008 0 1.758-.727 1.758-1.758V26.125l-.188-4.969 2.367 2.836 2.907 2.93c.304.351.726.515 1.242.515.96 0 1.71-.726 1.71-1.687 0-.492-.163-.914-.491-1.219l-7.922-7.922c-.469-.445-.867-.656-1.383-.656" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 border-b border-gray-200 table-cell-tags" data-content="tags">
|
||||
</td>
|
||||
<td class="py-3 px-4 border-b border-gray-200 table-cell-status" data-content="status">
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<div id="confirmation-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<h2 id="modal-title" class="text-xl font-bold mb-4 text-gray-900">
|
||||
Confirmation
|
||||
</h2>
|
||||
<div id="modal-message" class="text-gray-600 mb-6 text-left whitespace-pre-line">
|
||||
</div>
|
||||
<div class="flex justify-center space-x-4">
|
||||
<button id="modal-cancel-button"
|
||||
class="px-6 py-2 rounded-lg bg-gray-200 text-gray-700 hover:bg-gray-300 font-medium">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="modal-confirm-button"
|
||||
class="px-6 py-2 rounded-lg bg-red-500 text-white hover:bg-red-600 font-medium">
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="updates-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<h3 id="updates-modal-title" class="text-lg font-semibold mb-4 text-gray-900 font-medium">
|
||||
Updates Found
|
||||
</h3>
|
||||
<div id="updates-modal-message">
|
||||
<ul id="updates-list" class="list-none text-left mb-4">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex justify-center space-x-3 mt-4">
|
||||
<button id="updates-modal-ok-button"
|
||||
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 font-medium">
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
36
templates_old/login.html
Normal file
36
templates_old/login.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dockpeek Login</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/logo.svg">
|
||||
<link href="/static/css/tailwindcss.css" rel="stylesheet" >
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen flex items-center justify-center dark-mode">
|
||||
<div class="login-box shadow-xl rounded-lg p-8 max-w-md w-full">
|
||||
<div class="flex justify-center mb-6">
|
||||
<img src="/static/logo_2.svg" alt="SFG Logo" class="w-20 h-20 mb-6">
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold text-center mb-6">Log in to Dockpeek</h2>
|
||||
<form method="POST" action="/login">
|
||||
{% if error %}
|
||||
<p class="text-red-500 text-center mb-4">{{ error }}</p>
|
||||
{% endif %}
|
||||
<div class="mb-4">
|
||||
<label for="username" class="block text-sm font-medium mb-1">Username</label>
|
||||
<input type="text" id="username" name="username" required class="w-full p-3 border rounded-lg">
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label for="password" class="block text-sm font-medium mb-1">Password</label>
|
||||
<input type="password" id="password" name="password" required class="w-full p-3 border rounded-lg">
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="w-full text-white py-3 rounded-lg transition duration-150 ease-in-out">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user