chore: merge release-v0.1.4 into main
This commit is contained in:
@@ -1,9 +1,14 @@
|
|||||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.217.4/containers/rust/.devcontainer/base.Dockerfile
|
ARG VARIANT="bullseye"
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT}
|
||||||
# [Choice] Debian OS version (use bullseye on local arm64/Apple Silicon): buster, bullseye
|
|
||||||
ARG VARIANT="buster"
|
|
||||||
FROM mcr.microsoft.com/vscode/devcontainers/rust:0-${VARIANT}
|
|
||||||
|
|
||||||
RUN printf "alias cls='clear'\nalias ll='ls -l --human-readable --color=auto --group-directories-first --classify --time-style=long-iso -all'" >> /etc/bash.bashrc
|
RUN printf "alias cls='clear'\nalias ll='ls -l --human-readable --color=auto --group-directories-first --classify --time-style=long-iso -all'" >> /etc/bash.bashrc
|
||||||
|
|
||||||
# RUN apt-get update && apt-get -y install upx-ucl
|
ENV PATH="/home/vscode/.cargo/bin:${PATH}"
|
||||||
|
|
||||||
|
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
|
&& apt-get -y install --no-install-recommends build-essential pkg-config libssl-dev
|
||||||
|
|
||||||
|
USER vscode
|
||||||
|
RUN curl --proto '=https' --tlsv1.2 -sSf curl https://sh.rustup.rs | sh -s -- -y
|
||||||
|
# RUN rustup target add x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"extensions": [
|
"extensions": [
|
||||||
"vadimcn.vscode-lldb",
|
"vadimcn.vscode-lldb",
|
||||||
"mutantdino.resourcemonitor",
|
"mutantdino.resourcemonitor",
|
||||||
"matklad.rust-analyzer",
|
"rust-lang.rust-analyzer",
|
||||||
"tamasfe.even-better-toml",
|
"tamasfe.even-better-toml",
|
||||||
"serayuzgur.crates",
|
"serayuzgur.crates",
|
||||||
"christian-kohler.path-intellisense",
|
"christian-kohler.path-intellisense",
|
||||||
|
|||||||
+13
-5
@@ -1,14 +1,22 @@
|
|||||||
### 2022-08-04
|
### 2022-09-07
|
||||||
|
|
||||||
### Chores
|
### Chores
|
||||||
+ dependencies updated, [d9801cdf372521fe5624a8d68fac83ed39ef81f4]
|
+ dependencies updated, [a3168daa3f769a6747dfbe61103073a7e80a1485], [78e59160bb6a978ee80e3a99eb72f051fb64e737]
|
||||||
+ linting: nursery, pedantic, unused_unwraps, [1bd61d4ce8b369d6d078201add3eea0f59fe0dea], [1263662bd9412afacddbc10721bf216ae3a843f1], [ca3315a69f593ad705eb637f227f195edd7781b2]
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
+ build all production targets on release, [44f8140eaec330abe5a94f3ddae9e8b223688aa8]
|
+ containerize self, github action to build and push to [Docker Hub](https://hub.docker.com/r/mrjackwills/oxker), [07f972022a69f22bac57925e6ad84234381f7890]
|
||||||
|
+ gui_state is_loading use a HashSet to enable multiple things be loading at the same time, [66583e1b037b7e2f3e47948d70d8a4c6f6a2f2d5]
|
||||||
|
+ github action publish to crates.io, [90b2e3f6db0d5f63840cd80888a30da6ecc22f20]
|
||||||
|
+ derive Eq where appropriate, [d7c2601f959bc12a64cd25cef59c837e1e8c2b2a]
|
||||||
|
+ ignore containers 'oxker' containers, [1be9f52ad4a68f93142784e9df630c59cdec0a79]
|
||||||
|
+ update container info if container is either running OR restarting, [5f12362db7cb61ca68f75b99ecfc9725380d87d2]
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
+ toml keywords, [dd2d82d114537e09dbeb12f360157f0e68e7846e]
|
+ devcontainer updated, [3bde4f5629539cab3dbb57556663ab81685f9d7a]
|
||||||
|
+ Use Binate enum to enable two cycles of cpu/mem update to be executed (for each container) at the same time, refactor hashmap spawn insertions, [7ec58e79a1316ad1f7e50a2781dea0fe8422c588]
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
+ improved way to remove leading '/' of container name, [832e9782d7765872cbb84df6b3703fc08cb353c9]
|
||||||
|
|
||||||
|
|
||||||
see <a href='https://github.com/mrjackwills/oxker/blob/main/CHANGELOG.md'>CHANGELOG.md</a> for more details
|
see <a href='https://github.com/mrjackwills/oxker/blob/main/CHANGELOG.md'>CHANGELOG.md</a> for more details
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 434 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
@@ -7,16 +7,8 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
# cache some rust data?
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
target
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
# Build for linux x86_64
|
# Build for linux x86_64
|
||||||
- name: build release linux_x86_64
|
- name: build release linux_x86_64
|
||||||
@@ -72,6 +64,30 @@ jobs:
|
|||||||
- name: compress windows_x86_64 binary
|
- name: compress windows_x86_64 binary
|
||||||
run: zip -j ./oxker_windows_x86_64.zip target/x86_64-pc-windows-gnu/release/oxker.exe
|
run: zip -j ./oxker_windows_x86_64.zip target/x86_64-pc-windows-gnu/release/oxker.exe
|
||||||
|
|
||||||
|
# Build images for Dockerhub
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v2
|
||||||
|
id: buildx
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
- name: Build for Docker Hub
|
||||||
|
run: |
|
||||||
|
docker build --platform linux/arm/v6,linux/arm64,linux/amd64 \
|
||||||
|
-t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:latest \
|
||||||
|
--push \
|
||||||
|
-f containerised/Dockerfile .
|
||||||
|
|
||||||
|
# Publish to crates.io
|
||||||
|
- name: publish to crates.io
|
||||||
|
uses: katyo/publish-crates@v1
|
||||||
|
with:
|
||||||
|
registry-token: ${{ secrets.CRATES_IO_TOKEN }}
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
+22
-1
@@ -1,9 +1,30 @@
|
|||||||
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.4'>v0.1.4</a>
|
||||||
|
### 2022-09-07
|
||||||
|
|
||||||
|
### Chores
|
||||||
|
+ dependencies updated, [a3168daa](https://github.com/mrjackwills/oxker/commit/a3168daa3f769a6747dfbe61103073a7e80a1485),, [78e59160](https://github.com/mrjackwills/oxker/commit/78e59160bb6a978ee80e3a99eb72f051fb64e737),
|
||||||
|
|
||||||
|
### Features
|
||||||
|
+ containerize self, github action to build and push to [Docker Hub](https://hub.docker.com/r/mrjackwills/oxker), [07f97202](https://github.com/mrjackwills/oxker/commit/07f972022a69f22bac57925e6ad84234381f7890),
|
||||||
|
+ gui_state is_loading use a HashSet to enable multiple things be loading at the same time, [66583e1b](https://github.com/mrjackwills/oxker/commit/66583e1b037b7e2f3e47948d70d8a4c6f6a2f2d5),
|
||||||
|
+ github action publish to crates.io, [90b2e3f6](https://github.com/mrjackwills/oxker/commit/90b2e3f6db0d5f63840cd80888a30da6ecc22f20),
|
||||||
|
+ derive Eq where appropriate, [d7c2601f](https://github.com/mrjackwills/oxker/commit/d7c2601f959bc12a64cd25cef59c837e1e8c2b2a),
|
||||||
|
+ ignore containers 'oxker' containers, [1be9f52a](https://github.com/mrjackwills/oxker/commit/1be9f52ad4a68f93142784e9df630c59cdec0a79),
|
||||||
|
+ update container info if container is either running OR restarting, [5f12362d](https://github.com/mrjackwills/oxker/commit/5f12362db7cb61ca68f75b99ecfc9725380d87d2),
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
+ devcontainer updated, [3bde4f56](https://github.com/mrjackwills/oxker/commit/3bde4f5629539cab3dbb57556663ab81685f9d7a),
|
||||||
|
+ Use Binate enum to enable two cycles of cpu/mem update to be executed (for each container) at the same time, refactor hashmap spawn insertions, [7ec58e79](https://github.com/mrjackwills/oxker/commit/7ec58e79a1316ad1f7e50a2781dea0fe8422c588),
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
+ improved way to remove leading '/' of container name, [832e9782](https://github.com/mrjackwills/oxker/commit/832e9782d7765872cbb84df6b3703fc08cb353c9),
|
||||||
|
|
||||||
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.3'>v0.1.3</a>
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.3'>v0.1.3</a>
|
||||||
### 2022-08-04
|
### 2022-08-04
|
||||||
|
|
||||||
### Chores
|
### Chores
|
||||||
+ dependencies updated, [d9801cdf](https://github.com/mrjackwills/oxker/commit/d9801cdf372521fe5624a8d68fac83ed39ef81f4),
|
+ dependencies updated, [d9801cdf](https://github.com/mrjackwills/oxker/commit/d9801cdf372521fe5624a8d68fac83ed39ef81f4),
|
||||||
+ linting: nursery, pedantic, unused_unwraps, [1bd61d4c](https://github.com/mrjackwills/oxker/commit/1bd61d4ce8b369d6d078201add3eea0f59fe0dea),, [1263662b](https://github.com/mrjackwills/oxker/commit/1263662bd9412afacddbc10721bf216ae3a843f1),, [ca3315a6](https://github.com/mrjackwills/oxker/commit/ca3315a69f593ad705eb637f227f195edd7781b2),
|
+ linting: nursery, pedantic, unused_unwraps, [1bd61d4c](https://github.com/mrjackwills/oxker/commit/1bd61d4ce8b369d6d078201add3eea0f59fe0dea), [1263662b](https://github.com/mrjackwills/oxker/commit/1263662bd9412afacddbc10721bf216ae3a843f1), [ca3315a6](https://github.com/mrjackwills/oxker/commit/ca3315a69f593ad705eb637f227f195edd7781b2),
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
+ build all production targets on release, [44f8140e](https://github.com/mrjackwills/oxker/commit/44f8140eaec330abe5a94f3ddae9e8b223688aa8),
|
+ build all production targets on release, [44f8140e](https://github.com/mrjackwills/oxker/commit/44f8140eaec330abe5a94f3ddae9e8b223688aa8),
|
||||||
|
|||||||
+7
-6
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "oxker"
|
name = "oxker"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Jack Wills <email@mrjackwills.com>"]
|
authors = ["Jack Wills <email@mrjackwills.com>"]
|
||||||
description = "a simple tui to view & control docker containers"
|
description = "a simple tui to view & control docker containers"
|
||||||
@@ -8,21 +8,22 @@ repository = "https://github.com/mrjackwills/oxker"
|
|||||||
homepage = "https://github.com/mrjackwills/oxker"
|
homepage = "https://github.com/mrjackwills/oxker"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["docker", "tui", "tui-rs", "tokio", "terminal", "podman", "container"]
|
keywords = ["docker", "tui", "tokio", "terminal", "podman"]
|
||||||
categories = ["command-line-utilities"]
|
categories = ["command-line-utilities"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bollard = "0.13"
|
bollard = "0.13"
|
||||||
cansi = "2.1"
|
cansi = "2.2"
|
||||||
clap={version="3.2", features = ["derive", "unicode"] }
|
clap={version="3.2", features = ["derive", "unicode"] }
|
||||||
crossterm = "0.24"
|
crossterm = "0.25"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
parking_lot = {version= "0.12"}
|
parking_lot = {version= "0.12"}
|
||||||
tokio = {version = "1.20", features=["full"]}
|
tokio = {version = "1.21", features=["full"]}
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
tui = "0.18"
|
tui = "0.19"
|
||||||
|
uuid = {version = "1.1", features = ["v4", "fast-rng"]}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src='./.github/logo.svg' width='125px'/>
|
<img src='./.github/logo.svg' width='100px'/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -15,18 +15,23 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.jpg" target='_blank' rel='noopener noreferrer'>
|
<a href="https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.png" target='_blank' rel='noopener noreferrer'>
|
||||||
<img src='./.github/screenshot_01.jpg' width='100%'/>
|
<img src='./.github/screenshot_01.png' width='100%'/>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
## Run via Docker
|
||||||
|
|
||||||
|
Published on <a href='https://hub.docker.com/r/mrjackwills/oxker' target='_blank' rel='noopener noreferrer'>Docker Hub</a>, with images built for `linux/amd64`, `linux/arm64v8`, and `linux/armv6`
|
||||||
|
|
||||||
|
`docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro mrjackwills/oxker:latest`
|
||||||
|
|
||||||
|
|
||||||
## Download & install
|
## Download & install
|
||||||
|
|
||||||
Now published on <a href='https://www.crates.io/crates/oxker' target='_blank' rel='noopener noreferrer'>crates.io</a>, so if you have cargo installed, simply run
|
Published on <a href='https://www.crates.io/crates/oxker' target='_blank' rel='noopener noreferrer'>crates.io</a>, so if you have cargo installed, simply run
|
||||||
|
|
||||||
``` cargo install oxker```
|
|
||||||
|
|
||||||
|
```cargo install oxker```
|
||||||
|
|
||||||
else see the <a href="https://github.com/mrjackwills/oxker/releases/latest" target='_blank' rel='noopener noreferrer'>pre-built binaries</a>
|
else see the <a href="https://github.com/mrjackwills/oxker/releases/latest" target='_blank' rel='noopener noreferrer'>pre-built binaries</a>
|
||||||
|
|
||||||
@@ -107,11 +112,8 @@ using docker-compose.yml;
|
|||||||
|
|
||||||
or individually
|
or individually
|
||||||
|
|
||||||
|
|
||||||
```docker run --name redis -d redis:alpine3.16```
|
```docker run --name redis -d redis:alpine3.16```
|
||||||
|
|
||||||
```docker run --name postgres -e POSTGRES_PASSWORD=never_use_this_password_in_production -d postgres:alpine3.16```
|
```docker run --name postgres -e POSTGRES_PASSWORD=never_use_this_password_in_production -d postgres:alpine3.16```
|
||||||
|
|
||||||
```docker run -d --hostname my-rabbit --name rabbitmq rabbitmq:3```
|
```docker run -d --hostname my-rabbit --name rabbitmq rabbitmq:3```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src='https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/logo.svg' width='100px'/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<h1 align="center">oxker</h1>
|
||||||
|
<div align="center">
|
||||||
|
A simple tui to view & control docker containers
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.png" target='_blank' rel='noopener noreferrer'>
|
||||||
|
<img src='https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.png' width='60%'/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
Images built for `linux/amd64`, `linux/arm64v8`, and `linux/armv6`
|
||||||
|
|
||||||
|
`docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro mrjackwills/oxker:latest`
|
||||||
|
|
||||||
|
## Help
|
||||||
|
|
||||||
|
visit the<a href="https://github.com/mrjackwills/oxker" target='_blank' rel='noopener noreferrer'> Github repo</a>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#############
|
||||||
|
## Builder ##
|
||||||
|
#############
|
||||||
|
|
||||||
|
FROM --platform=linux/amd64 rust:slim as builder
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
# These are build platform depandant, but will be ignored if not needed
|
||||||
|
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER="aarch64-linux-gnu-gcc"
|
||||||
|
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-lgcc"
|
||||||
|
ENV CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER="arm-linux-gnueabihf-ld"
|
||||||
|
|
||||||
|
COPY ./containerised/platform.sh .
|
||||||
|
|
||||||
|
RUN chmod +x ./platform.sh && ./platform.sh
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install $(cat /.compiler) -y
|
||||||
|
|
||||||
|
WORKDIR /usr/src
|
||||||
|
|
||||||
|
# Create blank project
|
||||||
|
RUN cargo new oxker
|
||||||
|
|
||||||
|
# We want dependencies cached, so copy those first
|
||||||
|
COPY Cargo.* /usr/src/oxker/
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /usr/src/oxker
|
||||||
|
|
||||||
|
# Install target platform (Cross-Compilation)
|
||||||
|
RUN rustup target add $(cat /.platform)
|
||||||
|
|
||||||
|
# This is a dummy build to get the dependencies cached
|
||||||
|
RUN cargo build --target $(cat /.platform) --release
|
||||||
|
|
||||||
|
# Now copy in the rest of the sources
|
||||||
|
COPY src /usr/src/oxker/src/
|
||||||
|
|
||||||
|
## Touch main.rs to prevent cached release build
|
||||||
|
RUN touch /usr/src/oxker/src/main.rs
|
||||||
|
|
||||||
|
# This is the actual application build
|
||||||
|
RUN cargo build --release --target $(cat /.platform)
|
||||||
|
|
||||||
|
RUN cp /usr/src/oxker/target/$(cat /.platform)/release/oxker /
|
||||||
|
|
||||||
|
#############
|
||||||
|
## Runtime ##
|
||||||
|
#############
|
||||||
|
|
||||||
|
FROM alpine:latest AS runtime
|
||||||
|
|
||||||
|
# Copy application binary from builder image
|
||||||
|
COPY --from=builder /oxker /usr/local/bin
|
||||||
|
COPY ./containerised/start_oxker.sh ./
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
ENTRYPOINT [ "./start_oxker.sh"]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#############
|
||||||
|
## Runtime ##
|
||||||
|
#############
|
||||||
|
|
||||||
|
FROM alpine:latest AS runtime
|
||||||
|
|
||||||
|
# Copy application binary from builder image
|
||||||
|
COPY ./target/x86_64-unknown-linux-musl/release/oxker /usr/local/bin
|
||||||
|
COPY ./containerised/start_oxker.sh ./
|
||||||
|
|
||||||
|
## Run the application
|
||||||
|
ENTRYPOINT [ "./start_oxker.sh"]
|
||||||
|
|
||||||
|
## One liner to build musl program, build docker image, then execute the image
|
||||||
|
# cargo build --release --target x86_64-unknown-linux-musl && docker build -t oxker_dev -f containerised/Dockerfile_dev . && docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro oxker_dev
|
||||||
|
|
||||||
|
|
||||||
|
## Buildx command to build musl version for all three platforms, should probably be executed in create_release
|
||||||
|
# docker buildx create --use
|
||||||
|
# docker buildx build --platform linux/arm/v6,linux/arm64,linux/amd64 -t oxker_dev_all -o type=tar,dest=/tmp/oxker_dev_all.tar -f containerised/Dockerfile .
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Used in Docker build to set platform dependent variables
|
||||||
|
|
||||||
|
case $TARGETARCH in
|
||||||
|
|
||||||
|
"amd64")
|
||||||
|
echo "x86_64-unknown-linux-musl" > /.platform
|
||||||
|
echo "" > /.compiler
|
||||||
|
;;
|
||||||
|
"arm64")
|
||||||
|
echo "aarch64-unknown-linux-musl" > /.platform
|
||||||
|
echo "gcc-aarch64-linux-gnu" > /.compiler
|
||||||
|
;;
|
||||||
|
"arm")
|
||||||
|
echo "arm-unknown-linux-musleabihf" > /.platform
|
||||||
|
echo "gcc-arm-linux-gnueabihf" > /.compiler
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Without this sleep, the docker image will instantly close
|
||||||
|
# No idea why this is solving my issue, or even where the issue is originally coming from
|
||||||
|
sleep .1
|
||||||
|
|
||||||
|
exec /usr/local/bin/oxker "$@"
|
||||||
@@ -78,7 +78,7 @@ impl<T> StatefulList<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// States of the container
|
/// States of the container
|
||||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Dead,
|
Dead,
|
||||||
Exited,
|
Exited,
|
||||||
@@ -113,6 +113,20 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<String> for State {
|
||||||
|
fn from(input: String) -> Self {
|
||||||
|
match input.as_ref() {
|
||||||
|
"dead" => Self::Dead,
|
||||||
|
"exited" => Self::Exited,
|
||||||
|
"paused" => Self::Paused,
|
||||||
|
"removing" => Self::Removing,
|
||||||
|
"restarting" => Self::Restarting,
|
||||||
|
"running" => Self::Running,
|
||||||
|
_ => Self::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&str> for State {
|
impl From<&str> for State {
|
||||||
fn from(input: &str) -> Self {
|
fn from(input: &str) -> Self {
|
||||||
match input {
|
match input {
|
||||||
|
|||||||
+28
-20
@@ -20,7 +20,7 @@ pub struct AppData {
|
|||||||
sorted_by: Option<(Header, SortedOrder)>,
|
sorted_by: Option<(Header, SortedOrder)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum SortedOrder {
|
pub enum SortedOrder {
|
||||||
Asc,
|
Asc,
|
||||||
Desc,
|
Desc,
|
||||||
@@ -410,7 +410,7 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update, or insert, containers
|
/// Update, or insert, containers
|
||||||
pub fn update_containers(&mut self, containers: &[ContainerSummary]) {
|
pub fn update_containers(&mut self, containers: &mut [ContainerSummary]) {
|
||||||
let all_ids = self.get_all_ids();
|
let all_ids = self.get_all_ids();
|
||||||
|
|
||||||
if !containers.is_empty() && self.containers.state.selected().is_none() {
|
if !containers.is_empty() && self.containers.state.selected().is_none() {
|
||||||
@@ -435,29 +435,33 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in containers.iter() {
|
for i in containers.iter_mut() {
|
||||||
if let Some(id) = i.id.as_ref() {
|
if let Some(id) = i.id.as_ref() {
|
||||||
let mut name = i
|
// maybe if no name then continue?
|
||||||
.names
|
let name = i.names.as_mut().map_or("".to_owned(), |n| {
|
||||||
.as_ref()
|
n.get_mut(0).map_or("".to_owned(), |f| {
|
||||||
.unwrap_or(&vec!["".to_owned()])
|
if f.starts_with('/') {
|
||||||
.get(0)
|
f.remove(0);
|
||||||
.unwrap_or(&String::from(""))
|
|
||||||
.clone();
|
|
||||||
if let Some(c) = name.chars().next() {
|
|
||||||
if c == '/' {
|
|
||||||
name.remove(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
f.clone()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let state = State::from(i.state.as_ref().unwrap_or(&"dead".to_owned()).trim());
|
let state = State::from(
|
||||||
|
i.state
|
||||||
|
.as_ref()
|
||||||
|
.map_or("dead".to_owned(), |f| f.trim().to_owned()),
|
||||||
|
);
|
||||||
let status = i
|
let status = i
|
||||||
.status
|
.status
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&"".to_owned())
|
.map_or("".to_owned(), |f| f.trim().to_owned());
|
||||||
.trim()
|
|
||||||
.to_owned();
|
let image = i
|
||||||
let image = i.image.as_ref().unwrap_or(&"".to_owned()).trim().to_owned();
|
.image
|
||||||
|
.as_ref()
|
||||||
|
.map_or("".to_owned(), std::clone::Clone::clone);
|
||||||
|
|
||||||
if let Some(current_container) = self.get_container_by_id(id) {
|
if let Some(current_container) = self.get_container_by_id(id) {
|
||||||
if current_container.name != name {
|
if current_container.name != name {
|
||||||
current_container.name = name;
|
current_container.name = name;
|
||||||
@@ -478,9 +482,13 @@ impl AppData {
|
|||||||
current_container.state = state;
|
current_container.state = state;
|
||||||
};
|
};
|
||||||
if current_container.image != image {
|
if current_container.image != image {
|
||||||
|
// limit image name to 64 chars?
|
||||||
|
// current_container.image = image.chars().into_iter().take(64).collect();
|
||||||
current_container.image = image;
|
current_container.image = image;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
// limit image name to 64 chars?
|
||||||
|
// let mut container = ContainerItem::new(id.clone(), status, image.chars().into_iter().take(64).collect(), state, name);
|
||||||
let mut container = ContainerItem::new(id.clone(), status, image, state, name);
|
let mut container = ContainerItem::new(id.clone(), status, image, state, name);
|
||||||
container.logs.end();
|
container.logs.end();
|
||||||
self.containers.items.push(container);
|
self.containers.items.push(container);
|
||||||
@@ -511,7 +519,7 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if container.logs.state.selected().is_none()
|
if container.logs.state.selected().is_none()
|
||||||
|| container.logs.state.selected().unwrap_or_default() + 1 == current_len
|
|| container.logs.state.selected().map_or(1, |f| f + 1) == current_len
|
||||||
{
|
{
|
||||||
container.logs.end();
|
container.logs.end();
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-9
@@ -16,17 +16,16 @@ pub enum AppError {
|
|||||||
/// Convert errors into strings to display
|
/// Convert errors into strings to display
|
||||||
impl fmt::Display for AppError {
|
impl fmt::Display for AppError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let disp = match self {
|
match self {
|
||||||
Self::DockerConnect => "Unable to access docker daemon".to_owned(),
|
Self::DockerConnect => write!(f, "Unable to access docker daemon"),
|
||||||
Self::DockerInterval => "Docker update interval needs to be greater than 0".to_owned(),
|
Self::DockerInterval => write!(f, "Docker update interval needs to be greater than 0"),
|
||||||
Self::InputPoll => "Unable to poll user input".to_owned(),
|
Self::InputPoll => write!(f, "Unable to poll user input"),
|
||||||
Self::Terminal => "Unable to draw to terminal".to_owned(),
|
Self::Terminal => write!(f, "Unable to draw to terminal"),
|
||||||
Self::DockerCommand(s) => format!("Unable to {} container", s),
|
Self::DockerCommand(s) => write!(f, "Unable to {} container", s),
|
||||||
Self::MouseCapture(x) => {
|
Self::MouseCapture(x) => {
|
||||||
let reason = if *x { "en" } else { "dis" };
|
let reason = if *x { "en" } else { "dis" };
|
||||||
format!("Unable to {}able mouse capture", reason)
|
write!(f, "Unbale to {}able mouse capture", reason)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
write!(f, "{}", disp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+97
-73
@@ -1,18 +1,19 @@
|
|||||||
use bollard::{
|
use bollard::{
|
||||||
container::{ListContainersOptions, LogsOptions, StartContainerOptions, Stats, StatsOptions},
|
container::{ListContainersOptions, LogsOptions, StartContainerOptions, Stats, StatsOptions},
|
||||||
|
service::ContainerSummary,
|
||||||
Docker,
|
Docker,
|
||||||
};
|
};
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt,
|
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tokio::{sync::mpsc::Receiver, task::JoinHandle};
|
use tokio::{sync::mpsc::Receiver, task::JoinHandle};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{AppData, DockerControls},
|
app_data::{AppData, DockerControls},
|
||||||
@@ -25,17 +26,25 @@ pub use message::DockerMessage;
|
|||||||
|
|
||||||
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
|
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
|
||||||
enum SpawnId {
|
enum SpawnId {
|
||||||
Stats(String),
|
Stats((String, Binate)),
|
||||||
Log(String),
|
Log(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SpawnId {
|
/// Cpu & Mem stats take twice as long as the update interval to get a value, so will have two being executed at the same time
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
/// SpawnId::Stats takes container_id and binate value to enable both cycles of the same container_id to be inserted into the hashmap
|
||||||
let disp = match self {
|
/// Binate value is toggled when all join handles have been spawned off
|
||||||
Self::Stats(id) => format!("stats::{id}"),
|
#[derive(Debug, Hash, Clone, PartialEq, Eq, Copy)]
|
||||||
Self::Log(id) => format!("logs::{id}"),
|
enum Binate {
|
||||||
};
|
One,
|
||||||
write!(f, "{}", disp)
|
Two,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Binate {
|
||||||
|
const fn toggle(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::One => Self::Two,
|
||||||
|
Self::Two => Self::One,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +57,7 @@ pub struct DockerData {
|
|||||||
receiver: Receiver<DockerMessage>,
|
receiver: Receiver<DockerMessage>,
|
||||||
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
||||||
timestamps: bool,
|
timestamps: bool,
|
||||||
|
binate: Binate,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DockerData {
|
impl DockerData {
|
||||||
@@ -87,6 +97,7 @@ impl DockerData {
|
|||||||
app_data: Arc<Mutex<AppData>>,
|
app_data: Arc<Mutex<AppData>>,
|
||||||
is_running: bool,
|
is_running: bool,
|
||||||
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
||||||
|
spawn_id: SpawnId,
|
||||||
) {
|
) {
|
||||||
let mut stream = docker
|
let mut stream = docker
|
||||||
.stats(
|
.stats(
|
||||||
@@ -109,14 +120,15 @@ impl DockerData {
|
|||||||
|
|
||||||
let cpu_stats = Self::calculate_usage(&stats);
|
let cpu_stats = Self::calculate_usage(&stats);
|
||||||
|
|
||||||
let no_bytes = (0, 0);
|
let no_bytes = || (0, 0);
|
||||||
|
|
||||||
let (rx, tx) = if let Some(key) = some_key {
|
let (rx, tx) = if let Some(key) = some_key {
|
||||||
match stats.networks.unwrap_or_default().get(&key) {
|
match stats.networks.unwrap_or_default().get(&key) {
|
||||||
Some(data) => (data.rx_bytes.to_owned(), data.tx_bytes.to_owned()),
|
Some(data) => (data.rx_bytes, data.tx_bytes),
|
||||||
None => no_bytes,
|
None => no_bytes(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
no_bytes
|
no_bytes()
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_running {
|
if is_running {
|
||||||
@@ -133,7 +145,7 @@ impl DockerData {
|
|||||||
.lock()
|
.lock()
|
||||||
.update_stats(&id, None, None, mem_limit, rx, tx);
|
.update_stats(&id, None, None, mem_limit, rx, tx);
|
||||||
}
|
}
|
||||||
spawns.lock().remove(&SpawnId::Stats(id.clone()));
|
spawns.lock().remove(&spawn_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,26 +155,28 @@ impl DockerData {
|
|||||||
let docker = Arc::clone(&self.docker);
|
let docker = Arc::clone(&self.docker);
|
||||||
let app_data = Arc::clone(&self.app_data);
|
let app_data = Arc::clone(&self.app_data);
|
||||||
let spawns = Arc::clone(&self.spawns);
|
let spawns = Arc::clone(&self.spawns);
|
||||||
let is_running = *is_running;
|
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
|
|
||||||
let key = SpawnId::Stats(id.clone());
|
let key = SpawnId::Stats((id.clone(), self.binate));
|
||||||
let spawn_contains_id = spawns.lock().contains_key(&key);
|
|
||||||
let s = tokio::spawn(Self::update_container_stat(
|
let spawn_key = key.clone();
|
||||||
|
self.spawns.lock().entry(key).or_insert_with(|| {
|
||||||
|
tokio::spawn(Self::update_container_stat(
|
||||||
docker,
|
docker,
|
||||||
id.clone(),
|
id.clone(),
|
||||||
app_data,
|
app_data,
|
||||||
is_running,
|
*is_running,
|
||||||
spawns,
|
spawns,
|
||||||
));
|
spawn_key,
|
||||||
if !spawn_contains_id {
|
))
|
||||||
self.spawns.lock().insert(key, s);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.binate = self.binate.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all current containers, handle into ContainerItem in the app_data struct rather than here
|
/// Get all current containers, handle into ContainerItem in the app_data struct rather than here
|
||||||
/// Just make sure that items sent are guaranteed to have an id
|
/// Just make sure that items sent are guaranteed to have an id
|
||||||
|
/// Will ignore any container that contains `oxker` as an entry point
|
||||||
pub async fn update_all_containers(&mut self) -> Vec<(bool, String)> {
|
pub async fn update_all_containers(&mut self) -> Vec<(bool, String)> {
|
||||||
let containers = self
|
let containers = self
|
||||||
.docker
|
.docker
|
||||||
@@ -173,24 +187,33 @@ impl DockerData {
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let mut output = vec![];
|
let mut output = containers
|
||||||
// iter over containers, to only send ones which have an id, as use id for identification throughout!
|
|
||||||
containers
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|i| i.id.is_some())
|
.filter_map(|f| match f.id {
|
||||||
.for_each(|c| output.push(c.clone()));
|
Some(_) => {
|
||||||
|
if f.command.as_ref().map_or(false, |c| c.contains("oxker")) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(f.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<ContainerSummary>>();
|
||||||
|
|
||||||
self.app_data.lock().update_containers(&output);
|
self.app_data.lock().update_containers(&mut output);
|
||||||
|
|
||||||
let current_sort = self.app_data.lock().get_sorted();
|
let current_sort = self.app_data.lock().get_sorted();
|
||||||
self.app_data.lock().set_sorted(current_sort);
|
self.app_data.lock().set_sorted(current_sort);
|
||||||
|
|
||||||
|
// Just get the containers that are currently running, or being restarted, no point updating info on paused or dead containers
|
||||||
output
|
output
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|i| {
|
.filter_map(|i| {
|
||||||
i.id.as_ref().map(|id| {
|
i.id.as_ref().map(|id| {
|
||||||
(
|
(
|
||||||
i.state.as_ref().unwrap_or(&String::new()) == "running",
|
i.state == Some("running".to_owned())
|
||||||
|
|| i.state == Some("restarting".to_owned()),
|
||||||
id.clone(),
|
id.clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -241,60 +264,59 @@ impl DockerData {
|
|||||||
let app_data = Arc::clone(&self.app_data);
|
let app_data = Arc::clone(&self.app_data);
|
||||||
let spawns = Arc::clone(&self.spawns);
|
let spawns = Arc::clone(&self.spawns);
|
||||||
let key = SpawnId::Log(id.clone());
|
let key = SpawnId::Log(id.clone());
|
||||||
let s = tokio::spawn(Self::update_log(
|
self.spawns.lock().insert(
|
||||||
|
key,
|
||||||
|
tokio::spawn(Self::update_log(
|
||||||
docker, id, timestamps, 0, app_data, spawns,
|
docker, id, timestamps, 0, app_data, spawns,
|
||||||
));
|
)),
|
||||||
|
);
|
||||||
self.spawns.lock().insert(key, s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update all cpu_mem, and selected container log (if a log update join_handle isn't currently being executed)
|
||||||
async fn update_everything(&mut self) {
|
async fn update_everything(&mut self) {
|
||||||
let all_ids = self.update_all_containers().await;
|
let all_ids = self.update_all_containers().await;
|
||||||
let optional_index = self.app_data.lock().get_selected_log_index();
|
let optional_index = self.app_data.lock().get_selected_log_index();
|
||||||
if let Some(index) = optional_index {
|
if let Some(index) = optional_index {
|
||||||
|
// this could be neater
|
||||||
let id = self.app_data.lock().containers.items[index].id.clone();
|
let id = self.app_data.lock().containers.items[index].id.clone();
|
||||||
|
|
||||||
let key = SpawnId::Log(id.clone());
|
let key = SpawnId::Log(id.clone());
|
||||||
let running = self.spawns.lock().contains_key(&key);
|
|
||||||
|
|
||||||
if !running {
|
self.spawns.lock().entry(key).or_insert_with(|| {
|
||||||
let since = self.app_data.lock().containers.items[index].last_updated as i64;
|
let since = self.app_data.lock().containers.items[index].last_updated as i64;
|
||||||
let docker = Arc::clone(&self.docker);
|
let docker = Arc::clone(&self.docker);
|
||||||
let timestamps = self.timestamps;
|
let timestamps = self.timestamps;
|
||||||
|
|
||||||
let app_data = Arc::clone(&self.app_data);
|
let app_data = Arc::clone(&self.app_data);
|
||||||
let spawns = Arc::clone(&self.spawns);
|
let spawns = Arc::clone(&self.spawns);
|
||||||
let s = tokio::spawn(Self::update_log(
|
tokio::spawn(Self::update_log(
|
||||||
docker, id, timestamps, since, app_data, spawns,
|
docker, id, timestamps, since, app_data, spawns,
|
||||||
));
|
))
|
||||||
self.spawns.lock().insert(key, s);
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.update_all_container_stats(&all_ids).await;
|
self.update_all_container_stats(&all_ids).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Animate the loading icon
|
/// Animate the loading icon
|
||||||
async fn loading_spin(&mut self) -> JoinHandle<()> {
|
async fn loading_spin(&mut self, loading_uuid: Uuid) -> JoinHandle<()> {
|
||||||
let gui_state = Arc::clone(&self.gui_state);
|
let gui_state = Arc::clone(&self.gui_state);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
gui_state.lock().next_loading();
|
gui_state.lock().next_loading(loading_uuid);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the loading_spin function, and reset gui loading status
|
/// Stop the loading_spin function, and reset gui loading status
|
||||||
fn stop_loading_spin(&mut self, handle: &JoinHandle<()>) {
|
fn stop_loading_spin(&mut self, handle: &JoinHandle<()>, loading_uuid: Uuid) {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
self.gui_state.lock().reset_loading();
|
self.gui_state.lock().remove_loading(loading_uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize docker container data, before any messages are received
|
// Initialize docker container data, before any messages are received
|
||||||
async fn initialise_container_data(&mut self) {
|
async fn initialise_container_data(&mut self) {
|
||||||
let loading_spin = self.loading_spin().await;
|
let loading_uuid = Uuid::new_v4();
|
||||||
|
let loading_spin = self.loading_spin(loading_uuid).await;
|
||||||
|
|
||||||
let all_ids = self.update_all_containers().await;
|
let all_ids = self.update_all_containers().await;
|
||||||
self.update_all_container_stats(&all_ids).await;
|
self.update_all_container_stats(&all_ids).await;
|
||||||
@@ -312,7 +334,7 @@ impl DockerData {
|
|||||||
self.initialised = self.app_data.lock().initialised(&all_ids);
|
self.initialised = self.app_data.lock().initialised(&all_ids);
|
||||||
}
|
}
|
||||||
self.app_data.lock().init = true;
|
self.app_data.lock().init = true;
|
||||||
self.stop_loading_spin(&loading_spin);
|
self.stop_loading_spin(&loading_spin, loading_uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle incoming messages, container controls & all container information update
|
/// Handle incoming messages, container controls & all container information update
|
||||||
@@ -320,57 +342,58 @@ impl DockerData {
|
|||||||
while let Some(message) = self.receiver.recv().await {
|
while let Some(message) = self.receiver.recv().await {
|
||||||
let docker = Arc::clone(&self.docker);
|
let docker = Arc::clone(&self.docker);
|
||||||
let app_data = Arc::clone(&self.app_data);
|
let app_data = Arc::clone(&self.app_data);
|
||||||
|
let loading_uuid = Uuid::new_v4();
|
||||||
match message {
|
match message {
|
||||||
DockerMessage::Pause(id) => {
|
DockerMessage::Pause(id) => {
|
||||||
let loading_spin = self.loading_spin().await;
|
let loading_spin = self.loading_spin(loading_uuid).await;
|
||||||
docker.pause_container(&id).await.unwrap_or_else(|_| {
|
if docker.pause_container(&id).await.is_err() {
|
||||||
app_data
|
app_data
|
||||||
.lock()
|
.lock()
|
||||||
.set_error(AppError::DockerCommand(DockerControls::Pause));
|
.set_error(AppError::DockerCommand(DockerControls::Pause));
|
||||||
});
|
};
|
||||||
self.stop_loading_spin(&loading_spin);
|
self.stop_loading_spin(&loading_spin, loading_uuid);
|
||||||
}
|
}
|
||||||
DockerMessage::Restart(id) => {
|
DockerMessage::Restart(id) => {
|
||||||
let loading_spin = self.loading_spin().await;
|
let loading_spin = self.loading_spin(loading_uuid).await;
|
||||||
docker
|
if docker.restart_container(&id, None).await.is_err() {
|
||||||
.restart_container(&id, None)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
app_data
|
app_data
|
||||||
.lock()
|
.lock()
|
||||||
.set_error(AppError::DockerCommand(DockerControls::Restart));
|
.set_error(AppError::DockerCommand(DockerControls::Restart));
|
||||||
});
|
};
|
||||||
self.stop_loading_spin(&loading_spin);
|
self.stop_loading_spin(&loading_spin, loading_uuid);
|
||||||
}
|
}
|
||||||
DockerMessage::Start(id) => {
|
DockerMessage::Start(id) => {
|
||||||
let loading_spin = self.loading_spin().await;
|
let loading_spin = self.loading_spin(loading_uuid).await;
|
||||||
docker
|
if docker
|
||||||
.start_container(&id, None::<StartContainerOptions<String>>)
|
.start_container(&id, None::<StartContainerOptions<String>>)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|_| {
|
.is_err()
|
||||||
|
{
|
||||||
app_data
|
app_data
|
||||||
.lock()
|
.lock()
|
||||||
.set_error(AppError::DockerCommand(DockerControls::Start));
|
.set_error(AppError::DockerCommand(DockerControls::Start));
|
||||||
});
|
};
|
||||||
self.stop_loading_spin(&loading_spin);
|
self.stop_loading_spin(&loading_spin, loading_uuid);
|
||||||
}
|
}
|
||||||
DockerMessage::Stop(id) => {
|
DockerMessage::Stop(id) => {
|
||||||
let loading_spin = self.loading_spin().await;
|
let loading_spin = self.loading_spin(loading_uuid).await;
|
||||||
docker.stop_container(&id, None).await.unwrap_or_else(|_| {
|
if docker.stop_container(&id, None).await.is_err() {
|
||||||
app_data
|
app_data
|
||||||
.lock()
|
.lock()
|
||||||
.set_error(AppError::DockerCommand(DockerControls::Stop));
|
.set_error(AppError::DockerCommand(DockerControls::Stop));
|
||||||
});
|
};
|
||||||
self.stop_loading_spin(&loading_spin);
|
self.stop_loading_spin(&loading_spin, loading_uuid);
|
||||||
}
|
}
|
||||||
DockerMessage::Unpause(id) => {
|
DockerMessage::Unpause(id) => {
|
||||||
let loading_spin = self.loading_spin().await;
|
let loading_spin = self.loading_spin(loading_uuid).await;
|
||||||
docker.unpause_container(&id).await.unwrap_or_else(|_| {
|
if docker.unpause_container(&id).await.is_err() {
|
||||||
app_data
|
app_data
|
||||||
.lock()
|
.lock()
|
||||||
.set_error(AppError::DockerCommand(DockerControls::Unpause));
|
.set_error(AppError::DockerCommand(DockerControls::Unpause));
|
||||||
});
|
};
|
||||||
self.stop_loading_spin(&loading_spin);
|
// loading sping take uuid to remove
|
||||||
|
// stop_loading_sping(uuid)
|
||||||
|
self.stop_loading_spin(&loading_spin, loading_uuid);
|
||||||
self.update_everything().await;
|
self.update_everything().await;
|
||||||
}
|
}
|
||||||
DockerMessage::Update => self.update_everything().await,
|
DockerMessage::Update => self.update_everything().await,
|
||||||
@@ -405,6 +428,7 @@ impl DockerData {
|
|||||||
spawns: Arc::new(Mutex::new(HashMap::new())),
|
spawns: Arc::new(Mutex::new(HashMap::new())),
|
||||||
timestamps: args.timestamp,
|
timestamps: args.timestamp,
|
||||||
is_running,
|
is_running,
|
||||||
|
binate: Binate::One,
|
||||||
};
|
};
|
||||||
inner.initialise_container_data().await;
|
inner.initialise_container_data().await;
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -143,8 +143,8 @@ impl InputHandler {
|
|||||||
|
|
||||||
if show_error {
|
if show_error {
|
||||||
match key_code {
|
match key_code {
|
||||||
KeyCode::Char('q') => self.quit().await,
|
KeyCode::Char('q' | 'Q') => self.quit().await,
|
||||||
KeyCode::Char('c') => {
|
KeyCode::Char('c' | 'C') => {
|
||||||
self.app_data.lock().show_error = false;
|
self.app_data.lock().show_error = false;
|
||||||
self.app_data.lock().remove_error();
|
self.app_data.lock().remove_error();
|
||||||
}
|
}
|
||||||
@@ -152,9 +152,9 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
} else if show_info {
|
} else if show_info {
|
||||||
match key_code {
|
match key_code {
|
||||||
KeyCode::Char('q') => self.quit().await,
|
KeyCode::Char('q' | 'Q') => self.quit().await,
|
||||||
KeyCode::Char('h') => self.gui_state.lock().show_help = false,
|
KeyCode::Char('h' | 'H') => self.gui_state.lock().show_help = false,
|
||||||
KeyCode::Char('m') => self.m_button(),
|
KeyCode::Char('m' | 'M') => self.m_button(),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -169,9 +169,9 @@ impl InputHandler {
|
|||||||
KeyCode::Char('7') => self.sort(Header::Image),
|
KeyCode::Char('7') => self.sort(Header::Image),
|
||||||
KeyCode::Char('8') => self.sort(Header::Rx),
|
KeyCode::Char('8') => self.sort(Header::Rx),
|
||||||
KeyCode::Char('9') => self.sort(Header::Tx),
|
KeyCode::Char('9') => self.sort(Header::Tx),
|
||||||
KeyCode::Char('q') => self.quit().await,
|
KeyCode::Char('q' | 'Q') => self.quit().await,
|
||||||
KeyCode::Char('h') => self.gui_state.lock().show_help = true,
|
KeyCode::Char('h' | 'H') => self.gui_state.lock().show_help = true,
|
||||||
KeyCode::Char('m') => self.m_button(),
|
KeyCode::Char('m' | 'M') => self.m_button(),
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
// Skip control panel if no containers, could be refactored
|
// Skip control panel if no containers, could be refactored
|
||||||
let has_containers = self.app_data.lock().get_container_len() == 0;
|
let has_containers = self.app_data.lock().get_container_len() == 0;
|
||||||
@@ -216,13 +216,13 @@ impl InputHandler {
|
|||||||
SelectablePanel::Commands => locked_data.docker_command_end(),
|
SelectablePanel::Commands => locked_data.docker_command_end(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Up | KeyCode::Char('k') => self.previous(),
|
KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(),
|
||||||
KeyCode::PageUp => {
|
KeyCode::PageUp => {
|
||||||
for _ in 0..=6 {
|
for _ in 0..=6 {
|
||||||
self.previous();
|
self.previous();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Down | KeyCode::Char('j') => self.next(),
|
KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(),
|
||||||
KeyCode::PageDown => {
|
KeyCode::PageDown => {
|
||||||
for _ in 0..=6 {
|
for _ in 0..=6 {
|
||||||
self.next();
|
self.next();
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use ui::{create_ui, GuiState};
|
|||||||
|
|
||||||
fn setup_tracing() {
|
fn setup_tracing() {
|
||||||
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
|
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
|
||||||
|
// TODO write to file?
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -99,6 +100,7 @@ async fn main() {
|
|||||||
.unwrap_or(());
|
.unwrap_or(());
|
||||||
} else {
|
} else {
|
||||||
loop {
|
loop {
|
||||||
|
// TODO this needs to be improved to display something useful
|
||||||
info!("in debug mode");
|
info!("in debug mode");
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(5000)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(5000)).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub mod log_sanitizer {
|
|||||||
text::{Span, Spans},
|
text::{Span, Spans},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Attempt to colorize the given string to tui-rs standars
|
/// Attempt to colorize the given string to tui-rs standards
|
||||||
pub fn colorize_logs(input: &str) -> Vec<Spans<'static>> {
|
pub fn colorize_logs(input: &str) -> Vec<Spans<'static>> {
|
||||||
vec![Spans::from(
|
vec![Spans::from(
|
||||||
categorise_text(input)
|
categorise_text(input)
|
||||||
|
|||||||
@@ -279,8 +279,8 @@ pub fn chart<B: Backend>(
|
|||||||
.style(Style::default().fg(Color::Cyan))
|
.style(Style::default().fg(Color::Cyan))
|
||||||
.graph_type(GraphType::Line)
|
.graph_type(GraphType::Line)
|
||||||
.data(&mem.0)];
|
.data(&mem.0)];
|
||||||
let cpu_stats = CpuStats::new(cpu.0.last().unwrap_or(&(0.00, 0.00)).1);
|
let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1));
|
||||||
let mem_stats = ByteStats::new(mem.0.last().unwrap_or(&(0.0, 0.0)).1 as u64);
|
let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64));
|
||||||
let cpu_chart = make_chart(&cpu.2, "cpu", cpu_dataset, &cpu_stats, &cpu.1);
|
let cpu_chart = make_chart(&cpu.2, "cpu", cpu_dataset, &cpu_stats, &cpu.1);
|
||||||
let mem_chart = make_chart(&mem.2, "memory", mem_dataset, &mem_stats, &mem.1);
|
let mem_chart = make_chart(&mem.2, "memory", mem_dataset, &mem_stats, &mem.1);
|
||||||
|
|
||||||
|
|||||||
+49
-49
@@ -1,5 +1,9 @@
|
|||||||
use std::{collections::HashMap, fmt};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
use tui::layout::{Constraint, Rect};
|
use tui::layout::{Constraint, Rect};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::app_data::Header;
|
use crate::app_data::Header;
|
||||||
|
|
||||||
@@ -10,6 +14,30 @@ pub enum SelectablePanel {
|
|||||||
Logs,
|
Logs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SelectablePanel {
|
||||||
|
pub const fn title(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Containers => "Containers",
|
||||||
|
Self::Logs => "Logs",
|
||||||
|
Self::Commands => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn next(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Containers => Self::Commands,
|
||||||
|
Self::Commands => Self::Logs,
|
||||||
|
Self::Logs => Self::Containers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn prev(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Containers => Self::Logs,
|
||||||
|
Self::Commands => Self::Containers,
|
||||||
|
Self::Logs => Self::Commands,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum Region {
|
pub enum Region {
|
||||||
Panel(SelectablePanel),
|
Panel(SelectablePanel),
|
||||||
Header(Header),
|
Header(Header),
|
||||||
@@ -93,7 +121,8 @@ impl BoxLocation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// State for the loading animation
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Loading {
|
pub enum Loading {
|
||||||
One,
|
One,
|
||||||
Two,
|
Two,
|
||||||
@@ -108,7 +137,7 @@ pub enum Loading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Loading {
|
impl Loading {
|
||||||
pub const fn next(&self) -> Self {
|
pub const fn next(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::One => Self::Two,
|
Self::One => Self::Two,
|
||||||
Self::Two => Self::Three,
|
Self::Two => Self::Three,
|
||||||
@@ -127,57 +156,28 @@ impl Loading {
|
|||||||
impl fmt::Display for Loading {
|
impl fmt::Display for Loading {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let disp = match self {
|
let disp = match self {
|
||||||
Self::One => "⠋",
|
Self::One => '⠋',
|
||||||
Self::Two => "⠙",
|
Self::Two => '⠙',
|
||||||
Self::Three => "⠹",
|
Self::Three => '⠹',
|
||||||
Self::Four => "⠸",
|
Self::Four => '⠸',
|
||||||
Self::Five => "⠼",
|
Self::Five => '⠼',
|
||||||
Self::Six => "⠴",
|
Self::Six => '⠴',
|
||||||
Self::Seven => "⠦",
|
Self::Seven => '⠦',
|
||||||
Self::Eight => "⠧",
|
Self::Eight => '⠧',
|
||||||
Self::Nine => "⠇",
|
Self::Nine => '⠇',
|
||||||
Self::Ten => "⠏",
|
Self::Ten => '⠏',
|
||||||
};
|
};
|
||||||
write!(f, "{}", disp)
|
write!(f, "{}", disp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectablePanel {
|
|
||||||
pub const fn title(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Containers => "Containers",
|
|
||||||
Self::Logs => "Logs",
|
|
||||||
Self::Commands => "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn next(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Containers => Self::Commands,
|
|
||||||
Self::Commands => Self::Logs,
|
|
||||||
Self::Logs => Self::Containers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn prev(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Containers => Self::Logs,
|
|
||||||
Self::Commands => Self::Containers,
|
|
||||||
Self::Logs => Self::Commands,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Global gui_state, stored in an Arc<Mutex>
|
/// Global gui_state, stored in an Arc<Mutex>
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GuiState {
|
pub struct GuiState {
|
||||||
// Think this should be a BMapTree, so can define order when iterating over potential intersects
|
|
||||||
// Is an issue if two panels are in the same space, sush as a smaller panel embedded, yet infront of, a larger panel
|
|
||||||
// If a BMapTree think it would mean have to implement ordering for SelectablePanel
|
|
||||||
panel_map: HashMap<SelectablePanel, Rect>,
|
panel_map: HashMap<SelectablePanel, Rect>,
|
||||||
heading_map: HashMap<Header, Rect>,
|
heading_map: HashMap<Header, Rect>,
|
||||||
loading_icon: Loading,
|
loading_icon: Loading,
|
||||||
// Should be a vec, each time loading add a new to the vec, and reset remove from vec
|
is_loading: HashSet<Uuid>,
|
||||||
// for for if is_loading just check if vec is empty or not
|
|
||||||
is_loading: bool,
|
|
||||||
pub selected_panel: SelectablePanel,
|
pub selected_panel: SelectablePanel,
|
||||||
pub show_help: bool,
|
pub show_help: bool,
|
||||||
pub info_box_text: Option<String>,
|
pub info_box_text: Option<String>,
|
||||||
@@ -191,7 +191,7 @@ impl GuiState {
|
|||||||
loading_icon: Loading::One,
|
loading_icon: Loading::One,
|
||||||
selected_panel: SelectablePanel::Containers,
|
selected_panel: SelectablePanel::Containers,
|
||||||
show_help: false,
|
show_help: false,
|
||||||
is_loading: false,
|
is_loading: HashSet::new(),
|
||||||
info_box_text: None,
|
info_box_text: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,14 +251,14 @@ impl GuiState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Advance loading animation
|
/// Advance loading animation
|
||||||
pub fn next_loading(&mut self) {
|
pub fn next_loading(&mut self, uuid: Uuid) {
|
||||||
self.loading_icon = self.loading_icon.next();
|
self.loading_icon = self.loading_icon.next();
|
||||||
self.is_loading = true;
|
self.is_loading.insert(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// if is_loading, return loading animation frame, else single space
|
/// if is_loading, return loading animation frame, else single space
|
||||||
pub fn get_loading(&mut self) -> String {
|
pub fn get_loading(&mut self) -> String {
|
||||||
if self.is_loading {
|
if !self.is_loading.is_empty() {
|
||||||
self.loading_icon.to_string()
|
self.loading_icon.to_string()
|
||||||
} else {
|
} else {
|
||||||
String::from(" ")
|
String::from(" ")
|
||||||
@@ -266,8 +266,8 @@ impl GuiState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// set is_loading to false, but keep animation frame at same state
|
/// set is_loading to false, but keep animation frame at same state
|
||||||
pub fn reset_loading(&mut self) {
|
pub fn remove_loading(&mut self, uuid: Uuid) {
|
||||||
self.is_loading = false;
|
self.is_loading.remove(&uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set info box content
|
/// Set info box content
|
||||||
|
|||||||
+4
-6
@@ -57,13 +57,13 @@ pub async fn create_ui(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
disable_raw_mode().unwrap_or(());
|
disable_raw_mode()?;
|
||||||
execute!(
|
execute!(
|
||||||
terminal.backend_mut(),
|
terminal.backend_mut(),
|
||||||
LeaveAlternateScreen,
|
LeaveAlternateScreen,
|
||||||
DisableMouseCapture
|
DisableMouseCapture
|
||||||
)?;
|
)?;
|
||||||
terminal.show_cursor().unwrap_or(());
|
terminal.show_cursor()?;
|
||||||
|
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
println!("{}", err);
|
println!("{}", err);
|
||||||
@@ -85,8 +85,7 @@ async fn run_app<B: Backend + Send>(
|
|||||||
|
|
||||||
// Check for docker connect errors before attempting to draw the gui
|
// Check for docker connect errors before attempting to draw the gui
|
||||||
let e = app_data.lock().get_error();
|
let e = app_data.lock().get_error();
|
||||||
if let Some(error) = e {
|
if let Some(AppError::DockerConnect) = e {
|
||||||
if let AppError::DockerConnect = error {
|
|
||||||
let mut seconds = 5;
|
let mut seconds = 5;
|
||||||
loop {
|
loop {
|
||||||
if seconds < 1 {
|
if seconds < 1 {
|
||||||
@@ -102,14 +101,13 @@ async fn run_app<B: Backend + Send>(
|
|||||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
seconds -= 1;
|
seconds -= 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() {
|
if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() {
|
||||||
return Err(AppError::Terminal);
|
return Err(AppError::Terminal);
|
||||||
}
|
}
|
||||||
if crossterm::event::poll(input_poll_rate).unwrap_or_default() {
|
if crossterm::event::poll(input_poll_rate).unwrap_or(false) {
|
||||||
if let Ok(event) = event::read() {
|
if let Ok(event) = event::read() {
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
sender
|
sender
|
||||||
|
|||||||
Reference in New Issue
Block a user