integrate web ui project

This commit is contained in:
Bogdan Pilyugin 2023-08-20 11:53:06 +02:00
parent f69835c8d4
commit 11bac96cbe
81 changed files with 7385 additions and 808 deletions

4
.gitignore vendored
View File

@ -1,2 +1,2 @@
/build/
/build
/WEBUI_DIST

1542
.project

File diff suppressed because it is too large Load Diff

View File

@ -9,4 +9,4 @@ project(${ProjectId})
# Include espfs ROM in foldef HTML
include(components/webguiapp/extlibs/libespfs/cmake/include.cmake)
target_add_espfs(WebguiappTemplate.elf espfs WEBUI)
target_add_espfs(WebguiappTemplate.elf espfs WEBUI_DIST)

View File

@ -1 +0,0 @@
import{_ as t,V as o,W as s,a5 as a,af as e,Z as r,a4 as n}from"./index.e05c0c8e.js";const c=o({name:"ErrorNotFound"}),l={class:"fullscreen bg-blue text-white text-center q-pa-md flex flex-center"},d=e("div",{style:{"font-size":"30vh"}}," 404 ",-1),i=e("div",{class:"text-h2",style:{opacity:".4"}}," Oops. Nothing here... four-ow-four ",-1);function _(f,p,u,h,x,m){return s(),a("div",l,[e("div",null,[d,i,r(n,{class:"q-mt-xl",color:"white","text-color":"blue",unelevated:"",to:"/",label:"Go Home","no-caps":""})])])}var N=t(c,[["render",_]]);export{N as default};

View File

@ -1 +0,0 @@
import{S as f,o as v,T as C,a as d,W as _,X as h,Y as s,ad as H,Z as o,ae as m,af as t,a2 as i,_ as g,V as S,a3 as q,a5 as x}from"./index.e05c0c8e.js";import{S as l}from"./network.98844594.js";import{s as $}from"./helpers.6b816d79.js";import"./axios.15d8ef65.js";const k=t("div",{class:"text-h6"},"Home",-1),y={class:"q-pa-md"},B={class:"q-gutter-md q-pa-none q-pb-none"},I=Object.assign({name:"HomeCard"},{__name:"HomeCard",setup(p){const e=f({time:0,uptime:0});l(e,2,0,"mykey",!1);let a;v(()=>{a=setInterval(()=>{l(e,2,0,"mykey",!1)},1e3)}),C(()=>clearInterval(a));const n=d({get(){return new Date(e.time*1e3).toISOString()}}),r=d({get(){return $(e.uptime)}});return(c,D)=>(_(),h(H,{flat:"",bordered:"",class:"card"},{default:s(()=>[o(m,null,{default:s(()=>[k]),_:1}),o(m,{class:"q-pt-none"},{default:s(()=>[t("div",y,[t("div",B,[t("div",null,i(n.value),1),t("div",null,"Uptime: "+i(r.value),1)])])]),_:1})]),_:1}))}}),b=S({name:"HomePage",components:{HomeCard:I}}),V={class:"cardholder"};function w(p,u,e,a,n,r){const c=q("HomeCard");return _(),x("div",V,[o(c)])}var U=g(b,[["render",w]]);export{U as default};

Binary file not shown.

View File

@ -1 +0,0 @@
.menu{font-family:monospace;font-size:larger}

Binary file not shown.

View File

@ -1 +0,0 @@
import{c as d,D as g,F as k,a as u,h as o,d as v,g as y,a9 as Q,aa as R,ab as E,r as q,ac as A,B as D,p as K}from"./index.e05c0c8e.js";var P=d({name:"QList",props:{...g,bordered:Boolean,dense:Boolean,separator:Boolean,padding:Boolean,tag:{type:String,default:"div"}},setup(e,{slots:t}){const n=y(),i=k(e,n.proxy.$q),l=u(()=>"q-list"+(e.bordered===!0?" q-list--bordered":"")+(e.dense===!0?" q-list--dense":"")+(e.separator===!0?" q-list--separator":"")+(i.value===!0?" q-list--dark":"")+(e.padding===!0?" q-list--padding":""));return()=>o(e.tag,{class:l.value},v(t.default))}});function j(){if(window.getSelection!==void 0){const e=window.getSelection();e.empty!==void 0?e.empty():e.removeAllRanges!==void 0&&(e.removeAllRanges(),Q.is.mobile!==!0&&e.addRange(document.createRange()))}else document.selection!==void 0&&document.selection.empty()}function F(e,t,n){return n<=t?t:Math.min(n,Math.max(t,e))}function M(e,t,n){if(n<=t)return t;const i=n-t+1;let l=t+(e-t)%i;return l<t&&(l=i+l),l===0?0:l}var N=d({name:"QItemSection",props:{avatar:Boolean,thumbnail:Boolean,side:Boolean,top:Boolean,noWrap:Boolean},setup(e,{slots:t}){const n=u(()=>`q-item__section column q-item__section--${e.avatar===!0||e.side===!0||e.thumbnail===!0?"side":"main"}`+(e.top===!0?" q-item__section--top justify-start":" justify-center")+(e.avatar===!0?" q-item__section--avatar":"")+(e.thumbnail===!0?" q-item__section--thumbnail":"")+(e.noWrap===!0?" q-item__section--nowrap":""));return()=>o("div",{class:n.value},v(t.default))}}),T=d({name:"QItemLabel",props:{overline:Boolean,caption:Boolean,header:Boolean,lines:[Number,String]},setup(e,{slots:t}){const n=u(()=>parseInt(e.lines,10)),i=u(()=>"q-item__label"+(e.overline===!0?" q-item__label--overline text-overline":"")+(e.caption===!0?" q-item__label--caption text-caption":"")+(e.header===!0?" q-item__label--header":"")+(n.value===1?" ellipsis":"")),l=u(()=>e.lines!==void 0&&n.value>1?{overflow:"hidden",display:"-webkit-box","-webkit-box-orient":"vertical","-webkit-line-clamp":n.value}:null);return()=>o("div",{style:l.value,class:i.value},v(t.default))}}),z=d({name:"QItem",props:{...g,...R,tag:{type:String,default:"div"},active:{type:Boolean,default:null},clickable:Boolean,dense:Boolean,insetLevel:Number,tabindex:[String,Number],focused:Boolean,manualFocus:Boolean},emits:["click","keyup"],setup(e,{slots:t,emit:n}){const{proxy:{$q:i}}=y(),l=k(e,i),{hasLink:m,linkAttrs:h,linkClass:_,linkTag:B,navigateOnClick:w}=E(),r=q(null),c=q(null),f=u(()=>e.clickable===!0||m.value===!0||e.tag==="label"),s=u(()=>e.disable!==!0&&f.value===!0),x=u(()=>"q-item q-item-type row no-wrap"+(e.dense===!0?" q-item--dense":"")+(l.value===!0?" q-item--dark":"")+(m.value===!0&&e.active===null?_.value:e.active===!0?` q-item--active${e.activeClass!==void 0?` ${e.activeClass}`:""}`:"")+(e.disable===!0?" disabled":"")+(s.value===!0?" q-item--clickable q-link cursor-pointer "+(e.manualFocus===!0?"q-manual-focusable":"q-focusable q-hoverable")+(e.focused===!0?" q-manual-focusable--focused":""):"")),L=u(()=>{if(e.insetLevel===void 0)return null;const a=i.lang.rtl===!0?"Right":"Left";return{["padding"+a]:16+e.insetLevel*56+"px"}});function S(a){s.value===!0&&(c.value!==null&&(a.qKeyEvent!==!0&&document.activeElement===r.value?c.value.focus():document.activeElement===c.value&&r.value.focus()),w(a))}function C(a){if(s.value===!0&&A(a,13)===!0){D(a),a.qKeyEvent=!0;const b=new MouseEvent("click",a);b.qKeyEvent=!0,r.value.dispatchEvent(b)}n("keyup",a)}function I(){const a=K(t.default,[]);return s.value===!0&&a.unshift(o("div",{class:"q-focus-helper",tabindex:-1,ref:c})),a}return()=>{const a={ref:r,class:x.value,style:L.value,role:"listitem",onClick:S,onKeyup:C};return s.value===!0?(a.tabindex=e.tabindex||"0",Object.assign(a,h.value)):f.value===!0&&(a["aria-disabled"]="true"),o(B.value,a,I())}}});export{N as Q,T as a,F as b,j as c,z as d,P as e,M as n};

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
import"./index.e05c0c8e.js";function h(o){o=Number(o);var s=Math.floor(o/86400),r=Math.floor(o%86400/3600),a=Math.floor(o%3600/60),t=Math.floor(o%3600%60);return" "+s+"d "+(r<10?"0":"")+r+":"+(a<10?"0":"")+a+":"+(t<10?"0":"")+t}export{h as s};

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +0,0 @@
<!DOCTYPE html><html><head><title>ESP32 web interface</title><meta charset=utf-8><meta name=description content="Web interface for ESP32 devices"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/ico href="/favicon.ico"> <script type="module" crossorigin src="/assets/index.e05c0c8e.js"></script>
<link rel="stylesheet" href="/assets/index.6b461d14.css">
</head><body><div id=q-app></div></body></html>

