@lazycatcloud/lzc-cli 1.3.17 → 2.0.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -7
- package/changelog.md +14 -0
- package/lib/app/apkshell.js +7 -44
- package/lib/app/index.js +178 -64
- package/lib/app/lpk_build.js +446 -61
- package/lib/app/lpk_build_images.js +749 -0
- package/lib/app/lpk_create.js +192 -45
- package/lib/app/lpk_create_generator.js +141 -13
- package/lib/app/lpk_devshell.js +33 -19
- package/lib/app/lpk_embed_images.js +257 -0
- package/lib/app/lpk_installer.js +17 -9
- package/lib/app/manifest_build.js +259 -0
- package/lib/app/project_cp.js +59 -0
- package/lib/app/project_deploy.js +58 -0
- package/lib/app/project_exec.js +82 -0
- package/lib/app/project_info.js +106 -0
- package/lib/app/project_log.js +62 -0
- package/lib/app/project_runtime.js +356 -0
- package/lib/app/project_start.js +95 -0
- package/lib/app/project_sync.js +499 -0
- package/lib/appstore/apkshell.js +50 -0
- package/lib/box/index.js +101 -4
- package/lib/box/ssh_remote.js +259 -0
- package/lib/build_remote.js +21 -0
- package/lib/debug_bridge.js +891 -83
- package/lib/docker/index.js +30 -10
- package/lib/i18n/locales/en/translation.json +262 -255
- package/lib/i18n/locales/zh/translation.json +262 -255
- package/lib/lpk/core.js +488 -0
- package/lib/lpk/index.js +210 -0
- package/lib/migrate/index.js +52 -0
- package/lib/package_info.js +135 -0
- package/lib/shellapi.js +35 -1
- package/lib/sig/core.js +254 -0
- package/lib/sig/index.js +88 -0
- package/lib/utils.js +94 -15
- package/package.json +3 -3
- package/scripts/cli.js +6 -0
- package/scripts/smoke/frontend-dev-entry.mjs +104 -0
- package/scripts/smoke/template-project.mjs +311 -0
- package/template/_lpk/README.md +15 -4
- package/template/_lpk/gui-vnc.manifest.yml.in +18 -0
- package/template/_lpk/hello-vue.manifest.yml.in +38 -0
- package/template/_lpk/manifest.yml.in +4 -11
- package/template/_lpk/package.yml.in +7 -0
- package/template/_lpk/todolist-golang.manifest.yml.in +30 -0
- package/template/_lpk/todolist-java.manifest.yml.in +29 -0
- package/template/_lpk/todolist-python.manifest.yml.in +37 -0
- package/template/_lpk/todolist-serverless.manifest.yml.in +38 -0
- package/template/_lpk/vue.lzc-build.yml.in +0 -44
- package/template/blank/lzc-build.dev.yml +4 -0
- package/template/blank/lzc-build.yml +24 -41
- package/template/blank/lzc-manifest.yml +7 -9
- package/template/blank/package.yml +7 -0
- package/template/golang/Dockerfile +19 -0
- package/template/golang/Dockerfile.dev +20 -0
- package/template/golang/README.md +44 -0
- package/template/golang/_gitignore +3 -0
- package/template/golang/_lzcdevignore +21 -0
- package/template/golang/go.mod +3 -0
- package/template/golang/lzc-build.dev.yml +12 -0
- package/template/golang/lzc-build.yml +16 -0
- package/template/golang/lzc-icon.png +0 -0
- package/template/golang/main.go +252 -0
- package/template/golang/manifest.dev.page.js +24 -0
- package/template/golang/run.sh +10 -0
- package/template/golang/web/index.html +238 -0
- package/template/gui-vnc/README.md +23 -0
- package/template/gui-vnc/_gitignore +2 -0
- package/template/gui-vnc/images/Dockerfile +30 -0
- package/template/gui-vnc/images/kasmvnc.yaml +33 -0
- package/template/gui-vnc/images/startup-script.desktop +9 -0
- package/template/gui-vnc/images/startup-script.sh +6 -0
- package/template/gui-vnc/lzc-build.dev.yml +4 -0
- package/template/gui-vnc/lzc-build.yml +18 -0
- package/template/gui-vnc/lzc-icon.png +0 -0
- package/template/python/Dockerfile +15 -0
- package/template/python/Dockerfile.dev +18 -0
- package/template/python/README.md +50 -0
- package/template/python/_gitignore +3 -0
- package/template/python/_lzcdevignore +21 -0
- package/template/python/app.py +110 -0
- package/template/python/lzc-build.dev.yml +12 -0
- package/template/python/lzc-build.yml +16 -0
- package/template/python/lzc-icon.png +0 -0
- package/template/python/manifest.dev.page.js +25 -0
- package/template/python/requirements.txt +1 -0
- package/template/python/run.sh +14 -0
- package/template/python/web/index.html +238 -0
- package/template/springboot/Dockerfile +20 -0
- package/template/springboot/Dockerfile.dev +20 -0
- package/template/springboot/README.md +44 -0
- package/template/springboot/_gitignore +3 -0
- package/template/springboot/_lzcdevignore +21 -0
- package/template/springboot/lzc-build.dev.yml +12 -0
- package/template/springboot/lzc-build.yml +16 -0
- package/template/springboot/lzc-icon.png +0 -0
- package/template/springboot/manifest.dev.page.js +24 -0
- package/template/springboot/pom.xml +38 -0
- package/template/springboot/run.sh +10 -0
- package/template/springboot/src/main/java/cloud/lazycat/app/Application.java +132 -0
- package/template/springboot/src/main/resources/application.properties +1 -0
- package/template/springboot/src/main/resources/static/index.html +238 -0
- package/template/vue/README.md +18 -21
- package/template/vue/lzc-build.dev.yml +7 -0
- package/template/vue/lzc-build.yml +30 -43
- package/template/vue/manifest.dev.page.js +50 -0
- package/template/vue/src/App.vue +36 -25
- package/template/vue/src/style.css +106 -49
- package/template/vue-minidb/README.md +26 -0
- package/template/vue-minidb/_gitignore +25 -0
- package/template/vue-minidb/index.html +13 -0
- package/template/vue-minidb/lzc-build.dev.yml +7 -0
- package/template/vue-minidb/lzc-build.yml +46 -0
- package/template/vue-minidb/lzc-icon.png +0 -0
- package/template/vue-minidb/manifest.dev.page.js +50 -0
- package/template/vue-minidb/package.json +21 -0
- package/template/vue-minidb/public/vite.svg +1 -0
- package/template/vue-minidb/src/App.vue +206 -0
- package/template/vue-minidb/src/assets/vue.svg +1 -0
- package/template/vue-minidb/src/main.ts +5 -0
- package/template/vue-minidb/src/style.css +136 -0
- package/template/vue-minidb/src/vite-env.d.ts +1 -0
- package/template/vue-minidb/tsconfig.app.json +24 -0
- package/template/vue-minidb/tsconfig.json +7 -0
- package/template/vue-minidb/tsconfig.node.json +22 -0
- package/template/vue-minidb/vite.config.ts +10 -0
- /package/template/{vue → vue-minidb}/src/components/HelloWorld.vue +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3
|
|
2
|
+
|
|
3
|
+
RUN apk add --no-cache python3 py3-pip
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
ENV LZC_PY_DEV=1
|
|
7
|
+
ENV PYTHONUNBUFFERED=1
|
|
8
|
+
|
|
9
|
+
COPY requirements.txt /app/requirements.txt
|
|
10
|
+
RUN pip3 install --no-cache-dir -r /app/requirements.txt --break-system-packages
|
|
11
|
+
|
|
12
|
+
COPY app.py /app/app.py
|
|
13
|
+
COPY run.sh /app/run.sh
|
|
14
|
+
RUN chmod +x /app/run.sh
|
|
15
|
+
COPY web /app/web
|
|
16
|
+
|
|
17
|
+
EXPOSE 3000
|
|
18
|
+
CMD ["/app/run.sh"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Lazycat Python Todo App
|
|
2
|
+
|
|
3
|
+
## First Deploy
|
|
4
|
+
```bash
|
|
5
|
+
lzc-cli project deploy
|
|
6
|
+
lzc-cli project info
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
By default, project commands use `lzc-build.dev.yml` when it exists.
|
|
10
|
+
Each command prints the active `Build config`.
|
|
11
|
+
Use `--release` if you want to operate on `lzc-build.yml`.
|
|
12
|
+
|
|
13
|
+
Open the app first.
|
|
14
|
+
In dev mode, the Python service starts automatically.
|
|
15
|
+
If the page still shows the backend dev guide, it will tell you the expected port and next step.
|
|
16
|
+
|
|
17
|
+
## Recommended Backend Dev Loop
|
|
18
|
+
```bash
|
|
19
|
+
lzc-cli project sync --watch
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The dev image starts the Python service automatically and reloads on source changes.
|
|
23
|
+
If you changed dependencies or need to restart the process manually:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
lzc-cli project exec /bin/sh
|
|
27
|
+
/app/run.sh
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You can also build locally and use `lzc-cli project cp` instead of `project sync --watch`.
|
|
31
|
+
|
|
32
|
+
## Troubleshooting
|
|
33
|
+
```bash
|
|
34
|
+
lzc-cli project log -f
|
|
35
|
+
lzc-cli project exec /bin/sh
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
```text
|
|
40
|
+
GET /api/health
|
|
41
|
+
GET /api/todos
|
|
42
|
+
POST /api/todos
|
|
43
|
+
PUT /api/todos/{id}/toggle
|
|
44
|
+
DELETE /api/todos/{id}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Data Path
|
|
48
|
+
```text
|
|
49
|
+
/lzcapp/var/todos.json
|
|
50
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.git
|
|
2
|
+
.git/**
|
|
3
|
+
node_modules
|
|
4
|
+
node_modules/**
|
|
5
|
+
.venv
|
|
6
|
+
.venv/**
|
|
7
|
+
dist
|
|
8
|
+
dist/**
|
|
9
|
+
build
|
|
10
|
+
build/**
|
|
11
|
+
__pycache__
|
|
12
|
+
__pycache__/**
|
|
13
|
+
.idea
|
|
14
|
+
.idea/**
|
|
15
|
+
.vscode
|
|
16
|
+
.vscode/**
|
|
17
|
+
.DS_Store
|
|
18
|
+
.lzc-cli-*
|
|
19
|
+
.lzc-cli-*/**
|
|
20
|
+
*.lpk
|
|
21
|
+
*.lpk.tar
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
from flask import Flask, send_from_directory, request
|
|
5
|
+
|
|
6
|
+
app = Flask(__name__, static_folder="/app/web", static_url_path="")
|
|
7
|
+
DATA_FILE = "/lzcapp/var/todos.json"
|
|
8
|
+
todos = []
|
|
9
|
+
next_id = 1
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def now_ms():
|
|
13
|
+
return int(time.time() * 1000)
|
|
14
|
+
|
|
15
|
+
def load_todos():
|
|
16
|
+
global todos, next_id
|
|
17
|
+
os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True)
|
|
18
|
+
if not os.path.exists(DATA_FILE):
|
|
19
|
+
return
|
|
20
|
+
with open(DATA_FILE, "r", encoding="utf-8") as fp:
|
|
21
|
+
raw = fp.read().strip()
|
|
22
|
+
if not raw:
|
|
23
|
+
return
|
|
24
|
+
loaded = json.loads(raw)
|
|
25
|
+
if not isinstance(loaded, list):
|
|
26
|
+
raise ValueError("todos data must be a list")
|
|
27
|
+
todos = loaded
|
|
28
|
+
max_id = 0
|
|
29
|
+
for item in todos:
|
|
30
|
+
if isinstance(item, dict):
|
|
31
|
+
max_id = max(max_id, int(item.get("id", 0)))
|
|
32
|
+
next_id = max_id + 1
|
|
33
|
+
|
|
34
|
+
def save_todos():
|
|
35
|
+
os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True)
|
|
36
|
+
tmp_file = DATA_FILE + ".tmp"
|
|
37
|
+
with open(tmp_file, "w", encoding="utf-8") as fp:
|
|
38
|
+
json.dump(todos, fp, ensure_ascii=False, indent=2)
|
|
39
|
+
os.replace(tmp_file, DATA_FILE)
|
|
40
|
+
|
|
41
|
+
load_todos()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.get("/")
|
|
45
|
+
def index():
|
|
46
|
+
return send_from_directory(app.static_folder, "index.html")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.get('/api/health')
|
|
50
|
+
def health():
|
|
51
|
+
return {'status': 'ok'}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.get("/api/todos")
|
|
55
|
+
def list_todos():
|
|
56
|
+
sorted_todos = sorted(todos, key=lambda item: item["updatedAt"], reverse=True)
|
|
57
|
+
return {"items": sorted_todos}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.post("/api/todos")
|
|
61
|
+
def add_todo():
|
|
62
|
+
global next_id
|
|
63
|
+
payload = request.get_json(silent=True) or {}
|
|
64
|
+
title = str(payload.get("title", "")).strip()
|
|
65
|
+
if not title:
|
|
66
|
+
return {"error": "title is required"}, 400
|
|
67
|
+
todo = {
|
|
68
|
+
"id": next_id,
|
|
69
|
+
"title": title,
|
|
70
|
+
"done": False,
|
|
71
|
+
"updatedAt": now_ms(),
|
|
72
|
+
}
|
|
73
|
+
next_id += 1
|
|
74
|
+
todos.append(todo)
|
|
75
|
+
try:
|
|
76
|
+
save_todos()
|
|
77
|
+
except Exception:
|
|
78
|
+
return {"error": "failed to persist todos"}, 500
|
|
79
|
+
return todo
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.put("/api/todos/<int:todo_id>/toggle")
|
|
83
|
+
def toggle_todo(todo_id):
|
|
84
|
+
for todo in todos:
|
|
85
|
+
if todo["id"] == todo_id:
|
|
86
|
+
todo["done"] = not todo["done"]
|
|
87
|
+
todo["updatedAt"] = now_ms()
|
|
88
|
+
try:
|
|
89
|
+
save_todos()
|
|
90
|
+
except Exception:
|
|
91
|
+
return {"error": "failed to persist todos"}, 500
|
|
92
|
+
return todo
|
|
93
|
+
return {"error": "todo not found"}, 404
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.delete("/api/todos/<int:todo_id>")
|
|
97
|
+
def delete_todo(todo_id):
|
|
98
|
+
for index, todo in enumerate(todos):
|
|
99
|
+
if todo["id"] == todo_id:
|
|
100
|
+
todos.pop(index)
|
|
101
|
+
try:
|
|
102
|
+
save_todos()
|
|
103
|
+
except Exception:
|
|
104
|
+
return {"error": "failed to persist todos"}, 500
|
|
105
|
+
return {"ok": True}
|
|
106
|
+
return {"error": "todo not found"}, 404
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == '__main__':
|
|
110
|
+
app.run(host='0.0.0.0', port=3000)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# manifest: 指定 lpk 的 manifest 文件
|
|
2
|
+
manifest: ./lzc-manifest.yml
|
|
3
|
+
|
|
4
|
+
# pkgout: lpk 输出目录
|
|
5
|
+
pkgout: ./
|
|
6
|
+
|
|
7
|
+
# icon: 应用图标(png)
|
|
8
|
+
icon: ./lzc-icon.png
|
|
9
|
+
|
|
10
|
+
# images: 构建容器镜像并写入 lpk v2 的 images 目录
|
|
11
|
+
images:
|
|
12
|
+
app-runtime:
|
|
13
|
+
# context: Docker 构建上下文目录
|
|
14
|
+
context: ./
|
|
15
|
+
# dockerfile: 镜像构建文件
|
|
16
|
+
dockerfile: ./Dockerfile
|
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function renderDevPage(port) {
|
|
2
|
+
const title = "Python dev service is not ready";
|
|
3
|
+
const subtitle = "This app starts the Python dev service automatically in dev mode. If the page still shows this guide, the process is still starting or needs attention.";
|
|
4
|
+
const pageTitle = "Backend Dev";
|
|
5
|
+
const steps = [
|
|
6
|
+
"Sync your latest code into the container with <code>lzc-cli project sync --watch</code> or copy it with <code>lzc-cli project cp</code>.",
|
|
7
|
+
"Check the service log with <code>lzc-cli project log -f</code>.",
|
|
8
|
+
"If you need to restart the process manually, open <code>lzc-cli project exec /bin/sh</code> and run <code>/app/run.sh</code>.",
|
|
9
|
+
"Refresh this page after port <code>" + String(port) + "</code> is ready.",
|
|
10
|
+
];
|
|
11
|
+
const portText = port === undefined || port === null ? "" : String(port);
|
|
12
|
+
const items = steps.map(function (step) {
|
|
13
|
+
return "<li>" + step + "</li>";
|
|
14
|
+
}).join("");
|
|
15
|
+
const portBadge = portText ? "<div class=\"pill\">Expected local port: " + portText + "</div>" : "";
|
|
16
|
+
return [
|
|
17
|
+
"<!doctype html>",
|
|
18
|
+
"<html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>", pageTitle, "</title><style>",
|
|
19
|
+
"body{margin:0;padding:32px;font-family:ui-sans-serif,system-ui,sans-serif;background:#f5f8fc;color:#112033}",
|
|
20
|
+
".card{max-width:760px;margin:0 auto;background:#fff;border:1px solid #d9e4f2;border-radius:18px;padding:24px;box-shadow:0 16px 40px rgba(17,32,51,.08)}",
|
|
21
|
+
"h1{margin:0 0 12px;font-size:32px}.muted{color:#51627a}.pill{display:inline-block;margin:16px 0 0;padding:6px 10px;border-radius:999px;background:#eef5ff;color:#0c5dd6;font-weight:600}",
|
|
22
|
+
"ol{margin:18px 0 0;padding-left:20px;line-height:1.8}code{background:#f2f6fb;border-radius:8px;padding:2px 6px}button{margin-top:20px;border:0;border-radius:10px;padding:10px 14px;background:#112033;color:#fff;cursor:pointer}",
|
|
23
|
+
"</style></head><body><main class=\"card\"><h1>", title, "</h1><p class=\"muted\">", subtitle, "</p>", portBadge, "<ol>", items, "</ol><button onclick=\"location.reload()\">Refresh</button></main></body></html>",
|
|
24
|
+
].join("");
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flask==3.0.3
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
mirror_root="/lzcapp/cache/project-mirror"
|
|
5
|
+
app_file="/app/app.py"
|
|
6
|
+
if [ -f "$mirror_root/app.py" ]; then
|
|
7
|
+
app_file="$mirror_root/app.py"
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
if [ "${LZC_PY_DEV:-}" = "1" ]; then
|
|
11
|
+
exec python3 -m flask --app "$app_file" run --debug --host 0.0.0.0 --port 3000
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
exec python3 "$app_file"
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Lazycat Python Todo Template</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg-1: #f8fbff;
|
|
10
|
+
--bg-2: #ecf6ff;
|
|
11
|
+
--card: #ffffff;
|
|
12
|
+
--text: #142033;
|
|
13
|
+
--muted: #4e617b;
|
|
14
|
+
--line: #d8e4f2;
|
|
15
|
+
--brand: #0076d6;
|
|
16
|
+
--brand-2: #00a5a5;
|
|
17
|
+
}
|
|
18
|
+
* { box-sizing: border-box; }
|
|
19
|
+
body {
|
|
20
|
+
margin: 0;
|
|
21
|
+
min-height: 100vh;
|
|
22
|
+
display: grid;
|
|
23
|
+
place-items: center;
|
|
24
|
+
padding: 24px;
|
|
25
|
+
font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
|
|
26
|
+
background: radial-gradient(circle at top right, var(--bg-2), var(--bg-1) 42%);
|
|
27
|
+
color: var(--text);
|
|
28
|
+
}
|
|
29
|
+
.card {
|
|
30
|
+
width: min(920px, 100%);
|
|
31
|
+
background: var(--card);
|
|
32
|
+
border: 1px solid var(--line);
|
|
33
|
+
border-radius: 22px;
|
|
34
|
+
padding: 26px;
|
|
35
|
+
box-shadow: 0 18px 48px rgba(20, 32, 51, 0.08);
|
|
36
|
+
}
|
|
37
|
+
.tag {
|
|
38
|
+
margin: 0;
|
|
39
|
+
color: var(--brand);
|
|
40
|
+
font-weight: 700;
|
|
41
|
+
letter-spacing: .08em;
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
font-size: .85rem;
|
|
44
|
+
}
|
|
45
|
+
h1 { margin: 10px 0 12px; font-size: clamp(1.8rem, 3.2vw, 2.7rem); }
|
|
46
|
+
p { margin: 0; color: var(--muted); }
|
|
47
|
+
.btns {
|
|
48
|
+
margin-top: 18px;
|
|
49
|
+
display: flex;
|
|
50
|
+
gap: 10px;
|
|
51
|
+
flex-wrap: wrap;
|
|
52
|
+
}
|
|
53
|
+
a {
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
text-decoration: none;
|
|
58
|
+
border-radius: 10px;
|
|
59
|
+
padding: 10px 14px;
|
|
60
|
+
border: 1px solid var(--line);
|
|
61
|
+
color: var(--text);
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
}
|
|
64
|
+
a.primary {
|
|
65
|
+
color: #fff;
|
|
66
|
+
border-color: transparent;
|
|
67
|
+
background: linear-gradient(135deg, var(--brand), var(--brand-2));
|
|
68
|
+
}
|
|
69
|
+
.panel {
|
|
70
|
+
margin-top: 22px;
|
|
71
|
+
border: 1px solid var(--line);
|
|
72
|
+
border-radius: 14px;
|
|
73
|
+
padding: 14px;
|
|
74
|
+
background: #fbfdff;
|
|
75
|
+
}
|
|
76
|
+
.sync-note {
|
|
77
|
+
margin-top: 8px;
|
|
78
|
+
font-size: .92rem;
|
|
79
|
+
}
|
|
80
|
+
.form {
|
|
81
|
+
margin-top: 12px;
|
|
82
|
+
display: flex;
|
|
83
|
+
gap: 10px;
|
|
84
|
+
}
|
|
85
|
+
.form input {
|
|
86
|
+
flex: 1;
|
|
87
|
+
border: 1px solid var(--line);
|
|
88
|
+
border-radius: 10px;
|
|
89
|
+
padding: 10px 12px;
|
|
90
|
+
min-width: 0;
|
|
91
|
+
}
|
|
92
|
+
button {
|
|
93
|
+
border: 1px solid var(--line);
|
|
94
|
+
border-radius: 10px;
|
|
95
|
+
padding: 9px 12px;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
background: #fff;
|
|
98
|
+
}
|
|
99
|
+
ul {
|
|
100
|
+
margin: 12px 0 0;
|
|
101
|
+
padding: 0;
|
|
102
|
+
list-style: none;
|
|
103
|
+
display: grid;
|
|
104
|
+
gap: 10px;
|
|
105
|
+
}
|
|
106
|
+
li {
|
|
107
|
+
border: 1px solid var(--line);
|
|
108
|
+
border-radius: 10px;
|
|
109
|
+
padding: 10px 12px;
|
|
110
|
+
display: flex;
|
|
111
|
+
justify-content: space-between;
|
|
112
|
+
align-items: center;
|
|
113
|
+
gap: 8px;
|
|
114
|
+
background: #fff;
|
|
115
|
+
}
|
|
116
|
+
label {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 8px;
|
|
120
|
+
}
|
|
121
|
+
.done {
|
|
122
|
+
text-decoration: line-through;
|
|
123
|
+
color: var(--muted);
|
|
124
|
+
}
|
|
125
|
+
.danger {
|
|
126
|
+
color: #a63e37;
|
|
127
|
+
}
|
|
128
|
+
@media (max-width: 640px) {
|
|
129
|
+
.form {
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
}
|
|
132
|
+
li {
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
align-items: flex-start;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
</style>
|
|
138
|
+
</head>
|
|
139
|
+
<body>
|
|
140
|
+
<main class="card">
|
|
141
|
+
<p class="tag">Python Version</p>
|
|
142
|
+
<h1>Todo demo powered by Flask API</h1>
|
|
143
|
+
<p>
|
|
144
|
+
This template includes a Python backend and a simple Todo app to help you start fast.
|
|
145
|
+
</p>
|
|
146
|
+
|
|
147
|
+
<div class="btns">
|
|
148
|
+
<a class="primary" href="https://lazycat.cloud/" target="_blank" rel="noreferrer">Visit lazycat.cloud</a>
|
|
149
|
+
<a href="https://developer.lazycat.cloud/" target="_blank" rel="noreferrer">Open Developer Docs</a>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<section class="panel">
|
|
153
|
+
<strong>Todo quick demo</strong>
|
|
154
|
+
<p class="sync-note">Data syncs automatically across devices when opened with the same app account.</p>
|
|
155
|
+
<div class="form">
|
|
156
|
+
<input id="todoInput" placeholder="Add a task for your app kickoff" />
|
|
157
|
+
<button id="addBtn" type="button">Add</button>
|
|
158
|
+
</div>
|
|
159
|
+
<ul id="todoList"></ul>
|
|
160
|
+
</section>
|
|
161
|
+
</main>
|
|
162
|
+
<script>
|
|
163
|
+
const todoInput = document.getElementById('todoInput');
|
|
164
|
+
const addBtn = document.getElementById('addBtn');
|
|
165
|
+
const todoList = document.getElementById('todoList');
|
|
166
|
+
|
|
167
|
+
async function fetchTodos() {
|
|
168
|
+
const resp = await fetch('/api/todos');
|
|
169
|
+
const data = await resp.json();
|
|
170
|
+
return Array.isArray(data.items) ? data.items : [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function renderTodos(items) {
|
|
174
|
+
todoList.innerHTML = '';
|
|
175
|
+
for (const todo of items) {
|
|
176
|
+
const li = document.createElement('li');
|
|
177
|
+
|
|
178
|
+
const label = document.createElement('label');
|
|
179
|
+
const checkbox = document.createElement('input');
|
|
180
|
+
checkbox.type = 'checkbox';
|
|
181
|
+
checkbox.checked = !!todo.done;
|
|
182
|
+
checkbox.addEventListener('change', async () => {
|
|
183
|
+
await fetch(`/api/todos/${todo.id}/toggle`, { method: 'PUT' });
|
|
184
|
+
await refreshTodos();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const span = document.createElement('span');
|
|
188
|
+
span.textContent = todo.title;
|
|
189
|
+
if (todo.done) {
|
|
190
|
+
span.className = 'done';
|
|
191
|
+
}
|
|
192
|
+
label.append(checkbox, span);
|
|
193
|
+
|
|
194
|
+
const delBtn = document.createElement('button');
|
|
195
|
+
delBtn.type = 'button';
|
|
196
|
+
delBtn.className = 'danger';
|
|
197
|
+
delBtn.textContent = 'Delete';
|
|
198
|
+
delBtn.addEventListener('click', async () => {
|
|
199
|
+
await fetch(`/api/todos/${todo.id}`, { method: 'DELETE' });
|
|
200
|
+
await refreshTodos();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
li.append(label, delBtn);
|
|
204
|
+
todoList.appendChild(li);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function refreshTodos() {
|
|
209
|
+
const items = await fetchTodos();
|
|
210
|
+
renderTodos(items);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function addTodo() {
|
|
214
|
+
const title = todoInput.value.trim();
|
|
215
|
+
if (!title) return;
|
|
216
|
+
await fetch('/api/todos', {
|
|
217
|
+
method: 'POST',
|
|
218
|
+
headers: { 'Content-Type': 'application/json' },
|
|
219
|
+
body: JSON.stringify({ title }),
|
|
220
|
+
});
|
|
221
|
+
todoInput.value = '';
|
|
222
|
+
await refreshTodos();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
addBtn.addEventListener('click', addTodo);
|
|
226
|
+
todoInput.addEventListener('keyup', async (event) => {
|
|
227
|
+
if (event.key === 'Enter') {
|
|
228
|
+
await addTodo();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
refreshTodos();
|
|
233
|
+
setInterval(refreshTodos, 2500);
|
|
234
|
+
document.addEventListener('visibilitychange', refreshTodos);
|
|
235
|
+
window.addEventListener('focus', refreshTodos);
|
|
236
|
+
</script>
|
|
237
|
+
</body>
|
|
238
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3 AS builder
|
|
2
|
+
|
|
3
|
+
RUN apk add --no-cache openjdk17 maven
|
|
4
|
+
WORKDIR /workspace
|
|
5
|
+
|
|
6
|
+
COPY pom.xml ./
|
|
7
|
+
COPY src ./src
|
|
8
|
+
RUN mvn -DskipTests package
|
|
9
|
+
|
|
10
|
+
FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3
|
|
11
|
+
|
|
12
|
+
RUN apk add --no-cache openjdk17-jre-headless
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
|
|
15
|
+
COPY --from=builder /workspace/target/*.jar /app/app.jar
|
|
16
|
+
COPY run.sh /app/run.sh
|
|
17
|
+
RUN chmod +x /app/run.sh
|
|
18
|
+
|
|
19
|
+
EXPOSE 8080
|
|
20
|
+
CMD ["/app/run.sh"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3 AS builder
|
|
2
|
+
|
|
3
|
+
RUN apk add --no-cache openjdk17 maven
|
|
4
|
+
WORKDIR /workspace
|
|
5
|
+
|
|
6
|
+
COPY pom.xml ./
|
|
7
|
+
COPY src ./src
|
|
8
|
+
RUN mvn -DskipTests package
|
|
9
|
+
|
|
10
|
+
FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3
|
|
11
|
+
|
|
12
|
+
RUN apk add --no-cache openjdk17 maven
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
|
|
15
|
+
COPY --from=builder /workspace/target/*.jar /app/app.jar
|
|
16
|
+
COPY run.sh /app/run.sh
|
|
17
|
+
RUN chmod +x /app/run.sh
|
|
18
|
+
|
|
19
|
+
EXPOSE 8080
|
|
20
|
+
CMD ["sh", "-lc", "trap : TERM INT; sleep infinity & wait"]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Lazycat Java Spring Boot Todo App
|
|
2
|
+
|
|
3
|
+
## First Deploy
|
|
4
|
+
```bash
|
|
5
|
+
lzc-cli project deploy
|
|
6
|
+
lzc-cli project info
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
By default, project commands use `lzc-build.dev.yml` when it exists.
|
|
10
|
+
Each command prints the active `Build config`.
|
|
11
|
+
Use `--release` if you want to operate on `lzc-build.yml`.
|
|
12
|
+
|
|
13
|
+
Open the app first.
|
|
14
|
+
If the backend dev service is not ready yet, the app page will show the expected port and what to do next.
|
|
15
|
+
In dev mode, the Spring Boot service is started manually inside the app container.
|
|
16
|
+
|
|
17
|
+
## Recommended Backend Dev Loop
|
|
18
|
+
```bash
|
|
19
|
+
lzc-cli project sync --watch
|
|
20
|
+
lzc-cli project exec /bin/sh
|
|
21
|
+
/app/run.sh
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
You can also build locally and use `lzc-cli project cp` instead of `project sync --watch`.
|
|
25
|
+
|
|
26
|
+
## Troubleshooting
|
|
27
|
+
```bash
|
|
28
|
+
lzc-cli project log -f
|
|
29
|
+
lzc-cli project exec /bin/sh
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
```text
|
|
34
|
+
GET /api/health
|
|
35
|
+
GET /api/todos
|
|
36
|
+
POST /api/todos
|
|
37
|
+
PUT /api/todos/{id}/toggle
|
|
38
|
+
DELETE /api/todos/{id}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Data Path
|
|
42
|
+
```text
|
|
43
|
+
/lzcapp/var/todos.json
|
|
44
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.git
|
|
2
|
+
.git/**
|
|
3
|
+
node_modules
|
|
4
|
+
node_modules/**
|
|
5
|
+
.venv
|
|
6
|
+
.venv/**
|
|
7
|
+
dist
|
|
8
|
+
dist/**
|
|
9
|
+
build
|
|
10
|
+
build/**
|
|
11
|
+
__pycache__
|
|
12
|
+
__pycache__/**
|
|
13
|
+
.idea
|
|
14
|
+
.idea/**
|
|
15
|
+
.vscode
|
|
16
|
+
.vscode/**
|
|
17
|
+
.DS_Store
|
|
18
|
+
.lzc-cli-*
|
|
19
|
+
.lzc-cli-*/**
|
|
20
|
+
*.lpk
|
|
21
|
+
*.lpk.tar
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# manifest: 指定 lpk 的 manifest 文件
|
|
2
|
+
manifest: ./lzc-manifest.yml
|
|
3
|
+
|
|
4
|
+
# pkgout: lpk 输出目录
|
|
5
|
+
pkgout: ./
|
|
6
|
+
|
|
7
|
+
# icon: 应用图标(png)
|
|
8
|
+
icon: ./lzc-icon.png
|
|
9
|
+
|
|
10
|
+
# images: 构建容器镜像并写入 lpk v2 的 images 目录
|
|
11
|
+
images:
|
|
12
|
+
app-runtime:
|
|
13
|
+
# context: Docker 构建上下文目录
|
|
14
|
+
context: ./
|
|
15
|
+
# dockerfile: 镜像构建文件
|
|
16
|
+
dockerfile: ./Dockerfile
|
|
Binary file
|