add vuejs POC with README procedure

This commit is contained in:
Jordan Blasenhauer 2024-05-15 18:54:57 +02:00
parent 9ae9c6b3e2
commit 6396e4779e
30 changed files with 4989 additions and 2 deletions

View file

@ -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)

Binary file not shown.

106
vuejs/build.js Normal file
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

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
View 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"
}
}

View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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>

View 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>

View 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>

View file

@ -0,0 +1,4 @@
import { createApp } from "vue";
import Test from "./Test.vue";
createApp(Test).mount("#app");

File diff suppressed because it is too large Load diff

View 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
View 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')

File diff suppressed because one or more lines are too long

1
vuejs/static/css/flag-icons.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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>

View file

1
wtforms/README.md Normal file
View file

@ -0,0 +1 @@
# test-ui

View file

@ -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):