9
WEBUI_SRC/.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

7
WEBUI_SRC/.eslintignore Normal file
View File

@ -0,0 +1,7 @@
/dist
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.js
/quasar.config.*.temporary.compiled*

66
WEBUI_SRC/.eslintrc.cjs Normal file
View File

@ -0,0 +1,66 @@
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
parserOptions: {
ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features
},
env: {
node: true,
browser: true,
'vue/setup-compiler-macros': true
},
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
// 'eslint:recommended',
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
// https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'.
'prettier'
],
plugins: [
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue',
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
// Prettier has not been included as plugin to avoid performance impact
// add it as an extension for your IDE
],
globals: {
ga: 'readonly', // Google Analytics
cordova: 'readonly',
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly'
},
// add your custom rules here
rules: {
'prefer-promise-reject-errors': 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}

33
WEBUI_SRC/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
.DS_Store
.thumbs.db
node_modules
# Quasar core related directories
.quasar
/dist
/quasar.config.*.temporary.compiled*
# Cordova related directories and files
/src-cordova/node_modules
/src-cordova/platforms
/src-cordova/plugins
/src-cordova/www
# Capacitor related directories and files
/src-capacitor/www
/src-capacitor/node_modules
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
# local .env files
.env.local*

3
WEBUI_SRC/.npmrc Normal file
View File

@ -0,0 +1,3 @@
# pnpm-related options
shamefully-hoist=true
strict-peer-dependencies=false

17
WEBUI_SRC/.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>webui</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
<classpathentry kind="src" path=""/>
<classpathentry kind="output" path=""/>
</classpath>

View File

@ -0,0 +1 @@
org.eclipse.wst.jsdt.launching.JRE_CONTAINER

View File

@ -0,0 +1 @@
Global

15
WEBUI_SRC/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"vue.volar",
"wayou.vscode-todo-highlight"
],
"unwantedRecommendations": [
"octref.vetur",
"hookyqr.beautify",
"dbaeumer.jshint",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}

15
WEBUI_SRC/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

21
WEBUI_SRC/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": [
"source.fixAll.eslint"
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"vue"
],
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
},
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
}

44
WEBUI_SRC/README.md Normal file
View File

@ -0,0 +1,44 @@
<<<<<<< HEAD
# ESP32 web interface (webui)
Web interface for ESP32 devices
## Install the dependencies
```bash
yarn
# or
npm install
```
### Start the app in development mode (hot-code reloading, error reporting, etc.)
```bash
quasar dev
```
### Lint the files
```bash
yarn lint
# or
npm run lint
```
### Format the files
```bash
yarn format
# or
npm run format
```
### Build the app for production
```bash
quasar build
```
### Customize the configuration
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
=======
>>>>>>> refs/remotes/origin/main

14
WEBUI_SRC/build_ui.bat Normal file
View File

@ -0,0 +1,14 @@
@ECHO ON
SETLOCAL
SET "sourcedir=C:\BOGD-PROJECTS\WebguiappTemplate\WEBUI"
SET "keepfile=espfs.paths"
SET "keepdir=keep"
FOR /d %%a IN ("%sourcedir%\*") DO IF /i NOT "%%~nxa"=="%keepdir%" RD /S /Q "%%a"
FOR %%a IN ("%sourcedir%\*") DO IF /i NOT "%%~nxa"=="%keepfile%" DEL "%%a
xcopy /s "C:\BOGD-PROJECTS\webui\dist\spa" "C:\BOGD-PROJECTS\WebguiappTemplate\WEBUI"

5
WEBUI_SRC/build_ui.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
quasar b
cd /home/bogdan/BOGD_PROJECTS/WebguiappTemplate/WEBUI_DIST
ls | grep -xv "espfs.paths" | xargs rm -r
cp -r /home/bogdan/BOGD_PROJECTS/WebguiappTemplate/WEBUI_SRC/dist/spa/. /home/bogdan/BOGD_PROJECTS/WebguiappTemplate/WEBUI_DIST/

17
WEBUI_SRC/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title><%= productName %></title>
<meta charset="utf-8">
<meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
<link rel="icon" type="image/ico" href="favicon.ico">
</head>
<body>
<!-- quasar:entry-point -->
</body>
</html>

39
WEBUI_SRC/jsconfig.json Normal file
View File

@ -0,0 +1,39 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": [
"src/*"
],
"app/*": [
"*"
],
"components/*": [
"src/components/*"
],
"layouts/*": [
"src/layouts/*"
],
"pages/*": [
"src/pages/*"
],
"assets/*": [
"src/assets/*"
],
"boot/*": [
"src/boot/*"
],
"stores/*": [
"src/stores/*"
],
"vue$": [
"node_modules/vue/dist/vue.runtime.esm-bundler.js"
]
}
},
"exclude": [
"dist",
".quasar",
"node_modules"
]
}

4924
WEBUI_SRC/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

39
WEBUI_SRC/package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "webui",
"version": "0.0.1",
"description": "Web interface for ESP32 devices",
"productName": "ESP32 web interface",
"author": "bogdan <bogd@live.ru>",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev",
"build": "quasar build"
},
"dependencies": {
"@quasar/extras": "^1.16.4",
"axios": "^1.2.1",
"js-sha256": "^0.9.0",
"quasar": "^2.6.0",
"three": "^0.155.0",
"vite-plugin-compression2": "^0.10.3",
"vue": "^3.0.0",
"vue-router": "^4.0.0"
},
"devDependencies": {
"@quasar/app-vite": "^1.3.0",
"autoprefixer": "^10.4.2",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-vue": "^9.0.0",
"postcss": "^8.4.14",
"prettier": "^2.5.1"
},
"engines": {
"node": "^18 || ^16 || ^14.19",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
}

View File

