chore: init repo
This commit is contained in:
commit
9067b69eac
239
README.md
Normal file
239
README.md
Normal file
@ -0,0 +1,239 @@
|
||||
# provctl - Machine Orchestration & Service Control Platform
|
||||
|
||||
<div align="center">
|
||||
<img src="imgs/provctl_logo_h.svg" alt="provctl Logo" width="600" />
|
||||
</div>
|
||||
|
||||
[](https://www.rust-lang.org)
|
||||
[](LICENSE)
|
||||
|
||||
**provctl** is a production-grade Machine Orchestration & Service Control Platform for managing SSH-based deployments across multiple machines with sophisticated deployment strategies, resilience features, and comprehensive observability.
|
||||
|
||||
## Features
|
||||
|
||||
✅ **Local Service Management**
|
||||
- 🔧 **Multi-Backend Support** - systemd (Linux), launchd (macOS), PID files (Unix)
|
||||
- 🎯 **Unified Interface** - Single CLI for all platforms
|
||||
- 📊 **Status & Logging** - Comprehensive service monitoring
|
||||
- ⚙️ **Configuration-Driven** - TOML-based zero-hardcoded-strings approach
|
||||
|
||||
✅ **Machine Orchestration**
|
||||
- 🚀 **SSH Command Execution** - Real async SSH with ssh2-rs
|
||||
- 📦 **Multiple Deployment Strategies** - Rolling, Blue-Green, Canary
|
||||
- 🔄 **Connection Pooling** - Per-host reuse with configurable limits
|
||||
- 🎯 **Retry & Timeout Logic** - Exponential/linear/fibonacci backoff strategies
|
||||
- 🔒 **Host Key Verification** - SSH known_hosts integration
|
||||
- 💪 **Circuit Breaker** - Fault tolerance for failing hosts
|
||||
- 🏥 **Health Checks** - Command/HTTP/TCP monitoring
|
||||
- 📊 **Metrics & Audit Logging** - Comprehensive observability
|
||||
|
||||
✅ **REST API & Web Dashboard**
|
||||
- 🌐 **Axum REST API** - Production-grade HTTP server
|
||||
- 📱 **Leptos Dashboard** - Modern Rust web UI
|
||||
- 📡 **Programmatic Access** - Full API for automation
|
||||
|
||||
✅ **Production-Ready**
|
||||
- Zero unsafe code (`#![forbid(unsafe_code)]`)
|
||||
- Comprehensive error handling with Result<T> types
|
||||
- 100% documentation coverage (rustdoc)
|
||||
- No unwrap() calls in production code
|
||||
|
||||
✅ **Developer Friendly**
|
||||
- Async/await throughout (tokio-based)
|
||||
- Thread-safe metrics collection (Arc<Mutex<>>)
|
||||
- Trait-based design for extensibility
|
||||
- Full rustdoc for all public APIs
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Option 1: Use the installer
|
||||
nu scripts/install-provctl.nu
|
||||
|
||||
# Option 2: Build manually
|
||||
cargo build --release
|
||||
./target/release/provctl --help
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Start a service
|
||||
provctl start my-service
|
||||
|
||||
# Stop a service
|
||||
provctl stop my-service
|
||||
|
||||
# Restart a service
|
||||
provctl restart my-service
|
||||
|
||||
# Check status
|
||||
provctl status my-service
|
||||
|
||||
# View logs (last 50 lines)
|
||||
provctl logs my-service --lines 50
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description | Example |
|
||||
|---------|-------------|---------|
|
||||
| `start` | Start a service | `provctl start api` |
|
||||
| `stop` | Stop a service | `provctl stop api` |
|
||||
| `restart` | Restart a service | `provctl restart api` |
|
||||
| `status` | Check service status | `provctl status api` |
|
||||
| `logs` | View service logs | `provctl logs api --lines 100` |
|
||||
|
||||
## Configuration
|
||||
|
||||
provctl is **100% configuration-driven**. All user-facing strings and defaults come from TOML files:
|
||||
|
||||
- **`configs/messages.toml`** - All user-facing messages
|
||||
- **`configs/defaults.toml`** - Timeouts, paths, and operational parameters
|
||||
|
||||
Zero hardcoded strings in code!
|
||||
|
||||
## Architecture
|
||||
|
||||
provctl uses a **trait-based backend system** for easy extensibility:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ provctl-cli (clap) │
|
||||
├─────────────────────────────────────┤
|
||||
│ Backend Trait │
|
||||
├─────────────┬───────────┬───────────┤
|
||||
│ systemd │ launchd │ Pidfile │
|
||||
│ (Linux) │ (macOS) │ (Unix) │
|
||||
└─────────────┴───────────┴───────────┘
|
||||
```
|
||||
|
||||
### Backends
|
||||
|
||||
**systemd** (Linux)
|
||||
- Uses `systemctl` for service management
|
||||
- Queries `journalctl` for logs
|
||||
- Best for production Linux systems
|
||||
|
||||
**launchd** (macOS)
|
||||
- Generates plist files automatically
|
||||
- Uses `launchctl` for management
|
||||
- Best for macOS development and production
|
||||
|
||||
**PID files** (Universal)
|
||||
- Works on any Unix system
|
||||
- File-based process tracking
|
||||
- Fallback when systemd/launchd unavailable
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
provctl/
|
||||
├── crates/
|
||||
│ ├── provctl-core/ # Service management fundamentals
|
||||
│ ├── provctl-config/ # Configuration loading
|
||||
│ ├── provctl-backend/ # Backend trait + implementations
|
||||
│ ├── provctl-cli/ # CLI interface
|
||||
│ ├── provctl-machines/ # Machine orchestration core
|
||||
│ │ ├── ssh_async.rs # Real async SSH
|
||||
│ │ ├── ssh_pool.rs # Connection pooling
|
||||
│ │ ├── ssh_retry.rs # Retry & timeout logic
|
||||
│ │ ├── ssh_host_key.rs # Host key verification
|
||||
│ │ ├── health_check.rs # Health monitoring
|
||||
│ │ ├── metrics.rs # Metrics & audit logging
|
||||
│ │ └── ... (other modules)
|
||||
│ ├── provctl-api/ # REST API server
|
||||
│ └── provctl-dashboard/ # Web UI (Leptos)
|
||||
├── configs/
|
||||
│ ├── messages.toml # User messages
|
||||
│ └── defaults.toml # Configuration defaults
|
||||
├── .coder/info/ # Project tracking & docs
|
||||
├── docs/ # Documentation
|
||||
└── scripts/ # Development & infrastructure scripts
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
cargo test --all
|
||||
```
|
||||
|
||||
### Lint & Format
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo clippy --all --all-targets
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
```bash
|
||||
cargo doc --open --no-deps
|
||||
```
|
||||
|
||||
## Integration with syntaxis-api
|
||||
|
||||
provctl provides service management for syntaxis-api:
|
||||
|
||||
```bash
|
||||
# In syntaxis-api.nu wrapper:
|
||||
provctl start syntaxis-api
|
||||
|
||||
# Or from provisioning schema:
|
||||
provctl deploy --config provisioning/syntaxis-api.toml
|
||||
```
|
||||
|
||||
See [provisioning/](provisioning/) for integration examples.
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
✅ **No unsafe code** - Enforced via `#![forbid(unsafe_code)]`
|
||||
✅ **100% documented** - All public APIs have rustdoc comments
|
||||
✅ **Fully tested** - 332+ unit tests, 100% passing
|
||||
✅ **Formatted & linted** - `cargo fmt` + `cargo clippy` clean
|
||||
✅ **Zero hardcoded strings** - All in TOML configuration
|
||||
✅ **Proper error handling** - Comprehensive Result<T> types
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See [LICENSE](LICENSE) for details.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions welcome! Please ensure:
|
||||
- `cargo fmt --all` passes
|
||||
- `cargo clippy --all --all-targets` passes
|
||||
- All tests pass: `cargo test --all`
|
||||
- No unsafe code
|
||||
|
||||
## Documentation Overview
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| **QUICKSTART.md** | Get started with machine orchestration in 5 minutes |
|
||||
| **docs/CLI_GUIDE.md** | Complete CLI reference for all commands |
|
||||
| **docs/ARCHITECTURE.md** | System design and architecture |
|
||||
| **docs/INSTALLATION_GUIDE.md** | Installation and setup instructions |
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
- **Quick Start**: See [QUICKSTART.md](QUICKSTART.md)
|
||||
- **CLI Reference**: See [docs/CLI_GUIDE.md](docs/CLI_GUIDE.md)
|
||||
- **Documentation**: See [docs/](docs/) directory
|
||||
- **Architecture**: See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ for the Rust community**
|
||||
246
imgs/provctl_logo.svg
Normal file
246
imgs/provctl_logo.svg
Normal file
@ -0,0 +1,246 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 210 160">
|
||||
<defs>
|
||||
<!-- Continuous flow gradient for orbital system -->
|
||||
<linearGradient id="flowGradient">
|
||||
<stop offset="0%" style="stop-color:#455aa5;stop-opacity:1">
|
||||
<animate attributeName="offset" values="0%;1%;0%" dur="4s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
<stop offset="25%" style="stop-color:#4cc2f1;stop-opacity:1">
|
||||
<animate attributeName="offset" values="0.25;1.25;0.25" dur="4s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
<stop offset="50%" style="stop-color:#f2b03f;stop-opacity:1">
|
||||
<animate attributeName="offset" values="0.5;1.5;0.5" dur="4s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
<stop offset="75%" style="stop-color:#05ab9e;stop-opacity:1">
|
||||
<animate attributeName="offset" values="0.75;1.75;0.75" dur="4s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
<stop offset="100%" style="stop-color:#5e74b7;stop-opacity:1">
|
||||
<animate attributeName="offset" values="1;2;1" dur="4s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
</linearGradient>
|
||||
|
||||
<!-- Radial gradient for nodes -->
|
||||
<radialGradient id="nodeGradient" cx="30%" cy="30%">
|
||||
<stop offset="0%" style="stop-color:#7085c9;stop-opacity:1"/>
|
||||
<stop offset="100%" style="stop-color:#5e74b7;stop-opacity:1"/>
|
||||
</radialGradient>
|
||||
|
||||
<!-- Glow filters -->
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
|
||||
<filter id="strongGlow">
|
||||
<feGaussianBlur stdDeviation="5" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
|
||||
<!-- Text gradient with animation -->
|
||||
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#455aa5;stop-opacity:1">
|
||||
<animate attributeName="stop-color" values="#455aa5;#4cc2f1;#455aa5" dur="4s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
<stop offset="50%" style="stop-color:#4cc2f1;stop-opacity:1">
|
||||
<animate attributeName="stop-color" values="#4cc2f1;#05ab9e;#4cc2f1" dur="4s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
<stop offset="100%" style="stop-color:#05ab9e;stop-opacity:1">
|
||||
<animate attributeName="stop-color" values="#05ab9e;#455aa5;#05ab9e" dur="4s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
</linearGradient>
|
||||
|
||||
<!-- Particle gradient for floating elements -->
|
||||
<linearGradient id="particleGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#4cc2f1;stop-opacity:0.8">
|
||||
<animate attributeName="stop-color" values="#4cc2f1;#f2b03f;#05ab9e;#4cc2f1" dur="3s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
<stop offset="100%" style="stop-color:#05ab9e;stop-opacity:0.8">
|
||||
<animate attributeName="stop-color" values="#05ab9e;#4cc2f1;#f2b03f;#05ab9e" dur="3s" repeatCount="indefinite"/>
|
||||
</stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<g id="logo-main">
|
||||
<!-- ORBITAL ICON SYSTEM (Left side) -->
|
||||
<g id="orbital-system" transform="translate(105, 45) scale(0.405)">
|
||||
|
||||
<!-- Outer orbital rings -->
|
||||
<ellipse cx="0" cy="0" rx="180" ry="90" fill="none" stroke="#8895b3" stroke-width="2" opacity="0.4">
|
||||
<animate attributeName="opacity" values="0.35;0.5;0.35" dur="3s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</ellipse>
|
||||
|
||||
<ellipse cx="0" cy="0" rx="150" ry="75" fill="none" stroke="#8895b3" stroke-width="2" opacity="0.4">
|
||||
<animate attributeName="opacity" values="0.35;0.5;0.35" dur="2.5s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</ellipse>
|
||||
|
||||
<!-- INFINITY LOOP - Continuous CI/CD flow -->
|
||||
<g id="infinity-flow">
|
||||
<path d="M -110,0 C -110,-45 -65,-60 -30,-35 C 5,-10 5,10 -30,35 C -65,60 -110,45 -110,0 M 110,0 C 110,45 65,60 30,35 C -5,10 -5,-10 30,-35 C 65,-60 110,-45 110,0" fill="none" stroke="url(#flowGradient)" stroke-width="8" stroke-linecap="round" opacity="0.7" filter="url(#glow)">
|
||||
<animate attributeName="opacity" values="0.6;0.9;0.6" dur="2s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</path>
|
||||
|
||||
<path d="M -110,0 C -110,-45 -65,-60 -30,-35 C 5,-10 5,10 -30,35 C -65,60 -110,45 -110,0 M 110,0 C 110,45 65,60 30,35 C -5,10 -5,-10 30,-35 C 65,-60 110,-45 110,0" fill="none" stroke="#8895b3" stroke-width="3" stroke-linecap="round" opacity="0.7"/>
|
||||
</g>
|
||||
|
||||
<!-- Data particles flowing along infinity path -->
|
||||
<g id="flow-particles">
|
||||
<circle r="4" fill="#f2b03f" filter="url(#strongGlow)">
|
||||
<animateMotion dur="6s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1">
|
||||
<mpath href="#infinity-path"/>
|
||||
</animateMotion>
|
||||
<animate attributeName="opacity" values="0.4;1;0.4" dur="6s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
|
||||
<circle r="4" fill="#05ab9e" filter="url(#strongGlow)">
|
||||
<animateMotion dur="6s" begin="2s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1">
|
||||
<mpath href="#infinity-path"/>
|
||||
</animateMotion>
|
||||
<animate attributeName="opacity" values="0.4;1;0.4" dur="6s" begin="2s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
|
||||
<circle r="4" fill="#4cc2f1" filter="url(#strongGlow)">
|
||||
<animateMotion dur="6s" begin="4s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1">
|
||||
<mpath href="#infinity-path"/>
|
||||
</animateMotion>
|
||||
<animate attributeName="opacity" values="0.4;1;0.4" dur="6s" begin="4s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
</g>
|
||||
|
||||
<!-- Hidden path for particle animation -->
|
||||
<path id="infinity-path" d="M -110,0 C -110,-45 -65,-60 -30,-35 C 5,-10 5,10 -30,35 C -65,60 -110,45 -110,0 Z M 30,-35 C 65,-60 110,-45 110,0 C 110,45 65,60 30,35 C -5,10 -5,-10 30,-35 Z" fill="none" stroke="none"/>
|
||||
|
||||
<!-- CENTRAL CONTROL NODE (local) -->
|
||||
<g id="central-node">
|
||||
<circle cx="0" cy="0" r="25" fill="#455aa5" opacity="0.2">
|
||||
<animate attributeName="r" values="22;28;22" dur="2.5s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
<animate attributeName="opacity" values="0.15;0.35;0.15" dur="2.5s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
|
||||
<circle cx="0" cy="0" r="20" fill="#455aa5" stroke="#fff" stroke-width="2.5" filter="url(#glow)"/>
|
||||
|
||||
<circle cx="0" cy="0" r="15" fill="none" stroke="#05ab9e" stroke-width="1.5" opacity="0.5"/>
|
||||
<circle cx="0" cy="0" r="10" fill="none" stroke="#05ab9e" stroke-width="2" opacity="0.7"/>
|
||||
|
||||
<circle cx="0" cy="0" r="6" fill="#05ab9e" filter="url(#strongGlow)">
|
||||
<animate attributeName="r" values="5;8;5" dur="2s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
<animate attributeName="opacity" values="0.7;1;0.7" dur="2s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
|
||||
<circle cx="-3" cy="-3" r="3" fill="#fff" opacity="0.9"/>
|
||||
</g>
|
||||
|
||||
<!-- LARGE ORBITAL REMOTE NODES -->
|
||||
<g id="remote-nodes">
|
||||
<!-- Node 1 - Top -->
|
||||
<g transform="translate(0, -70)">
|
||||
<circle cx="0" cy="0" r="22" fill="#4cc2f1" opacity="0.15">
|
||||
<animate attributeName="r" values="20;24;20" dur="2.2s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
<animate attributeName="opacity" values="0.1;0.25;0.1" dur="2.2s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
<circle cx="0" cy="0" r="18" fill="url(#nodeGradient)" stroke="#fff" stroke-width="2.5" filter="url(#glow)"/>
|
||||
<circle cx="0" cy="0" r="13" fill="none" stroke="#8895b3" stroke-width="1.5" opacity="0.7"/>
|
||||
<text x="0" y="6" font-family="'Courier New', 'Consolas', monospace" font-size="16" font-weight="bold" fill="#f2b03f" text-anchor="middle">>_</text>
|
||||
<line x1="0" y1="18" x2="0" y2="48" stroke="#8895b3" stroke-width="2.5" stroke-dasharray="4,4" opacity="0.75">
|
||||
<animate attributeName="stroke-dashoffset" values="0;8;0" dur="1.5s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
</g>
|
||||
|
||||
<!-- Node 2 - Right -->
|
||||
<g transform="translate(140, 0)">
|
||||
<circle cx="0" cy="0" r="22" fill="#4cc2f1" opacity="0.15">
|
||||
<animate attributeName="r" values="20;24;20" dur="2.4s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
<animate attributeName="opacity" values="0.1;0.25;0.1" dur="2.4s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
<circle cx="0" cy="0" r="18" fill="url(#nodeGradient)" stroke="#fff" stroke-width="2.5" filter="url(#glow)"/>
|
||||
<circle cx="0" cy="0" r="13" fill="none" stroke="#8895b3" stroke-width="1.5" opacity="0.7"/>
|
||||
<text x="0" y="6" font-family="'Courier New', 'Consolas', monospace" font-size="16" font-weight="bold" fill="#f2b03f" text-anchor="middle">>_</text>
|
||||
<line x1="-18" y1="0" x2="-118" y2="0" stroke="#8895b3" stroke-width="2.5" stroke-dasharray="4,4" opacity="0.75">
|
||||
<animate attributeName="stroke-dashoffset" values="0;8;0" dur="1.7s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
</g>
|
||||
|
||||
<!-- Node 3 - Bottom Right -->
|
||||
<g transform="translate(100, 50)">
|
||||
<circle cx="0" cy="0" r="22" fill="#4cc2f1" opacity="0.15">
|
||||
<animate attributeName="r" values="20;24;20" dur="2.1s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
<animate attributeName="opacity" values="0.1;0.25;0.1" dur="2.1s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
<circle cx="0" cy="0" r="18" fill="url(#nodeGradient)" stroke="#fff" stroke-width="2.5" filter="url(#glow)"/>
|
||||
<circle cx="0" cy="0" r="13" fill="none" stroke="#8895b3" stroke-width="1.5" opacity="0.7"/>
|
||||
<text x="0" y="6" font-family="'Courier New', 'Consolas', monospace" font-size="16" font-weight="bold" fill="#f2b03f" text-anchor="middle">>_</text>
|
||||
<line x1="-10" y1="-10" x2="-80" y2="-40" stroke="#8895b3" stroke-width="2.5" stroke-dasharray="4,4" opacity="0.75">
|
||||
<animate attributeName="stroke-dashoffset" values="0;8;0" dur="1.9s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
</g>
|
||||
|
||||
<!-- Node 4 - Bottom Left -->
|
||||
<g transform="translate(-100, 50)">
|
||||
<circle cx="0" cy="0" r="22" fill="#4cc2f1" opacity="0.15">
|
||||
<animate attributeName="r" values="20;24;20" dur="2.3s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
<animate attributeName="opacity" values="0.1;0.25;0.1" dur="2.3s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
<circle cx="0" cy="0" r="18" fill="url(#nodeGradient)" stroke="#fff" stroke-width="2.5" filter="url(#glow)"/>
|
||||
<circle cx="0" cy="0" r="13" fill="none" stroke="#8895b3" stroke-width="1.5" opacity="0.7"/>
|
||||
<text x="0" y="6" font-family="'Courier New', 'Consolas', monospace" font-size="16" font-weight="bold" fill="#f2b03f" text-anchor="middle">>_</text>
|
||||
<line x1="10" y1="-10" x2="80" y2="-40" stroke="#8895b3" stroke-width="2.5" stroke-dasharray="4,4" opacity="0.75">
|
||||
<animate attributeName="stroke-dashoffset" values="0;8;0" dur="2s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
</g>
|
||||
|
||||
<!-- Node 5 - Left -->
|
||||
<g transform="translate(-140, 0)">
|
||||
<circle cx="0" cy="0" r="22" fill="#4cc2f1" opacity="0.15">
|
||||
<animate attributeName="r" values="20;24;20" dur="2.5s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
<animate attributeName="opacity" values="0.1;0.25;0.1" dur="2.5s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</circle>
|
||||
<circle cx="0" cy="0" r="18" fill="url(#nodeGradient)" stroke="#fff" stroke-width="2.5" filter="url(#glow)"/>
|
||||
<circle cx="0" cy="0" r="13" fill="none" stroke="#8895b3" stroke-width="1.5" opacity="0.7"/>
|
||||
<text x="0" y="6" font-family="'Courier New', 'Consolas', monospace" font-size="16" font-weight="bold" fill="#f2b03f" text-anchor="middle">>_</text>
|
||||
<line x1="18" y1="0" x2="118" y2="0" stroke="#8895b3" stroke-width="2.5" stroke-dasharray="4,4" opacity="0.75">
|
||||
<animate attributeName="stroke-dashoffset" values="0;8;0" dur="1.8s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- TEXT SECTION (Below icon) -->
|
||||
<g id="text-group" transform="translate(-120, 75) scale(0.7)">
|
||||
|
||||
<!-- Main text paths with animation -->
|
||||
<g id="text-paths">
|
||||
<path fill="url(#textGradient)" d="M215.69,30.31c3.05-2.3,6.06-3.46,9.04-3.46s5.44,1.19,7.38,3.56,2.92,5.82,2.92,10.33c0,7.2-1.97,13.32-5.9,18.36-3.94,5.04-8.67,7.56-14.18,7.56-1.97,0-3.96-.38-5.98-1.15,4.7-1.1,8.45-4.15,11.23-9.14,2.78-4.99,4.18-9.96,4.18-14.9,0-2.3-.42-4.18-1.26-5.62-.84-1.44-2.03-2.16-3.56-2.16-2.93,0-5.52,1.78-7.78,5.33-2.26,3.55-3.98,8.41-5.18,14.58-1.2,6.17-1.8,12.6-1.8,19.3s.48,12.52,1.44,17.46c-3.94-.14-6.85-.78-8.75-1.91-1.9-1.13-2.84-3.28-2.84-6.44,0-1.15.73-7.24,2.2-18.25,1.46-11.02,2.2-19.5,2.2-25.45s-.36-10.97-1.08-15.05c3.84.96,6.49,2.74,7.96,5.33,1.46,2.59,2.2,6.38,2.2,11.38,2.02-4.13,4.55-7.34,7.6-9.65Z"/>
|
||||
<path fill="url(#textGradient)" d="M262.42,29.48c2.42-2.95,4.68-4.43,6.77-4.43s3.66.78,4.72,2.34c1.06,1.56,1.58,3.58,1.58,6.05s-.73,4.74-2.2,6.8c-1.46,2.06-3.47,3.1-6.01,3.1-.67,0-1.46-.19-2.38-.58,1.49-1.87,2.23-4.13,2.23-6.77,0-1.54-.62-2.3-1.87-2.3-.91,0-1.9.64-2.95,1.91-1.06,1.27-2.06,3.02-3.02,5.26-.96,2.23-1.76,5.09-2.41,8.57-.65,3.48-.97,7.19-.97,11.12,0,.48.12,2.52.36,6.12-1.54.1-2.52.14-2.95.14-2.74,0-4.71-.48-5.9-1.44-1.2-.96-1.8-2.54-1.8-4.75,0-.96.5-4.66,1.51-11.09,1.01-6.43,1.51-11.56,1.51-15.37s-.41-7.33-1.22-10.55c3.5,1.1,5.95,2.68,7.34,4.72,1.39,2.04,2.09,4.42,2.09,7.13s-.22,5.32-.65,7.81c1.73-6.24,3.8-10.84,6.23-13.79Z"/>
|
||||
<path fill="url(#textGradient)" d="M306.3,25.34c2.88,0,5.33,1.42,7.34,4.25,2.02,2.83,3.02,6.62,3.02,11.38,0,5.33-1.08,10.22-3.24,14.69-2.4,5.04-5.81,8.52-10.22,10.44-2.5,1.1-5.23,1.66-8.21,1.66-4.18,0-7.81-1.62-10.91-4.86-3.1-3.24-4.64-7.89-4.64-13.97s1.49-11.42,4.46-16.06c2.98-4.63,6.91-7.55,11.81-8.75.53,0,1.02.16,1.48.47.46.31.68.64.68.97-4.85,3.36-7.75,8.04-8.71,14.04,1.01-2.69,2.24-5.01,3.71-6.98,1.46-1.97,2.99-3.46,4.57-4.46,3.02-1.87,5.98-2.81,8.86-2.81ZM301.84,30.6c-3.12,0-5.9,2.1-8.35,6.3-2.45,4.2-3.67,8.39-3.67,12.56,0,8.74,2.26,13.1,6.77,13.1,3.22,0,5.83-2.1,7.85-6.3,2.02-4.2,3.02-8.92,3.02-14.15,0-7.68-1.87-11.52-5.62-11.52Z"/>
|
||||
<path fill="url(#textGradient)" d="M341.36,66.67c-1.54.67-3.14,1.01-4.82,1.01s-2.62-.36-2.81-1.08c-.77-3.07-1.66-7.67-2.66-13.79s-2.03-11.47-3.06-16.06c-1.03-4.58-2.27-8.05-3.71-10.4,2.64-1.25,4.75-1.87,6.34-1.87s2.78.36,3.6,1.08c.82.72,1.5,2.15,2.05,4.28.55,2.14.95,3.9,1.19,5.29.67,4.61,1.01,7.18,1.01,7.7s.19,2.68.58,6.44c.38,3.77.67,6.25.86,7.45,8.54-16.56,12.82-27.62,12.82-33.19,3.94,2.02,5.9,4.2,5.9,6.55,0,1.92-1,4.76-2.99,8.53-1.99,3.77-4.49,8.33-7.49,13.68-3,5.35-5.27,10.14-6.8,14.36Z"/>
|
||||
<path fill="url(#textGradient)" d="M375.2,50.4c0,8.02,2.35,12.02,7.06,12.02,1.82,0,3.67-.62,5.54-1.87,1.87-1.25,3.5-2.98,4.9-5.18,1.73.38,2.59,1.27,2.59,2.66-2.02,3.5-4.49,6-7.42,7.49s-5.86,2.23-8.78,2.23c-3.94,0-7.29-1.45-10.04-4.36-2.76-2.9-4.14-7.12-4.14-12.64,0-7.06,1.94-13.09,5.83-18.11s9.14-7.52,15.77-7.52c3.7,0,6.48.9,8.35,2.7s2.81,3.94,2.81,6.41-.76,4.62-2.27,6.44c-1.51,1.83-3.44,2.74-5.8,2.74-1.15,0-2.3-.24-3.46-.72.72-.82,1.39-2.1,2.02-3.85.62-1.75.94-3.37.94-4.86,0-2.93-1.27-4.39-3.82-4.39-2.98,0-5.4,2.18-7.27,6.55-1.87,4.37-2.81,9.12-2.81,14.26Z"/>
|
||||
<path fill="url(#textGradient)" d="M420.85,41.83c-.77,4.27-1.37,8.06-1.8,11.38-.43,3.31-.65,6.7-.65,10.15s.33,5.93,1.01,7.42c-3.7-.96-6.24-2.44-7.63-4.43-1.39-1.99-2.09-4.81-2.09-8.46,0-3.21.5-7.62,1.51-13.21,1.01-5.59,1.66-9.37,1.94-11.34-2.5.43-5.16,1.27-7.99,2.52-.1-1.05-.14-1.85-.14-2.38,0-2.54.77-4.28,2.3-5.22,1.54-.94,3.74-1.43,6.62-1.48.33-3.6.5-6.19.5-7.78s-.01-2.9-.04-3.96c-.03-1.06-.04-1.85-.04-2.38,3.17.34,5.47,1.29,6.91,2.84,1.44,1.56,2.16,3.78,2.16,6.66,0,1.15-.12,2.76-.36,4.82,4.03-.1,6.98-.89,8.86-2.38.09.96.14,1.68.14,2.16,0,2.3-.41,3.9-1.22,4.79-.82.89-2.19,1.33-4.1,1.33s-3.38-.02-4.39-.07c-.24,1.73-.75,4.73-1.51,9Z"/>
|
||||
<path fill="url(#textGradient)" d="M437.27,62.1c-.14-.89-.22-2.27-.22-4.14s.8-9.24,2.41-22.1c1.61-12.86,2.41-21.7,2.41-26.5s-.29-7.92-.86-9.36c3.6.96,6.06,2.41,7.38,4.36,1.32,1.94,1.98,4.69,1.98,8.24,0,2.11-.7,8.98-2.09,20.59-1.39,11.62-2.09,20.98-2.09,28.08,0,1.54.1,3.7.29,6.48-4.37-.19-7.08-1.15-8.14-2.88-.58-.96-.94-1.88-1.08-2.77Z"/>
|
||||
<!-- Text glow animation -->
|
||||
<animate attributeName="opacity" values="0.8;1;0.8" dur="2.5s" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</g>
|
||||
|
||||
<!-- Animated underline -->
|
||||
<g id="underline">
|
||||
<path d="M172.5,86.67h300" stroke="url(#textGradient)" stroke-width="3" fill="none" stroke-linecap="round" opacity="0.7">
|
||||
<animate attributeName="stroke-dasharray" values="0,300;300,0" dur="4s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0;0.7;0.7" dur="4s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
<path d="M172.5,86.67h300" stroke="#4cc2f1" stroke-width="4" fill="none" stroke-linecap="round" opacity="0.2">
|
||||
<animate attributeName="stroke-dasharray" values="0,300;300,0" dur="4s" begin="0.2s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0;0.2;0.2" dur="4s" begin="0.2s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
<path d="M172.5,86.67h300" stroke="url(#textGradient)" stroke-width="6" fill="none" stroke-linecap="round" opacity="0.3">
|
||||
<animate attributeName="stroke-dasharray" values="0,300;300,0" dur="4s" begin="0.1s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0;0.3;0.3" dur="4s" begin="0.1s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 19 KiB |
Loading…
x
Reference in New Issue
Block a user