Skip to content

Commit

Permalink
Support Docker Extension
Browse files Browse the repository at this point in the history
  • Loading branch information
vietanhdev committed Nov 3, 2023
1 parent 35c10b6 commit 8bf9785
Show file tree
Hide file tree
Showing 18 changed files with 268 additions and 763 deletions.
43 changes: 43 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
FROM --platform=$BUILDPLATFORM node:18.9-alpine3.15 AS client-builder

WORKDIR /ui
COPY frontend/package.json /ui/package.json
COPY frontend/package-lock.json /ui/package-lock.json
RUN npm install

COPY frontend /ui
RUN npm run build

FROM alpine

ENV PYTHONUNBUFFERED=1
RUN apk --update --upgrade add python3 python3-dev gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev docker-cli
RUN ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools

LABEL org.opencontainers.image.title="P8Hub - Private AI Hub" \
org.opencontainers.image.description="Host and use your own AI Services. Keep everything simple and private." \
org.opencontainers.image.vendor="Viet-Anh NGUYEN" \
com.docker.desktop.extension.api.version=">= 0.2.0" \
com.docker.desktop.extension.icon="https://raw.githubusercontent.com/vietanhdev/vietanhdev/main/p8hub/p8hub.png" \
com.docker.extension.screenshots='[{"alt":"P8Hub Screenshot", "url":"https://raw.githubusercontent.com/vietanhdev/vietanhdev/main/p8hub/screenshot.png"}]' \
com.docker.extension.detailed-description="<h1>Description</h1><p>Host and use your own AI Services. Keep everything simple and private.</p>" \
com.docker.extension.publisher-url="https://www.docker.com" \
com.docker.extension.additional-urls='[{"title":"SDK Documentation","url":"https://docs.docker.com/desktop/extensions-sdk"}]' \
com.docker.extension.changelog="<ul><li>Added metadata to provide more information about the extension.</li></ul>"

COPY docker-compose.yaml docker-compose.yaml
COPY metadata.json metadata.json
COPY images/p8hub.svg p8hub.svg
COPY extension-ui ui

COPY p8hub /workspace/p8hub
COPY setup.py /workspace
COPY README.md /workspace
COPY pyproject.toml /workspace
COPY MANIFEST.in /workspace
COPY --from=client-builder /ui/out /workspace/p8hub/frontend-dist
RUN cd /workspace && pip3 install -e .

CMD python3 -m p8hub.app
32 changes: 32 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
EXTENSION:=
ifeq ($(OS),Windows_NT)
EXTENSION:=.exe
endif

IMAGE?=vietanhdev/p8hub-extension
TAG?=latest
BUILDER=buildx-multi-arch

