From 18929bf203de34fce3b3c67b03e64ca9bc791557 Mon Sep 17 00:00:00 2001 From: "huxiaoyu.612" Date: Thu, 4 Sep 2025 21:14:22 +0800 Subject: [PATCH 1/2] feat: take web project as template,init command support template type --- veadk/cli/cli_init.py | 23 +- .../ve_faas/template/web/.dockerignore | 9 - .../ve_faas/template/web/.gitignore | 207 ---------- .../ve_faas/template/web/Dockerfile | 23 -- .../integrations/ve_faas/template/web/app.py | 121 ------ .../ve_faas/template/web/init_db.py | 46 --- .../ve_faas/template/web/models.py | 36 -- .../ve_faas/template/web/requirements.txt | 4 - .../integrations/ve_faas/template/web/run.sh | 11 - .../ve_faas/template/web/static/css/style.css | 368 ------------------ .../ve_faas/template/web/static/js/admin.js | 0 .../web/templates/admin/dashboard.html | 21 - .../web/templates/admin/edit_post.html | 24 -- .../template/web/templates/admin/login.html | 21 - .../template/web/templates/admin/posts.html | 53 --- .../ve_faas/template/web/templates/base.html | 45 --- .../ve_faas/template/web/templates/index.html | 29 -- .../ve_faas/template/web/templates/post.html | 14 - .../ve_faas/web_template/cookiecutter.json | 17 + 19 files changed, 35 insertions(+), 1037 deletions(-) delete mode 100644 veadk/integrations/ve_faas/template/web/.dockerignore delete mode 100644 veadk/integrations/ve_faas/template/web/.gitignore delete mode 100644 veadk/integrations/ve_faas/template/web/Dockerfile delete mode 100644 veadk/integrations/ve_faas/template/web/app.py delete mode 100644 veadk/integrations/ve_faas/template/web/init_db.py delete mode 100644 veadk/integrations/ve_faas/template/web/models.py delete mode 100644 veadk/integrations/ve_faas/template/web/requirements.txt delete mode 100755 veadk/integrations/ve_faas/template/web/run.sh delete mode 100644 veadk/integrations/ve_faas/template/web/static/css/style.css delete mode 100644 veadk/integrations/ve_faas/template/web/static/js/admin.js delete mode 100644 veadk/integrations/ve_faas/template/web/templates/admin/dashboard.html delete mode 100644 veadk/integrations/ve_faas/template/web/templates/admin/edit_post.html delete mode 100644 veadk/integrations/ve_faas/template/web/templates/admin/login.html delete mode 100644 veadk/integrations/ve_faas/template/web/templates/admin/posts.html delete mode 100644 veadk/integrations/ve_faas/template/web/templates/base.html delete mode 100644 veadk/integrations/ve_faas/template/web/templates/index.html delete mode 100644 veadk/integrations/ve_faas/template/web/templates/post.html create mode 100644 veadk/integrations/ve_faas/web_template/cookiecutter.json diff --git a/veadk/cli/cli_init.py b/veadk/cli/cli_init.py index e320badb..74c12d50 100644 --- a/veadk/cli/cli_init.py +++ b/veadk/cli/cli_init.py @@ -64,7 +64,12 @@ def _render_prompts() -> dict[str, Any]: @click.command() -def init() -> None: +@click.option( + "--vefaas-template-type", default="template", help="Expected template type" +) +def init( + vefaas_template_type: str, +) -> None: """Init a veadk project that can be deployed to Volcengine VeFaaS.""" import shutil from pathlib import Path @@ -73,9 +78,14 @@ def init() -> None: import veadk.integrations.ve_faas as vefaas - click.echo( - "Welcome use VeADK to create your project. We will generate a `weather-reporter` application for you." - ) + if vefaas_template_type == "web_template": + click.echo( + "Welcome use VeADK to create your project. We will generate a `simple-blog` web application for you." + ) + else: + click.echo( + "Welcome use VeADK to create your project. We will generate a `weather-reporter` application for you." + ) cwd = Path.cwd() local_dir_name = click.prompt("Local directory name", default="veadk-cloud-proj") @@ -91,7 +101,10 @@ def init() -> None: settings = _render_prompts() settings["local_dir_name"] = local_dir_name - template_dir_path = Path(vefaas.__file__).parent / "template" + if not vefaas_template_type: + vefaas_template_type = "template" + + template_dir_path = Path(vefaas.__file__).parent / vefaas_template_type cookiecutter( template=str(template_dir_path), diff --git a/veadk/integrations/ve_faas/template/web/.dockerignore b/veadk/integrations/ve_faas/template/web/.dockerignore deleted file mode 100644 index 857af8cd..00000000 --- a/veadk/integrations/ve_faas/template/web/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -venv/ -__pycache__/ -*.pyc -*.pyo -*.pyd -.env -.git/ -.gitignore -*.log \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/.gitignore b/veadk/integrations/ve_faas/template/web/.gitignore deleted file mode 100644 index b7faf403..00000000 --- a/veadk/integrations/ve_faas/template/web/.gitignore +++ /dev/null @@ -1,207 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[codz] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py.cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock -#poetry.toml - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control -#pdm.lock -#pdm.toml -.pdm-python -.pdm-build/ - -# pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. -#pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.envrc -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# Abstra -# Abstra is an AI-powered process automation framework. -# Ignore directories containing user credentials, local state, and settings. -# Learn more at https://abstra.io/docs -.abstra/ - -# Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore -# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, -# you could uncomment the following to ignore the entire vscode folder -# .vscode/ - -# Ruff stuff: -.ruff_cache/ - -# PyPI configuration file -.pypirc - -# Cursor -# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to -# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data -# refer to https://docs.cursor.com/context/ignore-files -.cursorignore -.cursorindexingignore - -# Marimo -marimo/_static/ -marimo/_lsp/ -__marimo__/ diff --git a/veadk/integrations/ve_faas/template/web/Dockerfile b/veadk/integrations/ve_faas/template/web/Dockerfile deleted file mode 100644 index d3e6bf73..00000000 --- a/veadk/integrations/ve_faas/template/web/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# 使用官方Python镜像作为基础镜像 -FROM python:3.9-slim - -# 设置工作目录 -WORKDIR /app - -# 复制依赖文件并安装依赖 -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# 复制项目文件 -COPY . . - -# 设置环境变量 -ENV FLASK_APP=app.py -ENV FLASK_ENV=production -ENV PYTHONUNBUFFERED=1 - -# 暴露端口 -EXPOSE 5000 - -# 启动命令 -CMD ["bash", "run.sh"] diff --git a/veadk/integrations/ve_faas/template/web/app.py b/veadk/integrations/ve_faas/template/web/app.py deleted file mode 100644 index b0b7c3f9..00000000 --- a/veadk/integrations/ve_faas/template/web/app.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from flask import Flask, render_template, request, redirect, url_for, flash, session -from models import db, Post, User -from werkzeug.security import generate_password_hash, check_password_hash -import os - -app = Flask(__name__) -app.config['SECRET_KEY'] = 'your-secret-key-here' -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db' -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - -db.init_app(app) - -# 前台首页 -@app.route('/') -def index(): - page = request.args.get('page', 1, type=int) - posts = Post.query.order_by(Post.created_at.desc()).paginate( - page=page, per_page=5, error_out=False) - return render_template('index.html', posts=posts) - -# 文章详情页 -@app.route('/post/') -def post_detail(post_id): - post = Post.query.get_or_404(post_id) - return render_template('post.html', post=post) - -# 后台登录页 -@app.route('/admin/login', methods=['GET', 'POST']) -def admin_login(): - if request.method == 'POST': - username = request.form['username'] - password = request.form['password'] - - user = User.query.filter_by(username=username).first() - - if user and check_password_hash(user.password, password): - session['admin_logged_in'] = True - return redirect(url_for('admin_dashboard')) - else: - flash('用户名或密码错误') - - return render_template('admin/login.html') - -# 后台登出 -@app.route('/admin/logout') -def admin_logout(): - session.pop('admin_logged_in', None) - return redirect(url_for('admin_login')) - -# 后台管理面板 -@app.route('/admin/dashboard') -def admin_dashboard(): - if not session.get('admin_logged_in'): - return redirect(url_for('admin_login')) - - post_count = Post.query.count() - return render_template('admin/dashboard.html', post_count=post_count) - -# 文章管理 -@app.route('/admin/posts') -def admin_posts(): - if not session.get('admin_logged_in'): - return redirect(url_for('admin_login')) - - page = request.args.get('page', 1, type=int) - posts = Post.query.order_by(Post.created_at.desc()).paginate( - page=page, per_page=10, error_out=False) - return render_template('admin/posts.html', posts=posts) - -# 创建/编辑文章 -@app.route('/admin/post', methods=['GET', 'POST']) -@app.route('/admin/post/', methods=['GET', 'POST']) -def admin_edit_post(post_id=None): - if not session.get('admin_logged_in'): - return redirect(url_for('admin_login')) - - if post_id: - post = Post.query.get_or_404(post_id) - else: - post = Post() - - if request.method == 'POST': - post.title = request.form['title'] - post.content = request.form['content'] - - if post_id is None: - db.session.add(post) - db.session.commit() - flash('文章保存成功') - return redirect(url_for('admin_posts')) - - return render_template('admin/edit_post.html', post=post) - -# 删除文章 -@app.route('/admin/post/delete/', methods=['POST']) -def admin_delete_post(post_id): - if not session.get('admin_logged_in'): - return redirect(url_for('admin_login')) - - post = Post.query.get_or_404(post_id) - db.session.delete(post) - db.session.commit() - flash('文章删除成功') - return redirect(url_for('admin_posts')) - -if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/init_db.py b/veadk/integrations/ve_faas/template/web/init_db.py deleted file mode 100644 index 5c3b8574..00000000 --- a/veadk/integrations/ve_faas/template/web/init_db.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from app import app, db -from models import User -from werkzeug.security import generate_password_hash -from sqlalchemy.exc import OperationalError - -def init_database(): - with app.app_context(): - try: - # 创建所有数据库表 - db.metadata.create_all(bind=db.engine, checkfirst=True) - print("数据库表创建成功") - except OperationalError as e: - if "table already exists" in str(e).lower(): - print("数据库表已存在,跳过创建") - else: - print(f"创建数据库表时出错: {e}") - raise - - # 创建默认管理员账户(如不存在) - if not User.query.filter_by(username='admin').first(): - admin = User( - username='admin', - password=generate_password_hash('admin123') - ) - db.session.add(admin) - db.session.commit() - print("默认管理员账户创建成功") - else: - print("默认管理员账户已存在,跳过创建") - -if __name__ == '__main__': - init_database() \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/models.py b/veadk/integrations/ve_faas/template/web/models.py deleted file mode 100644 index 8c110921..00000000 --- a/veadk/integrations/ve_faas/template/web/models.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from flask_sqlalchemy import SQLAlchemy -from datetime import datetime - -db = SQLAlchemy() - -class Post(db.Model): - id = db.Column(db.Integer, primary_key=True) - title = db.Column(db.String(200), nullable=False) - content = db.Column(db.Text, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow) - updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - def __repr__(self): - return f'' - -class User(db.Model): - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(80), unique=True, nullable=False) - password = db.Column(db.String(200), nullable=False) - - def __repr__(self): - return f'' \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/requirements.txt b/veadk/integrations/ve_faas/template/web/requirements.txt deleted file mode 100644 index f9326018..00000000 --- a/veadk/integrations/ve_faas/template/web/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -Flask==2.3.2 -Flask-SQLAlchemy==3.0.5 -Werkzeug==2.3.6 -gunicorn==20.1.0 \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/run.sh b/veadk/integrations/ve_faas/template/web/run.sh deleted file mode 100755 index 3f312ba1..00000000 --- a/veadk/integrations/ve_faas/template/web/run.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# 设置环境变量 -export FLASK_APP=app.py -export FLASK_ENV=production -# 初始化数据库 -python init_db.py - -echo "Starting Flask application..." -# 启动应用,使用生产服务器配置 -exec gunicorn -w 4 -b 0.0.0.0:5000 app:app \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/static/css/style.css b/veadk/integrations/ve_faas/template/web/static/css/style.css deleted file mode 100644 index 3cc9eb71..00000000 --- a/veadk/integrations/ve_faas/template/web/static/css/style.css +++ /dev/null @@ -1,368 +0,0 @@ -/* 基础样式 */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - line-height: 1.6; - color: #333; - background-color: #f8f9fa; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 0 20px; -} - -/* 头部 */ -header { - background-color: #fff; - box-shadow: 0 2px 5px rgba(0,0,0,0.1); - padding: 1rem 0; -} - -header h1 a { - text-decoration: none; - color: #2c3e50; -} - -header nav { - float: right; - margin-top: 10px; -} - -header nav a { - margin-left: 20px; - text-decoration: none; - color: #3498db; -} - -header nav a:hover { - text-decoration: underline; -} - -/* 主体内容 */ -main { - padding: 2rem 0; -} - -/* 消息提示 */ -.flash-messages { - margin-bottom: 20px; -} - -.flash-message { - padding: 10px; - background-color: #d4edda; - border: 1px solid #c3e6cb; - border-radius: 4px; - color: #155724; -} - -/* 文章列表 */ -.post-preview { - background: white; - margin-bottom: 20px; - padding: 20px; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); -} - -.post-preview h2 a { - text-decoration: none; - color: #2c3e50; -} - -.post-preview h2 a:hover { - color: #3498db; -} - -.post-meta { - color: #7f8c8d; - font-size: 0.9em; - margin: 10px 0; -} - -.read-more { - display: inline-block; - margin-top: 10px; - color: #3498db; - text-decoration: none; -} - -.read-more:hover { - text-decoration: underline; -} - -/* 文章详情 */ -.post-detail { - background: white; - padding: 30px; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); -} - -.post-detail h1 { - margin-bottom: 10px; -} - -.post-content { - margin: 20px 0; - white-space: pre-wrap; -} - -.back-link { - display: inline-block; - margin-top: 20px; - color: #3498db; - text-decoration: none; -} - -/* 分页 */ -.pagination { - text-align: center; - margin: 30px 0; -} - -.pagination a { - display: inline-block; - padding: 8px 16px; - margin: 0 5px; - text-decoration: none; - background-color: #3498db; - color: white; - border-radius: 4px; -} - -.pagination a:hover { - background-color: #2980b9; -} - -.pagination span { - display: inline-block; - padding: 8px 16px; - margin: 0 5px; -} - -/* 登录表单 */ -.login-form { - max-width: 400px; - margin: 50px auto; - background: white; - padding: 30px; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); -} - -.login-form h2 { - text-align: center; - margin-bottom: 20px; -} - -.form-group { - margin-bottom: 20px; -} - -.form-group label { - display: block; - margin-bottom: 5px; - font-weight: bold; -} - -.form-group input { - width: 100%; - padding: 10px; - border: 1px solid #ddd; - border-radius: 4px; - font-size: 16px; -} - -.form-group input:focus { - border-color: #3498db; - outline: none; -} - -button, .btn { - display: inline-block; - padding: 10px 20px; - background-color: #3498db; - color: white; - text-decoration: none; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 16px; -} - -button:hover, .btn:hover { - background-color: #2980b9; -} - -.hint { - margin-top: 20px; - text-align: center; - color: #7f8c8d; - font-size: 0.9em; -} - -/* 管理面板 */ -.admin-dashboard { - background: white; - padding: 30px; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); -} - -.stats { - display: flex; - margin: 30px 0; -} - -.stat-card { - flex: 1; - text-align: center; - padding: 20px; - background-color: #f1f8ff; - border-radius: 5px; - margin: 0 10px; -} - -.stat-number { - font-size: 2em; - font-weight: bold; - color: #3498db; -} - -.admin-links { - text-align: center; - margin-top: 30px; -} - -.admin-links .btn { - margin: 0 10px; -} - -/* 文章管理 */ -.admin-posts { - background: white; - padding: 30px; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); -} - -.admin-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; -} - -.posts-table { - width: 100%; - border-collapse: collapse; -} - -.posts-table th, -.posts-table td { - padding: 12px; - text-align: left; - border-bottom: 1px solid #ddd; -} - -.posts-table th { - background-color: #f8f9fa; - font-weight: bold; -} - -.posts-table td a { - color: #3498db; - text-decoration: none; - margin-right: 10px; -} - -.posts-table td a:hover { - text-decoration: underline; -} - -.delete-btn { - background: none; - border: none; - color: #e74c3c; - cursor: pointer; - padding: 0; - font-size: 1em; -} - -.delete-btn:hover { - text-decoration: underline; -} - -/* 编辑文章 */ -.edit-post { - background: white; - padding: 30px; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); -} - -.edit-post textarea { - width: 100%; - padding: 10px; - border: 1px solid #ddd; - border-radius: 4px; - font-family: inherit; - font-size: 16px; - resize: vertical; -} - -.form-actions { - margin-top: 20px; -} - -.btn-cancel { - background-color: #95a5a6; - margin-left: 10px; -} - -.btn-cancel:hover { - background-color: #7f8c8d; -} - -/* 页脚 */ -footer { - background-color: #2c3e50; - color: white; - text-align: center; - padding: 20px 0; - margin-top: 40px; -} - -/* 响应式设计 */ -@media (max-width: 768px) { - header nav { - float: none; - text-align: center; - margin-top: 10px; - } - - .admin-header { - flex-direction: column; - align-items: flex-start; - } - - .admin-header .btn { - margin-top: 10px; - } - - .stats { - flex-direction: column; - } - - .stat-card { - margin: 10px 0; - } -} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/static/js/admin.js b/veadk/integrations/ve_faas/template/web/static/js/admin.js deleted file mode 100644 index e69de29b..00000000 diff --git a/veadk/integrations/ve_faas/template/web/templates/admin/dashboard.html b/veadk/integrations/ve_faas/template/web/templates/admin/dashboard.html deleted file mode 100644 index d8b3b5c4..00000000 --- a/veadk/integrations/ve_faas/template/web/templates/admin/dashboard.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "base.html" %} - -{% block title %}管理面板{% endblock %} - -{% block content %} -
-

管理面板

- -
-
-

文章总数

-

{{ post_count }}

-
-
- - -
-{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/templates/admin/edit_post.html b/veadk/integrations/ve_faas/template/web/templates/admin/edit_post.html deleted file mode 100644 index 3267008e..00000000 --- a/veadk/integrations/ve_faas/template/web/templates/admin/edit_post.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{% if post.id %}编辑文章{% else %}新建文章{% endif %}{% endblock %} - -{% block content %} -
-

{% if post.id %}编辑文章{% else %}新建文章{% endif %}

- -
-
- - -
-
- - -
-
- - 取消 -
-
-
-{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/templates/admin/login.html b/veadk/integrations/ve_faas/template/web/templates/admin/login.html deleted file mode 100644 index 2653459e..00000000 --- a/veadk/integrations/ve_faas/template/web/templates/admin/login.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "base.html" %} - -{% block title %}管理员登录{% endblock %} - -{% block content %} - -{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/templates/admin/posts.html b/veadk/integrations/ve_faas/template/web/templates/admin/posts.html deleted file mode 100644 index add16818..00000000 --- a/veadk/integrations/ve_faas/template/web/templates/admin/posts.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "base.html" %} - -{% block title %}文章管理{% endblock %} - -{% block content %} -
-
-

文章管理

- 新建文章 -
- - - - - - - - - - - {% for post in posts.items %} - - - - - - {% else %} - - - - {% endfor %} - -
标题发布时间操作
{{ post.title }}{{ post.created_at.strftime('%Y-%m-%d %H:%M') }} - 编辑 -
- -
-
暂无文章
- - - -
-{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/templates/base.html b/veadk/integrations/ve_faas/template/web/templates/base.html deleted file mode 100644 index 1884a85f..00000000 --- a/veadk/integrations/ve_faas/template/web/templates/base.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - {% block title %}博客系统{% endblock %} - - - -
-
-

我的博客

- -
-
- -
- {% with messages = get_flashed_messages() %} - {% if messages %} -
- {% for message in messages %} -
{{ message }}
- {% endfor %} -
- {% endif %} - {% endwith %} - - {% block content %}{% endblock %} -
- -
-
-

© 2023 我的博客. All rights reserved.

-
-
- - \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/templates/index.html b/veadk/integrations/ve_faas/template/web/templates/index.html deleted file mode 100644 index 4bfc4a18..00000000 --- a/veadk/integrations/ve_faas/template/web/templates/index.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
- {% for post in posts.items %} -
-

{{ post.title }}

- -

{{ post.content[:200] }}{% if post.content|length > 200 %}...{% endif %}

- 阅读全文 -
- {% else %} -

暂无文章。

- {% endfor %} -
- - - -{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/web/templates/post.html b/veadk/integrations/ve_faas/template/web/templates/post.html deleted file mode 100644 index 995093f1..00000000 --- a/veadk/integrations/ve_faas/template/web/templates/post.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ post.title }} - 博客系统{% endblock %} - -{% block content %} -
-

{{ post.title }}

- -
- {{ post.content|replace('\n', '
')|safe }} -
- « 返回首页 -
-{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/cookiecutter.json b/veadk/integrations/ve_faas/web_template/cookiecutter.json new file mode 100644 index 00000000..ef5e8f3a --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/cookiecutter.json @@ -0,0 +1,17 @@ +{ + "local_dir_name": "veadk_vefaas_web_proj", + "app_name": "simple-blog", + "vefaas_application_name": "simple-blog", + "veapig_instance_name": "", + "veapig_service_name": "", + "veapig_upstream_name": "", + "use_adk_web": false, + "veadk_version": "", + "_copy_without_render": [ + "*.html", + "*.css", + "*.js", + "static/**/*", + "templates/**/*" + ] +} \ No newline at end of file From d7a391de585f6c1664b554cd0b9f15dc7db9747b Mon Sep 17 00:00:00 2001 From: "huxiaoyu.612" Date: Thu, 4 Sep 2025 21:15:38 +0800 Subject: [PATCH 2/2] feat: take web project as template,init command support template type --- .../__init__.py | 13 + .../{{cookiecutter.local_dir_name}}/clean.py | 23 ++ .../config.yaml.example | 2 + .../{{cookiecutter.local_dir_name}}/deploy.py | 41 ++ .../src/.dockerignore | 9 + .../src/.gitignore | 207 ++++++++++ .../src/Dockerfile | 23 ++ .../src/app.py | 123 ++++++ .../src/init_db.py | 46 +++ .../src/models.py | 36 ++ .../src/requirements.txt | 4 + .../src/run.sh | 21 + .../src/static/css/style.css | 368 ++++++++++++++++++ .../src/static/js/admin.js | 0 .../src/templates/admin/dashboard.html | 21 + .../src/templates/admin/edit_post.html | 24 ++ .../src/templates/admin/login.html | 21 + .../src/templates/admin/posts.html | 53 +++ .../src/templates/base.html | 45 +++ .../src/templates/index.html | 29 ++ .../src/templates/post.html | 14 + 21 files changed, 1123 insertions(+) create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/.dockerignore create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/.gitignore create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt create mode 100755 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html create mode 100644 veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py new file mode 100644 index 00000000..7f463206 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py new file mode 100644 index 00000000..ceba3f39 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py @@ -0,0 +1,23 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from veadk.cloud.cloud_app import CloudApp + +def main() -> None: + cloud_app = CloudApp(vefaas_application_name="{{cookiecutter.vefaas_application_name}}") + cloud_app.delete_self() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example new file mode 100644 index 00000000..2911de20 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example @@ -0,0 +1,2 @@ +VOLCENGINE_ACCESS_KEY: +VOLCENGINE_SECRET_KEY: \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py new file mode 100644 index 00000000..5855cd71 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py @@ -0,0 +1,41 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +from pathlib import Path + +from veadk.cloud.cloud_agent_engine import CloudAgentEngine + +async def main(): + engine = CloudAgentEngine() + + cloud_app = engine.deploy( + path=str(Path(__file__).parent / "src"), + application_name="{{cookiecutter.vefaas_application_name}}", + gateway_name="{{cookiecutter.veapig_instance_name}}", + gateway_service_name="{{cookiecutter.veapig_service_name}}", + gateway_upstream_name="{{cookiecutter.veapig_upstream_name}}", + use_adk_web={{cookiecutter.use_adk_web}}, + local_test=False, # Set to True for local testing before deploy to VeFaaS + ) + print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") + + if {{cookiecutter.use_adk_web}}: + print(f"Web is running at: {cloud_app.vefaas_endpoint}") + else: + print(f"Web template does not support use_adk_web=False") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/.dockerignore b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/.dockerignore new file mode 100644 index 00000000..857af8cd --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/.dockerignore @@ -0,0 +1,9 @@ +venv/ +__pycache__/ +*.pyc +*.pyo +*.pyd +.env +.git/ +.gitignore +*.log \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/.gitignore b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/.gitignore new file mode 100644 index 00000000..b7faf403 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/.gitignore @@ -0,0 +1,207 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile new file mode 100644 index 00000000..21e0e190 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile @@ -0,0 +1,23 @@ +# 使用官方Python镜像作为基础镜像 +FROM python:3.9-slim + +# 设置工作目录 +WORKDIR /app + +# 复制依赖文件并安装依赖 +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# 复制项目文件 +COPY . . + +# 设置环境变量 +ENV FLASK_APP=app.py +ENV FLASK_ENV=production +ENV PYTHONUNBUFFERED=1 + +# 暴露端口 +EXPOSE 8000 + +# 启动命令 +CMD ["bash", "run.sh"] diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py new file mode 100644 index 00000000..45e4d865 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py @@ -0,0 +1,123 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from flask import Flask, render_template, request, redirect, url_for, flash, session +from models import db, Post, User +from werkzeug.security import generate_password_hash, check_password_hash +import os + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'your-secret-key-here' +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.instance_path = os.path.join("/tmp", "flask_instance") +os.makedirs(app.instance_path, exist_ok=True) + +db.init_app(app) + +# 前台首页 +@app.route('/') +def index(): + page = request.args.get('page', 1, type=int) + posts = Post.query.order_by(Post.created_at.desc()).paginate( + page=page, per_page=5, error_out=False) + return render_template('index.html', posts=posts) + +# 文章详情页 +@app.route('/post/') +def post_detail(post_id): + post = Post.query.get_or_404(post_id) + return render_template('post.html', post=post) + +# 后台登录页 +@app.route('/admin/login', methods=['GET', 'POST']) +def admin_login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + + user = User.query.filter_by(username=username).first() + + if user and check_password_hash(user.password, password): + session['admin_logged_in'] = True + return redirect(url_for('admin_dashboard')) + else: + flash('用户名或密码错误') + + return render_template('admin/login.html') + +# 后台登出 +@app.route('/admin/logout') +def admin_logout(): + session.pop('admin_logged_in', None) + return redirect(url_for('admin_login')) + +# 后台管理面板 +@app.route('/admin/dashboard') +def admin_dashboard(): + if not session.get('admin_logged_in'): + return redirect(url_for('admin_login')) + + post_count = Post.query.count() + return render_template('admin/dashboard.html', post_count=post_count) + +# 文章管理 +@app.route('/admin/posts') +def admin_posts(): + if not session.get('admin_logged_in'): + return redirect(url_for('admin_login')) + + page = request.args.get('page', 1, type=int) + posts = Post.query.order_by(Post.created_at.desc()).paginate( + page=page, per_page=10, error_out=False) + return render_template('admin/posts.html', posts=posts) + +# 创建/编辑文章 +@app.route('/admin/post', methods=['GET', 'POST']) +@app.route('/admin/post/', methods=['GET', 'POST']) +def admin_edit_post(post_id=None): + if not session.get('admin_logged_in'): + return redirect(url_for('admin_login')) + + if post_id: + post = Post.query.get_or_404(post_id) + else: + post = Post() + + if request.method == 'POST': + post.title = request.form['title'] + post.content = request.form['content'] + + if post_id is None: + db.session.add(post) + db.session.commit() + flash('文章保存成功') + return redirect(url_for('admin_posts')) + + return render_template('admin/edit_post.html', post=post) + +# 删除文章 +@app.route('/admin/post/delete/', methods=['POST']) +def admin_delete_post(post_id): + if not session.get('admin_logged_in'): + return redirect(url_for('admin_login')) + + post = Post.query.get_or_404(post_id) + db.session.delete(post) + db.session.commit() + flash('文章删除成功') + return redirect(url_for('admin_posts')) + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py new file mode 100644 index 00000000..5c3b8574 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py @@ -0,0 +1,46 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from app import app, db +from models import User +from werkzeug.security import generate_password_hash +from sqlalchemy.exc import OperationalError + +def init_database(): + with app.app_context(): + try: + # 创建所有数据库表 + db.metadata.create_all(bind=db.engine, checkfirst=True) + print("数据库表创建成功") + except OperationalError as e: + if "table already exists" in str(e).lower(): + print("数据库表已存在,跳过创建") + else: + print(f"创建数据库表时出错: {e}") + raise + + # 创建默认管理员账户(如不存在) + if not User.query.filter_by(username='admin').first(): + admin = User( + username='admin', + password=generate_password_hash('admin123') + ) + db.session.add(admin) + db.session.commit() + print("默认管理员账户创建成功") + else: + print("默认管理员账户已存在,跳过创建") + +if __name__ == '__main__': + init_database() \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py new file mode 100644 index 00000000..8c110921 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py @@ -0,0 +1,36 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from flask_sqlalchemy import SQLAlchemy +from datetime import datetime + +db = SQLAlchemy() + +class Post(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(200), nullable=False) + content = db.Column(db.Text, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + def __repr__(self): + return f'' + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password = db.Column(db.String(200), nullable=False) + + def __repr__(self): + return f'' \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt new file mode 100644 index 00000000..f9326018 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt @@ -0,0 +1,4 @@ +Flask==2.3.2 +Flask-SQLAlchemy==3.0.5 +Werkzeug==2.3.6 +gunicorn==20.1.0 \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh new file mode 100755 index 00000000..9c056834 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# 兼容源码部署到faas & 镜像部署到faas +pip install -r requirements.txt + +HOST="0.0.0.0" +PORT="${_FAAS_RUNTIME_PORT:-8000}" + +export SERVER_HOST=$HOST +export SERVER_PORT=$PORT + +# 设置环境变量 +export FLASK_APP=app.py +export FLASK_ENV=production + +# 初始化数据库 +python init_db.py + +echo "Starting Web application..." +# 启动应用,使用生产服务器配置 +exec python -m gunicorn -w 4 -b $SERVER_HOST:$SERVER_PORT app:app diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css new file mode 100644 index 00000000..3cc9eb71 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css @@ -0,0 +1,368 @@ +/* 基础样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: #333; + background-color: #f8f9fa; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* 头部 */ +header { + background-color: #fff; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); + padding: 1rem 0; +} + +header h1 a { + text-decoration: none; + color: #2c3e50; +} + +header nav { + float: right; + margin-top: 10px; +} + +header nav a { + margin-left: 20px; + text-decoration: none; + color: #3498db; +} + +header nav a:hover { + text-decoration: underline; +} + +/* 主体内容 */ +main { + padding: 2rem 0; +} + +/* 消息提示 */ +.flash-messages { + margin-bottom: 20px; +} + +.flash-message { + padding: 10px; + background-color: #d4edda; + border: 1px solid #c3e6cb; + border-radius: 4px; + color: #155724; +} + +/* 文章列表 */ +.post-preview { + background: white; + margin-bottom: 20px; + padding: 20px; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.post-preview h2 a { + text-decoration: none; + color: #2c3e50; +} + +.post-preview h2 a:hover { + color: #3498db; +} + +.post-meta { + color: #7f8c8d; + font-size: 0.9em; + margin: 10px 0; +} + +.read-more { + display: inline-block; + margin-top: 10px; + color: #3498db; + text-decoration: none; +} + +.read-more:hover { + text-decoration: underline; +} + +/* 文章详情 */ +.post-detail { + background: white; + padding: 30px; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.post-detail h1 { + margin-bottom: 10px; +} + +.post-content { + margin: 20px 0; + white-space: pre-wrap; +} + +.back-link { + display: inline-block; + margin-top: 20px; + color: #3498db; + text-decoration: none; +} + +/* 分页 */ +.pagination { + text-align: center; + margin: 30px 0; +} + +.pagination a { + display: inline-block; + padding: 8px 16px; + margin: 0 5px; + text-decoration: none; + background-color: #3498db; + color: white; + border-radius: 4px; +} + +.pagination a:hover { + background-color: #2980b9; +} + +.pagination span { + display: inline-block; + padding: 8px 16px; + margin: 0 5px; +} + +/* 登录表单 */ +.login-form { + max-width: 400px; + margin: 50px auto; + background: white; + padding: 30px; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.login-form h2 { + text-align: center; + margin-bottom: 20px; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: bold; +} + +.form-group input { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 16px; +} + +.form-group input:focus { + border-color: #3498db; + outline: none; +} + +button, .btn { + display: inline-block; + padding: 10px 20px; + background-color: #3498db; + color: white; + text-decoration: none; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} + +button:hover, .btn:hover { + background-color: #2980b9; +} + +.hint { + margin-top: 20px; + text-align: center; + color: #7f8c8d; + font-size: 0.9em; +} + +/* 管理面板 */ +.admin-dashboard { + background: white; + padding: 30px; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.stats { + display: flex; + margin: 30px 0; +} + +.stat-card { + flex: 1; + text-align: center; + padding: 20px; + background-color: #f1f8ff; + border-radius: 5px; + margin: 0 10px; +} + +.stat-number { + font-size: 2em; + font-weight: bold; + color: #3498db; +} + +.admin-links { + text-align: center; + margin-top: 30px; +} + +.admin-links .btn { + margin: 0 10px; +} + +/* 文章管理 */ +.admin-posts { + background: white; + padding: 30px; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.admin-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.posts-table { + width: 100%; + border-collapse: collapse; +} + +.posts-table th, +.posts-table td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.posts-table th { + background-color: #f8f9fa; + font-weight: bold; +} + +.posts-table td a { + color: #3498db; + text-decoration: none; + margin-right: 10px; +} + +.posts-table td a:hover { + text-decoration: underline; +} + +.delete-btn { + background: none; + border: none; + color: #e74c3c; + cursor: pointer; + padding: 0; + font-size: 1em; +} + +.delete-btn:hover { + text-decoration: underline; +} + +/* 编辑文章 */ +.edit-post { + background: white; + padding: 30px; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.edit-post textarea { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-family: inherit; + font-size: 16px; + resize: vertical; +} + +.form-actions { + margin-top: 20px; +} + +.btn-cancel { + background-color: #95a5a6; + margin-left: 10px; +} + +.btn-cancel:hover { + background-color: #7f8c8d; +} + +/* 页脚 */ +footer { + background-color: #2c3e50; + color: white; + text-align: center; + padding: 20px 0; + margin-top: 40px; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + header nav { + float: none; + text-align: center; + margin-top: 10px; + } + + .admin-header { + flex-direction: column; + align-items: flex-start; + } + + .admin-header .btn { + margin-top: 10px; + } + + .stats { + flex-direction: column; + } + + .stat-card { + margin: 10px 0; + } +} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js new file mode 100644 index 00000000..e69de29b diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html new file mode 100644 index 00000000..d8b3b5c4 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block title %}管理面板{% endblock %} + +{% block content %} +
+

管理面板

+ +
+
+

文章总数

+

{{ post_count }}

+
+
+ + +
+{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html new file mode 100644 index 00000000..3267008e --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block title %}{% if post.id %}编辑文章{% else %}新建文章{% endif %}{% endblock %} + +{% block content %} +
+

{% if post.id %}编辑文章{% else %}新建文章{% endif %}

+ +
+
+ + +
+
+ + +
+
+ + 取消 +
+
+
+{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html new file mode 100644 index 00000000..2653459e --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block title %}管理员登录{% endblock %} + +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html new file mode 100644 index 00000000..add16818 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html @@ -0,0 +1,53 @@ +{% extends "base.html" %} + +{% block title %}文章管理{% endblock %} + +{% block content %} +
+
+

文章管理

+ 新建文章 +
+ + + + + + + + + + + {% for post in posts.items %} + + + + + + {% else %} + + + + {% endfor %} + +
标题发布时间操作
{{ post.title }}{{ post.created_at.strftime('%Y-%m-%d %H:%M') }} + 编辑 +
+ +
+
暂无文章
+ + + +
+{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html new file mode 100644 index 00000000..1884a85f --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html @@ -0,0 +1,45 @@ + + + + + + {% block title %}博客系统{% endblock %} + + + +
+
+

我的博客

+ +
+
+ +
+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+ {% for message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + + {% block content %}{% endblock %} +
+ +
+
+

© 2023 我的博客. All rights reserved.

+
+
+ + \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html new file mode 100644 index 00000000..4bfc4a18 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block content %} +
+ {% for post in posts.items %} +
+

{{ post.title }}

+ +

{{ post.content[:200] }}{% if post.content|length > 200 %}...{% endif %}

+ 阅读全文 +
+ {% else %} +

暂无文章。

+ {% endfor %} +
+ + + +{% endblock %} \ No newline at end of file diff --git a/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html new file mode 100644 index 00000000..995093f1 --- /dev/null +++ b/veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}{{ post.title }} - 博客系统{% endblock %} + +{% block content %} +
+

{{ post.title }}

+ +
+ {{ post.content|replace('\n', '
')|safe }} +
+ « 返回首页 +
+{% endblock %} \ No newline at end of file