@ -0,0 +1,27 @@
/* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: [
// https://github.com/postcss/autoprefixer
require('autoprefixer')({
overrideBrowserslist: [
'last 4 Chrome versions',
'last 4 Firefox versions',
'last 4 Edge versions',
'last 4 Safari versions',
'last 4 Android versions',
'last 4 ChromeAndroid versions',
'last 4 FirefoxAndroid versions',
'last 4 iOS versions'
]
})
// https://github.com/elchininet/postcss-rtlcss
// If you want to support RTL css, then
// 1. yarn/npm install postcss-rtlcss
// 2. optionally set quasar.config.js > framework > lang to an RTL language
// 3. uncomment the following line:
// require('postcss-rtlcss')
]
}

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

212
WEBUI_SRC/quasar.config.js Normal file
View File

@ -0,0 +1,212 @@
/* eslint-env node */
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
// Configuration for your app
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
const { QInput } = require("quasar");
const { configure } = require("quasar/wrappers");
module.exports = configure(function (/* ctx */) {
return {
eslint: {
// fix: true,
// include: [],
// exclude: [],
// rawOptions: {},
warnings: true,
errors: true,
},
// https://v2.quasar.dev/quasar-cli/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli/boot-files
boot: ["axios"],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ["app.scss"],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
// 'fontawesome-v6',
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
"roboto-font", // optional, you are not bound to it
"material-icons", // optional, you are not bound to it
],
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
build: {
target: {
browser: ["es2019", "edge88", "firefox78", "chrome87", "safari13.1"],
node: "node16",
},
vueRouterMode: "hash", // available values: 'hash', 'history'
// vueRouterBase,
// vueDevtools,
// vueOptionsAPI: false,
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
//publicPath: "/",
// analyze: true,
//env: { UITYPE: "sampleapp" },
//rawDefine: { curapp: "sampleapp" },
// ignorePublicFolder: true,
// minify: false,
// polyfillModulePreload: true,
//distDir: "/home/bogdan/BOGD_PROJECTS/WebguiappTemplate/WEBUI",
//distDir: "C:\\BOGD-PROJECTS\\WebguiappTemplate\\WEBUI",
// extendViteConf (viteConf) {},
// viteVuePluginOptions: {},
vitePlugins: [
[
"vite-plugin-compression2",
{
deleteOriginalAssets: true,
filename: "[path][base]",
threshold: 4096,
},
],
],
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
// https: true
open: true, // opens browser window automatically
},
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
framework: {
config: {
},
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack
// For special cases outside of where the auto-import strategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
components: [],
// directives: [],
// Quasar plugins
plugins: ["Dialog", "Notify"],
},
// animations: 'all', // --- includes all animations
// https://v2.quasar.dev/options/animations
animations: [],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
// sourceFiles: {
// rootComponent: 'src/App.vue',
// router: 'src/router/index',
// store: 'src/store/index',
// registerServiceWorker: 'src-pwa/register-service-worker',
// serviceWorker: 'src-pwa/custom-service-worker',
// pwaManifestFile: 'src-pwa/manifest.json',
// electronMain: 'src-electron/electron-main',
// electronPreload: 'src-electron/electron-preload'
// },
// https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: {
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
// will mess up SSR
// extendSSRWebserverConf (esbuildConf) {},
// extendPackageJson (json) {},
pwa: false,
// manualStoreHydration: true,
// manualPostHydrationTrigger: true,
prodPort: 3000, // The default port that the production server should use
// (gets superseded if process.env.PORT is specified at runtime)
middlewares: [
"render", // keep this as last one
],
},
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
pwa: {
workboxMode: "generateSW", // or 'injectManifest'
injectPwaMetaTags: true,
swFilename: "sw.js",
manifestFilename: "manifest.json",
useCredentialsForManifestTag: false,
// useFilenameHashes: true,
// extendGenerateSWOptions (cfg) {}
// extendInjectManifestOptions (cfg) {},
// extendManifestJson (json) {}
// extendPWACustomSWConf (esbuildConf) {}
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true,
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
electron: {
// extendElectronMainConf (esbuildConf)
// extendElectronPreloadConf (esbuildConf)
// specify the debugging port to use for the Electron app when running in development mode
inspectPort: 5858,
bundler: "packager", // 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
builder: {
// https://www.electron.build/configuration/configuration
appId: "webui",
},
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
bex: {
contentScripts: ["my-content-script"],
// extendBexScriptsConf (esbuildConf) {}
// extendBexManifestJson (json) {}
},
};
});

11
WEBUI_SRC/src/App.vue Normal file
View File

@ -0,0 +1,11 @@
<template>
<router-view />
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'App'
})
</script>

