Compare commits
9 Commits
v1.8.2
...
logo-propo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43de1ba8a9 | ||
|
|
b969c749be | ||
|
|
cfba027df6 | ||
|
|
18e5cc653b | ||
|
|
8dabc27272 | ||
|
|
5e08f1d712 | ||
|
|
8302f1d97a | ||
|
|
eac9338352 | ||
|
|
667931dc80 |
@@ -110,6 +110,10 @@ Browser tests are marked with `@pytest.mark.browser`. They use Playwright to tes
|
||||
Use `gh release create` to create releases. The tag is created automatically.
|
||||
|
||||
```bash
|
||||
# IMPORTANT: Ensure you're on latest origin/main before releasing!
|
||||
git fetch origin
|
||||
git checkout origin/main
|
||||
|
||||
# Check current version
|
||||
git tag --sort=-v:refname | head -1
|
||||
|
||||
@@ -134,7 +138,7 @@ CLI available as `cf` or `compose-farm`.
|
||||
| `stop` | Stop services without removing containers (`docker compose stop`) |
|
||||
| `pull` | Pull latest images |
|
||||
| `restart` | `down` + `up -d` |
|
||||
| `update` | `pull` + `build` + `down` + `up -d` |
|
||||
| `update` | Pull, build, recreate only if changed (`up -d --pull always --build`) |
|
||||
| `apply` | Make reality match config: migrate stacks + stop orphans. Use `--dry-run` to preview |
|
||||
| `compose` | Run any docker compose command on a stack (passthrough) |
|
||||
| `logs` | Show stack logs |
|
||||
|
||||
10
README.md
@@ -370,7 +370,7 @@ The CLI is available as both `compose-farm` and the shorter `cf` alias.
|
||||
| `cf down <stack>` | Stop and remove stack containers |
|
||||
| `cf stop <stack>` | Stop stack without removing containers |
|
||||
| `cf restart <stack>` | down + up |
|
||||
| `cf update <stack>` | pull + build + down + up |
|
||||
| `cf update <stack>` | Pull, build, recreate only if changed |
|
||||
| `cf pull <stack>` | Pull latest images |
|
||||
| `cf logs -f <stack>` | Follow logs |
|
||||
| `cf ps` | Show status of all stacks |
|
||||
@@ -403,7 +403,7 @@ cf pull --all
|
||||
# Restart (down + up)
|
||||
cf restart plex
|
||||
|
||||
# Update (pull + build + down + up) - the end-to-end update command
|
||||
# Update (pull + build, only recreates containers if images changed)
|
||||
cf update --all
|
||||
|
||||
# Update state from reality (discovers running stacks + captures digests)
|
||||
@@ -475,8 +475,7 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
|
||||
│ pull Pull latest images (docker compose pull). │
|
||||
│ restart Restart stacks (down + up). With --service, restarts just │
|
||||
│ that service. │
|
||||
│ update Update stacks (pull + build + down + up). With --service, │
|
||||
│ updates just that service. │
|
||||
│ update Update stacks. Only recreates containers if images changed. │
|
||||
│ apply Make reality match config (start, migrate, stop │
|
||||
│ strays/orphans as needed). │
|
||||
│ compose Run any docker compose command on a stack. │
|
||||
@@ -694,8 +693,7 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
|
||||
|
||||
Usage: cf update [OPTIONS] [STACKS]...
|
||||
|
||||
Update stacks (pull + build + down + up). With --service, updates just that
|
||||
service.
|
||||
Update stacks. Only recreates containers if images changed.
|
||||
|
||||
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
|
||||
│ stacks [STACKS]... Stacks to operate on │
|
||||
|
||||
@@ -15,7 +15,7 @@ The Compose Farm CLI is available as both `compose-farm` and the shorter alias `
|
||||
| | `down` | Stop stacks |
|
||||
| | `stop` | Stop services without removing containers |
|
||||
| | `restart` | Restart stacks (down + up) |
|
||||
| | `update` | Update stacks (pull + build + down + up) |
|
||||
| | `update` | Update stacks (only recreates if images changed) |
|
||||
| | `pull` | Pull latest images |
|
||||
| | `compose` | Run any docker compose command |
|
||||
| **Monitoring** | `ps` | Show stack status |
|
||||
@@ -225,7 +225,7 @@ cf restart immich --service database
|
||||
|
||||
### cf update
|
||||
|
||||
Update stacks (pull + build + down + up). With `--service`, updates just that service.
|
||||
Update stacks. Only recreates containers if images changed. With `--service`, updates just that service.
|
||||
|
||||
<video autoplay loop muted playsinline>
|
||||
<source src="/assets/update.webm" type="video/webm">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Update Demo
|
||||
# Shows updating stacks (pull + build + down + up)
|
||||
# Shows updating stacks (only recreates containers if images changed)
|
||||
|
||||
Output docs/assets/update.gif
|
||||
Output docs/assets/update.webm
|
||||
|
||||
@@ -329,7 +329,7 @@ cf apply
|
||||
|
||||
```bash
|
||||
cf update --all
|
||||
# Runs: pull + build + down + up for each stack
|
||||
# Only recreates containers if images changed
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Hatch build hook to vendor CDN assets for offline use.
|
||||
|
||||
During wheel builds, this hook:
|
||||
1. Parses base.html to find elements with data-vendor attributes
|
||||
1. Reads vendor-assets.json to find assets marked for vendoring
|
||||
2. Downloads each CDN asset to a temporary vendor directory
|
||||
3. Rewrites base.html to use local /static/vendor/ paths
|
||||
4. Fetches and bundles license information
|
||||
@@ -13,6 +13,7 @@ distributed wheel has vendored assets.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -23,22 +24,6 @@ from urllib.request import Request, urlopen
|
||||
|
||||
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
||||
|
||||
# Matches elements with data-vendor attribute: extracts URL and target filename
|
||||
# Example: <script src="https://..." data-vendor="htmx.js">
|
||||
# Captures: (1) src/href, (2) URL, (3) attributes between, (4) vendor filename
|
||||
VENDOR_PATTERN = re.compile(r'(src|href)="(https://[^"]+)"([^>]*?)data-vendor="([^"]+)"')
|
||||
|
||||
# License URLs for each package (GitHub raw URLs)
|
||||
LICENSE_URLS: dict[str, tuple[str, str]] = {
|
||||
"htmx": ("MIT", "https://raw.githubusercontent.com/bigskysoftware/htmx/master/LICENSE"),
|
||||
"xterm": ("MIT", "https://raw.githubusercontent.com/xtermjs/xterm.js/master/LICENSE"),
|
||||
"daisyui": ("MIT", "https://raw.githubusercontent.com/saadeghi/daisyui/master/LICENSE"),
|
||||
"tailwindcss": (
|
||||
"MIT",
|
||||
"https://raw.githubusercontent.com/tailwindlabs/tailwindcss/master/LICENSE",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _download(url: str) -> bytes:
|
||||
"""Download a URL, trying urllib first then curl as fallback."""
|
||||
@@ -61,7 +46,14 @@ def _download(url: str) -> bytes:
|
||||
return bytes(result.stdout)
|
||||
|
||||
|
||||
def _generate_licenses_file(temp_dir: Path) -> None:
|
||||
def _load_vendor_assets(root: Path) -> dict[str, Any]:
|
||||
"""Load vendor-assets.json from the web module."""
|
||||
json_path = root / "src" / "compose_farm" / "web" / "vendor-assets.json"
|
||||
with json_path.open() as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _generate_licenses_file(temp_dir: Path, licenses: dict[str, dict[str, str]]) -> None:
|
||||
"""Download and combine license files into LICENSES.txt."""
|
||||
lines = [
|
||||
"# Vendored Dependencies - License Information",
|
||||
@@ -73,7 +65,9 @@ def _generate_licenses_file(temp_dir: Path) -> None:
|
||||
"",
|
||||
]
|
||||
|
||||
for pkg_name, (license_type, license_url) in LICENSE_URLS.items():
|
||||
for pkg_name, license_info in licenses.items():
|
||||
license_type = license_info["type"]
|
||||
license_url = license_info["url"]
|
||||
lines.append(f"## {pkg_name} ({license_type})")
|
||||
lines.append(f"Source: {license_url}")
|
||||
lines.append("")
|
||||
@@ -107,44 +101,57 @@ class VendorAssetsHook(BuildHookInterface): # type: ignore[misc]
|
||||
if not base_html_path.exists():
|
||||
return
|
||||
|
||||
# Load vendor assets configuration
|
||||
vendor_config = _load_vendor_assets(Path(self.root))
|
||||
assets_to_vendor = vendor_config["assets"]
|
||||
|
||||
if not assets_to_vendor:
|
||||
return
|
||||
|
||||
# Create temp directory for vendored assets
|
||||
temp_dir = Path(tempfile.mkdtemp(prefix="compose_farm_vendor_"))
|
||||
vendor_dir = temp_dir / "vendor"
|
||||
vendor_dir.mkdir()
|
||||
|
||||
# Read and parse base.html
|
||||
# Read base.html
|
||||
html_content = base_html_path.read_text()
|
||||
|
||||
# Build URL to filename mapping and download assets
|
||||
url_to_filename: dict[str, str] = {}
|
||||
|
||||
# Find all elements with data-vendor attribute and download them
|
||||
for match in VENDOR_PATTERN.finditer(html_content):
|
||||
url = match.group(2)
|
||||
filename = match.group(4)
|
||||
|
||||
if url in url_to_filename:
|
||||
continue
|
||||
|
||||
for asset in assets_to_vendor:
|
||||
url = asset["url"]
|
||||
filename = asset["filename"]
|
||||
url_to_filename[url] = filename
|
||||
filepath = vendor_dir / filename
|
||||
filepath.parent.mkdir(parents=True, exist_ok=True)
|
||||
content = _download(url)
|
||||
(vendor_dir / filename).write_bytes(content)
|
||||
filepath.write_bytes(content)
|
||||
|
||||
if not url_to_filename:
|
||||
return
|
||||
# Generate LICENSES.txt from the JSON config
|
||||
_generate_licenses_file(vendor_dir, vendor_config["licenses"])
|
||||
|
||||
# Generate LICENSES.txt
|
||||
_generate_licenses_file(vendor_dir)
|
||||
# Rewrite HTML: replace CDN URLs with local paths and remove data-vendor attributes
|
||||
# Pattern matches: src="URL" ... data-vendor="filename" or href="URL" ... data-vendor="filename"
|
||||
vendor_pattern = re.compile(r'(src|href)="(https://[^"]+)"([^>]*?)data-vendor="([^"]+)"')
|
||||
|
||||
# Rewrite HTML to use local paths (remove data-vendor, update URL)
|
||||
def replace_vendor_tag(match: re.Match[str]) -> str:
|
||||
attr = match.group(1) # src or href
|
||||
url = match.group(2)
|
||||
between = match.group(3) # attributes between URL and data-vendor
|
||||
filename = match.group(4)
|
||||
if url in url_to_filename:
|
||||
filename = url_to_filename[url]
|
||||
return f'{attr}="/static/vendor/{filename}"{between}'
|
||||
return match.group(0)
|
||||
|
||||
modified_html = VENDOR_PATTERN.sub(replace_vendor_tag, html_content)
|
||||
modified_html = vendor_pattern.sub(replace_vendor_tag, html_content)
|
||||
|
||||
# Inject vendored mode flag for JavaScript to detect
|
||||
# Insert right after <head> tag so it's available early
|
||||
modified_html = modified_html.replace(
|
||||
"<head>",
|
||||
"<head>\n <script>window.CF_VENDORED=true;</script>",
|
||||
1, # Only replace first occurrence
|
||||
)
|
||||
|
||||
# Write modified base.html to temp
|
||||
templates_dir = temp_dir / "templates"
|
||||
|
||||
44
logo-proposals/01-barn-container.svg
Normal file
@@ -0,0 +1,44 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Barn with Container Doors -->
|
||||
<defs>
|
||||
<linearGradient id="barnRed" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#D94B4B"/>
|
||||
<stop offset="100%" style="stop-color:#B33A3A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="roofRed" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#C44040"/>
|
||||
<stop offset="100%" style="stop-color:#9E2E2E"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Barn body -->
|
||||
<rect x="40" y="90" width="120" height="80" fill="url(#barnRed)" rx="2"/>
|
||||
|
||||
<!-- Barn roof -->
|
||||
<polygon points="30,90 100,40 170,90" fill="url(#roofRed)"/>
|
||||
|
||||
<!-- Roof cupola -->
|
||||
<rect x="90" y="35" width="20" height="15" fill="#8B4513"/>
|
||||
<polygon points="85,35 100,20 115,35" fill="#A0522D"/>
|
||||
|
||||
<!-- Container doors (left) -->
|
||||
<rect x="48" y="105" width="45" height="58" fill="#2496ED" rx="2"/>
|
||||
<rect x="50" y="107" width="41" height="12" fill="#1A7AC7" rx="1"/>
|
||||
<rect x="50" y="121" width="41" height="12" fill="#1A7AC7" rx="1"/>
|
||||
<rect x="50" y="135" width="41" height="12" fill="#1A7AC7" rx="1"/>
|
||||
<rect x="50" y="149" width="41" height="12" fill="#1A7AC7" rx="1"/>
|
||||
|
||||
<!-- Container doors (right) -->
|
||||
<rect x="107" y="105" width="45" height="58" fill="#2496ED" rx="2"/>
|
||||
<rect x="109" y="107" width="41" height="12" fill="#1A7AC7" rx="1"/>
|
||||
<rect x="109" y="121" width="41" height="12" fill="#1A7AC7" rx="1"/>
|
||||
<rect x="109" y="135" width="41" height="12" fill="#1A7AC7" rx="1"/>
|
||||
<rect x="109" y="149" width="41" height="12" fill="#1A7AC7" rx="1"/>
|
||||
|
||||
<!-- Door handles -->
|
||||
<circle cx="90" cy="134" r="3" fill="#F0F0F0"/>
|
||||
<circle cx="110" cy="134" r="3" fill="#F0F0F0"/>
|
||||
|
||||
<!-- Ground -->
|
||||
<ellipse cx="100" cy="175" rx="70" ry="8" fill="#5D8A3E" opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
72
logo-proposals/02-container-wheat.svg
Normal file
@@ -0,0 +1,72 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Container with Wheat Growing -->
|
||||
<defs>
|
||||
<linearGradient id="containerBlue" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2496ED"/>
|
||||
<stop offset="100%" style="stop-color:#1A7AC7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="wheat" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#F4D03F"/>
|
||||
<stop offset="100%" style="stop-color:#D4AC0D"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Container base -->
|
||||
<rect x="35" y="110" width="130" height="70" fill="url(#containerBlue)" rx="4"/>
|
||||
|
||||
<!-- Container ridges -->
|
||||
<rect x="40" y="115" width="120" height="14" fill="#1A7AC7" rx="2"/>
|
||||
<rect x="40" y="133" width="120" height="14" fill="#1A7AC7" rx="2"/>
|
||||
<rect x="40" y="151" width="120" height="14" fill="#1A7AC7" rx="2"/>
|
||||
<rect x="40" y="169" width="120" height="8" fill="#1A7AC7" rx="2"/>
|
||||
|
||||
<!-- Container corner posts -->
|
||||
<rect x="35" y="110" width="8" height="70" fill="#1565C0"/>
|
||||
<rect x="157" y="110" width="8" height="70" fill="#1565C0"/>
|
||||
|
||||
<!-- Wheat stalks -->
|
||||
<g stroke="#8B7355" stroke-width="2" fill="none">
|
||||
<path d="M70 110 Q68 80 70 50"/>
|
||||
<path d="M100 110 Q100 70 100 35"/>
|
||||
<path d="M130 110 Q132 80 130 50"/>
|
||||
</g>
|
||||
|
||||
<!-- Wheat heads (left) -->
|
||||
<g fill="url(#wheat)">
|
||||
<ellipse cx="70" cy="50" rx="4" ry="8"/>
|
||||
<ellipse cx="65" cy="55" rx="4" ry="7"/>
|
||||
<ellipse cx="75" cy="55" rx="4" ry="7"/>
|
||||
<ellipse cx="63" cy="62" rx="3" ry="6"/>
|
||||
<ellipse cx="77" cy="62" rx="3" ry="6"/>
|
||||
</g>
|
||||
|
||||
<!-- Wheat heads (center - taller) -->
|
||||
<g fill="url(#wheat)">
|
||||
<ellipse cx="100" cy="35" rx="5" ry="9"/>
|
||||
<ellipse cx="94" cy="42" rx="4" ry="8"/>
|
||||
<ellipse cx="106" cy="42" rx="4" ry="8"/>
|
||||
<ellipse cx="91" cy="51" rx="4" ry="7"/>
|
||||
<ellipse cx="109" cy="51" rx="4" ry="7"/>
|
||||
<ellipse cx="94" cy="60" rx="3" ry="6"/>
|
||||
<ellipse cx="106" cy="60" rx="3" ry="6"/>
|
||||
</g>
|
||||
|
||||
<!-- Wheat heads (right) -->
|
||||
<g fill="url(#wheat)">
|
||||
<ellipse cx="130" cy="50" rx="4" ry="8"/>
|
||||
<ellipse cx="125" cy="55" rx="4" ry="7"/>
|
||||
<ellipse cx="135" cy="55" rx="4" ry="7"/>
|
||||
<ellipse cx="123" cy="62" rx="3" ry="6"/>
|
||||
<ellipse cx="137" cy="62" rx="3" ry="6"/>
|
||||
</g>
|
||||
|
||||
<!-- Small leaves on stalks -->
|
||||
<g fill="#6B8E23">
|
||||
<path d="M70 85 Q60 80 70 75"/>
|
||||
<path d="M70 75 Q80 70 70 65"/>
|
||||
<path d="M100 90 Q88 85 100 80"/>
|
||||
<path d="M100 75 Q112 70 100 65"/>
|
||||
<path d="M130 85 Q140 80 130 75"/>
|
||||
<path d="M130 75 Q120 70 130 65"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
56
logo-proposals/03-silo-stack.svg
Normal file
@@ -0,0 +1,56 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Silos as Stacked Containers -->
|
||||
<defs>
|
||||
<linearGradient id="silo1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#2496ED"/>
|
||||
<stop offset="50%" style="stop-color:#4AA8F2"/>
|
||||
<stop offset="100%" style="stop-color:#2496ED"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="silo2" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#1E88E5"/>
|
||||
<stop offset="50%" style="stop-color:#42A5F5"/>
|
||||
<stop offset="100%" style="stop-color:#1E88E5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="silo3" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#1976D2"/>
|
||||
<stop offset="50%" style="stop-color:#2196F3"/>
|
||||
<stop offset="100%" style="stop-color:#1976D2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Left silo (shortest) -->
|
||||
<rect x="25" y="100" width="40" height="75" fill="url(#silo1)" rx="3"/>
|
||||
<ellipse cx="45" cy="100" rx="20" ry="8" fill="#4AA8F2"/>
|
||||
<ellipse cx="45" cy="175" rx="20" ry="5" fill="#1565C0"/>
|
||||
<!-- Container ridges -->
|
||||
<path d="M25 115 h40 M25 130 h40 M25 145 h40 M25 160 h40" stroke="#1A7AC7" stroke-width="2"/>
|
||||
<!-- Roof dome -->
|
||||
<ellipse cx="45" cy="100" rx="18" ry="12" fill="#1565C0"/>
|
||||
<ellipse cx="45" cy="98" rx="16" ry="10" fill="#2196F3"/>
|
||||
|
||||
<!-- Middle silo (tallest) -->
|
||||
<rect x="80" y="55" width="45" height="120" fill="url(#silo2)" rx="3"/>
|
||||
<ellipse cx="102.5" cy="55" rx="22.5" ry="10" fill="#42A5F5"/>
|
||||
<ellipse cx="102.5" cy="175" rx="22.5" ry="5" fill="#1565C0"/>
|
||||
<!-- Container ridges -->
|
||||
<path d="M80 75 h45 M80 95 h45 M80 115 h45 M80 135 h45 M80 155 h45" stroke="#1976D2" stroke-width="2"/>
|
||||
<!-- Roof dome -->
|
||||
<ellipse cx="102.5" cy="55" rx="20" ry="14" fill="#1565C0"/>
|
||||
<ellipse cx="102.5" cy="52" rx="18" ry="12" fill="#2196F3"/>
|
||||
<!-- Cupola -->
|
||||
<rect x="97" y="35" width="11" height="17" fill="#1565C0"/>
|
||||
<polygon points="92,35 102.5,22 113,35" fill="#1976D2"/>
|
||||
|
||||
<!-- Right silo (medium) -->
|
||||
<rect x="140" y="85" width="38" height="90" fill="url(#silo3)" rx="3"/>
|
||||
<ellipse cx="159" cy="85" rx="19" ry="8" fill="#2196F3"/>
|
||||
<ellipse cx="159" cy="175" rx="19" ry="5" fill="#1565C0"/>
|
||||
<!-- Container ridges -->
|
||||
<path d="M140 100 h38 M140 115 h38 M140 130 h38 M140 145 h38 M140 160 h38" stroke="#1565C0" stroke-width="2"/>
|
||||
<!-- Roof dome -->
|
||||
<ellipse cx="159" cy="85" rx="17" ry="11" fill="#1565C0"/>
|
||||
<ellipse cx="159" cy="83" rx="15" ry="9" fill="#2196F3"/>
|
||||
|
||||
<!-- Ground -->
|
||||
<ellipse cx="100" cy="180" rx="85" ry="10" fill="#5D8A3E" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
69
logo-proposals/04-tractor-container.svg
Normal file
@@ -0,0 +1,69 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Minimalist Tractor with Container Trailer -->
|
||||
<defs>
|
||||
<linearGradient id="tractorGreen" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#4CAF50"/>
|
||||
<stop offset="100%" style="stop-color:#388E3C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="containerBlue2" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2496ED"/>
|
||||
<stop offset="100%" style="stop-color:#1976D2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Container trailer -->
|
||||
<rect x="90" y="95" width="85" height="50" fill="url(#containerBlue2)" rx="3"/>
|
||||
<!-- Container ridges -->
|
||||
<rect x="95" y="100" width="75" height="10" fill="#1A7AC7" rx="1"/>
|
||||
<rect x="95" y="113" width="75" height="10" fill="#1A7AC7" rx="1"/>
|
||||
<rect x="95" y="126" width="75" height="10" fill="#1A7AC7" rx="1"/>
|
||||
<!-- Corner posts -->
|
||||
<rect x="90" y="95" width="6" height="50" fill="#1565C0"/>
|
||||
<rect x="169" y="95" width="6" height="50" fill="#1565C0"/>
|
||||
|
||||
<!-- Trailer wheels -->
|
||||
<circle cx="115" cy="155" r="12" fill="#333"/>
|
||||
<circle cx="115" cy="155" r="6" fill="#666"/>
|
||||
<circle cx="150" cy="155" r="12" fill="#333"/>
|
||||
<circle cx="150" cy="155" r="6" fill="#666"/>
|
||||
|
||||
<!-- Trailer hitch bar -->
|
||||
<rect x="55" y="125" width="40" height="6" fill="#555"/>
|
||||
|
||||
<!-- Tractor body -->
|
||||
<rect x="20" y="90" width="50" height="40" fill="url(#tractorGreen)" rx="5"/>
|
||||
|
||||
<!-- Tractor hood -->
|
||||
<rect x="15" y="100" width="20" height="25" fill="url(#tractorGreen)" rx="3"/>
|
||||
|
||||
<!-- Tractor cabin -->
|
||||
<rect x="35" y="70" width="30" height="25" fill="#333" rx="3"/>
|
||||
<rect x="38" y="73" width="24" height="18" fill="#87CEEB" rx="2"/>
|
||||
|
||||
<!-- Exhaust pipe -->
|
||||
<rect x="18" y="80" width="5" height="20" fill="#444" rx="1"/>
|
||||
|
||||
<!-- Big rear wheel -->
|
||||
<circle cx="55" cy="145" r="22" fill="#333"/>
|
||||
<circle cx="55" cy="145" r="16" fill="#444"/>
|
||||
<circle cx="55" cy="145" r="8" fill="#666"/>
|
||||
<!-- Wheel treads -->
|
||||
<g stroke="#555" stroke-width="3">
|
||||
<line x1="55" y1="125" x2="55" y2="130"/>
|
||||
<line x1="55" y1="160" x2="55" y2="165"/>
|
||||
<line x1="35" y1="145" x2="40" y2="145"/>
|
||||
<line x1="70" y1="145" x2="75" y2="145"/>
|
||||
<line x1="40" y1="130" x2="43" y2="133"/>
|
||||
<line x1="67" y1="157" x2="70" y2="160"/>
|
||||
<line x1="40" y1="160" x2="43" y2="157"/>
|
||||
<line x1="67" y1="133" x2="70" y2="130"/>
|
||||
</g>
|
||||
|
||||
<!-- Small front wheel -->
|
||||
<circle cx="22" cy="145" r="12" fill="#333"/>
|
||||
<circle cx="22" cy="145" r="8" fill="#444"/>
|
||||
<circle cx="22" cy="145" r="4" fill="#666"/>
|
||||
|
||||
<!-- Ground line -->
|
||||
<line x1="5" y1="167" x2="195" y2="167" stroke="#5D8A3E" stroke-width="3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
57
logo-proposals/05-harvest-crate.svg
Normal file
@@ -0,0 +1,57 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Harvest Crate / Container Hybrid -->
|
||||
<defs>
|
||||
<linearGradient id="wood" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#DEB887"/>
|
||||
<stop offset="100%" style="stop-color:#B8860B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="blueAccent" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#2496ED"/>
|
||||
<stop offset="100%" style="stop-color:#1976D2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Crate back panel (3D effect) -->
|
||||
<polygon points="45,60 155,60 175,45 65,45" fill="#A0522D"/>
|
||||
<polygon points="155,60 155,150 175,135 175,45" fill="#8B4513"/>
|
||||
|
||||
<!-- Main crate body -->
|
||||
<rect x="45" y="60" width="110" height="90" fill="url(#wood)" rx="3"/>
|
||||
|
||||
<!-- Horizontal slats -->
|
||||
<rect x="45" y="60" width="110" height="18" fill="#C9A86C" rx="2"/>
|
||||
<rect x="45" y="82" width="110" height="18" fill="#C9A86C" rx="2"/>
|
||||
<rect x="45" y="104" width="110" height="18" fill="#C9A86C" rx="2"/>
|
||||
<rect x="45" y="126" width="110" height="18" fill="#C9A86C" rx="2"/>
|
||||
|
||||
<!-- Slat gaps (darker lines) -->
|
||||
<line x1="45" y1="78" x2="155" y2="78" stroke="#8B7355" stroke-width="2"/>
|
||||
<line x1="45" y1="100" x2="155" y2="100" stroke="#8B7355" stroke-width="2"/>
|
||||
<line x1="45" y1="122" x2="155" y2="122" stroke="#8B7355" stroke-width="2"/>
|
||||
<line x1="45" y1="144" x2="155" y2="144" stroke="#8B7355" stroke-width="2"/>
|
||||
|
||||
<!-- Blue container-style corner brackets -->
|
||||
<path d="M45 60 L45 90 L55 90 L55 70 L75 70 L75 60 Z" fill="url(#blueAccent)"/>
|
||||
<path d="M155 60 L155 90 L145 90 L145 70 L125 70 L125 60 Z" fill="url(#blueAccent)"/>
|
||||
<path d="M45 150 L45 120 L55 120 L55 140 L75 140 L75 150 Z" fill="url(#blueAccent)"/>
|
||||
<path d="M155 150 L155 120 L145 120 L145 140 L125 140 L125 150 Z" fill="url(#blueAccent)"/>
|
||||
|
||||
<!-- Docker/container symbol in center -->
|
||||
<g transform="translate(100, 105)">
|
||||
<rect x="-25" y="-15" width="50" height="30" fill="#2496ED" rx="3" opacity="0.9"/>
|
||||
<!-- Container boxes inside -->
|
||||
<rect x="-22" y="-12" width="12" height="8" fill="#fff" rx="1"/>
|
||||
<rect x="-7" y="-12" width="12" height="8" fill="#fff" rx="1"/>
|
||||
<rect x="8" y="-12" width="12" height="8" fill="#fff" rx="1"/>
|
||||
<rect x="-22" y="0" width="12" height="8" fill="#fff" rx="1"/>
|
||||
<rect x="-7" y="0" width="12" height="8" fill="#fff" rx="1"/>
|
||||
<rect x="8" y="0" width="12" height="8" fill="#fff" rx="1"/>
|
||||
</g>
|
||||
|
||||
<!-- Rope handles -->
|
||||
<ellipse cx="35" cy="100" rx="8" ry="15" fill="none" stroke="#8B7355" stroke-width="4"/>
|
||||
<ellipse cx="165" cy="100" rx="8" ry="15" fill="none" stroke="#8B7355" stroke-width="4"/>
|
||||
|
||||
<!-- Ground shadow -->
|
||||
<ellipse cx="100" cy="160" rx="60" ry="8" fill="#333" opacity="0.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
42
logo-proposals/06-minimal-icon.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Minimal Icon: Container with sprouting plant -->
|
||||
<defs>
|
||||
<linearGradient id="blueGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2496ED"/>
|
||||
<stop offset="100%" style="stop-color:#1565C0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="greenGrad" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#4CAF50"/>
|
||||
<stop offset="100%" style="stop-color:#81C784"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Container -->
|
||||
<rect x="40" y="100" width="120" height="70" fill="url(#blueGrad)" rx="6"/>
|
||||
|
||||
<!-- Container ridges -->
|
||||
<g fill="#1A7AC7">
|
||||
<rect x="48" y="108" width="104" height="12" rx="2"/>
|
||||
<rect x="48" y="124" width="104" height="12" rx="2"/>
|
||||
<rect x="48" y="140" width="104" height="12" rx="2"/>
|
||||
<rect x="48" y="156" width="104" height="10" rx="2"/>
|
||||
</g>
|
||||
|
||||
<!-- Corner posts -->
|
||||
<rect x="40" y="100" width="10" height="70" fill="#0D47A1" rx="2"/>
|
||||
<rect x="150" y="100" width="10" height="70" fill="#0D47A1" rx="2"/>
|
||||
|
||||
<!-- Plant stem -->
|
||||
<path d="M100 100 Q100 75 100 50" stroke="#4CAF50" stroke-width="6" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Leaves -->
|
||||
<ellipse cx="85" cy="65" rx="15" ry="8" fill="url(#greenGrad)" transform="rotate(-30 85 65)"/>
|
||||
<ellipse cx="115" cy="65" rx="15" ry="8" fill="url(#greenGrad)" transform="rotate(30 115 65)"/>
|
||||
<ellipse cx="80" cy="80" rx="12" ry="6" fill="url(#greenGrad)" transform="rotate(-45 80 80)"/>
|
||||
<ellipse cx="120" cy="80" rx="12" ry="6" fill="url(#greenGrad)" transform="rotate(45 120 80)"/>
|
||||
|
||||
<!-- Top leaves (crown) -->
|
||||
<ellipse cx="100" cy="45" rx="10" ry="18" fill="url(#greenGrad)"/>
|
||||
<ellipse cx="90" cy="50" rx="8" ry="14" fill="#66BB6A" transform="rotate(-20 90 50)"/>
|
||||
<ellipse cx="110" cy="50" rx="8" ry="14" fill="#66BB6A" transform="rotate(20 110 50)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
37
logo-proposals/07-hexagon-grid.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Hexagon grid: containers as honeycomb/farm cells -->
|
||||
<defs>
|
||||
<linearGradient id="hex1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2496ED"/>
|
||||
<stop offset="100%" style="stop-color:#1976D2"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="hex2" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#4CAF50"/>
|
||||
<stop offset="100%" style="stop-color:#388E3C"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Hexagon cluster -->
|
||||
<g transform="translate(100, 100)">
|
||||
<!-- Center hex -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="url(#hex1)"/>
|
||||
|
||||
<!-- Top hex -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="url(#hex1)" transform="translate(0, -52)" opacity="0.8"/>
|
||||
|
||||
<!-- Top-right hex -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="url(#hex1)" transform="translate(45, -26)" opacity="0.6"/>
|
||||
|
||||
<!-- Bottom-right hex -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="url(#hex1)" transform="translate(45, 26)" opacity="0.7"/>
|
||||
|
||||
<!-- Bottom hex - green accent -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="url(#hex2)" transform="translate(0, 52)"/>
|
||||
|
||||
<!-- Bottom-left hex -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="url(#hex1)" transform="translate(-45, 26)" opacity="0.7"/>
|
||||
|
||||
<!-- Top-left hex -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="url(#hex1)" transform="translate(-45, -26)" opacity="0.6"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
16
logo-proposals/08-container-sprout.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Minimal: geometric container with abstract sprout -->
|
||||
|
||||
<!-- Container base - single rounded rectangle -->
|
||||
<rect x="40" y="95" width="120" height="75" rx="8" fill="#2496ED"/>
|
||||
|
||||
<!-- Simple horizontal lines -->
|
||||
<line x1="50" y1="115" x2="150" y2="115" stroke="#1565C0" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="50" y1="135" x2="150" y2="135" stroke="#1565C0" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="50" y1="155" x2="150" y2="155" stroke="#1565C0" stroke-width="3" stroke-linecap="round"/>
|
||||
|
||||
<!-- Abstract sprout - two simple leaves -->
|
||||
<path d="M100 95 L100 55" stroke="#4CAF50" stroke-width="4" stroke-linecap="round"/>
|
||||
<ellipse cx="85" cy="55" rx="18" ry="10" fill="#4CAF50" transform="rotate(-35 85 55)"/>
|
||||
<ellipse cx="115" cy="55" rx="18" ry="10" fill="#4CAF50" transform="rotate(35 115 55)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 913 B |
16
logo-proposals/09-stacked-growth.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Stacked containers with upward growth arrow -->
|
||||
|
||||
<!-- Bottom container -->
|
||||
<rect x="50" y="130" width="100" height="35" rx="4" fill="#2496ED"/>
|
||||
|
||||
<!-- Middle container -->
|
||||
<rect x="60" y="90" width="80" height="35" rx="4" fill="#42A5F5"/>
|
||||
|
||||
<!-- Top container -->
|
||||
<rect x="70" y="50" width="60" height="35" rx="4" fill="#64B5F6"/>
|
||||
|
||||
<!-- Growth arrow emerging from top -->
|
||||
<path d="M100 50 L100 25" stroke="#4CAF50" stroke-width="6" stroke-linecap="round"/>
|
||||
<polygon points="100,15 90,28 110,28" fill="#4CAF50"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 605 B |
24
logo-proposals/10-negative-space.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Negative space: container with plant cutout -->
|
||||
<defs>
|
||||
<mask id="plantMask">
|
||||
<rect width="200" height="200" fill="white"/>
|
||||
<!-- Stem cutout -->
|
||||
<rect x="96" y="40" width="8" height="55" fill="black"/>
|
||||
<!-- Leaf cutouts -->
|
||||
<ellipse cx="82" cy="55" rx="16" ry="9" fill="black" transform="rotate(-40 82 55)"/>
|
||||
<ellipse cx="118" cy="55" rx="16" ry="9" fill="black" transform="rotate(40 118 55)"/>
|
||||
<ellipse cx="100" cy="38" rx="10" ry="14" fill="black"/>
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<!-- Container with mask -->
|
||||
<g mask="url(#plantMask)">
|
||||
<rect x="35" y="45" width="130" height="120" rx="12" fill="#2496ED"/>
|
||||
<!-- Ridges -->
|
||||
<rect x="35" y="70" width="130" height="4" fill="#1976D2"/>
|
||||
<rect x="35" y="95" width="130" height="4" fill="#1976D2"/>
|
||||
<rect x="35" y="120" width="130" height="4" fill="#1976D2"/>
|
||||
<rect x="35" y="145" width="130" height="4" fill="#1976D2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1018 B |
24
logo-proposals/11-cf-monogram.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- CF Monogram with container lines -->
|
||||
|
||||
<!-- C shape -->
|
||||
<path d="M45 100
|
||||
C45 55, 85 35, 115 45
|
||||
L108 62
|
||||
C88 55, 62 70, 62 100
|
||||
C62 130, 88 145, 108 138
|
||||
L115 155
|
||||
C85 165, 45 145, 45 100"
|
||||
fill="#2496ED"/>
|
||||
|
||||
<!-- F shape integrated with container lines -->
|
||||
<rect x="110" y="45" width="50" height="14" rx="2" fill="#2496ED"/>
|
||||
<rect x="110" y="45" width="14" height="110" rx="2" fill="#2496ED"/>
|
||||
<rect x="110" y="90" width="40" height="12" rx="2" fill="#2496ED"/>
|
||||
|
||||
<!-- Container ridge accents on F -->
|
||||
<rect x="128" y="63" width="32" height="3" fill="#4CAF50"/>
|
||||
<rect x="128" y="73" width="32" height="3" fill="#4CAF50"/>
|
||||
<rect x="128" y="108" width="22" height="3" fill="#4CAF50"/>
|
||||
<rect x="128" y="118" width="22" height="3" fill="#4CAF50"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 909 B |
25
logo-proposals/12-abstract-barn.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Ultra-minimal barn silhouette -->
|
||||
|
||||
<!-- Barn shape - single path -->
|
||||
<path d="M35 170
|
||||
L35 85
|
||||
L100 40
|
||||
L165 85
|
||||
L165 170
|
||||
Z"
|
||||
fill="#2496ED"/>
|
||||
|
||||
<!-- Container door accent - simple rectangle grid -->
|
||||
<g fill="#fff" opacity="0.95">
|
||||
<rect x="55" y="100" width="35" height="12" rx="2"/>
|
||||
<rect x="55" y="118" width="35" height="12" rx="2"/>
|
||||
<rect x="55" y="136" width="35" height="12" rx="2"/>
|
||||
<rect x="55" y="154" width="35" height="14" rx="2"/>
|
||||
|
||||
<rect x="110" y="100" width="35" height="12" rx="2"/>
|
||||
<rect x="110" y="118" width="35" height="12" rx="2"/>
|
||||
<rect x="110" y="136" width="35" height="12" rx="2"/>
|
||||
<rect x="110" y="154" width="35" height="14" rx="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 841 B |
42
logo-proposals/13-dots-grid.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Container grid made of dots - represents distributed containers -->
|
||||
|
||||
<g fill="#2496ED">
|
||||
<!-- Row 1 -->
|
||||
<circle cx="60" cy="50" r="12"/>
|
||||
<circle cx="100" cy="50" r="12"/>
|
||||
<circle cx="140" cy="50" r="12"/>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<circle cx="60" cy="90" r="12"/>
|
||||
<circle cx="100" cy="90" r="12"/>
|
||||
<circle cx="140" cy="90" r="12"/>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<circle cx="60" cy="130" r="12"/>
|
||||
<circle cx="100" cy="130" r="12" fill="#4CAF50"/>
|
||||
<circle cx="140" cy="130" r="12"/>
|
||||
</g>
|
||||
|
||||
<!-- Connection lines -->
|
||||
<g stroke="#2496ED" stroke-width="2" opacity="0.4">
|
||||
<line x1="60" y1="62" x2="60" y2="78"/>
|
||||
<line x1="100" y1="62" x2="100" y2="78"/>
|
||||
<line x1="140" y1="62" x2="140" y2="78"/>
|
||||
<line x1="60" y1="102" x2="60" y2="118"/>
|
||||
<line x1="100" y1="102" x2="100" y2="118"/>
|
||||
<line x1="140" y1="102" x2="140" y2="118"/>
|
||||
<line x1="72" y1="50" x2="88" y2="50"/>
|
||||
<line x1="112" y1="50" x2="128" y2="50"/>
|
||||
<line x1="72" y1="90" x2="88" y2="90"/>
|
||||
<line x1="112" y1="90" x2="128" y2="90"/>
|
||||
<line x1="72" y1="130" x2="88" y2="130"/>
|
||||
<line x1="112" y1="130" x2="128" y2="130"/>
|
||||
</g>
|
||||
|
||||
<!-- Sprout from green center -->
|
||||
<path d="M100 118 L100 155" stroke="#4CAF50" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="92" cy="158" r="6" fill="#4CAF50"/>
|
||||
<circle cx="108" cy="158" r="6" fill="#4CAF50"/>
|
||||
<circle cx="100" cy="165" r="5" fill="#66BB6A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
18
logo-proposals/14-single-mark.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<!-- Single bold mark: container + leaf combined -->
|
||||
|
||||
<!-- Main container shape -->
|
||||
<rect x="40" y="60" width="120" height="100" rx="10" fill="#2496ED"/>
|
||||
|
||||
<!-- Leaf growing from top-right corner -->
|
||||
<path d="M140 60
|
||||
Q170 30, 160 55
|
||||
Q175 25, 145 50
|
||||
Q165 15, 130 50
|
||||
L140 60"
|
||||
fill="#4CAF50"/>
|
||||
|
||||
<!-- Minimal internal lines -->
|
||||
<line x1="55" y1="95" x2="145" y2="95" stroke="#fff" stroke-width="4" stroke-linecap="round" opacity="0.9"/>
|
||||
<line x1="55" y1="125" x2="145" y2="125" stroke="#fff" stroke-width="4" stroke-linecap="round" opacity="0.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 681 B |
136
logo-proposals/15-current-logo.svg
Normal file
@@ -0,0 +1,136 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1052 523">
|
||||
<defs>
|
||||
<!-- Container blue colors -->
|
||||
<linearGradient id="containerFront" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#4AA8D8"/>
|
||||
<stop offset="100%" style="stop-color:#5BB5E0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="containerSide" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#3A8AB8"/>
|
||||
<stop offset="100%" style="stop-color:#4AA8D8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="containerTop" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#6BC4E8"/>
|
||||
<stop offset="100%" style="stop-color:#7DD0F0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="roofGrad" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#4AA8D8"/>
|
||||
<stop offset="100%" style="stop-color:#6BC4E8"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Green farm field -->
|
||||
<path d="M526 420 L120 280 L526 200 L932 280 Z" fill="#7CB342"/>
|
||||
<path d="M526 420 L526 200" stroke="#8BC34A" stroke-width="2"/>
|
||||
<path d="M526 420 L220 265" stroke="#8BC34A" stroke-width="2"/>
|
||||
<path d="M526 420 L832 265" stroke="#8BC34A" stroke-width="2"/>
|
||||
<path d="M526 420 L320 250" stroke="#8BC34A" stroke-width="2"/>
|
||||
<path d="M526 420 L732 250" stroke="#8BC34A" stroke-width="2"/>
|
||||
<!-- Field edge -->
|
||||
<ellipse cx="526" cy="420" rx="410" ry="30" fill="#5D8C2A"/>
|
||||
|
||||
<!-- Left container stack -->
|
||||
<g transform="translate(95, 180)">
|
||||
<!-- Back container -->
|
||||
<polygon points="60,40 130,10 130,70 60,100" fill="#3A8AB8"/>
|
||||
<polygon points="0,70 60,40 60,100 0,130" fill="url(#containerFront)"/>
|
||||
<polygon points="0,70 60,40 130,70 70,100" fill="url(#containerTop)"/>
|
||||
<!-- Container ridges -->
|
||||
<line x1="5" y1="80" x2="55" y2="52" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="5" y1="95" x2="55" y2="67" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="5" y1="110" x2="55" y2="82" stroke="#2D7A9E" stroke-width="2"/>
|
||||
|
||||
<!-- Front container -->
|
||||
<polygon points="60,80 130,50 130,110 60,140" fill="#3A8AB8"/>
|
||||
<polygon points="0,110 60,80 60,140 0,170" fill="url(#containerFront)"/>
|
||||
<polygon points="0,110 60,80 130,110 70,140" fill="url(#containerTop)"/>
|
||||
<!-- Container ridges -->
|
||||
<line x1="5" y1="120" x2="55" y2="92" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="5" y1="135" x2="55" y2="107" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="5" y1="150" x2="55" y2="122" stroke="#2D7A9E" stroke-width="2"/>
|
||||
</g>
|
||||
|
||||
<!-- Right container stack -->
|
||||
<g transform="translate(827, 180)">
|
||||
<!-- Back container -->
|
||||
<polygon points="0,10 70,40 70,100 0,70" fill="url(#containerFront)"/>
|
||||
<polygon points="70,40 130,70 130,130 70,100" fill="#3A8AB8"/>
|
||||
<polygon points="0,10 70,40 130,10 60,0" fill="url(#containerTop)"/>
|
||||
<!-- Container ridges -->
|
||||
<line x1="5" y1="20" x2="65" y2="48" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="5" y1="35" x2="65" y2="63" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="5" y1="50" x2="65" y2="78" stroke="#2D7A9E" stroke-width="2"/>
|
||||
|
||||
<!-- Front container -->
|
||||
<polygon points="0,50 70,80 70,140 0,110" fill="url(#containerFront)"/>
|
||||
<polygon points="70,80 130,110 130,170 70,140" fill="#3A8AB8"/>
|
||||
<polygon points="0,50 70,80 130,50 60,20" fill="url(#containerTop)"/>
|
||||
<!-- Container ridges -->
|
||||
<line x1="5" y1="60" x2="65" y2="88" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="5" y1="75" x2="65" y2="103" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="5" y1="90" x2="65" y2="118" stroke="#2D7A9E" stroke-width="2"/>
|
||||
</g>
|
||||
|
||||
<!-- Dashed connection lines -->
|
||||
<g stroke="#2D3E50" stroke-width="3" stroke-dasharray="8,6" fill="none">
|
||||
<path d="M225 320 Q350 280 430 300"/>
|
||||
<path d="M827 320 Q700 280 620 300"/>
|
||||
<path d="M225 350 Q300 340 380 360"/>
|
||||
<path d="M827 350 Q750 340 670 360"/>
|
||||
</g>
|
||||
|
||||
<!-- Central barn/container structure -->
|
||||
<g transform="translate(400, 85)">
|
||||
<!-- Base left container -->
|
||||
<polygon points="0,220 126,155 126,295 0,360" fill="url(#containerFront)"/>
|
||||
<!-- Container ridges -->
|
||||
<line x1="10" y1="232" x2="116" y2="172" stroke="#2D7A9E" stroke-width="3"/>
|
||||
<line x1="10" y1="262" x2="116" y2="202" stroke="#2D7A9E" stroke-width="3"/>
|
||||
<line x1="10" y1="292" x2="116" y2="232" stroke="#2D7A9E" stroke-width="3"/>
|
||||
<line x1="10" y1="322" x2="116" y2="262" stroke="#2D7A9E" stroke-width="3"/>
|
||||
|
||||
<!-- Base right container -->
|
||||
<polygon points="126,155 252,220 252,360 126,295" fill="#3A8AB8"/>
|
||||
<!-- Container ridges -->
|
||||
<line x1="136" y1="172" x2="242" y2="232" stroke="#2D7A9E" stroke-width="3"/>
|
||||
<line x1="136" y1="202" x2="242" y2="262" stroke="#2D7A9E" stroke-width="3"/>
|
||||
<line x1="136" y1="232" x2="242" y2="292" stroke="#2D7A9E" stroke-width="3"/>
|
||||
<line x1="136" y1="262" x2="242" y2="322" stroke="#2D7A9E" stroke-width="3"/>
|
||||
|
||||
<!-- Door opening -->
|
||||
<polygon points="80,260 126,235 172,260 172,360 126,335 80,360" fill="#1A2530"/>
|
||||
|
||||
<!-- Upper left container -->
|
||||
<polygon points="20,135 126,75 126,155 20,215" fill="url(#containerFront)"/>
|
||||
<line x1="28" y1="148" x2="118" y2="92" stroke="#2D7A9E" stroke-width="3"/>
|
||||
<line x1="28" y1="173" x2="118" y2="117" stroke="#2D7A9E" stroke-width="3"/>
|
||||
|
||||
<!-- Upper right container -->
|
||||
<polygon points="126,75 232,135 232,215 126,155" fill="#3A8AB8"/>
|
||||
<line x1="134" y1="92" x2="224" y2="148" stroke="#2D7A9E" stroke-width="3"/>
|
||||
<line x1="134" y1="117" x2="224" y2="173" stroke="#2D7A9E" stroke-width="3"/>
|
||||
|
||||
<!-- Roof -->
|
||||
<polygon points="126,0 20,70 20,135 126,75" fill="url(#roofGrad)"/>
|
||||
<polygon points="126,0 232,70 232,135 126,75" fill="#4AA8D8"/>
|
||||
<polygon points="126,0 20,70 126,75 232,70" fill="#7DD0F0"/>
|
||||
<!-- Roof ridges -->
|
||||
<line x1="30" y1="80" x2="118" y2="35" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="30" y1="100" x2="118" y2="55" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="134" y1="35" x2="222" y2="80" stroke="#2D7A9E" stroke-width="2"/>
|
||||
<line x1="134" y1="55" x2="222" y2="100" stroke="#2D7A9E" stroke-width="2"/>
|
||||
|
||||
<!-- Weather vane -->
|
||||
<line x1="126" y1="0" x2="126" y2="-35" stroke="#2D3E50" stroke-width="3"/>
|
||||
<!-- Crossbar -->
|
||||
<line x1="100" y1="-25" x2="152" y2="-25" stroke="#2D3E50" stroke-width="3"/>
|
||||
<!-- Terminal arrows -->
|
||||
<polygon points="100,-25 108,-20 108,-30" fill="#2D3E50"/>
|
||||
<polygon points="152,-25 144,-20 144,-30" fill="#2D3E50"/>
|
||||
<!-- Top arrow -->
|
||||
<polygon points="126,-35 120,-28 132,-28" fill="#2D3E50"/>
|
||||
</g>
|
||||
|
||||
<!-- COMPOSE FARM text -->
|
||||
<text x="526" y="500" text-anchor="middle" font-family="Arial Black, Arial, sans-serif" font-size="72" font-weight="900" fill="#2D3E50" letter-spacing="4">COMPOSE FARM</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
38
logo-proposals/16-ssh-keyed-container.svg
Normal file
@@ -0,0 +1,38 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<title>Compose Farm - SSH keyed access</title>
|
||||
<defs>
|
||||
<linearGradient id="cfBlue16" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2496ED"/>
|
||||
<stop offset="100%" stop-color="#1565C0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cfGreen16" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#66BB6A"/>
|
||||
<stop offset="100%" stop-color="#2E7D32"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Container -->
|
||||
<g>
|
||||
<rect x="70" y="70" width="115" height="90" rx="10" fill="url(#cfBlue16)"/>
|
||||
<rect x="70" y="70" width="14" height="90" rx="6" fill="#0D47A1" opacity="0.9"/>
|
||||
<rect x="171" y="70" width="14" height="90" rx="6" fill="#0D47A1" opacity="0.9"/>
|
||||
<g fill="#1A7AC7" opacity="0.9">
|
||||
<rect x="88" y="86" width="78" height="12" rx="4"/>
|
||||
<rect x="88" y="104" width="78" height="12" rx="4"/>
|
||||
<rect x="88" y="122" width="78" height="12" rx="4"/>
|
||||
<rect x="88" y="140" width="78" height="12" rx="4"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- SSH key -->
|
||||
<g>
|
||||
<circle cx="38" cy="118" r="18" fill="url(#cfGreen16)"/>
|
||||
<circle cx="38" cy="118" r="7" fill="#E8F5E9" opacity="0.95"/>
|
||||
<rect x="54" y="110" width="92" height="16" rx="8" fill="url(#cfGreen16)"/>
|
||||
<rect x="112" y="126" width="12" height="16" rx="3" fill="#2E7D32"/>
|
||||
<rect x="130" y="126" width="12" height="24" rx="3" fill="#2E7D32"/>
|
||||
</g>
|
||||
|
||||
<!-- Key insertion hint -->
|
||||
<path d="M62 118 L78 118" stroke="#E8F5E9" stroke-width="4" stroke-linecap="round" opacity="0.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
40
logo-proposals/17-reconcile-loop.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<title>Compose Farm - reconcile loop</title>
|
||||
<defs>
|
||||
<linearGradient id="cfBlue17" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2496ED"/>
|
||||
<stop offset="100%" stop-color="#1565C0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cfGreen17" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#66BB6A"/>
|
||||
<stop offset="100%" stop-color="#2E7D32"/>
|
||||
</linearGradient>
|
||||
<marker id="arrowGreen17" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 Z" fill="#2E7D32"/>
|
||||
</marker>
|
||||
<marker id="arrowBlue17" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 Z" fill="#1565C0"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Reconcile arrows -->
|
||||
<g fill="none" stroke-linecap="round" opacity="0.9">
|
||||
<path d="M46 112 A70 70 0 0 1 112 40" stroke="url(#cfGreen17)" stroke-width="6" marker-end="url(#arrowGreen17)"/>
|
||||
<path d="M154 88 A70 70 0 0 1 88 160" stroke="url(#cfBlue17)" stroke-width="6" marker-end="url(#arrowBlue17)"/>
|
||||
</g>
|
||||
|
||||
<!-- Central container -->
|
||||
<g transform="translate(60,78)">
|
||||
<rect x="0" y="0" width="80" height="52" rx="8" fill="url(#cfBlue17)"/>
|
||||
<rect x="0" y="0" width="10" height="52" rx="4" fill="#0D47A1" opacity="0.9"/>
|
||||
<rect x="70" y="0" width="10" height="52" rx="4" fill="#0D47A1" opacity="0.9"/>
|
||||
<g fill="#1A7AC7" opacity="0.9">
|
||||
<rect x="12" y="10" width="56" height="9" rx="3"/>
|
||||
<rect x="12" y="22" width="56" height="9" rx="3"/>
|
||||
<rect x="12" y="34" width="56" height="9" rx="3"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Apply check -->
|
||||
<path d="M84 105 L96 117 L118 93" fill="none" stroke="#E8F5E9" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" opacity="0.95"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
47
logo-proposals/18-auto-migration.svg
Normal file
@@ -0,0 +1,47 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<title>Compose Farm - auto migration</title>
|
||||
<defs>
|
||||
<linearGradient id="cfBlue18" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2496ED"/>
|
||||
<stop offset="100%" stop-color="#1565C0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cfGreen18" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#66BB6A"/>
|
||||
<stop offset="100%" stop-color="#2E7D32"/>
|
||||
</linearGradient>
|
||||
<marker id="arrow18" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 Z" fill="#2E7D32"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Hosts -->
|
||||
<g fill="#334155" opacity="0.9">
|
||||
<rect x="20" y="55" width="48" height="90" rx="8"/>
|
||||
<rect x="132" y="55" width="48" height="90" rx="8"/>
|
||||
</g>
|
||||
<g fill="#E2E8F0" opacity="0.85">
|
||||
<rect x="30" y="68" width="28" height="10" rx="3"/>
|
||||
<rect x="30" y="86" width="28" height="10" rx="3"/>
|
||||
<rect x="30" y="104" width="28" height="10" rx="3"/>
|
||||
<circle cx="56" cy="130" r="4"/>
|
||||
|
||||
<rect x="142" y="68" width="28" height="10" rx="3"/>
|
||||
<rect x="142" y="86" width="28" height="10" rx="3"/>
|
||||
<rect x="142" y="104" width="28" height="10" rx="3"/>
|
||||
<circle cx="168" cy="130" r="4"/>
|
||||
</g>
|
||||
|
||||
<!-- Migration arrow -->
|
||||
<path d="M72 100 L128 100" stroke="url(#cfGreen18)" stroke-width="6" stroke-linecap="round" marker-end="url(#arrow18)"/>
|
||||
|
||||
<!-- Moving container -->
|
||||
<g transform="translate(88,86)">
|
||||
<rect x="0" y="0" width="28" height="20" rx="4" fill="url(#cfBlue18)"/>
|
||||
<rect x="0" y="0" width="5" height="20" rx="3" fill="#0D47A1" opacity="0.9"/>
|
||||
<rect x="23" y="0" width="5" height="20" rx="3" fill="#0D47A1" opacity="0.9"/>
|
||||
<g fill="#1A7AC7" opacity="0.9">
|
||||
<rect x="6" y="5" width="16" height="4" rx="2"/>
|
||||
<rect x="6" y="11" width="16" height="4" rx="2"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
76
logo-proposals/19-multi-host-compose.svg
Normal file
@@ -0,0 +1,76 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<title>Compose Farm - multi-host compose</title>
|
||||
<defs>
|
||||
<linearGradient id="cfBlue15" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2496ED"/>
|
||||
<stop offset="100%" stop-color="#1565C0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cfGreen15" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#81C784"/>
|
||||
<stop offset="100%" stop-color="#2E7D32"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Field -->
|
||||
<polygon points="16,170 184,170 150,108 50,108" fill="url(#cfGreen15)" opacity="0.35"/>
|
||||
<g stroke="#2E7D32" stroke-width="2" opacity="0.22" stroke-linecap="round">
|
||||
<line x1="34" y1="170" x2="62" y2="108"/>
|
||||
<line x1="70" y1="170" x2="86" y2="108"/>
|
||||
<line x1="100" y1="170" x2="100" y2="108"/>
|
||||
<line x1="130" y1="170" x2="114" y2="108"/>
|
||||
<line x1="166" y1="170" x2="138" y2="108"/>
|
||||
</g>
|
||||
|
||||
<!-- SSH paths -->
|
||||
<g stroke="#334155" stroke-width="3" stroke-dasharray="4 6" opacity="0.75" fill="none" stroke-linecap="round">
|
||||
<path d="M100 132 C82 118 68 114 46 118"/>
|
||||
<path d="M100 132 C118 118 132 114 154 118"/>
|
||||
</g>
|
||||
<g fill="#334155" opacity="0.75">
|
||||
<circle cx="100" cy="132" r="3"/>
|
||||
<circle cx="46" cy="118" r="3"/>
|
||||
<circle cx="154" cy="118" r="3"/>
|
||||
</g>
|
||||
|
||||
<!-- Remote container: left -->
|
||||
<g transform="translate(18,118)">
|
||||
<rect x="0" y="0" width="56" height="34" rx="5" fill="url(#cfBlue15)"/>
|
||||
<rect x="0" y="0" width="7" height="34" rx="3" fill="#0D47A1" opacity="0.9"/>
|
||||
<rect x="49" y="0" width="7" height="34" rx="3" fill="#0D47A1" opacity="0.9"/>
|
||||
<g fill="#1A7AC7" opacity="0.9">
|
||||
<rect x="9" y="7" width="38" height="6" rx="2"/>
|
||||
<rect x="9" y="15" width="38" height="6" rx="2"/>
|
||||
<rect x="9" y="23" width="38" height="6" rx="2"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Remote container: right -->
|
||||
<g transform="translate(126,118)">
|
||||
<rect x="0" y="0" width="56" height="34" rx="5" fill="url(#cfBlue15)"/>
|
||||
<rect x="0" y="0" width="7" height="34" rx="3" fill="#0D47A1" opacity="0.9"/>
|
||||
<rect x="49" y="0" width="7" height="34" rx="3" fill="#0D47A1" opacity="0.9"/>
|
||||
<g fill="#1A7AC7" opacity="0.9">
|
||||
<rect x="9" y="7" width="38" height="6" rx="2"/>
|
||||
<rect x="9" y="15" width="38" height="6" rx="2"/>
|
||||
<rect x="9" y="23" width="38" height="6" rx="2"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Central container with prompt -->
|
||||
<g transform="translate(58,80)">
|
||||
<rect x="0" y="0" width="84" height="52" rx="7" fill="url(#cfBlue15)"/>
|
||||
<rect x="0" y="0" width="10" height="52" rx="4" fill="#0D47A1" opacity="0.9"/>
|
||||
<rect x="74" y="0" width="10" height="52" rx="4" fill="#0D47A1" opacity="0.9"/>
|
||||
<g fill="#1A7AC7" opacity="0.9">
|
||||
<rect x="12" y="10" width="60" height="9" rx="3"/>
|
||||
<rect x="12" y="22" width="60" height="9" rx="3"/>
|
||||
<rect x="12" y="34" width="60" height="9" rx="3"/>
|
||||
</g>
|
||||
|
||||
<!-- Prompt: >_ -->
|
||||
<g stroke="#F8FAFC" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.95">
|
||||
<polyline points="18,18 30,26 18,34"/>
|
||||
<line x1="36" y1="34" x2="58" y2="34"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -184,34 +184,17 @@ def update(
|
||||
service: ServiceOption = None,
|
||||
config: ConfigOption = None,
|
||||
) -> None:
|
||||
"""Update stacks (pull + build + down + up). With --service, updates just that service."""
|
||||
"""Update stacks. Only recreates containers if images changed."""
|
||||
stack_list, cfg = get_stacks(stacks or [], all_stacks, config)
|
||||
if service:
|
||||
if len(stack_list) != 1:
|
||||
print_error("--service requires exactly one stack")
|
||||
raise typer.Exit(1)
|
||||
# For service-level update: pull + build + stop + up (stop instead of down)
|
||||
raw = True
|
||||
results = run_async(
|
||||
run_sequential_on_stacks(
|
||||
cfg,
|
||||
stack_list,
|
||||
[
|
||||
f"pull --ignore-buildable {service}",
|
||||
f"build {service}",
|
||||
f"stop {service}",
|
||||
f"up -d {service}",
|
||||
],
|
||||
raw=raw,
|
||||
)
|
||||
)
|
||||
cmd = f"up -d --pull always --build {service}"
|
||||
else:
|
||||
raw = len(stack_list) == 1
|
||||
results = run_async(
|
||||
run_sequential_on_stacks(
|
||||
cfg, stack_list, ["pull --ignore-buildable", "build", "down", "up -d"], raw=raw
|
||||
)
|
||||
)
|
||||
cmd = "up -d --pull always --build"
|
||||
raw = len(stack_list) == 1
|
||||
results = run_async(run_on_stacks(cfg, stack_list, cmd, raw=raw))
|
||||
maybe_regenerate_traefik(cfg, results)
|
||||
report_results(results)
|
||||
|
||||
|
||||
@@ -1,78 +1,39 @@
|
||||
"""CDN asset definitions and caching for tests and demo recordings.
|
||||
|
||||
This module provides a single source of truth for CDN asset URLs used in
|
||||
browser tests and demo recordings. Assets are intercepted and served from
|
||||
a local cache to eliminate network variability.
|
||||
This module provides CDN asset URLs used in browser tests and demo recordings.
|
||||
Assets are intercepted and served from a local cache to eliminate network
|
||||
variability.
|
||||
|
||||
Note: The canonical list of CDN assets for production is in base.html
|
||||
(with data-vendor attributes). This module includes those plus dynamically
|
||||
loaded assets (like Monaco editor modules loaded by app.js).
|
||||
The canonical list of CDN assets is in vendor-assets.json. This module loads
|
||||
that file and provides the CDN_ASSETS dict for test caching.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _load_cdn_assets() -> dict[str, tuple[str, str]]:
|
||||
"""Load CDN assets from vendor-assets.json.
|
||||
|
||||
Returns:
|
||||
Dict mapping URL to (filename, content_type) tuple.
|
||||
|
||||
"""
|
||||
json_path = Path(__file__).parent / "vendor-assets.json"
|
||||
with json_path.open() as f:
|
||||
config = json.load(f)
|
||||
|
||||
return {asset["url"]: (asset["filename"], asset["content_type"]) for asset in config["assets"]}
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
# CDN assets to cache locally for tests/demos
|
||||
# Format: URL -> (local_filename, content_type)
|
||||
#
|
||||
# If tests fail with "Uncached CDN request", add the URL here.
|
||||
CDN_ASSETS: dict[str, tuple[str, str]] = {
|
||||
# From base.html (data-vendor attributes)
|
||||
"https://cdn.jsdelivr.net/npm/daisyui@5/themes.css": ("daisyui-themes.css", "text/css"),
|
||||
"https://cdn.jsdelivr.net/npm/daisyui@5": ("daisyui.css", "text/css"),
|
||||
"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4": (
|
||||
"tailwind.js",
|
||||
"application/javascript",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css": ("xterm.css", "text/css"),
|
||||
"https://unpkg.com/htmx.org@2.0.4": ("htmx.js", "application/javascript"),
|
||||
"https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.js": (
|
||||
"xterm.js",
|
||||
"application/javascript",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.js": (
|
||||
"xterm-fit.js",
|
||||
"application/javascript",
|
||||
),
|
||||
"https://unpkg.com/idiomorph/dist/idiomorph.min.js": (
|
||||
"idiomorph.js",
|
||||
"application/javascript",
|
||||
),
|
||||
"https://unpkg.com/idiomorph/dist/idiomorph-ext.min.js": (
|
||||
"idiomorph-ext.js",
|
||||
"application/javascript",
|
||||
),
|
||||
# Monaco editor - dynamically loaded by app.js
|
||||
"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js": (
|
||||
"monaco-loader.js",
|
||||
"application/javascript",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/editor/editor.main.js": (
|
||||
"monaco-editor-main.js",
|
||||
"application/javascript",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/editor/editor.main.css": (
|
||||
"monaco-editor-main.css",
|
||||
"text/css",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/base/worker/workerMain.js": (
|
||||
"monaco-workerMain.js",
|
||||
"application/javascript",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/basic-languages/yaml/yaml.js": (
|
||||
"monaco-yaml.js",
|
||||
"application/javascript",
|
||||
),
|
||||
"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/base/browser/ui/codicons/codicon/codicon.ttf": (
|
||||
"monaco-codicon.ttf",
|
||||
"font/ttf",
|
||||
),
|
||||
}
|
||||
# If tests fail with "Uncached CDN request", add the URL to vendor-assets.json.
|
||||
CDN_ASSETS: dict[str, tuple[str, str]] = _load_cdn_assets()
|
||||
|
||||
|
||||
def download_url(url: str) -> bytes | None:
|
||||
@@ -107,6 +68,7 @@ def ensure_vendor_cache(cache_dir: Path) -> Path:
|
||||
filepath = cache_dir / filename
|
||||
if filepath.exists():
|
||||
continue
|
||||
filepath.parent.mkdir(parents=True, exist_ok=True)
|
||||
content = download_url(url)
|
||||
if not content:
|
||||
msg = f"Failed to download {url} - check network/curl"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
@@ -14,6 +15,9 @@ if TYPE_CHECKING:
|
||||
from compose_farm.web.deps import get_config
|
||||
from compose_farm.web.streaming import run_cli_streaming, run_compose_streaming, tasks
|
||||
|
||||
# Environment variable to identify the web stack (for exclusion from bulk updates)
|
||||
CF_WEB_STACK = os.environ.get("CF_WEB_STACK", "")
|
||||
|
||||
router = APIRouter(tags=["actions"])
|
||||
|
||||
# Store task references to prevent garbage collection
|
||||
@@ -96,7 +100,15 @@ async def pull_all() -> dict[str, Any]:
|
||||
|
||||
@router.post("/update-all")
|
||||
async def update_all() -> dict[str, Any]:
|
||||
"""Update all stacks (pull + build + down + up)."""
|
||||
"""Update all stacks, excluding the web stack. Only recreates if images changed.
|
||||
|
||||
The web stack is excluded to prevent the UI from shutting down mid-operation.
|
||||
Use 'cf update <web-stack>' manually to update the web UI.
|
||||
"""
|
||||
config = get_config()
|
||||
task_id = _start_task(lambda tid: run_cli_streaming(config, ["update", "--all"], tid))
|
||||
return {"task_id": task_id, "command": "update --all"}
|
||||
# Get all stacks except the web stack to avoid self-shutdown
|
||||
stacks = [s for s in config.stacks if s != CF_WEB_STACK]
|
||||
if not stacks:
|
||||
return {"task_id": "", "command": "update (no stacks)", "skipped": True}
|
||||
task_id = _start_task(lambda tid: run_cli_streaming(config, ["update", *stacks], tid))
|
||||
return {"task_id": task_id, "command": f"update {' '.join(stacks)}"}
|
||||
|
||||
@@ -332,10 +332,14 @@ function loadMonaco(callback) {
|
||||
monacoLoading = true;
|
||||
|
||||
// Load the Monaco loader script
|
||||
// Use local paths when running from vendored wheel, CDN otherwise
|
||||
const monacoBase = window.CF_VENDORED
|
||||
? '/static/vendor/monaco'
|
||||
: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs';
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js';
|
||||
script.src = monacoBase + '/loader.js';
|
||||
script.onload = function() {
|
||||
require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' }});
|
||||
require.config({ paths: { vs: monacoBase }});
|
||||
require(['vs/editor/editor.main'], function() {
|
||||
monacoLoaded = true;
|
||||
monacoLoading = false;
|
||||
@@ -604,7 +608,7 @@ function playFabIntro() {
|
||||
cmd('action', 'Apply', 'Make reality match config', dashboardAction('apply'), icons.check),
|
||||
cmd('action', 'Refresh', 'Update state from reality', dashboardAction('refresh'), icons.refresh_cw),
|
||||
cmd('action', 'Pull All', 'Pull latest images for all stacks', dashboardAction('pull-all'), icons.cloud_download),
|
||||
cmd('action', 'Update All', 'Update all stacks', dashboardAction('update-all'), icons.refresh_cw),
|
||||
cmd('action', 'Update All', 'Update all stacks except web', dashboardAction('update-all'), icons.refresh_cw),
|
||||
cmd('app', 'Theme', 'Change color theme', openThemePicker, icons.palette),
|
||||
cmd('app', 'Dashboard', 'Go to dashboard', nav('/'), icons.home),
|
||||
cmd('app', 'Live Stats', 'View all containers across hosts', nav('/live-stats'), icons.box),
|
||||
|
||||
@@ -97,8 +97,8 @@
|
||||
|
||||
<!-- Scripts - HTMX first -->
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4" data-vendor="htmx.js"></script>
|
||||
<script src="https://unpkg.com/idiomorph/dist/idiomorph.min.js"></script>
|
||||
<script src="https://unpkg.com/idiomorph/dist/idiomorph-ext.min.js"></script>
|
||||
<script src="https://unpkg.com/idiomorph/dist/idiomorph.min.js" data-vendor="idiomorph.js"></script>
|
||||
<script src="https://unpkg.com/idiomorph/dist/idiomorph-ext.min.js" data-vendor="idiomorph-ext.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.js" data-vendor="xterm.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.js" data-vendor="xterm-fit.js"></script>
|
||||
<script src="/static/app.js"></script>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{{ action_btn("Apply", "/api/apply", "primary", "Make reality match config", check()) }}
|
||||
{{ action_btn("Refresh", "/api/refresh", "outline", "Update state from reality", refresh_cw()) }}
|
||||
{{ action_btn("Pull All", "/api/pull-all", "outline", "Pull latest images for all stacks", cloud_download()) }}
|
||||
{{ action_btn("Update All", "/api/update-all", "outline", "Update all stacks (pull + build + down + up)", rotate_cw()) }}
|
||||
{{ action_btn("Update All", "/api/update-all", "outline", "Update all stacks except web (only restarts if changed)", rotate_cw()) }}
|
||||
<div class="tooltip" data-tip="Save compose-farm.yaml config file"><button id="save-config-btn" class="btn btn-outline">{{ save() }} Save Config</button></div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{{ action_btn("Up", "/api/stack/" ~ name ~ "/up", "primary", "Start stack (docker compose up -d)", play()) }}
|
||||
{{ action_btn("Down", "/api/stack/" ~ name ~ "/down", "outline", "Stop stack (docker compose down)", square()) }}
|
||||
{{ action_btn("Restart", "/api/stack/" ~ name ~ "/restart", "secondary", "Restart stack (down + up)", rotate_cw()) }}
|
||||
{{ action_btn("Update", "/api/stack/" ~ name ~ "/update", "accent", "Update to latest (pull + build + down + up)", download()) }}
|
||||
{{ action_btn("Update", "/api/stack/" ~ name ~ "/update", "accent", "Update to latest (only restarts if changed)", download()) }}
|
||||
|
||||
<div class="divider divider-horizontal mx-0"></div>
|
||||
|
||||
|
||||
122
src/compose_farm/web/vendor-assets.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$comment": "CDN assets vendored into production builds and cached for tests",
|
||||
"assets": [
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/daisyui@5",
|
||||
"filename": "daisyui.css",
|
||||
"content_type": "text/css",
|
||||
"package": "daisyui"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/daisyui@5/themes.css",
|
||||
"filename": "daisyui-themes.css",
|
||||
"content_type": "text/css",
|
||||
"package": "daisyui"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4",
|
||||
"filename": "tailwind.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "tailwindcss"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css",
|
||||
"filename": "xterm.css",
|
||||
"content_type": "text/css",
|
||||
"package": "xterm"
|
||||
},
|
||||
{
|
||||
"url": "https://unpkg.com/htmx.org@2.0.4",
|
||||
"filename": "htmx.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "htmx"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.js",
|
||||
"filename": "xterm.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "xterm"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.js",
|
||||
"filename": "xterm-fit.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "xterm"
|
||||
},
|
||||
{
|
||||
"url": "https://unpkg.com/idiomorph/dist/idiomorph.min.js",
|
||||
"filename": "idiomorph.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "idiomorph"
|
||||
},
|
||||
{
|
||||
"url": "https://unpkg.com/idiomorph/dist/idiomorph-ext.min.js",
|
||||
"filename": "idiomorph-ext.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "idiomorph"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js",
|
||||
"filename": "monaco/loader.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "monaco-editor"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/editor/editor.main.js",
|
||||
"filename": "monaco/editor/editor.main.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "monaco-editor"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/editor/editor.main.css",
|
||||
"filename": "monaco/editor/editor.main.css",
|
||||
"content_type": "text/css",
|
||||
"package": "monaco-editor"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/base/worker/workerMain.js",
|
||||
"filename": "monaco/base/worker/workerMain.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "monaco-editor"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/basic-languages/yaml/yaml.js",
|
||||
"filename": "monaco/basic-languages/yaml/yaml.js",
|
||||
"content_type": "application/javascript",
|
||||
"package": "monaco-editor"
|
||||
},
|
||||
{
|
||||
"url": "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/base/browser/ui/codicons/codicon/codicon.ttf",
|
||||
"filename": "monaco/base/browser/ui/codicons/codicon/codicon.ttf",
|
||||
"content_type": "font/ttf",
|
||||
"package": "monaco-editor"
|
||||
}
|
||||
],
|
||||
"licenses": {
|
||||
"htmx": {
|
||||
"type": "MIT",
|
||||
"url": "https://raw.githubusercontent.com/bigskysoftware/htmx/master/LICENSE"
|
||||
},
|
||||
"idiomorph": {
|
||||
"type": "BSD-2-Clause",
|
||||
"url": "https://raw.githubusercontent.com/bigskysoftware/idiomorph/main/LICENSE"
|
||||
},
|
||||
"xterm": {
|
||||
"type": "MIT",
|
||||
"url": "https://raw.githubusercontent.com/xtermjs/xterm.js/master/LICENSE"
|
||||
},
|
||||
"daisyui": {
|
||||
"type": "MIT",
|
||||
"url": "https://raw.githubusercontent.com/saadeghi/daisyui/master/LICENSE"
|
||||
},
|
||||
"tailwindcss": {
|
||||
"type": "MIT",
|
||||
"url": "https://raw.githubusercontent.com/tailwindlabs/tailwindcss/master/LICENSE"
|
||||
},
|
||||
"monaco-editor": {
|
||||
"type": "MIT",
|
||||
"url": "https://raw.githubusercontent.com/microsoft/monaco-editor/main/LICENSE.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,19 +98,18 @@ class TestMigrationCommands:
|
||||
class TestUpdateCommandSequence:
|
||||
"""Tests for update command sequence."""
|
||||
|
||||
def test_update_command_sequence_includes_build(self) -> None:
|
||||
"""Update command should use pull --ignore-buildable and build."""
|
||||
def test_update_command_uses_pull_always_and_build(self) -> None:
|
||||
"""Update command should use --pull always --build flags."""
|
||||
# This is a static check of the command sequence in lifecycle.py
|
||||
# The actual command sequence is defined in the update function
|
||||
|
||||
source = inspect.getsource(lifecycle.update)
|
||||
|
||||
# Verify the command sequence includes pull --ignore-buildable
|
||||
assert "pull --ignore-buildable" in source
|
||||
# Verify build is included
|
||||
assert '"build"' in source or "'build'" in source
|
||||
# Verify the sequence is pull, build, down, up
|
||||
assert "down" in source
|
||||
# Verify the command uses --pull always (only recreates if image changed)
|
||||
assert "--pull always" in source
|
||||
# Verify --build is included for buildable services
|
||||
assert "--build" in source
|
||||
# Verify up -d is used
|
||||
assert "up -d" in source
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Run with: uv run pytest tests/web/test_htmx_browser.py -v --no-cov
|
||||
|
||||
CDN assets are cached locally (in .pytest_cache/vendor/) to eliminate network
|
||||
variability. If a test fails with "Uncached CDN request", add the URL to
|
||||
compose_farm.web.cdn.CDN_ASSETS.
|
||||
src/compose_farm/web/vendor-assets.json.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -90,7 +90,7 @@ def page(page: Page, vendor_cache: Path) -> Page:
|
||||
return
|
||||
# Uncached CDN request - abort with helpful error
|
||||
route.abort("failed")
|
||||
msg = f"Uncached CDN request: {url}\n\nAdd this URL to CDN_ASSETS in tests/web/test_htmx_browser.py"
|
||||
msg = f"Uncached CDN request: {url}\n\nAdd this URL to src/compose_farm/web/vendor-assets.json"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
page.route(re.compile(r"https://(cdn\.jsdelivr\.net|unpkg\.com)/.*"), handle_cdn)
|
||||
|
||||