-
Notifications
You must be signed in to change notification settings - Fork 122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
initial commit of docker desktop extension #218
Changes from 15 commits
369a30f
be720f3
b231ea5
7627908
b206d52
d786353
15e9b04
822e861
c3b4181
4b9f562
f20ffc4
a8e2473
c91c7c1
e1ffa72
e6cb426
1402780
09d2a38
0c192a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ui/node_modules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
ui/build |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"makefile.configureOnOpen": false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
FROM --platform=$BUILDPLATFORM node:21.6-alpine3.18 AS client-builder | ||
WORKDIR /ui | ||
# cache packages in layer | ||
COPY ui/package.json /ui/package.json | ||
COPY ui/package-lock.json /ui/package-lock.json | ||
RUN --mount=type=cache,target=/usr/src/app/.npm \ | ||
npm set cache /usr/src/app/.npm && \ | ||
npm ci | ||
# install | ||
COPY ui /ui | ||
RUN npm run build | ||
|
||
|
||
FROM python:3.11-slim | ||
ENV PYTHONUNBUFFERED=1 | ||
|
||
RUN apt-get update\ | ||
&& apt-get install --no-install-recommends -y curl \ | ||
&& rm -rf /var/lib/apt/lists/*\ | ||
&& pip install --upgrade pip | ||
|
||
COPY docker-compose.yaml . | ||
COPY metadata.json . | ||
COPY logo.svg . | ||
|
||
# Copy python dependecies file | ||
COPY ./backend/requirements.txt /app/requirements.txt | ||
RUN pip install --no-cache-dir -r /app/requirements.txt | ||
# Copy source code | ||
COPY ./backend /app/ | ||
|
||
LABEL org.opencontainers.image.title="Plate Recognizer Installer" \ | ||
org.opencontainers.image.description="Plate Recognizer Installer for Stream and Snapshot SDKs" \ | ||
org.opencontainers.image.vendor="Plate Recognizer" \ | ||
com.docker.desktop.extension.api.version="0.3.4" \ | ||
com.docker.extension.screenshots="" \ | ||
com.docker.desktop.extension.icon="https://platerecognizer.com/wp-content/uploads/2020/07/plate-recognizer-alpr-anpr-logo.png" \ | ||
com.docker.extension.detailed-description="Select correct docker image and generate command for installing, updating or uninstalling Snapshot or Stream SDK" \ | ||
com.docker.extension.publisher-url="https://platerecognizer.com/" \ | ||
com.docker.extension.additional-urls="https://parkpow.com/" \ | ||
com.docker.extension.categories="utility-tools" \ | ||
com.docker.extension.changelog="Initial Release" \ | ||
com.docker.extension.account-info="required" | ||
|
||
|
||
COPY --from=client-builder /ui/build ui | ||
CMD ["gunicorn", "--workers", "1", "--chdir", "/app", "--bind", "unix:/run/guest-services/backend.sock", "wsgi:app"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
IMAGE?=platerecognizer/installer | ||
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: help |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# Plate Recognizer Installer | ||
|
||
Plate Recognizer Installer Docker extension. | ||
|
||
This extension is composed of: | ||
|
||
- A [frontend](./ui) app in React. | ||
- A [backend](./backend) container that runs an API in Python. Makes request to verify token | ||
|
||
## Local development | ||
|
||
You can use `docker` to build, install and push your extension. Also, we provide an opinionated [Makefile](Makefile) that could be convenient for you. There isn't a strong preference of using one over the other, so just use the one you're most comfortable with. | ||
|
||
To build the extension, use `make build-extension` **or**: | ||
|
||
```shell | ||
docker buildx build -t platerecognizer/installer:latest . --load | ||
``` | ||
|
||
To install the extension, use `make install-extension` **or**: | ||
|
||
```shell | ||
docker extension install platerecognizer/installer:latest | ||
``` | ||
|
||
To Publish | ||
```shell | ||
docker buildx build --push --platform=linux/amd64,linux/arm64 --tag=platerecognizer/installer:0.0.1 . | ||
``` | ||
|
||
> If you want to automate this command, use the `-f` or `--force` flag to accept the warning message. | ||
|
||
To preview the extension in Docker Desktop, open Docker Dashboard once the installation is complete. The left-hand menu displays a new tab with the name of your extension. You can also use `docker extension ls` to see that the extension has been installed successfully. | ||
|
||
### Frontend development | ||
|
||
During the development of the frontend part, it's helpful to use hot reloading to test your changes without rebuilding your entire extension. To do this, you can configure Docker Desktop to load your UI from a development server. | ||
Assuming your app runs on the default port, start your UI app and then run: | ||
|
||
```shell | ||
cd ui | ||
npm install | ||
npm run dev | ||
``` | ||
|
||
This starts a development server that listens on port `3000`. | ||
|
||
You can now tell Docker Desktop to use this as the frontend source. In another terminal run: | ||
|
||
```shell | ||
docker extension dev ui-source platerecognizer/installer:latest http://localhost:3000 | ||
``` | ||
|
||
In order to open the Chrome Dev Tools for your extension when you click on the extension tab, run: | ||
|
||
```shell | ||
docker extension dev debug platerecognizer/installer:latest | ||
``` | ||
|
||
Each subsequent click on the extension tab will also open Chrome Dev Tools. To stop this behaviour, run: | ||
|
||
```shell | ||
docker extension dev reset platerecognizer/installer:latest | ||
``` | ||
|
||
### Backend development | ||
|
||
Whenever you make changes in the [backend](./backend) source code, you will need to compile them and re-deploy a new version of your backend container. | ||
Use the `docker extension update` command to remove and re-install the extension automatically: | ||
|
||
```shell | ||
docker extension update platerecognizer/installer:latest | ||
``` | ||
|
||
> If you want to automate this command, use the `-f` or `--force` flag to accept the warning message. | ||
|
||
> Extension containers are hidden from the Docker Dashboard by default. You can change this in Settings > Extensions > Show Docker Extensions system containers. | ||
|
||
### Clean up | ||
|
||
To remove the extension: | ||
|
||
```shell | ||
docker extension rm platerecognizer/installer:latest | ||
``` | ||
|
||
## What's next? | ||
|
||
- To learn more about how to build your extension refer to the Extension SDK docs at <https://docs.docker.com/desktop/extensions-sdk/>. | ||
- To publish your extension in the Marketplace visit <https://www.docker.com/products/extensions/submissions/>. | ||
- To report issues and feedback visit <https://github.com/docker/extensions-sdk/issues>. | ||
- To look for other ideas of new extensions, or propose new ideas of extensions you would like to see, visit <https://github.com/docker/extension-ideas/discussions>. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import logging | ||
import os | ||
import sys | ||
|
||
from flask import Flask, request | ||
from utils import verify_token | ||
|
||
LOG_LEVEL = os.environ.get("LOGGING", "DEBUG").upper() | ||
|
||
logging.basicConfig( | ||
stream=sys.stdout, | ||
level=LOG_LEVEL, | ||
style="{", | ||
format="{asctime} {levelname} {name} {threadName} : {message}", | ||
) | ||
|
||
lgr = logging.getLogger(__name__) | ||
|
||
|
||
app = Flask(__name__) | ||
|
||
|
||
@app.route("/verify-token", methods=["POST"]) | ||
def verify_token_license(): | ||
lgr.debug(f"verify token data: {request.data}") | ||
token = request.json.get("token") | ||
license = request.json.get("license") | ||
valid, message = verify_token(token, license, "port" not in request.json) | ||
lgr.info(f"verify result: {valid} - {message}") | ||
|
||
return {"valid": valid, "message": message} | ||
danleyb2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
if __name__ == "__main__": | ||
app.run() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Flask==3.0.3 | ||
gunicorn==23.0.0 | ||
danleyb2 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
try: | ||
from urllib.error import URLError | ||
from urllib.request import Request, urlopen | ||
except ImportError: | ||
from urllib2 import Request, URLError, urlopen # type: ignore | ||
from ssl import SSLError | ||
|
||
|
||
def verify_token(token, license_key, is_stream=True): | ||
if not token or not license_key: | ||
raise ValueError("API token and license key is required.") | ||
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
path = "stream/license" if is_stream else "sdk-webhooks" | ||
try: | ||
req = Request( | ||
f"https://api.platerecognizer.com/v1/{path}/{license_key.strip()}/" | ||
) | ||
req.add_header("Authorization", f"Token {token.strip()}") | ||
urlopen(req).read() | ||
return True, None | ||
|
||
except SSLError: | ||
req = Request( | ||
f"http://api.platerecognizer.com/v1/{path}/{license_key.strip()}/" | ||
) | ||
req.add_header("Authorization", f"Token {token.strip()}") | ||
urlopen(req).read() | ||
return True, None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handling SSL errors by switching to HTTP is insecure. Instead, consider logging the error or notifying the user without downgrading security protocols. Here's a safer approach: - except SSLError:
- req = Request(
- f"http://api.platerecognizer.com/v1/{path}/{license_key.strip()}/"
- )
- req.add_header("Authorization", f"Token {token.strip()}")
- urlopen(req).read()
- return True, None
+ except SSLError as e:
+ return False, f"SSL Error occurred: {e}. Please check your network settings." |
||
|
||
except URLError as e: | ||
if "404" in str(e): | ||
return ( | ||
False, | ||
"The License Key cannot be found. Please use the correct License Key.", | ||
) | ||
elif str(403) in str(e): | ||
return False, "The API Token cannot be found. Please use the correct Token." | ||
|
||
else: | ||
return True, None | ||
danleyb2 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from main import app | ||
|
||
if __name__ == "__main__": | ||
app.run() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
services: | ||
platerecognizer-installer: | ||
image: ${DESKTOP_PLUGIN_IMAGE} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"icon": "logo.svg", | ||
"vm": { | ||
"composefile": "docker-compose.yaml", | ||
"exposes": { | ||
"socket": "backend.sock" | ||
} | ||
}, | ||
"ui": { | ||
"dashboard-tab": { | ||
"title": "Plate Recognizer Installer", | ||
"src": "index.html", | ||
"root": "ui" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Electron 17.1.1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import globals from "globals"; | ||
import pluginJs from "@eslint/js"; | ||
import tseslint from "typescript-eslint"; | ||
import pluginReact from "eslint-plugin-react"; | ||
|
||
export default [ | ||
{ | ||
files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], | ||
}, | ||
{ | ||
languageOptions: { | ||
globals: globals.browser, | ||
}, | ||
}, | ||
pluginJs.configs.recommended, | ||
...tseslint.configs.recommended, | ||
pluginReact.configs.flat.recommended, | ||
]; | ||
Pefington marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="color-scheme" content="light dark" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CodeRabbit
The
push-extension
target uses a combination ofdocker pull
andecho
to check if a tag already exists. This approach can be improved for clarity and reliability. Consider using a more explicit check withdocker manifest inspect
, which is specifically designed for this purpose.