View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 356 360">
<path
d="M43.4 303.4c0 3.8-2.3 6.3-7.1 6.3h-15v-22h14.4c4.3 0 6.2 2.2 6.2 5.2 0 2.6-1.5 4.4-3.4 5 2.8.4 4.9 2.5 4.9 5.5zm-8-13H24.1v6.9H35c2.1 0 4-1.3 4-3.8 0-2.2-1.3-3.1-3.7-3.1zm5.1 12.6c0-2.3-1.8-3.7-4-3.7H24.2v7.7h11.7c3.4 0 4.6-1.8 4.6-4zm36.3 4v2.7H56v-22h20.6v2.7H58.9v6.8h14.6v2.3H58.9v7.5h17.9zm23-5.8v8.5H97v-8.5l-11-13.4h3.4l8.9 11 8.8-11h3.4l-10.8 13.4zm19.1-1.8V298c0-7.9 5.2-10.7 12.7-10.7 7.5 0 13 2.8 13 10.7v1.4c0 7.9-5.5 10.8-13 10.8s-12.7-3-12.7-10.8zm22.7 0V298c0-5.7-3.9-8-10-8-6 0-9.8 2.3-9.8 8v1.4c0 5.8 3.8 8.1 9.8 8.1 6 0 10-2.3 10-8.1zm37.2-11.6v21.9h-2.9l-15.8-17.9v17.9h-2.8v-22h3l15.6 18v-18h2.9zm37.9 10.2v1.3c0 7.8-5.2 10.4-12.4 10.4H193v-22h11.2c7.2 0 12.4 2.8 12.4 10.3zm-3 0c0-5.3-3.3-7.6-9.4-7.6h-8.4V307h8.4c6 0 9.5-2 9.5-7.7V298zm50.8-7.6h-9.7v19.3h-3v-19.3h-9.7v-2.6h22.4v2.6zm34.4-2.6v21.9h-3v-10.1h-16.8v10h-2.8v-21.8h2.8v9.2H296v-9.2h2.9zm34.9 19.2v2.7h-20.7v-22h20.6v2.7H316v6.8h14.5v2.3H316v7.5h17.8zM24 340.2v7.3h13.9v2.4h-14v9.6H21v-22h20v2.7H24zm41.5 11.4h-9.8v7.9H53v-22h13.3c5.1 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6H66c3.1 0 5.3-1.5 5.3-4.7 0-3.3-2.2-4.1-5.3-4.1H55.7v8.8zm47.9 6.2H89l-2 4.3h-3.2l10.7-22.2H98l10.7 22.2h-3.2l-2-4.3zm-1-2.3l-6.3-13-6 13h12.2zm46.3-15.3v21.9H146v-17.2L135.7 358h-2.1l-10.2-15.6v17h-2.8v-21.8h3l11 16.9 11.3-17h3zm35 19.3v2.6h-20.7v-22h20.6v2.7H166v6.8h14.5v2.3H166v7.6h17.8zm47-19.3l-8.3 22h-3l-7.1-18.6-7 18.6h-3l-8.2-22h3.3L204 356l6.8-18.5h3.4L221 356l6.6-18.5h3.3zm10 11.6v-1.4c0-7.8 5.2-10.7 12.7-10.7 7.6 0 13 2.9 13 10.7v1.4c0 7.9-5.4 10.8-13 10.8-7.5 0-12.7-3-12.7-10.8zm22.8 0v-1.4c0-5.7-4-8-10-8s-9.9 2.3-9.9 8v1.4c0 5.8 3.8 8.2 9.8 8.2 6.1 0 10-2.4 10-8.2zm28.3 2.4h-9.8v7.9h-2.8v-22h13.2c5.2 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6h10.2c3 0 5.2-1.5 5.2-4.7 0-3.3-2.1-4.1-5.2-4.1h-10.2v8.8zm40.3-1.5l-6.8 5.6v6.4h-2.9v-22h2.9v12.3l15.2-12.2h3.7l-9.9 8.1 10.3 13.8h-3.6l-8.9-12z" />
<path fill="#050A14"
d="M188.4 71.7a10.4 10.4 0 01-20.8 0 10.4 10.4 0 1120.8 0zM224.2 45c-2.2-3.9-5-7.5-8.2-10.7l-12 7c-3.7-3.2-8-5.7-12.6-7.3a49.4 49.4 0 00-9.7 13.9 59 59 0 0140.1 14l7.6-4.4a57 57 0 00-5.2-12.5zM178 125.1c4.5 0 9-.6 13.4-1.7v-14a40 40 0 0012.5-7.2 47.7 47.7 0 00-7.1-15.3 59 59 0 01-32.2 27.7v8.7c4.4 1.2 8.9 1.8 13.4 1.8zM131.8 45c-2.3 4-4 8.1-5.2 12.5l12 7a40 40 0 000 14.4c5.7 1.5 11.3 2 16.9 1.5a59 59 0 01-8-41.7l-7.5-4.3c-3.2 3.2-6 6.7-8.2 10.6z" />
<path fill="#00B4FF"
d="M224.2 98.4c2.3-3.9 4-8 5.2-12.4l-12-7a40 40 0 000-14.5c-5.7-1.5-11.3-2-16.9-1.5a59 59 0 018 41.7l7.5 4.4c3.2-3.2 6-6.8 8.2-10.7zm-92.4 0c2.2 4 5 7.5 8.2 10.7l12-7a40 40 0 0012.6 7.3c4-4.1 7.3-8.8 9.7-13.8a59 59 0 01-40-14l-7.7 4.4c1.2 4.3 3 8.5 5.2 12.4zm46.2-80c-4.5 0-9 .5-13.4 1.7V34a40 40 0 00-12.5 7.2c1.5 5.7 4 10.8 7.1 15.4a59 59 0 0132.2-27.7V20a53.3 53.3 0 00-13.4-1.8z" />
<path fill="#00B4FF"
d="M178 9.2a62.6 62.6 0 11-.1 125.2A62.6 62.6 0 01178 9.2m0-9.2a71.7 71.7 0 100 143.5A71.7 71.7 0 00178 0z" />
<path fill="#050A14"
d="M96.6 212v4.3c-9.2-.8-15.4-5.8-15.4-17.8V180h4.6v18.4c0 8.6 4 12.6 10.8 13.5zm16-31.9v18.4c0 8.9-4.3 12.8-10.9 13.5v4.4c9.2-.7 15.5-5.6 15.5-18v-18.3h-4.7zM62.2 199v-2.2c0-12.7-8.8-17.4-21-17.4-12.1 0-20.7 4.7-20.7 17.4v2.2c0 12.8 8.6 17.6 20.7 17.6 1.5 0 3-.1 4.4-.3l11.8 6.2 2-3.3-8.2-4-6.4-3.1a32 32 0 01-3.6.2c-9.8 0-16-3.9-16-13.3v-2.2c0-9.3 6.2-13.1 16-13.1 9.9 0 16.3 3.8 16.3 13.1v2.2c0 5.3-2.1 8.7-5.6 10.8l4.8 2.4c3.4-2.8 5.5-7 5.5-13.2zM168 215.6h5.1L156 179.7h-4.8l17 36zM143 205l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.8-3.7H143zm133.7 10.7h5.2l-17.3-35.9h-4.8l17 36zm-25-10.7l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.7-3.7h-14.8zm73.8-2.5c6-1.2 9-5.4 9-11.4 0-8-4.5-10.9-12.9-10.9h-21.4v35.5h4.6v-31.3h16.5c5 0 8.5 1.4 8.5 6.7 0 5.2-3.5 7.7-8.5 7.7h-11.4v4.1h10.7l9.3 12.8h5.5l-9.9-13.2zm-117.4 9.9c-9.7 0-14.7-2.5-18.6-6.3l-2.2 3.8c5.1 5 11 6.7 21 6.7 1.6 0 3.1-.1 4.6-.3l-1.9-4h-3zm18.4-7c0-6.4-4.7-8.6-13.8-9.4l-10.1-1c-6.7-.7-9.3-2.2-9.3-5.6 0-2.5 1.4-4 4.6-5l-1.8-3.8c-4.7 1.4-7.5 4.2-7.5 8.9 0 5.2 3.4 8.7 13 9.6l11.3 1.2c6.4.6 8.9 2 8.9 5.4 0 2.7-2.1 4.7-6 5.8l1.8 3.9c5.3-1.6 8.9-4.7 8.9-10zm-20.3-21.9c7.9 0 13.3 1.8 18.1 5.7l1.8-3.9a30 30 0 00-19.6-5.9c-2 0-4 .1-5.7.3l1.9 4 3.5-.2z" />
<path fill="#00B4FF"
d="M.5 251.9c29.6-.5 59.2-.8 88.8-1l88.7-.3 88.7.3 44.4.4 44.4.6-44.4.6-44.4.4-88.7.3-88.7-.3a7981 7981 0 01-88.8-1z" />
<path fill="none" d="M-565.2 324H-252v15.8h-313.2z" />
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

View File

@ -0,0 +1,24 @@
import { boot } from "quasar/wrappers";
import axios from "axios";
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: "/" });
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { api };

View File

@ -0,0 +1,37 @@
import { Dialog } from "quasar";
function secondsToHms(d) {
d = Number(d);
var dd = Math.floor(d / 86400);
var h = Math.floor(d % 86400 / 3600);
var m = Math.floor(d % 3600 / 60);
var s = Math.floor(d % 3600 % 60);
return (' ' + dd + 'd ' + (h < 10 ? "0" : "") + h + ":" + (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s);
}
function ShowDelayDialog(mess, time, onexpire) {
const dialog = Dialog.create({
message: '',
progress: true,
persistent: true, // we want the user to not be able to close it
ok: false // we want the user to not be able to close it
})
let percentage = 0
const interval = setInterval(() => {
percentage = Math.min(100, percentage + 10000 / time);
dialog.update({ message: `${mess} ${percentage}%` })
// if we are done, we're gonna close it
if (percentage === 100) {
clearInterval(interval)
setTimeout(() => {
dialog.hide();
onexpire();
}, 350)
}
}, 100)
}
export { secondsToHms, ShowDelayDialog }

View File

@ -0,0 +1,64 @@
import { api } from "boot/axios";
import { sha256 } from "js-sha256";
import { Notify, Dialog } from "quasar";
const API_URL = "/api";
const SHA256_HMAC_KEY = "mykey";
function ShowSaveDialog(apltype) {
const opername = ['Data applying...', 'Data saving...', 'Data saving and reboot...'];
let step = (apltype == 2) ? 1 : 10;
let percentage = 0;
const dialog = Dialog.create({ message: opername[apltype], progress: true, persistent: true, ok: false })
const interval = setInterval(() => {
percentage = Math.min(100, percentage + step);
dialog.update({
message: `${opername[apltype]} ${percentage}%`
})
if (percentage === 100) {
clearInterval(interval);
setTimeout(() => { dialog.hide() }, 350)
}
}, 100)
}
function PostData(varlist, messtype, applytype, onfinished) {
var pld = {};
var data = {};
data.msgid = Math.floor(Date.now() / 1000);
data.time = new Date().toISOString();
data.msgtype = messtype;
data.payloadtype = 1;
data.payload = {};
data.payload.applytype = applytype;
data.payload.variables = varlist;
pld.data = data;
pld.signature = sha256.hmac(SHA256_HMAC_KEY, JSON.stringify(data));
api
.post(API_URL, JSON.stringify(pld), {
headers: { "Content-Type": "application/json" },
})
.then((response) => {
var resp = response.data.data.payload.variables;
for (var k in resp) varlist[k] = resp[k];
if (onfinished) onfinished();
})
.catch((err) => {
Notify.create({ color: "negative", position: "top", message: err.message, icon: "report_problem", });
});
}
function SendAndRequest(varlist, mstp, apltp, shakey, okreport) {
var onfinish = (okreport) ? () => { ShowSaveDialog(apltp) } : null;
PostData(varlist, mstp, apltp, onfinish);
}
export { SendAndRequest, PostData };

View File

@ -0,0 +1,11 @@
<template>
<q-card-actions>
<q-btn flat v-on:click="SendAndRequest(senddata, 1, 0, 'mykey', true)">Apply</q-btn>
<q-btn flat v-on:click="SendAndRequest(senddata, 1, 1, 'mykey', true)">Save</q-btn>
<q-btn flat v-on:click="SendAndRequest(senddata, 1, 2, 'mykey', true)">Save&Reboot</q-btn>
</q-card-actions>
</template>
<script setup>
import { SendAndRequest } from "boot/network";
</script>

View File

@ -0,0 +1,48 @@
<template>
<q-item clickable tag="a" target="_self" :href="link">
<q-item-section v-if="icon" avatar>
<q-icon :name="icon" />
</q-item-section>
<q-item-section class="menu">
<q-item-label>{{ title }}</q-item-label>
<q-item-label caption>{{ caption }}</q-item-label>
</q-item-section>
</q-item>
</template>
<style>
.menu {
font-family: monospace;
font-size: larger;
}
</style>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "EssentialLink",
props: {
title: {
type: String,
required: true,
},
caption: {
type: String,
default: "",
},
link: {
type: String,
default: "#",
},
icon: {
type: String,
default: "",
},
},
});
</script>

