Skip to content

Commit

Permalink
feat: handles container exit and finished at
Browse files Browse the repository at this point in the history
  • Loading branch information
amir20 committed Jan 3, 2025
1 parent 294ad89 commit 5ad0747
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 13 deletions.
18 changes: 14 additions & 4 deletions assets/components/ContainerPopup.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
<template>
<div>
<span class="font-light capitalize"> RUNNING </span>
<span class="font-light capitalize"> STATE </span>
<span class="font-semibold uppercase"> {{ container.state }} </span>
</div>
<div v-if="container.startedAt.getFullYear() > 0">
<span class="font-light capitalize"> STARTED </span>
<span class="font-semibold">
<DistanceTime :date="container.created" strict :suffix="false" />
<DistanceTime :date="container.startedAt" strict />
</span>
</div>
<div>
<div v-if="container.state != 'running' && container.finishedAt.getFullYear() > 0">
<span class="font-light capitalize"> Finished </span>
<span class="font-semibold">
<DistanceTime :date="container.finishedAt" strict />
</span>
</div>
<div v-if="container.state == 'running'">
<span class="font-light capitalize"> Load </span>
<span class="font-semibold"> {{ container.stat.cpu.toFixed(2) }}% </span>
</div>
<div>
<div v-if="container.state == 'running'">
<span class="font-light capitalize"> MEM </span>
<span class="font-semibold"> {{ formatBytes(container.stat.memoryUsage) }} </span>
</div>
Expand Down
2 changes: 1 addition & 1 deletion assets/components/HostMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
</span>
</router-link>
<template #content>
<ContainerPopup :container="item as Container" />
<ContainerPopup :container="item" />
</template>
</Popup>
</li>
Expand Down
3 changes: 2 additions & 1 deletion assets/composable/eventStreams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,12 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
const event = JSON.parse((e as MessageEvent).data) as {
actorId: string;
name: "container-stopped" | "container-started";
time: string;
};
const containerEvent = new ContainerEventLogEntry(
event.name == "container-started" ? "Container started" : "Container stopped",
event.actorId,
new Date(),
new Date(event.time),
event.name,
);

Expand Down
2 changes: 2 additions & 0 deletions assets/models/Container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export class Container {
constructor(
public readonly id: string,
public readonly created: Date,
public startedAt: Date,
public finishedAt: Date,
public readonly image: string,
name: string,
public readonly command: string,
Expand Down
16 changes: 15 additions & 1 deletion assets/stores/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const useContainerStore = defineStore("container", () => {
}
});
es.addEventListener("container-event", (e) => {
const event = JSON.parse((e as MessageEvent).data) as { actorId: string; name: string };
const event = JSON.parse((e as MessageEvent).data) as { actorId: string; name: string; time: string };
const container = allContainersById.value[event.actorId];
if (container) {
switch (event.name) {
Expand All @@ -75,6 +75,18 @@ export const useContainerStore = defineStore("container", () => {
}
});

es.addEventListener("container-updated", (e) => {
const container = JSON.parse((e as MessageEvent).data) as ContainerJson;
const existing = allContainersById.value[container.id];
if (existing) {
existing.state = container.state;
existing.health = container.health;
existing.name = container.name;
existing.startedAt = new Date(container.startedAt);
existing.finishedAt = new Date(container.finishedAt);
}
});

es.addEventListener("update-host", (e) => {
const host = JSON.parse((e as MessageEvent).data) as Host;
updateHost(host);
Expand Down Expand Up @@ -133,6 +145,8 @@ export const useContainerStore = defineStore("container", () => {
return new Container(
c.id,
new Date(c.created),
new Date(c.startedAt),
new Date(c.finishedAt),
c.image,
c.name,
c.command,
Expand Down
4 changes: 3 additions & 1 deletion assets/types/Container.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export interface ContainerStat {

export type ContainerJson = {
readonly id: string;
readonly created: number;
readonly created: string;
readonly startedAt: string;
readonly finishedAt: string;
readonly image: string;
readonly name: string;
readonly command: string;
Expand Down
28 changes: 28 additions & 0 deletions internal/docker/container_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,34 @@ func (s *ContainerStore) FindContainer(id string, filter ContainerFilter) (Conta
}

if container, ok := s.containers.Load(id); ok {
if container.StartedAt.IsZero() {
log.Debug().Str("id", id).Msg("container doesn't have detailed information, fetching it")
if newContainer, ok := s.containers.Compute(id, func(c *Container, loaded bool) (*Container, bool) {
if loaded {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if newContainer, err := s.client.FindContainer(ctx, id); err == nil {
return &newContainer, false
}
}
return c, false
}); ok {
event := ContainerEvent{
Name: "update",
Host: s.client.Host().ID,
ActorID: id,
}
s.subscribers.Range(func(c context.Context, events chan<- ContainerEvent) bool {
select {
case events <- event:
case <-c.Done():
s.subscribers.Delete(c)
}
return true
})
return *newContainer, nil
}
}
return *container, nil
} else {
log.Warn().Str("id", id).Msg("container not found")
Expand Down
8 changes: 8 additions & 0 deletions internal/web/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
return
}

case "update":
log.Debug().Str("id", event.ActorID).Msg("container updated")
if containerService, err := h.multiHostService.FindContainer(event.Host, event.ActorID, usersFilter); err == nil {
if err := sseWriter.Event("container-updated", containerService.Container); err != nil {
log.Error().Err(err).Msg("error writing event to event stream")
return
}
}
case "health_status: healthy", "health_status: unhealthy":
log.Debug().Str("container", event.ActorID).Str("health", event.Name).Msg("container health status")
healthy := "unhealthy"
Expand Down
1 change: 1 addition & 0 deletions internal/web/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
log.Error().Err(err).Msg("error while finding container")
return
}
container = containerService.Container
start := utils.Max(absoluteTime, container.StartedAt)
err = containerService.StreamLogs(r.Context(), start, stdTypes, liveLogs)
if err != nil {
Expand Down
10 changes: 5 additions & 5 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ export default defineConfig(() => ({
target: "esnext",
},
plugins: [
VueMacros({
plugins: {
vue: Vue(),
},
}),
VueRouter({
routesFolder: {
src: "./assets/pages",
},
dts: "./assets/typed-router.d.ts",
importMode: "sync",
}),
VueMacros({
plugins: {
vue: Vue(),
},
}),
Icons({
autoInstall: true,
}),
Expand Down

0 comments on commit 5ad0747

Please sign in to comment.