INFO_COLOR = \033[0;36m
NO_COLOR = \033[m

build-extension: ## Build service image to be deployed as a desktop extension
docker build --tag=$(IMAGE):$(TAG) .

install-extension: build-extension ## Install the extension
docker extension install $(IMAGE):$(TAG)

update-extension: build-extension ## Update the extension
docker extension update $(IMAGE):$(TAG)

prepare-buildx: ## Create buildx builder for multi-arch build, if not exists
docker buildx inspect $(BUILDER) || docker buildx create --name=$(BUILDER) --driver=docker-container --driver-opt=network=host

push-extension: prepare-buildx ## Build & Upload extension image to hub. Do not push if tag already exists: make push-extension tag=0.1
docker pull $(IMAGE):$(TAG) && echo "Failure: Tag already exists" || docker buildx build --push --builder=$(BUILDER) --platform=linux/amd64,linux/arm64 --build-arg TAG=$(TAG) --tag=$(IMAGE):$(TAG) .

help: ## Show this help
@echo Please specify a build target. The choices are:
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(INFO_COLOR)%-30s$(NO_COLOR) %s\n", $$1, $$2}'

.PHONY: build-extension push-extension help
10 changes: 10 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
devenv-volumes:
image: ${DESKTOP_PLUGIN_IMAGE}
cap_add:
- DAC_OVERRIDE
- FOWNER
volumes:
- /var/lib/docker:/var/lib/docker
ports:
- "18180:5678"
18 changes: 18 additions & 0 deletions extension-ui/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>P8Hub Extension</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
</style>
</head>
<body>
<iframe src="http://localhost:18180" frameborder="0" style="width: 100%; height: 100vh;"></iframe>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
"use client"

import Link from "next/link"
import { useSearchParams } from "next/navigation"
import { CircleIcon } from "@radix-ui/react-icons"
import moment from "moment"
import useSWR from "swr"
import { useState } from "react"

import { CircleIcon } from "@radix-ui/react-icons"
import { Checkbox } from "@/registry/default/ui/checkbox"
import { cn } from "@/lib/utils"

export default function ServicePage({ params }: { params: { id: number } }) {
const { data: service } = useSWR(
`/api/services/${params.id}`,
export default function ServicePage() {
const params = useSearchParams()
const serviceId = params.get("serviceId")
const { data: service, error } = useSWR(
`/api/services/${serviceId}`,
(url) => fetch(url).then((res) => res.json()),
{ refreshInterval: 5000 }
)
const { data: logs } = useSWR(
`/api/services/${params.id}/container_logs`,
`/api/services/${serviceId}/container_logs`,
(url) => fetch(url).then((res) => res.json()),
{ refreshInterval: 5000 }
)
const [autoScrollLogs, setAutoScrollLogs] = useState(true)

// TODO: handle error
if (service && service?.detail) {
window.location.href = "/"
}

return (
<section className="container grid items-center gap-6 pb-8 pt-6 md:py-10">
Expand All @@ -42,7 +48,7 @@ export default function ServicePage({ params }: { params: { id: number } }) {
: "fill-orange-500 text-orange-400"
)}
/>
{service?.status.toUpperCase().replaceAll("_", " ")}
{service?.status?.toUpperCase()?.replaceAll("_", " ")}
</div>
)}
<div>
Expand Down
7 changes: 5 additions & 2 deletions frontend/components/service-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ export function ServiceCard({
<CardHeader className="grid grid-cols-[1fr_110px] items-start gap-4 space-y-0">
<div className="space-y-1">
<CardTitle>
<Link href={`/services/${id}`} className="hover:underline">
<Link
href={`/services?serviceId=${id}`}
className="hover:underline"
>
{name}
</Link>
</CardTitle>
Expand Down Expand Up @@ -116,7 +119,7 @@ export function ServiceCard({
className="w-[200px]"
forceMount
>
<Link href={`/services/${id}`}>
<Link href={`/services?serviceId=${id}`}>
<DropdownMenuCheckboxItem>View logs</DropdownMenuCheckboxItem>
</Link>
<DropdownMenuCheckboxItem
Expand Down
9 changes: 9 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"date-fns": "^2.30.0",
"jotai": "^2.5.0",
"lucide-react": "0.105.0-alpha.4",
"moment": "^2.29.4",
"next": "^13.4.8",
"next-themes": "^0.2.1",
"react": "^18.2.0",
Expand Down
Binary file added images/p8hub.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions images/p8hub.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"icon": "p8hub.svg",
"vm": {
"composefile": "docker-compose.yaml",
"exposes": { "socket": "extension-volumes.sock" }
},
"ui": {
"dashboard-tab": {
"title": "P8Hub",
"root": "/ui",
"src": "index.html",
"backend": { "socket": "extension-volumes.sock" }
}
}
}
49 changes: 32 additions & 17 deletions p8hub/app.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import os

import argparse
import logging
import pathlib

import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles

from p8hub.app_info import __appname__, __description__, __version__
from p8hub.config import DATA_ROOT
from p8hub.database import engine
from p8hub.routers import apps, system_monitor, services
from p8hub.utils import extract_frontend_dist, extract_apps
from p8hub.database import Base
from p8hub.core.app_manager import AppManager
from p8hub.core.service_manager import ServiceManager
from p8hub import globals

from p8hub import globals as global_data

def main():
parser = argparse.ArgumentParser(
Expand All @@ -26,7 +18,7 @@ def main():
parser.add_argument(
"--host",
type=str,
default="127.0.0.1",
default="0.0.0.0",
help="Host to run the server on",
)
parser.add_argument(
Expand All @@ -40,6 +32,12 @@ def main():
action="store_true",
help="Print version and exit",
)
parser.add_argument(
"--data_root",
default=os.path.abspath(
os.path.join(os.path.expanduser("~"), "p8hub-data")
),
)
args = parser.parse_args()

if args.version:
Expand All @@ -49,17 +47,33 @@ def main():
logging.info(f"Starting {__appname__}...")
logging.info(f"Version: {__version__}")

logging.info(f"Data root: {args.data_root}")
global_data.data_root = args.data_root
pathlib.Path(global_data.data_root).mkdir(parents=True, exist_ok=True)

# Import these after setting data_root
from p8hub.routers import apps, system_monitor, services
from p8hub.utils import extract_frontend_dist, extract_apps
from p8hub.database import engine, Base
from p8hub.core.app_manager import AppManager
from p8hub.core.service_manager import ServiceManager

logging.info("Extracting frontend distribution...")
static_folder = os.path.abspath(os.path.join(DATA_ROOT, "frontend-dist"))
static_folder = os.path.abspath(
os.path.join(global_data.data_root, "frontend-dist")
)
extract_frontend_dist(static_folder)
print(static_folder)

logging.info("Extracting apps...")
static_folder = os.path.abspath(os.path.join(DATA_ROOT, "apps"))
extract_apps(static_folder)
apps_folder = os.path.abspath(os.path.join(global_data.data_root, "apps"))
extract_apps(apps_folder)

# Initialize globals
globals.app_manager = AppManager(apps_dir=os.path.join(DATA_ROOT, "apps"))
globals.service_manager = ServiceManager(globals.app_manager)
# Initialize global_data
global_data.app_manager = AppManager(
apps_dir=os.path.join(global_data.data_root, "apps")
)
global_data.service_manager = ServiceManager(global_data.app_manager, global_data.data_root)

logging.info("Creating database tables...")
Base.metadata.create_all(bind=engine)
Expand All @@ -85,5 +99,6 @@ def main():

uvicorn.run(app, host=args.host, port=args.port, workers=1)


if __name__ == "__main__":
main()
6 changes: 1 addition & 5 deletions p8hub/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import os
import pathlib

DATA_ROOT = os.path.abspath(
os.path.join(os.path.expanduser("~"), "p8hub-data")
)
pathlib.Path(DATA_ROOT).mkdir(parents=True, exist_ok=True)

DATABASE_PATH = os.path.abspath(os.path.join(DATA_ROOT, "p8hub.db"))

Loading

0 comments on commit 8bf9785

Please sign in to comment.