View File

@ -0,0 +1,67 @@
<template>
<q-dialog ref="dialogRef" @hide="onDialogHide">
<q-card class="q-dialog-plugin">
<div class="q-pa-md q-gutter-md">
<q-card-section>
<q-inner-loading :showing="visible" label="Scaning WiFi..." label-class="text-teal"
label-style="font-size: 1.1em" />
</q-card-section>
<q-card-section>
<q-table title="WiFi networks" :rows="scandata.wifi_scan_res" :columns="columns" row-key="ssid"
hide-no-data="true" wrap-cells="true" @row-click="ClickOnRaw" />
</q-card-section>
<q-card-actions>
<q-btn label="OK" @click="onOKClick" />
<q-btn label="Cancel" @click="onDialogCancel" />
</q-card-actions>
</div>
</q-card>
</q-dialog>
</template>
<script setup>
import { useDialogPluginComponent } from 'quasar'
import { PostData } from "boot/network";
import { reactive, ref } from 'vue';
defineEmits([
// REQUIRED; need to specify some events that your
// component will emit through useDialogPluginComponent()
...useDialogPluginComponent.emits
])
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
// dialogRef - Vue ref to be applied to QDialog
// onDialogHide - Function to be used as handler for @hide on QDialog
// onDialogOK - Function to call to settle dialog with "ok" outcome
// example: onDialogOK() - no payload
// example: onDialogOK({ /*...*/ }) - with payload
// onDialogCancel - Function to call to settle dialog with "cancel" outcome
// this is part of our example (so not required)
function onOKClick() {
// on OK, it is REQUIRED to
// call onDialogOK (with optional payload)
onDialogOK()
// or with payload: onDialogOK({ ... })
// ...and it will also hide the dialog automatically
}
function ClickOnRaw(evt, row, index) {
alert(`clicked SSID ${row.ssid}`)
}
function onDataReady() {
visible.value = false;
}
let visible = ref(true);
const scandata = reactive({ wifi_scan_res: 10 });
PostData({ wifi_scan: 1 }, 2, 0, () => { });
setTimeout(() => { PostData(scandata, 2, 0, () => onDataReady()) }, 7000);
const columns = [
{ name: 'ssid', label: 'SSID', align: 'left', field: 'ssid', sortable: true },
{ name: 'rssi', label: 'RSSI', field: 'rssi', sortable: true },
{ name: 'ch', label: 'CHANNEL', field: 'ch', sortable: true },
]
</script>

View File

@ -0,0 +1,45 @@
<template>
<q-card flat bordered class="card" v-show="data.eth_visible">
<q-card-section>
<div class="text-h6">ETHERNET</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<q-toggle :dense="true" v-model="data.eth_enab" label="Ethernet enable" />
<q-input :dense="true" v-model="data.eth_ip" label="IP address:" />
<q-input :dense="true" v-model="data.eth_mask" label="Subnet mask:" />
<q-input :dense="true" v-model="data.eth_gw" label="Gateway address:" />
<q-toggle :dense="true" v-model="data.eth_isdhcp" label="DHCP enabled"></q-toggle>
<q-input :dense=true v-model="data.eth_dns1" label="DNS1:" />
<q-input :dense=true v-model="data.eth_dns2" label="DNS2:" />
<q-input :dense=true v-model="data.eth_dns3" label="DNS3:" />
<q-input :dense=true v-model="data.eth_mac" label="MAC:" />
</div>
</div>
</q-card-section>
<q-card-actions>
<q-btn flat v-on:click="SendAndRequest(data, 1, 0, 'mykey', true)">Apply</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 1, 'mykey', true)">Save</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 2, 'mykey', true)">Save&Reboot</q-btn>
</q-card-actions>
</q-card>
</template>
<script setup>
import { reactive } from "vue";
import { SendAndRequest } from "boot/network";
defineOptions({
name: 'EthSetCard'
})
const init = {
eth_visible: false,
eth_enab: true, eth_isdhcp: true, eth_ip: "", eth_mask: "",
eth_gw: "", eth_dns1: "", eth_dns2: "", eth_dns3: "", eth_mac: ""
}
const data = reactive(init);
SendAndRequest(data, 2, 0, 'mykey', false);
</script>

View File

@ -0,0 +1,41 @@
<template>
<q-card flat bordered class="card">
<q-card-section>
<div class="text-h6">FIRMWARE</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<q-toggle :dense="true" v-model="data.ota_enab" label="Eanble OTA autoudate" />
<q-toggle :dense="true" v-model="data.res_ota_enab" label="Enable reset after update" />
<q-input :dense="true" v-model="data.ota_url" label="OTA firmware file URL" />
<q-input :dense="true" v-model="data.ota_auto_int" label="New firmware check interval, sec" />
<div>Current firmware version:: {{ data.fw_rev }}</div>
<div>Available firmware version:: {{ data.fw_rev }}</div>
</div>
</div>
</q-card-section>
<q-card-actions>
<q-btn flat v-on:click="SendAndRequest(data, 1, 0, 'mykey', true)">Apply</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 1, 'mykey', true)">Save</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 2, 'mykey', true)">Save&Reboot</q-btn>
</q-card-actions>
</q-card>
</template>
<script setup>
import { reactive } from "vue";
import { SendAndRequest } from "boot/network";
defineOptions({
name: 'FirmwareCard'
})
const init = {
ota_enab: false, res_ota_enab: false, ota_url: "", ota_auto_int: 0, fw_rev: ""
}
const data = reactive(init);
SendAndRequest(data, 2, 0, 'mykey', false);
</script>

View File

@ -0,0 +1,44 @@
<template>
<q-card flat bordered class="card">
<q-card-section>
<div class="text-h6">Home</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<div>{{ timestr }}</div>
<div>Uptime: {{ uptimestr }}</div>
</div>
</div>
</q-card-section>
</q-card>
</template>
<script setup>
import { computed, onUnmounted, reactive, onMounted } from "vue";
import { SendAndRequest } from "boot/network";
import { secondsToHms } from "boot/helpers"
defineOptions({
name: 'HomeCard'
})
const init = {
time: 0,
uptime: 0
}
const data = reactive(init);
SendAndRequest(data, 2, 0, 'mykey', false);
let intervalId
onMounted(() => {
intervalId = setInterval(() => {
SendAndRequest(data, 2, 0, 'mykey', false);
}, 1000)
})
onUnmounted(() => clearInterval(intervalId))
const timestr = computed({ get() { return (new Date(data.time * 1000).toISOString()) } })
const uptimestr = computed({ get() { return (secondsToHms(data.uptime)) } })
</script>

