mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
add vuejs POC with README procedure
This commit is contained in:
parent
9ae9c6b3e2
commit
6396e4779e
30 changed files with 4989 additions and 2 deletions
27
README.md
27
README.md
|
|
@ -1 +1,26 @@
|
|||
# test-ui
|
||||
# Vuejs
|
||||
|
||||
## Principle
|
||||
|
||||
We can develop the front-end using Vue and Vite framework.
|
||||
I have all my front-end logic inside `client` folder.
|
||||
|
||||
We can use all the facilities of the framework, for this example you have :
|
||||
- `component` folder : create a component to use in your page
|
||||
- `pages` folder : contains each pages to render, we have only one page test ATM
|
||||
|
||||
Inside `pages` folder, you have 3 files :
|
||||
- `index.html` that is entry point, we define entry point for Vue app and a div with attribut data-flask to store futur data from template
|
||||
- `test.js` is js entry point that define the Vue components, plugins and settings to use for this page
|
||||
- `Test.vue` is the page (global context) where we are importing components, executing logic... For example, we are retrieving data-flask from here
|
||||
|
||||
We have others files for the configuration of node modules and Vite.
|
||||
|
||||
We need to work on the front-end here, then build and move to flask app the resources when needed.
|
||||
|
||||
## Installation
|
||||
|
||||
- Need to install `node.js` and `npm` on computer
|
||||
- Go inside `vuejs` folder and run `node build.js`
|
||||
- Run `flask --app main.py run` then access `/test` to get Vue.js rendering app using flask data `Title from Flask !`
|
||||
- `/test2` is case with no flask data (can be handle on a div or directly on Vue app)
|
||||
BIN
vuejs/__pycache__/main.cpython-312.pyc
Normal file
BIN
vuejs/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
106
vuejs/build.js
Normal file
106
vuejs/build.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
const { execSync } = require("child_process");
|
||||
const { resolve } = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const clientBuildDir = "static";
|
||||
const setupBuildDir = "setup/output";
|
||||
|
||||
// Run subprocess command on specific dir
|
||||
function runCommand(dir, command) {
|
||||
let isErr = false;
|
||||
try {
|
||||
execSync(
|
||||
command,
|
||||
{ cwd: resolve(__dirname + dir) },
|
||||
function (err, stdout, stderr) {
|
||||
console.log(stdout);
|
||||
console.log(stderr);
|
||||
if (err !== null) {
|
||||
isErr = true;
|
||||
console.log(`exec error: ${err}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
isErr = true;
|
||||
}
|
||||
|
||||
return isErr;
|
||||
}
|
||||
|
||||
// Install deps and build vite (work for client and setup)
|
||||
function buildVite(dir) {
|
||||
let isErr = false;
|
||||
// Install packages
|
||||
isErr = runCommand(dir, "npm install");
|
||||
if (isErr) return isErr;
|
||||
// Build vite
|
||||
isErr = runCommand(dir, "npm run build");
|
||||
return isErr;
|
||||
}
|
||||
|
||||
// CLIENT : Change dir structure
|
||||
function updateClientDir() {
|
||||
let isErr = false;
|
||||
const srcDir = resolve(`./${clientBuildDir}/src/pages`);
|
||||
const destDir = resolve(`./${clientBuildDir}/templates`);
|
||||
const dirToRem = resolve(`./${clientBuildDir}/src`);
|
||||
|
||||
try {
|
||||
// Change dir position for html
|
||||
fs.cpSync(srcDir, destDir, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
// Remove prev dir
|
||||
fs.rmSync(dirToRem, { recursive: true, force: true });
|
||||
// Change templates/page/index.html by templates/{page_name}.html
|
||||
// And move from static to templates
|
||||
const templateDir = resolve(`./${clientBuildDir}/templates`);
|
||||
|
||||
fs.readdir(templateDir, (err, subdirs) => {
|
||||
subdirs.forEach((subdir) => {
|
||||
// Get absolute path of current subdir
|
||||
const currPath = resolve(`./${clientBuildDir}/templates/${subdir}`);
|
||||
// Rename index.html by subdir name
|
||||
fs.renameSync(`${currPath}/index.html`, `${currPath}/${subdir}.html`);
|
||||
// Copy file to move it from /template/page to /template
|
||||
fs.copyFileSync(
|
||||
`${currPath}/${subdir}.html`,
|
||||
resolve(`./static/templates/${subdir}.html`),
|
||||
);
|
||||
fs.rmSync(currPath, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
isErr = true;
|
||||
}
|
||||
return isErr;
|
||||
}
|
||||
|
||||
// SETUP : rename and move to /static as html file
|
||||
function setSetup() {
|
||||
let isErr = false;
|
||||
const srcDir = resolve(`./${setupBuildDir}`);
|
||||
const destDir = resolve(`./${clientBuildDir}`);
|
||||
|
||||
try {
|
||||
// Copy file from src to dest
|
||||
fs.copyFileSync(`${srcDir}/index.html`, `${destDir}/setup.html`);
|
||||
} catch (err) {
|
||||
isErr = true;
|
||||
}
|
||||
return isErr;
|
||||
}
|
||||
|
||||
|
||||
// Build client and setup
|
||||
const buildClientErr = buildVite("/client");
|
||||
if (buildClientErr)
|
||||
return console.log("Error while building client. Impossible to continue.");
|
||||
// Change client dir structure
|
||||
const isUpdateDirErr = updateClientDir();
|
||||
if (isUpdateDirErr)
|
||||
return console.log(
|
||||
"Error while changing client dir structure. Impossible to continue.",
|
||||
);
|
||||
24
vuejs/client/.gitignore
vendored
Normal file
24
vuejs/client/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
7
vuejs/client/README.md
Normal file
7
vuejs/client/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
1178
vuejs/client/input.css
Normal file
1178
vuejs/client/input.css
Normal file
File diff suppressed because one or more lines are too long
1920
vuejs/client/package-lock.json
generated
Normal file
1920
vuejs/client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
vuejs/client/package.json
Normal file
27
vuejs/client/package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"ace-builds": "^1.24.2",
|
||||
"flag-icons": "^6.15.0",
|
||||
"flatpickr": "^4.6.13",
|
||||
"pinia": "^2.1.6",
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"postcss": "^8.4.29",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"uuid": "^9.0.1",
|
||||
"vite": "^4.4.5",
|
||||
"vue-i18n": "^9.6.5"
|
||||
}
|
||||
}
|
||||
6
vuejs/client/postcss.config.js
Normal file
6
vuejs/client/postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
1
vuejs/client/public/css/flag-icons.min.css
vendored
Normal file
1
vuejs/client/public/css/flag-icons.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
vuejs/client/public/css/style.css
Normal file
1
vuejs/client/public/css/style.css
Normal file
File diff suppressed because one or more lines are too long
15
vuejs/client/src/components/TestTitle.vue
Normal file
15
vuejs/client/src/components/TestTitle.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
const props = defineProps({
|
||||
// id && value && method
|
||||
title : {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-full">
|
||||
<h1 class="text-4xl font-bold">{{ title }}</h1>
|
||||
</div>
|
||||
</template>
|
||||
23
vuejs/client/src/pages/test/Test.vue
Normal file
23
vuejs/client/src/pages/test/Test.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script setup>
|
||||
import { reactive, onBeforeMount } from "vue";
|
||||
import TestTile from "@components/TestTitle.vue";
|
||||
|
||||
// Define reactive properties
|
||||
const data = reactive({
|
||||
// Define properties here
|
||||
title : 'No title'
|
||||
})
|
||||
|
||||
// Retrieve default or flask data if available
|
||||
onBeforeMount(() => {
|
||||
const dataEl = document.querySelector('[data-flask]');
|
||||
data.title = dataEl.getAttribute('data-flask') && dataEl.getAttribute('data-flask') !== "{{ flask_data }}" ? dataEl.getAttribute('data-flask') : dataEl.getAttribute('data-default-value');
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-secondary flex flex-col items-center justify-center h-full">
|
||||
<TestTile :title="data.title" />
|
||||
</div>
|
||||
</template>
|
||||
16
vuejs/client/src/pages/test/index.html
Normal file
16
vuejs/client/src/pages/test/index.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<link rel="stylesheet" href="/css/flag-icons.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BunkerWeb | TEST</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-default-value="Title default !" data-flask="{{ flask_data }}" class="hidden"></div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="test.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
4
vuejs/client/src/pages/test/test.js
Normal file
4
vuejs/client/src/pages/test/test.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { createApp } from "vue";
|
||||
import Test from "./Test.vue";
|
||||
|
||||
createApp(Test).mount("#app");
|
||||
1505
vuejs/client/tailwind.config.js
Normal file
1505
vuejs/client/tailwind.config.js
Normal file
File diff suppressed because it is too large
Load diff
28
vuejs/client/vite.config.js
Normal file
28
vuejs/client/vite.config.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { resolve } from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
host: true,
|
||||
port: 3000,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": resolve(__dirname, "./src"),
|
||||
"@pages": resolve(__dirname, "./src/pages"),
|
||||
"@components": resolve(__dirname, "./src/components"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: "../static",
|
||||
emptyOutDir: "../static",
|
||||
rollupOptions: {
|
||||
input: {
|
||||
test: resolve(__dirname, "./src/pages/test/index.html"),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
36
vuejs/main.py
Normal file
36
vuejs/main.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from flask import Flask, render_template
|
||||
from flask import Blueprint
|
||||
from flask import redirect, url_for
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# I want to setup templates and static files
|
||||
app = Flask(__name__, template_folder="templates", static_folder="static")
|
||||
|
||||
templates = Blueprint("static", __name__, template_folder="static/templates")
|
||||
assets = Blueprint("assets", __name__, static_folder="static/assets")
|
||||
images = Blueprint("images", __name__, static_folder="static/images")
|
||||
style = Blueprint("style", __name__, static_folder="static/css")
|
||||
js = Blueprint("js", __name__, static_folder="static/js")
|
||||
app.register_blueprint(templates)
|
||||
app.register_blueprint(assets)
|
||||
app.register_blueprint(images)
|
||||
app.register_blueprint(style)
|
||||
app.register_blueprint(js)
|
||||
|
||||
@app.route("/", methods=['GET', 'POST'])
|
||||
def render_index():
|
||||
# redirect to test
|
||||
return redirect(url_for('render_test'))
|
||||
|
||||
@app.route("/test", methods=['GET', 'POST'])
|
||||
def render_test():
|
||||
return render_template("test.html", flask_data="Title from Flask !")
|
||||
|
||||
@app.route("/test2", methods=['GET', 'POST'])
|
||||
def render_test2():
|
||||
return render_template("test.html")
|
||||
|
||||
|
||||
app.debug = True
|
||||
app.run(host='0.0.0.0')
|
||||
1
vuejs/static/assets/test-e985cac8.js
Normal file
1
vuejs/static/assets/test-e985cac8.js
Normal file
File diff suppressed because one or more lines are too long
1
vuejs/static/css/flag-icons.min.css
vendored
Normal file
1
vuejs/static/css/flag-icons.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
vuejs/static/css/style.css
Normal file
1
vuejs/static/css/style.css
Normal file
File diff suppressed because one or more lines are too long
17
vuejs/static/templates/test.html
Normal file
17
vuejs/static/templates/test.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<link rel="stylesheet" href="/css/flag-icons.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BunkerWeb | TEST</title>
|
||||
<script type="module" crossorigin src="/assets/test-e985cac8.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div data-default-value="DEFAULT TITLE" data-flask="{{ flask_data }}" class="hidden"></div>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
0
.gitignore → wtforms/.gitignore
vendored
0
.gitignore → wtforms/.gitignore
vendored
1
wtforms/README.md
Normal file
1
wtforms/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
# test-ui
|
||||
|
|
@ -3,11 +3,55 @@ from wtforms.fields import Field, StringField, BooleanField, SelectField, Passwo
|
|||
from wtforms.validators import Regexp
|
||||
from wtforms.widgets import CheckboxInput
|
||||
|
||||
|
||||
|
||||
from re import search
|
||||
|
||||
class CheckboxSettingWidget(CheckboxInput):
|
||||
def __init__(self, error_class='has_errors'):
|
||||
super(CheckboxInput, self).__init__()
|
||||
self.error_class = error_class
|
||||
|
||||
def __call__(self, field, **kwargs):
|
||||
kwargs.setdefault('id', field.id)
|
||||
kwargs.setdefault('name', field.name)
|
||||
kwargs.setdefault('type', 'checkbox')
|
||||
kwargs.setdefault('data-default-method', "mode" if kwargs['name'] in ('AUTOCONF_MODE', 'SWARM_MODE', 'KUBERNETES_MODE') else field.method if hasattr(field, 'method') else 'default')
|
||||
kwargs.setdefault('value', field.global_config_value)
|
||||
kwargs.setdefault('data-pattern', field.regex)
|
||||
|
||||
kwargs.setdefault('checked', "")
|
||||
return f"""<div data-checkbox-handler="{kwargs['id']}">
|
||||
class="relative mb-7 md:mb-0 z-10 ">
|
||||
{self.input(field, **kwargs)} {self.label(field, {"class": "sr-only", "for": kwargs['name']})}
|
||||
<input id="{kwargs['name']}"
|
||||
name="{kwargs['name']}"
|
||||
data-default-method="{% if inp_name in ['AUTOCONF_MODE', 'SWARM_MODE', 'KUBERNETES_MODE'] %}mode{% else %}{{ global_config_method }}{% endif %}"
|
||||
data-default-value="{{ global_config[inp_name]['value'] }}"
|
||||
{% if inp_name in ['AUTOCONF_MODE', 'SWARM_MODE', 'KUBERNETES_MODE'] or global_config_method != 'ui' and global_config_method != 'default' or is_read_only %} disabled {% endif %}
|
||||
data-checked="{% if global_config_value == "yes" %}true{% else %}false{% endif %}"
|
||||
{% if global_config_value == "yes" %}checked{% endif %}
|
||||
id="checkbox-{kwargs['id']}"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
data-pattern="{{ inp_regex|safe }}"
|
||||
value="{{ global_config_value }}"
|
||||
{% if is_multiple %} data-is-multiple {% endif %}
|
||||
/>
|
||||
<svg data-checkbox-handler="{kwargs['id']}"
|
||||
class="pointer-events-none absolute fill-white dark:fill-gray-300 left-0 top-0 translate-x-1 translate-y-2 h-3 w-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512">
|
||||
<path class="pointer-events-none" d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
class BWBooleanField(Field):
|
||||
|
||||
widget = CheckboxInput()
|
||||
widget = CheckboxSetting()
|
||||
false_values = (False, "false", "")
|
||||
|
||||
def __init__(self, label=None, validators=None, false_values=None, **kwargs):
|
||||
Loading…
Reference in a new issue