Initial commit
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
client_id
|
||||
.idea/
|
||||
114
.vscode/.ropeproject/config.py
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# The default ``config.py``
|
||||
# flake8: noqa
|
||||
|
||||
|
||||
def set_prefs(prefs):
|
||||
"""This function is called before opening the project"""
|
||||
|
||||
# Specify which files and folders to ignore in the project.
|
||||
# Changes to ignored resources are not added to the history and
|
||||
# VCSs. Also they are not returned in `Project.get_files()`.
|
||||
# Note that ``?`` and ``*`` match all characters but slashes.
|
||||
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
|
||||
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
|
||||
# '.svn': matches 'pkg/.svn' and all of its children
|
||||
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
|
||||
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
|
||||
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
|
||||
'.hg', '.svn', '_svn', '.git', '.tox']
|
||||
|
||||
# Specifies which files should be considered python files. It is
|
||||
# useful when you have scripts inside your project. Only files
|
||||
# ending with ``.py`` are considered to be python files by
|
||||
# default.
|
||||
# prefs['python_files'] = ['*.py']
|
||||
|
||||
# Custom source folders: By default rope searches the project
|
||||
# for finding source folders (folders that should be searched
|
||||
# for finding modules). You can add paths to that list. Note
|
||||
# that rope guesses project source folders correctly most of the
|
||||
# time; use this if you have any problems.
|
||||
# The folders should be relative to project root and use '/' for
|
||||
# separating folders regardless of the platform rope is running on.
|
||||
# 'src/my_source_folder' for instance.
|
||||
# prefs.add('source_folders', 'src')
|
||||
|
||||
# You can extend python path for looking up modules
|
||||
# prefs.add('python_path', '~/python/')
|
||||
|
||||
# Should rope save object information or not.
|
||||
prefs['save_objectdb'] = True
|
||||
prefs['compress_objectdb'] = False
|
||||
|
||||
# If `True`, rope analyzes each module when it is being saved.
|
||||
prefs['automatic_soa'] = True
|
||||
# The depth of calls to follow in static object analysis
|
||||
prefs['soa_followed_calls'] = 0
|
||||
|
||||
# If `False` when running modules or unit tests "dynamic object
|
||||
# analysis" is turned off. This makes them much faster.
|
||||
prefs['perform_doa'] = True
|
||||
|
||||
# Rope can check the validity of its object DB when running.
|
||||
prefs['validate_objectdb'] = True
|
||||
|
||||
# How many undos to hold?
|
||||
prefs['max_history_items'] = 32
|
||||
|
||||
# Shows whether to save history across sessions.
|
||||
prefs['save_history'] = True
|
||||
prefs['compress_history'] = False
|
||||
|
||||
# Set the number spaces used for indenting. According to
|
||||
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
|
||||
# unit-tests use 4 spaces it is more reliable, too.
|
||||
prefs['indent_size'] = 4
|
||||
|
||||
# Builtin and c-extension modules that are allowed to be imported
|
||||
# and inspected by rope.
|
||||
prefs['extension_modules'] = []
|
||||
|
||||
# Add all standard c-extensions to extension_modules list.
|
||||
prefs['import_dynload_stdmods'] = True
|
||||
|
||||
# If `True` modules with syntax errors are considered to be empty.
|
||||
# The default value is `False`; When `False` syntax errors raise
|
||||
# `rope.base.exceptions.ModuleSyntaxError` exception.
|
||||
prefs['ignore_syntax_errors'] = False
|
||||
|
||||
# If `True`, rope ignores unresolvable imports. Otherwise, they
|
||||
# appear in the importing namespace.
|
||||
prefs['ignore_bad_imports'] = False
|
||||
|
||||
# If `True`, rope will insert new module imports as
|
||||
# `from <package> import <module>` by default.
|
||||
prefs['prefer_module_from_imports'] = False
|
||||
|
||||
# If `True`, rope will transform a comma list of imports into
|
||||
# multiple separate import statements when organizing
|
||||
# imports.
|
||||
prefs['split_imports'] = False
|
||||
|
||||
# If `True`, rope will remove all top-level import statements and
|
||||
# reinsert them at the top of the module when making changes.
|
||||
prefs['pull_imports_to_top'] = True
|
||||
|
||||
# If `True`, rope will sort imports alphabetically by module name instead
|
||||
# of alphabetically by import statement, with from imports after normal
|
||||
# imports.
|
||||
prefs['sort_imports_alphabetically'] = False
|
||||
|
||||
# Location of implementation of
|
||||
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
|
||||
# case, you don't have to change this value, unless you're an rope expert.
|
||||
# Change this value to inject you own implementations of interfaces
|
||||
# listed in module rope.base.oi.type_hinting.providers.interfaces
|
||||
# For example, you can add you own providers for Django Models, or disable
|
||||
# the search type-hinting in a class hierarchy, etc.
|
||||
prefs['type_hinting_factory'] = (
|
||||
'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
|
||||
|
||||
|
||||
def project_opened(project):
|
||||
"""This function is called after opening the project"""
|
||||
# Do whatever you like here!
|
||||
37
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
// 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": "vuejs: chrome",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}/src",
|
||||
"breakOnLoad": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///src/*": "${webRoot}/*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Python: Flask",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "flask",
|
||||
"cwd": "${fileDirname}",
|
||||
"env": {
|
||||
"FLASK_APP": "server.py",
|
||||
"FLASK_ENV": "development",
|
||||
"FLASK_DEBUG": "0"
|
||||
},
|
||||
"args": [
|
||||
"run",
|
||||
"--no-debugger",
|
||||
"--no-reload"
|
||||
],
|
||||
"jinja": true
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.pythonPath": "/home/fergalm/.virtualenvs/BitchMin/bin/python"
|
||||
}
|
||||
1
client/.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
13
client/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
0
client/.env
Normal file
30
client/.eslntrc.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"eslint:recommended",
|
||||
"prettier",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"no-debugger": "off",
|
||||
"prettier/prettier": "error",
|
||||
"max-len": [
|
||||
"error",
|
||||
{
|
||||
"code": 80
|
||||
}
|
||||
],
|
||||
"indent": [
|
||||
"error",
|
||||
2
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
"prettier"
|
||||
]
|
||||
}
|
||||
23
client/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
6
client/.nginx/default.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
server {
|
||||
location / {
|
||||
root /var/www/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
23
client/.nginx/nginx.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile off;
|
||||
|
||||
keepalive_timeout 60;
|
||||
|
||||
#gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
4
client/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true
|
||||
}
|
||||
33
client/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM node:14-stretch as build-stage
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Create the container from the alpine linux image
|
||||
FROM alpine:3.7 as production-image
|
||||
|
||||
# Add nginx and nodejs
|
||||
RUN apk add --update nginx
|
||||
|
||||
# Create the directories we will need
|
||||
RUN mkdir -p /var/log/nginx
|
||||
RUN mkdir -p /var/www/html
|
||||
|
||||
# Copy the respective nginx configuration files
|
||||
COPY .nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY .nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# copy the built app to our served directory
|
||||
COPY --from=build-stage /app/dist /var/www/html
|
||||
|
||||
# make all files belong to the nginx user
|
||||
RUN chown nginx:nginx /var/www/html
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
# start nginx and keep the process from backgrounding and the container from quitting
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
24
client/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# client
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
0
client/app.db
Normal file
5
client/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
9
client/build.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# docker build --no-cache -t fergalmoran/bitchns .
|
||||
# --platform linux/arm/v7,linux/amd64 \
|
||||
|
||||
docker buildx build \
|
||||
--push \
|
||||
--platform linux/arm/v7,linux/amd64 \
|
||||
-t fergalmoran/bitchmin-web .
|
||||
77
client/package.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"name": "bitchmin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^5.5.55",
|
||||
"@types/jquery": "^3.5.1",
|
||||
"@types/jwt-decode": "^2.2.1",
|
||||
"@types/vue-select": "^2.5.0",
|
||||
"axios": "^0.20.0",
|
||||
"axios-auth-refresh": "^3.0.0",
|
||||
"bootstrap": "^4.5.2",
|
||||
"compass-mixins": "^0.12.10",
|
||||
"core-js": "^3.6.5",
|
||||
"dayjs": "^1.8.35",
|
||||
"ee-first": "^1.1.1",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"fs": "^0.0.1-security",
|
||||
"jquery": "^3.5.1",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"prettier": "^2.1.1",
|
||||
"qs": "^6.9.4",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-color": "^2.7.1",
|
||||
"vue-property-decorator": "^8.4.2",
|
||||
"vue-router": "^3.2.0",
|
||||
"vue-select": "^3.10.8",
|
||||
"vue-toasted": "^1.1.28",
|
||||
"vuex": "^3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"eslint": "^7.7.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass-loader": "^10.0.1",
|
||||
"style-resources-loader": "^1.3.2",
|
||||
"typescript": "~3.9.3",
|
||||
"vue-cli-plugin-style-resources-loader": "~0.1.4",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
BIN
client/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
client/public/images/dashboard/circle.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
23
client/public/images/dashboard/circle.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 153 187" style="enable-background:new 0 0 153 187;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0.2;fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<title>3</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Mask-_x2B_-Mask-Mask" transform="translate(14.000000, 0.000000)">
|
||||
<g id="Mask">
|
||||
</g>
|
||||
<g id="Mask_1_">
|
||||
<path class="st0" d="M138,141.2c-3.6,0.5-7.3,0.8-11,0.8c-29.6,0-55.4-16.5-68.6-40.9c-6-11-9.4-23.7-9.4-37.1
|
||||
c0-26.5,13.2-49.9,33.4-64H138v129.5"/>
|
||||
</g>
|
||||
<g id="Mask_2_">
|
||||
<path class="st0" d="M138,141.2V187H-15c0.2-43.3,31.9-79.1,73.4-85.9c4.6-0.8,9.3-1.1,14.1-1.1c26.1,0,49.5,11.4,65.5,29.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 920 B |
BIN
client/public/images/dashboard/img_1.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
client/public/images/dashboard/img_2.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
client/public/images/dashboard/img_3.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
client/public/images/dashboard/img_4.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
client/public/images/faces-clipart/pic-1.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
client/public/images/faces-clipart/pic-2.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
client/public/images/faces-clipart/pic-3.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
client/public/images/faces-clipart/pic-4.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
client/public/images/faces/face1.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
client/public/images/faces/face10.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
client/public/images/faces/face11.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
client/public/images/faces/face12.jpg
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
client/public/images/faces/face13.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
client/public/images/faces/face14.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
client/public/images/faces/face15.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
client/public/images/faces/face16.jpg
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
client/public/images/faces/face17.jpg
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
client/public/images/faces/face18.jpg
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
client/public/images/faces/face19.jpg
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
client/public/images/faces/face2.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
client/public/images/faces/face20.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
client/public/images/faces/face21.jpg
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
client/public/images/faces/face22.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
client/public/images/faces/face23.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
client/public/images/faces/face24.jpg
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
client/public/images/faces/face25.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
client/public/images/faces/face26.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
client/public/images/faces/face27.jpg
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
client/public/images/faces/face3.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
client/public/images/faces/face4.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
client/public/images/faces/face5.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
client/public/images/faces/face6.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
client/public/images/faces/face7.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
client/public/images/faces/face8.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
client/public/images/faces/face9.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
client/public/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
client/public/images/favicon.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
41
client/public/images/logo-mini.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 102.5 102.5" style="enable-background:new 0 0 102.5 102.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:url(#SVGID_2_);}
|
||||
.st2{fill:url(#SVGID_3_);}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="27.6825" x2="656.5567" y2="27.6825">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st0" d="M100.2,24L53.1,0.4c-1.2-0.6-2.5-0.6-3.7,0L2.3,24C0.9,24.7,0,26.1,0,27.7s0.9,3,2.3,3.7l47.1,23.5
|
||||
c0.6,0.3,1.2,0.4,1.9,0.4c0.6,0,1.3-0.1,1.9-0.4l47.1-23.5c1.4-0.7,2.3-2.1,2.3-3.7C102.5,26.1,101.6,24.7,100.2,24z M51.2,46.6
|
||||
L13.4,27.7L51.2,8.8l37.8,18.9L51.2,46.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="86.5418" x2="656.5567" y2="86.5418">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st1" d="M102,72.9c-1-2-3.5-2.9-5.6-1.9L51.2,93.7L6,71.1c-2-1-4.5-0.2-5.6,1.9c-1,2-0.2,4.5,1.9,5.6L49.4,102
|
||||
c0.6,0.3,1.2,0.4,1.9,0.4c0.6,0,1.3-0.1,1.9-0.4l47.1-23.5C102.2,77.4,103,75,102,72.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="62.9979" x2="656.5567" y2="62.9979">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st2" d="M102,49.4c-1-2-3.5-2.9-5.6-1.9L51.2,70.1L6,47.5c-2-1-4.5-0.2-5.6,1.9c-1,2-0.2,4.5,1.9,5.6l47.1,23.5
|
||||
c0.6,0.3,1.2,0.4,1.9,0.4c0.6,0,1.3-0.1,1.9-0.4l47.1-23.5C102.2,53.9,103,51.4,102,49.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
BIN
client/public/images/logo.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
90
client/public/images/logo.svg
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 636.1 140.4" style="enable-background:new 0 0 636.1 140.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:url(#SVGID_2_);}
|
||||
.st2{fill:url(#SVGID_3_);}
|
||||
.st3{fill:url(#SVGID_4_);}
|
||||
.st4{fill:url(#SVGID_5_);}
|
||||
.st5{fill:url(#SVGID_6_);}
|
||||
.st6{fill:url(#SVGID_7_);}
|
||||
.st7{fill:url(#SVGID_8_);}
|
||||
.st8{fill:url(#SVGID_9_);}
|
||||
</style>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="54.2922" x2="656.5567" y2="54.2922">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st0" d="M202.9,65.2v38.1h-23.9v-98h38.7c7.6,0,14.2,1.4,19.6,4.2c5.4,2.8,9.5,6.5,12.3,11c2.7,4.6,4.1,9.5,4.1,14.8
|
||||
c0,4.5-1.1,9-3.3,13.5c-2.2,4.5-6,8.4-11.4,11.6c-5.4,3.2-12.5,4.8-21.4,4.8H202.9z M226.3,43.8c2.1-2.1,3.1-5,3.1-8.5
|
||||
c0-3.4-1.1-6.3-3.2-8.5c-2.1-2.2-5-3.3-8.5-3.3h-14.8V47h14.8C221.4,47,224.2,46,226.3,43.8z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="64.76" x2="656.5567" y2="64.76">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st1" d="M340.9,25.4v77.9H317V91.8c-2.3,3.8-5.5,6.8-9.6,9c-4,2.2-8.7,3.3-14,3.3c-5.9,0-11-1.3-15.6-4
|
||||
c-4.5-2.7-8-6.5-10.6-11.5c-2.6-5-3.8-10.9-3.8-17.7V25.4h23.7v42.3c0,5,1.3,8.9,4,11.7c2.7,2.7,6.3,4.1,11,4.1
|
||||
c4.7,0,8.3-1.4,11-4.1c2.7-2.7,4-6.6,4-11.7V25.4H340.9z"/>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="63.9224" x2="656.5567" y2="63.9224">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st2" d="M395.3,28.7c3.8-2.7,8-4.1,12.7-4.1v24.3h-5.9c-5.5,0-9.6,1.3-12.4,3.9c-2.7,2.6-4.1,7.2-4.1,13.7v36.8h-23.9
|
||||
V25.4h23.9v14.5C388.3,35.2,391.5,31.4,395.3,28.7z"/>
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="82.4849" x2="656.5567" y2="82.4849">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st3" d="M485.5,29.7c5.4,3.4,9.7,8.1,12.9,14.2c3.2,6,4.7,12.9,4.7,20.7c0,7.7-1.6,14.6-4.7,20.7
|
||||
c-3.2,6-7.5,10.7-12.9,14.1s-11.4,5-17.8,5c-9.7,0-17.3-4.1-22.9-12.3v48.4H421v-115h23.9v10.2c5.3-7.3,12.9-11,22.9-11
|
||||
C474.2,24.6,480.1,26.3,485.5,29.7z M453.7,47.4c-2.6,1.4-4.7,3.6-6.4,6.4c-1.6,2.8-2.4,6.4-2.4,10.5c0,4.2,0.8,7.7,2.4,10.6
|
||||
c1.6,2.9,3.8,5,6.4,6.5c2.7,1.4,5.4,2.2,8.2,2.2c2.9,0,5.6-0.7,8.2-2.2c2.6-1.4,4.7-3.6,6.3-6.4c1.6-2.8,2.4-6.4,2.4-10.5
|
||||
c0-4.2-0.8-7.7-2.4-10.6c-1.6-2.9-3.8-5-6.4-6.5c-2.7-1.4-5.4-2.2-8.2-2.2C459,45.2,456.3,45.9,453.7,47.4z"/>
|
||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="51.6404" x2="656.5567" y2="51.6404">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st4" d="M542.3,0v103.3h-23.9V0H542.3z"/>
|
||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="64.4106" x2="656.5567" y2="64.4106">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st5" d="M635.5,72.2h-53.6c0.5,3.4,2,6.3,4.6,8.4c2.6,2.2,5.9,3.3,9.8,3.3c3,0,5.2-0.4,6.7-1.3c1.5-0.9,3.1-2.3,4.7-4.4
|
||||
h25.8c-2.2,7.8-6.7,14.1-13.4,18.9c-6.7,4.8-14.7,7.2-23.9,7.2c-7.4,0-14.1-1.6-20-4.9c-5.9-3.3-10.5-7.9-13.8-14
|
||||
c-3.3-6.1-5-13.1-5-21.1c0-8,1.7-15,5-21c3.3-6,7.9-10.6,13.7-13.8c5.9-3.2,12.5-4.8,20-4.8c7.5,0,14.3,1.6,20.4,4.7
|
||||
c6,3.1,10.8,7.7,14.3,13.7c3.5,6,5.2,13.3,5.2,21.6C636.1,67.3,635.9,69.8,635.5,72.2z M611.2,57.2c-0.4-3.6-2-6.6-4.7-8.9
|
||||
c-2.8-2.3-6.2-3.4-10.3-3.4c-3.5,0-6.6,1.1-9.2,3.4c-2.6,2.3-4.2,5.2-4.9,8.9H611.2z"/>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="33.7483" x2="656.5567" y2="33.7483">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st6" d="M100.2,30L53.1,6.5c-1.2-0.6-2.5-0.6-3.7,0L2.3,30C0.9,30.7,0,32.2,0,33.7c0,1.6,0.9,3,2.3,3.7L49.4,61
|
||||
c0.6,0.3,1.2,0.4,1.9,0.4c0.6,0,1.3-0.1,1.9-0.4l47.1-23.5c1.4-0.7,2.3-2.1,2.3-3.7C102.5,32.2,101.6,30.7,100.2,30z M51.2,52.7
|
||||
L13.4,33.7l37.8-18.9l37.8,18.9L51.2,52.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="92.6076" x2="656.5567" y2="92.6076">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st7" d="M102,79c-1-2-3.5-2.9-5.6-1.9L51.2,99.8L6,77.1c-2-1-4.5-0.2-5.6,1.9c-1,2-0.2,4.5,1.9,5.6l47.1,23.5
|
||||
c0.6,0.3,1.2,0.4,1.9,0.4c0.6,0,1.3-0.1,1.9-0.4l47.1-23.5C102.2,83.5,103,81,102,79z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="-14.647" y1="69.0637" x2="656.5567" y2="69.0637">
|
||||
<stop offset="1.502404e-03" style="stop-color:#DA8CFF"/>
|
||||
<stop offset="1" style="stop-color:#9A55FF"/>
|
||||
</linearGradient>
|
||||
<path class="st8" d="M102,55.4c-1-2-3.5-2.9-5.6-1.9L51.2,76.2L6,53.6c-2-1-4.5-0.2-5.6,1.9c-1,2-0.2,4.5,1.9,5.6l47.1,23.5
|
||||
c0.6,0.3,1.2,0.4,1.9,0.4c0.6,0,1.3-0.1,1.9-0.4L100.2,61C102.2,60,103,57.5,102,55.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
BIN
client/public/images/sprites/blue.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
client/public/images/sprites/dark.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
client/public/images/sprites/flag.png
Normal file
|
After Width: | Height: | Size: 400 B |
BIN
client/public/images/sprites/green.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
client/public/images/sprites/jsgrid-icons.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
client/public/images/sprites/red.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
client/public/images/sprites/yellow.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
25
client/public/index.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible"
|
||||
content="IE=edge">
|
||||
<meta name="viewport"
|
||||
content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon"
|
||||
href="<%= BASE_URL %>favicon.ico">
|
||||
<title>Bitch::Min</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but BitchMin doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
53
client/src/App.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-scroller" v-if="isAuthenticated">
|
||||
<TopBarNav />
|
||||
<div class="container-fluid page-body-wrapper">
|
||||
<SideBarNav />
|
||||
<div class="main-panel">
|
||||
<div class="content-wrapper">
|
||||
<router-view />
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAuthenticated">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import TopBarNav from '@/components/TopBarNav.vue'; // @ is an alias to /src
|
||||
import SideBarNav from '@/components/SideBarNav.vue'; // @ is an alias to /src
|
||||
import Footer from '@/components/Footer.vue'; // @ is an alias to /src
|
||||
import vSelect from 'vue-select';
|
||||
|
||||
import store from '@/store';
|
||||
|
||||
Vue.component('v-select', vSelect);
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
TopBarNav,
|
||||
SideBarNav,
|
||||
Footer,
|
||||
},
|
||||
})
|
||||
export default class App extends Vue {
|
||||
async mounted() {
|
||||
console.log('App', 'mounted');
|
||||
store.dispatch('loadInitialState');
|
||||
}
|
||||
get isAuthenticated() {
|
||||
return store.getters.isLoggedIn;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/assets/styles/_mixins.scss';
|
||||
</style>
|
||||
236
client/src/api/apiBase.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
import axios, {
|
||||
AxiosError,
|
||||
AxiosInstance,
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
} from 'axios';
|
||||
import createAuthRefreshInterceptor from 'axios-auth-refresh';
|
||||
import store from '@/store';
|
||||
|
||||
export class Api {
|
||||
refreshing = false;
|
||||
api: AxiosInstance;
|
||||
tokenRefreshCallback = (failedRequest: any) => {
|
||||
this.refreshing = true;
|
||||
return this.api
|
||||
.post(`${process.env.VUE_APP_API_SERVER}/auth/token/refresh`)
|
||||
.then((tokenRefreshResponse) => {
|
||||
this.refreshing = false;
|
||||
store.dispatch;
|
||||
localStorage.setItem(
|
||||
'access_token',
|
||||
tokenRefreshResponse.data.accessToken
|
||||
);
|
||||
localStorage.setItem(
|
||||
'refresh_token',
|
||||
tokenRefreshResponse.data.refreshToken
|
||||
);
|
||||
failedRequest.response.config.headers['Authorization'] =
|
||||
'Bearer ' + tokenRefreshResponse.data.accessToken;
|
||||
store.dispatch('updateToken', {
|
||||
accessToken: tokenRefreshResponse.data.accessToken,
|
||||
refreshToken: tokenRefreshResponse.data.refreshToken,
|
||||
});
|
||||
return Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
public constructor(config?: AxiosRequestConfig) {
|
||||
this.api = axios.create(config);
|
||||
this.__setupInterceptors();
|
||||
|
||||
this.getUri = this.getUri.bind(this);
|
||||
this.request = this.request.bind(this);
|
||||
this.get = this.get.bind(this);
|
||||
this.delete = this.delete.bind(this);
|
||||
this.head = this.head.bind(this);
|
||||
this.post = this.post.bind(this);
|
||||
this.put = this.put.bind(this);
|
||||
this.patch = this.patch.bind(this);
|
||||
}
|
||||
_getAccessToken = () =>
|
||||
this.refreshing
|
||||
? localStorage.getItem('refresh_token')
|
||||
: localStorage.getItem('access_token');
|
||||
|
||||
private __setupInterceptors(): void {
|
||||
// Use interceptor to inject the token to requests
|
||||
this.api.interceptors.request.use((request) => {
|
||||
request.headers[
|
||||
'Authorization'
|
||||
] = `Bearer ${this._getAccessToken()}`;
|
||||
return request;
|
||||
});
|
||||
createAuthRefreshInterceptor(this.api, this.tokenRefreshCallback);
|
||||
}
|
||||
/**
|
||||
* Get Uri
|
||||
*
|
||||
* @param {import("axios").AxiosRequestConfig} [config]
|
||||
* @returns {string}
|
||||
* @memberof Api
|
||||
*/
|
||||
public getUri(config?: AxiosRequestConfig): string {
|
||||
return this.api.getUri(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic request.
|
||||
*
|
||||
* @access public
|
||||
* @template T - `TYPE`: expected object.
|
||||
* @template R - `RESPONSE`: expected object inside a axios response format.
|
||||
* @param {import("axios").AxiosRequestConfig} [config] - axios request configuration.
|
||||
* @returns {Promise<R>} - HTTP axios response payload.
|
||||
* @memberof Api
|
||||
*
|
||||
* @example
|
||||
* api.request({
|
||||
* method: "GET|POST|DELETE|PUT|PATCH"
|
||||
* baseUrl: "http://www.domain.com",
|
||||
* url: "/api/v1/users",
|
||||
* headers: {
|
||||
* "Content-Type": "application/json"
|
||||
* }
|
||||
* }).then((response: AxiosResponse<User>) => response.data)
|
||||
*
|
||||
*/
|
||||
public request<T, R = AxiosResponse<T>>(
|
||||
config: AxiosRequestConfig
|
||||
): Promise<R> {
|
||||
return this.api.request(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP GET method, used to fetch data `statusCode`: 200.
|
||||
*
|
||||
* @access public
|
||||
* @template T - `TYPE`: expected object.
|
||||
* @template R - `RESPONSE`: expected object inside a axios response format.
|
||||
* @param {string} url - endpoint you want to reach.
|
||||
* @param {import("axios").AxiosRequestConfig} [config] - axios request configuration.
|
||||
* @returns {Promise<R>} HTTP `axios` response payload.
|
||||
* @memberof Api
|
||||
*/
|
||||
public get<T, R = AxiosResponse<T>>(
|
||||
url: string,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<R> {
|
||||
return this.api.get(url, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP DELETE method, `statusCode`: 204 No Content.
|
||||
*
|
||||
* @access public
|
||||
* @template T - `TYPE`: expected object.
|
||||
* @template R - `RESPONSE`: expected object inside a axios response format.
|
||||
* @param {string} url - endpoint you want to reach.
|
||||
* @param {import("axios").AxiosRequestConfig} [config] - axios request configuration.
|
||||
* @returns {Promise<R>} - HTTP [axios] response payload.
|
||||
* @memberof Api
|
||||
*/
|
||||
public delete<T, R = AxiosResponse<T>>(
|
||||
url: string,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<R> {
|
||||
return this.api.delete(url, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP HEAD method.
|
||||
*
|
||||
* @access public
|
||||
* @template T - `TYPE`: expected object.
|
||||
* @template R - `RESPONSE`: expected object inside a axios response format.
|
||||
* @param {string} url - endpoint you want to reach.
|
||||
* @param {import("axios").AxiosRequestConfig} [config] - axios request configuration.
|
||||
* @returns {Promise<R>} - HTTP [axios] response payload.
|
||||
* @memberof Api
|
||||
*/
|
||||
public head<T, R = AxiosResponse<T>>(
|
||||
url: string,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<R> {
|
||||
return this.api.head(url, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP POST method `statusCode`: 201 Created.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @template T - `TYPE`: expected object.
|
||||
* @template B - `BODY`: body request object.
|
||||
* @template R - `RESPONSE`: expected object inside a axios response format.
|
||||
* @param {string} url - endpoint you want to reach.
|
||||
* @param {B} data - payload to be send as the `request body`,
|
||||
* @param {import("axios").AxiosRequestConfig} [config] - axios request configuration.
|
||||
* @returns {Promise<R>} - HTTP [axios] response payload.
|
||||
* @memberof Api
|
||||
*/
|
||||
public post<T, B, R = AxiosResponse<T>>(
|
||||
url: string,
|
||||
data?: B,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<R> {
|
||||
return this.api.post(url, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP PUT method.
|
||||
*
|
||||
* @access public
|
||||
* @template T - `TYPE`: expected object.
|
||||
* @template B - `BODY`: body request object.
|
||||
* @template R - `RESPONSE`: expected object inside a axios response format.
|
||||
* @param {string} url - endpoint you want to reach.
|
||||
* @param {B} data - payload to be send as the `request body`,
|
||||
* @param {import("axios").AxiosRequestConfig} [config] - axios request configuration.
|
||||
* @returns {Promise<R>} - HTTP [axios] response payload.
|
||||
* @memberof Api
|
||||
*/
|
||||
public put<T, B, R = AxiosResponse<T>>(
|
||||
url: string,
|
||||
data?: B,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<R> {
|
||||
return this.api.put(url, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP PATCH method.
|
||||
*
|
||||
* @access public
|
||||
* @template T - `TYPE`: expected object.
|
||||
* @template B - `BODY`: body request object.
|
||||
* @template R - `RESPONSE`: expected object inside a axios response format.
|
||||
* @param {string} url - endpoint you want to reach.
|
||||
* @param {B} data - payload to be send as the `request body`,
|
||||
* @param {import("axios").AxiosRequestConfig} [config] - axios request configuration.
|
||||
* @returns {Promise<R>} - HTTP [axios] response payload.
|
||||
* @memberof Api
|
||||
*/
|
||||
public patch<T, B, R = AxiosResponse<T>>(
|
||||
url: string,
|
||||
data?: B,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<R> {
|
||||
return this.api.patch(url, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @template T - type.
|
||||
* @param {import("axios").AxiosResponse<T>} response - axios response.
|
||||
* @returns {T} - expected object.
|
||||
* @memberof Api
|
||||
*/
|
||||
public success<T>(response: AxiosResponse<T>): T {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public error(error: AxiosError<Error>) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
7
client/src/api/apiResult.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface ApiResult {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface DataApiResult<T> extends ApiResult {
|
||||
payload?: T;
|
||||
}
|
||||
23
client/src/api/authApi.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Api } from './apiBase';
|
||||
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import {} from '@/models/interfaces/';
|
||||
import { apiConfig } from './config';
|
||||
import { AuthResult } from '@/models/interfaces';
|
||||
import axios from 'axios';
|
||||
import { UserLoginModel } from '@/models/interfaces/userLoginModel';
|
||||
import store from '@/store';
|
||||
|
||||
export class AuthApi extends Api {
|
||||
constructor(config: AxiosRequestConfig) {
|
||||
// NEVER FORGET THE SUPER
|
||||
super(config);
|
||||
}
|
||||
|
||||
public login(user: UserLoginModel): Promise<AxiosResponse<AuthResult>> {
|
||||
return this.post('/auth/login/', user);
|
||||
}
|
||||
public register(user: UserLoginModel) {
|
||||
return this.post('/auth/register/', user);
|
||||
}
|
||||
}
|
||||
export const authApi = new AuthApi(apiConfig);
|
||||
19
client/src/api/config.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as qs from 'qs';
|
||||
export const API_BASE = process.env.VUE_APP_API_SERVER;
|
||||
|
||||
export const apiConfig = {
|
||||
returnRejectedPromiseOnError: true,
|
||||
withCredentials: true,
|
||||
credentials: 'same-origin',
|
||||
timeout: 30000,
|
||||
baseURL: API_BASE,
|
||||
headers: {
|
||||
common: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
},
|
||||
paramsSerializer: (params: any) => qs.stringify(params, { indices: false }),
|
||||
};
|
||||
18
client/src/api/debugApi.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { Api } from '@/api/apiBase';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { apiConfig } from '@/api/config';
|
||||
import { ApiResult, DataApiResult } from '@/api/apiResult';
|
||||
import { User } from '@/models/interfaces';
|
||||
|
||||
export class DebugApi extends Api {
|
||||
constructor(config: AxiosRequestConfig) {
|
||||
// NEVER FORGET THE SUPER
|
||||
super(config);
|
||||
}
|
||||
public async getDebug(): Promise<string> {
|
||||
const result = await this.get<DataApiResult<string>>('/debug');
|
||||
return result.data.payload || '';
|
||||
}
|
||||
}
|
||||
export const debugApi = new DebugApi(apiConfig);
|
||||
77
client/src/api/dnsApi.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Api } from '@/api/apiBase';
|
||||
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { apiConfig } from '@/api/config';
|
||||
import { ApiResult, DataApiResult } from '@/api/apiResult';
|
||||
import { DnsUpdate } from '@/models/interfaces';
|
||||
import { DnsRecord } from '@/models/interfaces/dnsRecord';
|
||||
|
||||
export class DnsApi extends Api {
|
||||
constructor(config: AxiosRequestConfig) {
|
||||
// NEVER FORGET THE SUPER
|
||||
super(config);
|
||||
}
|
||||
public async updateDnsRecord(
|
||||
hostName: string,
|
||||
ipAddress: string
|
||||
): Promise<DataApiResult<DnsRecord>> {
|
||||
const result = await this.post<
|
||||
ApiResult,
|
||||
any,
|
||||
AxiosResponse<DataApiResult<DnsRecord>>
|
||||
>('/dns/', {
|
||||
host: hostName,
|
||||
ip: ipAddress,
|
||||
});
|
||||
return result.data;
|
||||
}
|
||||
public async deleteDnsRecord(
|
||||
hostName: string
|
||||
): Promise<number> {
|
||||
const result = await this.delete(`/dns/?host=${hostName}`);
|
||||
return result.status;
|
||||
}
|
||||
|
||||
public async refreshDnsRecord(
|
||||
hostName: string,
|
||||
ip: string
|
||||
): Promise<DataApiResult<DnsRecord>> {
|
||||
const result = await this.post<
|
||||
ApiResult,
|
||||
any,
|
||||
AxiosResponse<DataApiResult<DnsRecord>>
|
||||
>('/dns/refresh', {
|
||||
host: hostName,
|
||||
ip: ip,
|
||||
});
|
||||
return result.data;
|
||||
}
|
||||
|
||||
public async verifyDnsRecord(
|
||||
hostName: string,
|
||||
ip: string
|
||||
): Promise<DataApiResult<string>> {
|
||||
const result = await this.post<
|
||||
ApiResult,
|
||||
any,
|
||||
AxiosResponse<DataApiResult<string>>
|
||||
>('/dns/check/', {
|
||||
host: hostName,
|
||||
ip: ip,
|
||||
});
|
||||
return result.data;
|
||||
}
|
||||
|
||||
public async getDnsRecords(): Promise<DnsRecord[]> {
|
||||
const result = await this.get<DnsRecord[]>('/dns/list');
|
||||
return result.data;
|
||||
}
|
||||
public async getMyIP(): Promise<string> {
|
||||
const result = await this.get<DataApiResult<string>>('/dns/myip');
|
||||
return result.data.payload || 'Unknown IP';
|
||||
}
|
||||
public async getHeaders(): Promise<any> {
|
||||
const result = await this.get<DataApiResult<any>>('/dns/headers');
|
||||
return result.data.payload;
|
||||
}
|
||||
}
|
||||
export const dnsApi = new DnsApi(apiConfig);
|
||||
7
client/src/api/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { authApi } from './authApi';
|
||||
import { userApi } from './userApi';
|
||||
import { dnsApi } from './dnsApi';
|
||||
import { lightsApi } from './lightsApi';
|
||||
import { debugApi } from './debugApi';
|
||||
|
||||
export { authApi, userApi, dnsApi, lightsApi, debugApi };
|
||||
48
client/src/api/lightsApi.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { Api } from '@/api/apiBase';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { apiConfig } from '@/api/config';
|
||||
import { Light } from '@/models/interfaces';
|
||||
import { ApiResult, DataApiResult } from './apiResult';
|
||||
|
||||
export class LightsApi extends Api {
|
||||
constructor(config: AxiosRequestConfig) {
|
||||
// NEVER FORGET THE SUPER
|
||||
super(config);
|
||||
}
|
||||
async getLights(): Promise<Light[]> {
|
||||
const lights = await this.get<DataApiResult<Light[]>>(
|
||||
'/lights/getlights'
|
||||
);
|
||||
return lights.data.payload || [];
|
||||
}
|
||||
async changeBrightness(
|
||||
lightId: number,
|
||||
brightness: number
|
||||
): Promise<boolean> {
|
||||
const result = await this.post<
|
||||
ApiResult,
|
||||
any,
|
||||
AxiosResponse<ApiResult>
|
||||
>('/lights/setbrightness', {
|
||||
lightId: lightId,
|
||||
brightness: brightness,
|
||||
});
|
||||
return result.data.status === 'success';
|
||||
}
|
||||
async changeColour(
|
||||
lightId: number,
|
||||
colour: string
|
||||
): Promise<boolean>{
|
||||
const result = await this.post<
|
||||
ApiResult,
|
||||
any,
|
||||
AxiosResponse<ApiResult>
|
||||
>('/lights/changecolour', {
|
||||
lightId: lightId,
|
||||
rgbColour: colour,
|
||||
});
|
||||
return result.data.status === 'success';
|
||||
}
|
||||
}
|
||||
export const lightsApi = new LightsApi(apiConfig);
|
||||
18
client/src/api/userApi.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { Api } from '@/api/apiBase';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { apiConfig } from '@/api/config';
|
||||
import { ApiResult, DataApiResult } from '@/api/apiResult';
|
||||
import { User } from '@/models/interfaces';
|
||||
|
||||
export class UserApi extends Api {
|
||||
constructor(config: AxiosRequestConfig) {
|
||||
// NEVER FORGET THE SUPER
|
||||
super(config);
|
||||
}
|
||||
public async getUser(): Promise<User> {
|
||||
const result = await this.get<DataApiResult<User>>('/user');
|
||||
return result.data.payload || { fullName: '' };
|
||||
}
|
||||
}
|
||||
export const userApi = new UserApi(apiConfig);
|
||||
BIN
client/src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
8
client/src/assets/styles/_mixins.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
// @import '~@mdi/font/scss/variables';
|
||||
// @import '~@mdi/font/scss/icons';
|
||||
|
||||
@import 'assets/styles/vendor/mdi/css/materialdesignicons.min.css';
|
||||
@import 'theme/style';
|
||||
|
||||
@import '~vue-select/src/scss/vue-select';
|
||||
0
client/src/assets/styles/_variables.scss
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Bold.eot
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Bold.ttf
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Bold.woff
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Bold.woff2
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Light.eot
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Light.ttf
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Light.woff
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Light.woff2
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Medium.eot
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Medium.ttf
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Medium.woff
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Medium.woff2
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Regular.eot
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Regular.ttf
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Regular.woff
Normal file
BIN
client/src/assets/styles/fonts/Ubuntu/Ubuntu-Regular.woff2
Normal file
272
client/src/assets/styles/theme/_dashboard.scss
Normal file
@@ -0,0 +1,272 @@
|
||||
/* Dashboard */
|
||||
|
||||
.card-statistics {
|
||||
.highlight-icon {
|
||||
height: 53px;
|
||||
width: 53px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50px;
|
||||
|
||||
i {
|
||||
font-size: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-col {
|
||||
border-right: 1px solid $border-color;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
position: relative;
|
||||
border: none;
|
||||
|
||||
&:first-child,
|
||||
&:nth-child(2),
|
||||
&:nth-child(3) {
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background: $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
&:before {
|
||||
bottom: 0;
|
||||
width: 94.2%;
|
||||
height: 1px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
&:before {
|
||||
bottom: 0;
|
||||
width: 94.2%;
|
||||
height: 1px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
&:before {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-revenue-table {
|
||||
.revenue-item {
|
||||
border-bottom: 1px solid $border-color;
|
||||
@extend .py-3;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
@extend .pb-0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@extend .pt-0;
|
||||
}
|
||||
|
||||
.revenue-desc {
|
||||
margin-right: auto;
|
||||
width: 80%;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.revenue-amount {
|
||||
margin-left: auto;
|
||||
width: 40%;
|
||||
|
||||
p {
|
||||
font-size: 1.25rem;
|
||||
font-family: $type-1;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
|
||||
.rtl & {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-revenue {
|
||||
background: linear-gradient(120deg, #556cdc, #128bfc, #18bef1);
|
||||
background-size: cover;
|
||||
color: color(white);
|
||||
|
||||
.highlight-text {
|
||||
font-size: 1.875rem;
|
||||
font-family: $type-1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background-color: rgba(color(white), .2);
|
||||
font-size: 1.125rem;
|
||||
padding: 0.5rem 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.product-chart-wrapper {
|
||||
height: 92%;
|
||||
}
|
||||
|
||||
#dashboardTrendingProgress {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.dashboard-bar-chart-legend {
|
||||
.col {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.bg {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 5px;
|
||||
width: 30px;
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
.bg {
|
||||
background: theme-color(info);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.bg {
|
||||
background: theme-color(primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
.bg {
|
||||
background: theme-color(danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.intro-banner {
|
||||
background: #dbe4ec;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-radius: 2px;
|
||||
|
||||
.banner-image {
|
||||
width: 24%;
|
||||
max-width: 100%;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.content-area {
|
||||
width: 58%;
|
||||
color: $black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
}
|
||||
}
|
||||
|
||||
.sales-mini-tabs {
|
||||
border-bottom: none;
|
||||
|
||||
.nav-item {
|
||||
.nav-link {
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
padding: 5px 15px;
|
||||
border-radius: 2px;
|
||||
transition-duration: 0.7s;
|
||||
transition-property: "color";
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
background: lighten($card-bg-varient, 4%);
|
||||
color: theme-color(primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.purchase-popup {
|
||||
@extend .grid-margin;
|
||||
background: #fff;
|
||||
padding: 15px 20px;
|
||||
@include border-radius(3px);
|
||||
|
||||
.btn {
|
||||
margin-right: 20px;
|
||||
font-weight: 500;
|
||||
color: $white;
|
||||
@include border-radius(5px);
|
||||
@include transition-duration(0.2s);
|
||||
|
||||
&.download-button {
|
||||
background: rgba(249, 249, 249, 0.7);
|
||||
color: #969292;
|
||||
border: 1px solid darken(#e4e4e4,5%);
|
||||
}
|
||||
|
||||
&.purchase-button {
|
||||
background-color: rgba(172, 50 ,228, 0.9);
|
||||
@include background-image(theme-gradient-color(primary));
|
||||
color: $white;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: auto;
|
||||
margin-top: auto;
|
||||
color: darken(#e4e4e4,40%);
|
||||
font-weight: 400;
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
i {
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
margin: auto 0;
|
||||
color: darken(#e4e4e4,20%);
|
||||
}
|
||||
}
|
||||
163
client/src/assets/styles/theme/_demo.scss
Normal file
@@ -0,0 +1,163 @@
|
||||
/* Demo Styles */
|
||||
// Add spacing to Boostrap components for demo purpose
|
||||
.template-demo {
|
||||
> .btn {
|
||||
@extend .mt-3;
|
||||
@extend .mr-3;
|
||||
}
|
||||
|
||||
.slider-wrap {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
> .btn-toolbar {
|
||||
@extend .mt-3;
|
||||
@extend .mr-3;
|
||||
}
|
||||
|
||||
> .btn-group,
|
||||
> .btn-group-vertical {
|
||||
@extend .mt-2;
|
||||
@extend .mr-2;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.circle-progress {
|
||||
@extend .mt-2;
|
||||
}
|
||||
|
||||
> h2,
|
||||
> h3,
|
||||
> h4,
|
||||
> h5,
|
||||
> h6,
|
||||
> h1 {
|
||||
border-top: 1px solid $border-color;
|
||||
padding: 0.5rem 0 0;
|
||||
}
|
||||
|
||||
.ul-slider {
|
||||
&.noUi-horizontal {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
&.noUi-vertical {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
> .dropdown {
|
||||
display: inline-block;
|
||||
@extend .mr-2;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
.breadcrumb {
|
||||
margin-bottom: 1.375rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.breadcrumb {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editable-form {
|
||||
> .form-group {
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding-bottom: 0.8rem;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.circle-progress {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.circle-progress-block {
|
||||
@extend .mb-3;
|
||||
@extend .px-2;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-modal {
|
||||
position: static;
|
||||
display: block;
|
||||
|
||||
.modal-dialog {
|
||||
@extend .mt-0;
|
||||
|
||||
&.modal-lg {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loader-demo-box {
|
||||
@extend .border;
|
||||
@extend .border-secondary;
|
||||
@extend .rounded;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
@extend .d-flex;
|
||||
@extend .align-items-center;
|
||||
}
|
||||
|
||||
.dropdown-menu-static-demo {
|
||||
height: 250px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.rounded-legend {
|
||||
ul {
|
||||
li {
|
||||
list-style-type: none;
|
||||
color: $text-muted;
|
||||
font-size: .75rem;
|
||||
.legend-dots {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: text-bottom;
|
||||
margin-right: .5rem;
|
||||
.rtl & {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.legend-horizontal {
|
||||
@include display-flex;
|
||||
ul {
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-right: 1.5rem;
|
||||
.rtl & {
|
||||
margin-right: auto;
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.legend-top-right {
|
||||
ul {
|
||||
float: right;
|
||||
.rtl & {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.legend-vertical {
|
||||
ul {
|
||||
li {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||