View File

@ -0,0 +1,46 @@
<template>
<q-card flat bordered class="card">
<q-card-section>
<div class="text-h6">MQTT 1</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<q-toggle :dense="true" v-model="data.mqtt_1_enab" label="Eanble MQTT 1" />
<q-input :dense="true" v-model="data.mqtt_1_serv" label="MQTT broker URL " />
<q-input :dense="true" v-model="data.mqtt_1_port" label="MQTT broker port " />
<q-input :dense="true" v-model="data.mqtt_1_syst" label="Global system name " />
<q-input :dense="true" v-model="data.mqtt_1_group" label="Group name " />
<q-input :dense="true" v-model="data.mqtt_1_clid" label="Device ID prefix" />
<q-input :dense="true" v-model="data.mqtt_1_uname" label="Login" />
<q-input :dense="true" v-model="data.mqtt_1_pass" label="Password" />
</div>
</div>
</q-card-section>
<q-card-actions>
<q-btn flat v-on:click="SendAndRequest(data, 1, 0, 'mykey', true)">Apply</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 1, 'mykey', true)">Save</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 2, 'mykey', true)">Save&Reboot</q-btn>
</q-card-actions>
</q-card>
</template>
<script setup>
import { reactive } from "vue";
import { SendAndRequest } from "boot/network";
const init = {
mqtt_1_enab: false,
mqtt_1_serv: "",
mqtt_1_port: 1000,
mqtt_1_syst: "",
mqtt_1_group: "",
mqtt_1_clid: "",
mqtt_1_uname: "",
mqtt_1_pass: ""
}
const data = reactive(init);
SendAndRequest(data, 2, 0, 'mykey', false);
</script>

View File

@ -0,0 +1,51 @@
<template>
<q-card flat bordered class="card">
<q-card-section>
<div class="text-h6">MQTT 2</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<q-toggle :dense="true" v-model="data.mqtt_2_enab" label="Eanble MQTT 2" />
<q-input :dense="true" v-model="data.mqtt_2_serv" label="MQTT broker URL " />
<q-input :dense="true" v-model="data.mqtt_2_port" label="MQTT broker port " />
<q-input :dense="true" v-model="data.mqtt_2_syst" label="Global system name " />
<q-input :dense="true" v-model="data.mqtt_2_group" label="Group name " />
<q-input :dense="true" v-model="data.mqtt_2_clid" label="Device ID prefix" />
<q-input :dense="true" v-model="data.mqtt_2_uname" label="Login" />
<q-input :dense="true" v-model="data.mqtt_2_pass" label="Password" />
</div>
</div>
</q-card-section>
<q-card-actions>
<q-btn flat v-on:click="SendAndRequest(data, 1, 0, 'mykey', true)">Apply</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 1, 'mykey', true)">Save</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 2, 'mykey', true)">Save&Reboot</q-btn>
</q-card-actions>
<CardActions />
</q-card>
</template>
<script setup>
import { reactive } from "vue";
import { SendAndRequest } from "boot/network";
const init = {
mqtt_2_enab: false,
mqtt_2_serv: "",
mqtt_2_port: 1000,
mqtt_2_syst: "",
mqtt_2_group: "",
mqtt_2_clid: "",
mqtt_2_uname: "",
mqtt_2_pass: ""
}
const data = reactive(init);
SendAndRequest(data, 2, 0, 'mykey', false);
</script>

View File

@ -0,0 +1,39 @@
<template>
<q-card flat bordered class="card">
<q-card-section>
<div class="text-h6">SNTP</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<q-toggle :dense="true" v-model="data.sntp_enab" label="Eanble SNTP" />
<q-input :dense="true" v-model="data.sntp_serv1" label="SNTP server 1:" />
<q-input :dense="true" v-model="data.sntp_serv2" label="SNTP server 2:" />
<q-input :dense="true" v-model="data.sntp_serv3" label="SNTP server 3:" />
</div>
</div>
</q-card-section>
<q-card-actions>
<q-btn flat v-on:click="SendAndRequest(data, 1, 0, 'mykey', true)">Apply</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 1, 'mykey', true)">Save</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 2, 'mykey', true)">Save&Reboot</q-btn>
</q-card-actions>
</q-card>
</template>
<script setup>
import { reactive } from "vue";
import { SendAndRequest } from "boot/network";
const init = {
sntp_serv1: "",
sntp_serv2: "",
sntp_serv3: "",
sntp_enab: false
}
const data = reactive(init);
SendAndRequest(data, 2, 0, 'mykey', false);
</script>

View File

@ -0,0 +1,65 @@
<template>
<q-card flat bordered class="card">
<q-card-section>
<div class="text-h6">INFO</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<div>{{ timestr }}</div>
<div>Uptime: {{ uptimestr }}</div>
<div>WiFi signal: {{ data.wifi_level }}</div>
<q-separator inset />
<div>Ethernet state: {{ data.eth_stat }}</div>
<div>WiFi state: {{ data.wifi_stat }}</div>
<div>GPRS state: {{ data.gsm_stat }}</div>
<q-separator inset />
<div>MQTT1 state: {{ data.mqtt_1_stat }}</div>
<div>MQTT2 state: {{ data.mqtt_2_stat }}</div>
<q-separator inset />
<div>Free RAM: {{ data.free_ram }}</div>
<div>Minimal free RAM: {{ data.free_ram_min }}</div>
</div>
</div>
</q-card-section>
</q-card>
</template>
<script setup>
import { computed, onUnmounted, reactive, onMounted } from "vue";
import { SendAndRequest } from "boot/network";
import { secondsToHms } from "boot/helpers"
defineOptions({
name: 'StatCard'
})
const init = {
time: 0,
uptime: 0,
wifi_level: "",
eth_stat: "",
wifi_stat: "",
gsm_stat: "",
mqtt_1_stat: "",
mqtt_2_stat: "",
free_ram: 0,
free_ram_min: 0
}
const data = reactive(init);
SendAndRequest(data, 2, 0, 'mykey', false);
let intervalId
onMounted(() => {
intervalId = setInterval(() => {
SendAndRequest(data, 2, 0, 'mykey', false);
}, 1000)
})
onUnmounted(() => clearInterval(intervalId))
const timestr = computed({ get() { return (new Date(data.time * 1000).toISOString()) } })
const uptimestr = computed({ get() { return (secondsToHms(data.uptime)) } })
</script>

View File

@ -0,0 +1,43 @@
<template>
<q-card flat bordered class="card">
<q-card-section>
<div class="text-h6">SYSTEM</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<q-input :dense="true" v-model="data.net_bios_name" label="Device bios name" />
<q-input :dense="true" v-model="data.sys_name" label="User name" />
<q-input :dense="true" v-model="data.sys_pass" label="User password" />
<div>Device model: {{ data.model_name }}</div>
<div>Hardware revision: {{ data.hw_rev }}</div>
<div>Firmware version: {{ data.fw_rev }}</div>
<div>IDF version: {{ data.idf_rev }}</div>
<div>Build date: {{ data.build_date }}</div>
<div>Serial number: {{ data.ser_num }}</div>
</div>
</div>
</q-card-section>
<q-card-actions>
<q-btn flat v-on:click="SendAndRequest(data, 1, 0, 'mykey', true)">Apply</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 1, 'mykey', true)">Save</q-btn>
<q-btn flat v-on:click="SendAndRequest(data, 1, 2, 'mykey', true)">Save&Reboot</q-btn>
</q-card-actions>
</q-card>
</template>
<script setup>
import { reactive } from "vue";
import { SendAndRequest } from "boot/network";
defineOptions({
name: 'SystemCard'
})
const init = {
net_bios_name: "", sys_name: "", sys_pass: "",
model_name: "", hw_rev: 0, fw_rev: "", idf_rev: "", build_date: "", ser_num: ""
}
const data = reactive(init);
SendAndRequest(data, 2, 0, 'mykey', false);
</script>

