feat: add port clickable
This commit is contained in:
24
README.md
24
README.md
@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
Interface web pour gérer les containers Docker de plusieurs machines depuis un seul endroit.
|
Interface web pour gérer les containers Docker de plusieurs machines depuis un seul endroit.
|
||||||
|
|
||||||
- Visualisation en temps réel de tous les containers par host
|
- Visualisation en temps réel de tous les containers, groupés par host et par projet compose
|
||||||
- Actions : start, stop, restart, remove
|
- Actions : start, stop, restart sur un container ou sur tout un projet compose
|
||||||
- Streaming de logs en direct
|
- Streaming de logs en direct (par container ou pour tout un projet simultanément)
|
||||||
|
- Auto-update automatique et manuel des images Docker
|
||||||
|
- Gestion des volumes, images et réseaux
|
||||||
|
- Liens directs vers les services exposés (clic sur un port)
|
||||||
- Gestion des agents depuis l'interface admin
|
- Gestion des agents depuis l'interface admin
|
||||||
- PWA installable sur mobile
|
- PWA installable sur mobile
|
||||||
|
|
||||||
@ -71,8 +74,11 @@ curl -fsSL https://gitea.anthonybouteiller.ovh/blomios/Containarr/raw/branch/mai
|
|||||||
environment:
|
environment:
|
||||||
CONTAINARR_SERVER_URL: "http://<ip-du-serveur>:9090"
|
CONTAINARR_SERVER_URL: "http://<ip-du-serveur>:9090"
|
||||||
CONTAINARR_AGENT_TOKEN: "<token-copié-depuis-l-admin>"
|
CONTAINARR_AGENT_TOKEN: "<token-copié-depuis-l-admin>"
|
||||||
|
CONTAINARR_HOST_IP: "<ip-lan-de-la-vm>" # ex: 192.168.1.95 — utilisé pour les liens de ports
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> `CONTAINARR_HOST_IP` est optionnel mais recommandé : sans lui, l'IP affichée sera l'adresse réseau Docker interne, et les liens vers les services exposés ne fonctionneront pas correctement.
|
||||||
|
|
||||||
### 4. Lancer
|
### 4. Lancer
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -83,6 +89,18 @@ L'agent apparaît dans l'interface dans les secondes qui suivent.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Auto-update
|
||||||
|
|
||||||
|
Containarr peut surveiller et mettre à jour automatiquement les images Docker de vos containers.
|
||||||
|
|
||||||
|
- **Par container** : clic sur l'icône ↻ d'un container → activer l'auto-update et choisir l'intervalle de vérification
|
||||||
|
- **Par projet** : même bouton au niveau du groupe projet → applique la policy à tous les containers du projet
|
||||||
|
- **Mise à jour manuelle** : bouton "Mettre à jour maintenant" dans le panneau auto-update
|
||||||
|
|
||||||
|
Les mises à jour utilisent l'API Docker directement (pull de la nouvelle image + recréation du container avec la même configuration).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Ports
|
## Ports
|
||||||
|
|
||||||
| Port | Usage |
|
| Port | Usage |
|
||||||
|
|||||||
@ -69,6 +69,7 @@ async fn run(url: &str, token: &str, hostname: &str, docker: DockerClient) -> Re
|
|||||||
hostname: hostname.to_string(),
|
hostname: hostname.to_string(),
|
||||||
arch: std::env::consts::ARCH.to_string(),
|
arch: std::env::consts::ARCH.to_string(),
|
||||||
os: std::env::consts::OS.to_string(),
|
os: std::env::consts::OS.to_string(),
|
||||||
|
ip_address: std::env::var("CONTAINARR_HOST_IP").unwrap_or_default(),
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -845,9 +846,11 @@ mod tests {
|
|||||||
hostname: "host".to_string(),
|
hostname: "host".to_string(),
|
||||||
arch: "x86_64".to_string(),
|
arch: "x86_64".to_string(),
|
||||||
os: "linux".to_string(),
|
os: "linux".to_string(),
|
||||||
|
ip_address: "192.168.1.10".to_string(),
|
||||||
};
|
};
|
||||||
assert_eq!(hs.token, "tok");
|
assert_eq!(hs.token, "tok");
|
||||||
assert_eq!(hs.hostname, "host");
|
assert_eq!(hs.hostname, "host");
|
||||||
|
assert_eq!(hs.ip_address, "192.168.1.10");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -858,6 +861,7 @@ mod tests {
|
|||||||
hostname: "h".to_string(),
|
hostname: "h".to_string(),
|
||||||
arch: "arm64".to_string(),
|
arch: "arm64".to_string(),
|
||||||
os: "linux".to_string(),
|
os: "linux".to_string(),
|
||||||
|
ip_address: String::new(),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|||||||
@ -28,10 +28,11 @@ message ContainerInfo {
|
|||||||
// ── Agent → Server ────────────────────────────────────────────────────────────
|
// ── Agent → Server ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
message AgentHandshake {
|
message AgentHandshake {
|
||||||
string token = 1;
|
string token = 1;
|
||||||
string hostname = 2;
|
string hostname = 2;
|
||||||
string arch = 3;
|
string arch = 3;
|
||||||
string os = 4;
|
string os = 4;
|
||||||
|
string ip_address = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ImageInfo {
|
message ImageInfo {
|
||||||
|
|||||||
@ -53,6 +53,10 @@ func (g *Gateway) Tunnel(stream agentv1.AgentGateway_TunnelServer) error {
|
|||||||
ipAddress = host
|
ipAddress = host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If the agent advertises its own LAN IP, prefer it over the peer address.
|
||||||
|
if hs.IpAddress != "" {
|
||||||
|
ipAddress = hs.IpAddress
|
||||||
|
}
|
||||||
|
|
||||||
agentID := existing.ID
|
agentID := existing.ID
|
||||||
slog.Info("agent connected", "id", agentID, "hostname", hs.Hostname, "ip", ipAddress)
|
slog.Info("agent connected", "id", agentID, "hostname", hs.Hostname, "ip", ipAddress)
|
||||||
|
|||||||
@ -898,9 +898,14 @@
|
|||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
{#each uniquePorts(container.ports) as port}
|
{#each uniquePorts(container.ports) as port}
|
||||||
<span class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20">
|
<a
|
||||||
|
href="http://{byAgent[agent_id]?.[0]?.ip_address ?? ''}:{port.host_port}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20 hover:bg-signal-cyan/25 hover:border-signal-cyan/40 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
{port.host_port}:{port.container_port}
|
{port.host_port}:{port.container_port}
|
||||||
</span>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -941,10 +946,14 @@
|
|||||||
{#if uniquePorts(container.ports).length > 0}
|
{#if uniquePorts(container.ports).length > 0}
|
||||||
<div class="flex flex-wrap gap-1 mb-3">
|
<div class="flex flex-wrap gap-1 mb-3">
|
||||||
{#each uniquePorts(container.ports) as port}
|
{#each uniquePorts(container.ports) as port}
|
||||||
<span class="font-mono text-xs px-1.5 py-0.5 rounded
|
<a
|
||||||
bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20">
|
href="http://{byAgent[agent_id]?.[0]?.ip_address ?? ''}:{port.host_port}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20 hover:bg-signal-cyan/25 hover:border-signal-cyan/40 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
{port.host_port}:{port.container_port}
|
{port.host_port}:{port.container_port}
|
||||||
</span>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -1005,9 +1014,14 @@
|
|||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
{#each uniquePorts(container.ports) as port}
|
{#each uniquePorts(container.ports) as port}
|
||||||
<span class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20">
|
<a
|
||||||
|
href="http://{byAgent[agent_id]?.[0]?.ip_address ?? ''}:{port.host_port}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20 hover:bg-signal-cyan/25 hover:border-signal-cyan/40 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
{port.host_port}:{port.container_port}
|
{port.host_port}:{port.container_port}
|
||||||
</span>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -1048,10 +1062,14 @@
|
|||||||
{#if uniquePorts(container.ports).length > 0}
|
{#if uniquePorts(container.ports).length > 0}
|
||||||
<div class="flex flex-wrap gap-1 mb-3">
|
<div class="flex flex-wrap gap-1 mb-3">
|
||||||
{#each uniquePorts(container.ports) as port}
|
{#each uniquePorts(container.ports) as port}
|
||||||
<span class="font-mono text-xs px-1.5 py-0.5 rounded
|
<a
|
||||||
bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20">
|
href="http://{byAgent[agent_id]?.[0]?.ip_address ?? ''}:{port.host_port}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20 hover:bg-signal-cyan/25 hover:border-signal-cyan/40 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
{port.host_port}:{port.container_port}
|
{port.host_port}:{port.container_port}
|
||||||
</span>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user