Initial commit

This commit is contained in:
Fergal Moran
2020-09-04 04:15:12 +01:00
commit 256612f9cb
217 changed files with 29262 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
client_id
.idea/

114
.vscode/.ropeproject/config.py vendored Normal file
View 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
View 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
View File

@@ -0,0 +1,3 @@
{
"python.pythonPath": "/home/fergalm/.virtualenvs/BitchMin/bin/python"
}

1
client/.dockerignore Normal file
View File

@@ -0,0 +1 @@
node_modules

13
client/.editorconfig Normal file
View 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
View File

30
client/.eslntrc.json Normal file
View 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
View 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?

View File

@@ -0,0 +1,6 @@
server {
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
}

23
client/.nginx/nginx.conf Normal file
View 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
View File

@@ -0,0 +1,4 @@
{
"tabWidth": 4,
"singleQuote": true
}

33
client/Dockerfile Normal file
View 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
View 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
View File

5
client/babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

9
client/build.sh Executable file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

25
client/public/index.html Normal file
View 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
View 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
View 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;
}
}

View 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
View 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
View 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 }),
};

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

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

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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%);
}
}

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

Some files were not shown because too many files have changed in this diff Show More