View File

@ -0,0 +1,80 @@
<template>
<q-card flat bordered class="card">
<q-card-section>
<div class="text-h6">WiFi</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="q-pa-md">
<div class="q-gutter-md q-pa-none q-pb-none">
<q-toggle :dense="true" v-model="data.wifi_enab" label="WiFi enable" />
<q-btn :dense="true" label="Scan WiFi" @click="OpenScanDialog()"></q-btn>
<q-select :dense="true" v-model="wifimodestr" :options="wifimodes" :map-options="true" :emit-value="true"
label="WiFi mode"></q-select>
<q-input :dense="true" v-model="wifipwr" label="Max power, dBm" />
<q-input :dense="true" v-model="data.wifi_ap_ssid" label="WiFi network name(AP):" />
<q-input :dense="true" v-model="data.wifi_ap_key" label="WiFi network key(AP):" />
<q-input :dense="true" v-model="data.wifi_ap_ip" label="IP address(AP):" />
<q-input :dense="true" v-model="data.wifi_sta_ssid" label="WiFi network name(CLN):" />
<q-input :dense="true" v-model="data.wifi_sta_key" label="WiFi network key(CLN):" />
<q-toggle :dense="true" v-model="data.wifi_isdhcp" label="DHCP enabled"></q-toggle>
<q-input :dense="true" v-model="data.wifi_sta_ip" label="IP address(CLN):" />
<q-input :dense="true" v-model="data.wifi_sta_mask" label="Subnet mask(CLN):" />
<q-input :dense="true" v-model="data.wifi_sta_gw" label="Gateway address(CLN):" />
<q-input :dense=true v-model="data.wifi_dns1" label="DNS1:" />
<q-input :dense=true v-model="data.wifi_dns2" label="DNS2:" />
<q-input :dense=true v-model="data.wifi_dns3" label="DNS3:" />
<q-input :dense=true v-model="data.wifi_sta_mac" label="MAC(CLN):" />
<q-input :dense=true v-model="data.wifi_ap_mac" label="MAC(AP):" />
</div>
</div>
</q-card-section>
<CardActions :senddata="data"></CardActions>
</q-card>
</template>
<script setup>
import { computed, reactive } from "vue";
import { SendAndRequest } from "components/webguicomp/network";
import { useQuasar } from 'quasar'
import SelectWiFiDialog from 'components/webguicomp/SelectWiFiDialog.vue'
import CardActions from "components/webguicomp/CardActions.vue"
const $q = useQuasar();
defineOptions({
name: 'WifiSetCard'
})
const init = {
wifi_enab: true, wifi_mode: 1, wifi_power: 0, wifi_ap_ssid: "", wifi_ap_key: "",
wifi_ap_ip: "", wifi_sta_ssid: "", wifi_sta_key: "", wifi_isdhcp: true, wifi_sta_ip: "",
wifi_sta_mask: "", wifi_sta_gw: "", wifi_dns1: "", wifi_dns2: "", wifi_dns3: "",
wifi_sta_mac: "", wifi_ap_mac: ""
}
const wifimodes = [
{ label: 'Station (STA)', value: '1' },
{ label: 'Access point (AP)', value: '2' },
{ label: 'Mixed mode (AP+STA)', value: '3' }];
const data = reactive(init);
const wifimodestr = computed({
get() { return (data.wifi_mode).toString() },
set(val) { data.wifi_mode = Number(val); }
})
const wifipwr = computed({
get() { return (data.wifi_power) / 4 },
set(val) { data.wifi_power = val * 4; }
})
function OpenScanDialog() {
$q.dialog({
component: SelectWiFiDialog
});
};
SendAndRequest(data, 2, 0, 'mykey', false);
</script>

View File

@ -0,0 +1,64 @@
import { api } from "boot/axios";
import { sha256 } from "js-sha256";
import { Notify, Dialog } from "quasar";
const API_URL = "/api";
const SHA256_HMAC_KEY = "mykey";
function ShowSaveDialog(apltype) {
const opername = ['Data applying...', 'Data saving...', 'Data saving and reboot...'];
let step = (apltype == 2) ? 1 : 10;
let percentage = 0;
const dialog = Dialog.create({ message: opername[apltype], progress: true, persistent: true, ok: false })
const interval = setInterval(() => {
percentage = Math.min(100, percentage + step);
dialog.update({
message: `${opername[apltype]} ${percentage}%`
})
if (percentage === 100) {
clearInterval(interval);
setTimeout(() => { dialog.hide() }, 350)
}
}, 100)
}
function PostData(varlist, messtype, applytype, onfinished) {
var pld = {};
var data = {};
data.msgid = Math.floor(Date.now() / 1000);
data.time = new Date().toISOString();
data.msgtype = messtype;
data.payloadtype = 1;
data.payload = {};
data.payload.applytype = applytype;
data.payload.variables = varlist;
pld.data = data;
pld.signature = sha256.hmac(SHA256_HMAC_KEY, JSON.stringify(data));
api
.post(API_URL, JSON.stringify(pld), {
headers: { "Content-Type": "application/json" },
})
.then((response) => {
var resp = response.data.data.payload.variables;
for (var k in resp) varlist[k] = resp[k];
if (onfinished) onfinished();
})
.catch((err) => {
Notify.create({ color: "negative", position: "top", message: err.message, icon: "report_problem", });
});
}
function SendAndRequest(varlist, mstp, apltp, shakey, okreport) {
var onfinish = (okreport) ? () => { ShowSaveDialog(apltp) } : null;
PostData(varlist, mstp, apltp, onfinish);
}
export { SendAndRequest, PostData };

View File

@ -0,0 +1,28 @@
// app global css in SCSS form
.body{
background: #e0e0e0;
}
.card {
border-radius: 10px;
font-family: monospace;
font-size: medium;
break-inside: avoid;
margin: 0 0 10px 10px;
}
.cardholder {
column-count: 1;
column-gap: 0;
padding: 10px 10px 10px 0;
}
@media (min-width: 1024px) {
.cardholder {
column-count: 2;
}
}
@media (min-width: 1400px) {
.cardholder {
column-count: 3;
}
}

View File

@ -0,0 +1,25 @@
// Quasar SCSS (& Sass) Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
// Check documentation for full list of Quasar variables
// Your own variables (that are declared here) and Quasar's own
// ones will be available out of the box in your .vue/.scss/.sass files
// It's highly recommended to change the default colors
// to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #1976D2;
$secondary : #26A69A;
$accent : #9C27B0;
$dark : #1D1D1D;
$dark-page : #121212;
$positive : #21BA45;
$negative : #C10015;
$info : #31CCEC;
$warning : #F2C037;

View File

@ -0,0 +1,37 @@
<template>
<q-layout view="hLr Lpr lFf" class="body">
<q-header>
<q-toolbar>
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
<q-toolbar-title> ESP32 WEBGUIAPP </q-toolbar-title>
</q-toolbar>
</q-header>
<q-drawer v-model="leftDrawerOpen" show-if-above bordered>
<q-list>
<EssentialLink v-for="link in linksList" :key="link.title" v-bind="link" />
</q-list>
</q-drawer>
<q-page-container>
<router-view></router-view>
</q-page-container>
</q-layout>
</template>
<script setup>
import { ref, defineAsyncComponent } from "vue";
import EssentialLink from "components/webguicomp/EssentialLink.vue";
const linksList = [
{ title: "HOME", caption: "Main page", icon: "home", link: "#/home" },
{ title: "APPLICATION", caption: "Application", icon: "apps", link: "#/page2" },
{ title: "NETWORK", caption: "Network settings", icon: "public", link: "#/ifsettings" },
{ title: "SERVICES", caption: "System services", icon: "miscellaneous_services", link: "#/services" },
{ title: "SYSTEM", caption: "System tools", icon: "build", link: "#/system" }
];
function toggleLeftDrawer() { leftDrawerOpen.value = !leftDrawerOpen.value; }
const leftDrawerOpen = ref(false);
</script>

View File

@ -0,0 +1,31 @@
<template>
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
<div>
<div style="font-size: 30vh">
404
</div>
<div class="text-h2" style="opacity:.4">
Oops. Nothing here... four-ow-four
</div>
<q-btn
class="q-mt-xl"
color="white"
text-color="blue"
unelevated
to="/"
label="Go Home"
no-caps
/>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'ErrorNotFound'
})
</script>

View File

@ -0,0 +1,15 @@
<template>
<div class="cardholder">
<HomeCard></HomeCard>
</div>
</template>
<script>
import HomeCard from "components/webguicomp/cards/HomeCard.vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "HomePage",
components: { HomeCard }
});
</script>

View File

@ -0,0 +1,36 @@
<template>
<q-page class="flex flex-center">
<div class="q-gutter-md row justify-center" style="font-size: 15em">
<q-spinner-audio color="secondary" />
<q-spinner-ball color="red" />
<q-spinner-bars color="purple" />
<q-spinner-box color="deep-orange" />
<q-spinner-clock color="brown" />
<q-spinner-comment color="deep-purple" />
<q-spinner-cube color="indigo" />
<q-spinner-dots color="blue" />
<q-spinner-facebook color="light-blue" />
<q-spinner-gears color="cyan" />
<q-spinner-grid color="teal" />
<q-spinner-hearts color="green" />
<q-spinner-hourglass color="light-green" />
<q-spinner-infinity color="lime" />
<q-spinner-ios color="yellow" />
<q-spinner-orbit color="blue" />
<q-spinner-oval color="amber" />
<q-spinner-pie color="orange" />
<q-spinner-puff color="deep-orange" />
<q-spinner-radio color="brown" />
<q-spinner-rings color="grey" />
<q-spinner-tail color="blue-grey" />
</div>
</q-page>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "IndexPage2",
});
</script>

View File

@ -0,0 +1,15 @@
<template>
<div class="cardholder">
<WifiSetCard></WifiSetCard>
<EthSetCard></EthSetCard>
</div>
</template>
<script setup>
import WifiSetCard from "components/webguicomp/cards/WifiSetCard.vue";
import EthSetCard from "components/webguicomp/cards/EthSetCard.vue";
defineOptions({
name: 'InterfacesSettings'
})
</script>

View File

@ -0,0 +1,17 @@
<template>
<div class="cardholder">
<SNTPCard></SNTPCard>
<MQTT1Card></MQTT1Card>
<MQTT2Card></MQTT2Card>
</div>
</template>
<script setup>
import SNTPCard from "components/webguicomp/cards/SNTPCard.vue";
import MQTT1Card from "components/webguicomp/cards/MQTT1Card.vue";
import MQTT2Card from "components/webguicomp/cards/MQTT2Card.vue";
defineOptions({
name: 'ServicesPage'
})
</script>

View File

@ -0,0 +1,17 @@
<template>
<div class="cardholder">
<SystemCard></SystemCard>
<FirmwareCard></FirmwareCard>
<StatCard></StatCard>
</div>
</template>
<script setup>
import SystemCard from "components/webguicomp/cards/SystemCard.vue";
import FirmwareCard from "components/webguicomp/cards/FirmwareCard.vue";
import StatCard from "components/webguicomp/cards/StatCard.vue";
defineOptions({
name: 'SystemPage'
})
</script>

View File

@ -0,0 +1,30 @@
import { route } from 'quasar/wrappers'
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'
import routes from './routes'
/*
* If not building with SSR mode, you can
* directly export the Router instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Router instance.
*/
export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER
? createMemoryHistory
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory)
const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
routes,
// Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
history: createHistory(process.env.VUE_ROUTER_BASE)
})
return Router
})

View File

@ -0,0 +1,23 @@
const routes = [
{
path: "/",
component: () => import("layouts/MainLayout.vue"),
children: [
{ path: "", component: () => import("pages/Home.vue") },
{ path: "home", component: () => import("pages/Home.vue") },
{ path: "page2", component: () => import("pages/IndexPage2.vue") },
{ path: "ifsettings", component: () => import("pages/InterfacesSettings.vue") },
{ path: "services", component: () => import("pages/ServicesPage.vue") },
{ path: "system", component: () => import("pages/SystemPage.vue") },
],
},
// Always leave this as last one,
// but you can also remove it
{
path: "/:catchAll(.*)*",
component: () => import("pages/ErrorNotFound.vue"),
},
];
export default routes;

@ -1 +1 @@
Subproject commit ac3be6bfdc745ec23e97961657fc9bc19e55333f
Subproject commit fc2b1879e4ae43213c255567077b46fa41092159

View File

@ -1,26 +1,26 @@
assets
assets/ErrorNotFound.20af62e3.js
assets/Home.937c89f4.js
assets/IndexPage2.cfd8712b.js
assets/InterfacesSettings.b0f34fc0.js
assets/KFOkCnqEu92Fr1MmgVxIIzQ.34e9582c.woff
assets/KFOlCnqEu92Fr1MmEU9fBBc-.9ce7f3ac.woff
assets/KFOlCnqEu92Fr1MmSU5fBBc-.bf14c7d7.woff
assets/KFOlCnqEu92Fr1MmWUlfBBc-.e0fd57c0.woff
assets/KFOlCnqEu92Fr1MmYUtfBBc-.f6537e32.woff
assets/KFOmCnqEu92Fr1Mu4mxM.f2abf7fb.woff
assets/MainLayout.c9e97164.css
assets/MainLayout.d8111854.js
assets/QItem.9b856cf7.js
assets/ServicesPage.e7ae390c.js
assets/SystemPage.60d49a6a.js
assets/axios.15d8ef65.js
assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.fd84f88b.woff
assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.4a4dbc62.woff2
assets/helpers.6b816d79.js
assets/index.6b461d14.css
assets/index.e05c0c8e.js
assets/network.98844594.js
favicon.ico
index.html
assets
assets/ErrorNotFound.9e15f11d.js
assets/Home.4d198b4c.js
assets/IndexPage2.9b840c18.js
assets/InterfacesSettings.bbb3ac8c.js
assets/KFOkCnqEu92Fr1MmgVxIIzQ.34e9582c.woff
assets/KFOlCnqEu92Fr1MmEU9fBBc-.9ce7f3ac.woff
assets/KFOlCnqEu92Fr1MmSU5fBBc-.bf14c7d7.woff
assets/KFOlCnqEu92Fr1MmWUlfBBc-.e0fd57c0.woff
assets/KFOlCnqEu92Fr1MmYUtfBBc-.f6537e32.woff
assets/KFOmCnqEu92Fr1Mu4mxM.f2abf7fb.woff
assets/MainLayout.3e18d42f.js
assets/MainLayout.c9e97164.css
assets/QItem.d253ebaa.js
assets/ServicesPage.cf8d157c.js
assets/SystemPage.719ebe80.js
assets/axios.06fc16f1.js
assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.fd84f88b.woff
assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.4a4dbc62.woff2
assets/helpers.8d380ce8.js
assets/index.4735cda6.js
assets/index.6b461d14.css
assets/network.fb530964.js
favicon.ico
index.html
test.json