├── .yarnrc.yml
├── public
├── favicon.ico
├── assets
│ ├── img
│ │ ├── logo.jpg
│ │ ├── footer.jpg
│ │ └── arrow-down.svg
│ ├── js
│ │ ├── script.js
│ │ └── main.js
│ ├── css
│ │ ├── style.css
│ │ └── dashboard.css
│ └── vendor
│ │ └── boxicons.min.css
├── vercel.svg
└── main.css
├── pages
├── api
│ ├── index.js
│ ├── subs
│ │ ├── count.js
│ │ ├── [id].js
│ │ └── index.js
│ ├── systems
│ │ ├── status.js
│ │ ├── index.js
│ │ └── [id].js
│ ├── users
│ │ ├── me.js
│ │ ├── create.js
│ │ ├── index.js
│ │ └── [id].js
│ ├── auth.js
│ └── config
│ │ └── index.js
├── _app.js
├── login.jsx
├── register.jsx
├── index.js
└── admin
│ ├── manage-admin.jsx
│ └── index.jsx
├── screenshot
├── homepage.png
└── dashboard.png
├── postcss.config.js
├── utils
├── admin.js
├── auth.js
└── db.js
├── theme.js
├── next.config.js
├── styles
├── globals.css
└── Home.module.css
├── services
├── userService.js
├── logService.js
├── httpService.js
├── movieService.js
└── authService.js
├── tailwind.config.js
├── models
├── config.js
├── subscriber.js
├── system.js
└── user.js
├── .gitignore
├── package.json
├── LICENSE
├── README.md
├── .github
└── workflows
│ └── codeql-analysis.yml
└── components
└── Layout.jsx
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstats/UpStats/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/pages/api/index.js:
--------------------------------------------------------------------------------
1 | export default async (req, res) => {
2 | res.send("Home");
3 | };
4 |
--------------------------------------------------------------------------------
/screenshot/homepage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstats/UpStats/HEAD/screenshot/homepage.png
--------------------------------------------------------------------------------
/public/assets/img/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstats/UpStats/HEAD/public/assets/img/logo.jpg
--------------------------------------------------------------------------------
/screenshot/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstats/UpStats/HEAD/screenshot/dashboard.png
--------------------------------------------------------------------------------
/public/assets/img/footer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstats/UpStats/HEAD/public/assets/img/footer.jpg
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/utils/admin.js:
--------------------------------------------------------------------------------
1 | export default async (req, res) => {
2 | if (!req.user.email === process.env.admin_email)
3 | return res.status(403).send("Access denied.");
4 | };
5 |
--------------------------------------------------------------------------------
/theme.js:
--------------------------------------------------------------------------------
1 | import { extendTheme } from "@chakra-ui/react";
2 |
3 | const theme = extendTheme({
4 | fonts: {
5 | heading: "Poppins",
6 | },
7 | });
8 |
9 | export default theme;
10 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | future: {
3 | webpack5: true,
4 | },
5 | env: {
6 | MONGODB_URI: "",
7 | jwtPrivateKey: "",
8 | admin_email: "",
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/public/assets/img/arrow-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;600&display=swap");
6 |
7 | .root {
8 | font-family: "Poppins", sans-serif;
9 | }
10 |
--------------------------------------------------------------------------------
/services/userService.js:
--------------------------------------------------------------------------------
1 | import http from "./httpService";
2 |
3 | const apiEndpoint = "/users";
4 |
5 | export function register(user) {
6 | return http.post(apiEndpoint, {
7 | email: user.username,
8 | password: user.password,
9 | name: user.name,
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
4 | darkMode: false, // or 'media' or 'class'
5 | theme: {
6 | extend: {},
7 | },
8 | variants: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | };
13 |
--------------------------------------------------------------------------------
/services/logService.js:
--------------------------------------------------------------------------------
1 | // For simplicity, I changed the implementation of this module
2 | // and removed Raven. We can always add that in the future
3 | // and this module is the only module we need to modify.
4 |
5 | function init() {}
6 |
7 | function log(error) {
8 | console.error(error);
9 | }
10 |
11 | export default {
12 | init,
13 | log
14 | };
15 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import { ChakraProvider } from "@chakra-ui/react";
3 | import "@fontsource/poppins/300.css";
4 | import theme from "../theme";
5 |
6 | function MyApp({ Component, pageProps }) {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default MyApp;
15 |
--------------------------------------------------------------------------------
/pages/api/subs/count.js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import Email from "../../../models/subscriber";
3 | export default async (req, res) => {
4 | await db();
5 | if (req.method === "GET") {
6 | const emails = await Email.find().sort("email");
7 |
8 | res.send(emails.length);
9 | } else {
10 | res.setHeader("Allow", ["GET"]);
11 | res.status(405).end(`Method Not Allowed`);
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/utils/auth.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 |
3 | export default async (req, res, fn) => {
4 | const token = req.headers["x-auth-token"];
5 | if (!token) return res.status(401).send("Access denied. No token provided.");
6 |
7 | try {
8 | const decoded = jwt.verify(token, process.env.jwtPrivateKey);
9 | req.user = decoded;
10 | } catch (ex) {
11 | return res.status(400).send("Invalid token.");
12 | }
13 | await fn();
14 | };
15 |
--------------------------------------------------------------------------------
/models/config.js:
--------------------------------------------------------------------------------
1 | import Joi from "joi";
2 | import mongoose from "mongoose";
3 |
4 | const ConfigSchema = new mongoose.Schema({
5 | mailing: { type: Boolean, required: true },
6 | });
7 |
8 | export function validateConfig(config) {
9 | const schema = Joi.object({
10 | mailing: Joi.boolean().required(),
11 | });
12 |
13 | return schema.validate(config);
14 | }
15 |
16 | export default mongoose.models.Config || mongoose.model("Config", ConfigSchema);
17 |
--------------------------------------------------------------------------------
/public/assets/js/script.js:
--------------------------------------------------------------------------------
1 | mybutton = document.getElementById("scroll-to-top");
2 |
3 | window.onscroll = function() { scrollFunction() };
4 |
5 | function scrollFunction() {
6 | if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
7 | mybutton.style.display = "block";
8 | } else {
9 | mybutton.style.display = "none";
10 | }
11 | }
12 |
13 | function topFunction() {
14 | document.body.scrollTop = 0;
15 | document.documentElement.scrollTop = 0;
16 | }
--------------------------------------------------------------------------------
/pages/api/subs/[id].js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import Email from "../../../models/subscriber";
3 | export default async (req, res) => {
4 | await db();
5 | if (req.method === "DELETE") {
6 | const email = await Email.findByIdAndRemove(req.query.id);
7 |
8 | if (!email)
9 | return res.status(404).send("The email with the given ID was not found.");
10 |
11 | res.send(email);
12 | } else {
13 | res.setHeader("Allow", ["DELETE"]);
14 | res.status(405).end(`Method Not Allowed`);
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/pages/api/systems/status.js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import System from "../../../models/system";
3 | export default async (req, res) => {
4 | await db();
5 |
6 | let isOperational = false;
7 | let outageCount = 0;
8 | const systems = await System.find();
9 | for (let system of systems) {
10 | isOperational = system.status === "up" ? true : false;
11 | if (!isOperational) outageCount += 1;
12 | }
13 |
14 | res.status(200).send({
15 | operational: isOperational,
16 | ...(!isOperational && { outageCount }),
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/models/subscriber.js:
--------------------------------------------------------------------------------
1 | import Joi from "joi";
2 | import mongoose from "mongoose";
3 |
4 | const emailSchema = new mongoose.Schema({
5 | email: {
6 | type: String,
7 | required: true,
8 | minlength: 5,
9 | maxlength: 255,
10 | unique: true,
11 | },
12 | });
13 |
14 | export function validateEmail(email) {
15 | const schema = Joi.object({
16 | email: Joi.string().min(5).max(255).required().email(),
17 | });
18 |
19 | return schema.validate(email);
20 | }
21 |
22 | export default mongoose.models.Email || mongoose.model("Email", emailSchema);
23 |
--------------------------------------------------------------------------------
/pages/api/users/me.js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import User from "../../../models/user";
3 | import withAuth from "../../../utils/auth";
4 | export default async (req, res) => {
5 | await db();
6 | switch (req.method) {
7 | case "GET":
8 | withAuth(req, res, async () => {
9 | const user = await User.findById(req.user._id).select("-password");
10 |
11 | res.send(user);
12 | });
13 | break;
14 | default:
15 | res.setHeader("Allow", ["POST"]);
16 | res.status(405).end(`Method Not Allowed`);
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | yarn.lock
37 |
38 | .yarn/
39 | .pnp.js
40 | .pnp.cjs
41 |
--------------------------------------------------------------------------------
/services/httpService.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | axios.defaults.baseURL = "/api";
4 |
5 | axios.interceptors.response.use(null, (error) => {
6 | const expectedError =
7 | error.response &&
8 | error.response.status >= 400 &&
9 | error.response.status < 500;
10 |
11 | if (!expectedError) {
12 | alert("An unexpected error occurrred.");
13 | }
14 |
15 | return Promise.reject(error);
16 | });
17 |
18 | function setJwt(jwt) {
19 | axios.defaults.headers.common["x-auth-token"] = jwt;
20 | }
21 |
22 | export default {
23 | get: axios.get,
24 | post: axios.post,
25 | put: axios.put,
26 | delete: axios.delete,
27 | setJwt,
28 | };
29 |
--------------------------------------------------------------------------------
/models/system.js:
--------------------------------------------------------------------------------
1 | import Joi from "joi";
2 | import mongoose from "mongoose";
3 |
4 | const SystemSchema = new mongoose.Schema({
5 | name: { type: String, required: true },
6 | url: { type: String, required: true },
7 | type: { type: String, required: true },
8 | status: String,
9 | logs: [{ status: String, time: Date }],
10 | });
11 |
12 | export function validateSystem(system) {
13 | const schema = Joi.object({
14 | name: Joi.string().max(50).required(),
15 | url: Joi.required(),
16 | type: Joi.string().required(),
17 | });
18 |
19 | return schema.validate(system);
20 | }
21 |
22 | export default mongoose.models.System || mongoose.model("System", SystemSchema);
23 |
--------------------------------------------------------------------------------
/services/movieService.js:
--------------------------------------------------------------------------------
1 | import http from "./httpService";
2 |
3 | const apiEndpoint = "/movies";
4 |
5 | function movieUrl(id) {
6 | return `${apiEndpoint}/${id}`;
7 | }
8 |
9 | export function getMovies() {
10 | return http.get(apiEndpoint);
11 | }
12 |
13 | export function getMovie(movieId) {
14 | return http.get(movieUrl(movieId));
15 | }
16 |
17 | export function saveMovie(movie) {
18 | if (movie._id) {
19 | const body = { ...movie };
20 | delete body._id;
21 | return http.put(movieUrl(movie._id), body);
22 | }
23 |
24 | return http.post(apiEndpoint, movie);
25 | }
26 |
27 | export function deleteMovie(movieId) {
28 | return http.delete(movieUrl(movieId));
29 | }
30 |
--------------------------------------------------------------------------------
/pages/api/subs/index.js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import Email, { validateEmail } from "../../../models/subscriber";
3 | import withAuth from "../../../utils/auth";
4 | export default async (req, res) => {
5 | await db();
6 | if (req.method === "GET") {
7 | withAuth(req, res, async () => {
8 | const emails = await Email.find().sort("email");
9 | res.send(emails);
10 | });
11 | } else if (req.method === "POST") {
12 | const { error } = validateEmail(req.body);
13 | if (error) return res.status(400).send(error.details[0].message);
14 |
15 | const email = new Email({
16 | email: req.body.email,
17 | });
18 | await email.save();
19 |
20 | res.send(email);
21 | } else {
22 | res.setHeader("Allow", ["GET", "POST"]);
23 | res.status(405).end(`Method Not Allowed`);
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "upstats",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@chakra-ui/icons": "^1.0.12",
12 | "@chakra-ui/react": "^1.6.0",
13 | "@emotion/react": "^11.1.5",
14 | "@emotion/styled": "^11.3.0",
15 | "@fontsource/poppins": "^4.2.2",
16 | "apisauce": "^2.1.1",
17 | "axios": "^0.21.2",
18 | "bcrypt": "^5.0.1",
19 | "formik": "^2.2.6",
20 | "framer-motion": "^4.1.11",
21 | "joi": "^17.4.0",
22 | "jsonwebtoken": "^8.5.1",
23 | "jwt-decode": "^3.1.2",
24 | "lodash": "^4.17.21",
25 | "mongoose": "^5.13.20",
26 | "next": "^12.1.0",
27 | "react": "17.0.2",
28 | "react-dom": "17.0.2",
29 | "yup": "^0.32.9"
30 | },
31 | "devDependencies": {
32 | "autoprefixer": "^10.2.5",
33 | "postcss": "^8.2.13",
34 | "tailwindcss": "^2.1.2"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/services/authService.js:
--------------------------------------------------------------------------------
1 | import jwtDecode from "jwt-decode";
2 | import http from "./httpService";
3 |
4 | const apiEndpoint = "/auth";
5 | const tokenKey = "token";
6 |
7 | http.setJwt(getJwt());
8 |
9 | export async function login(email, password) {
10 | const { data: jwt } = await http.post(apiEndpoint, { email, password });
11 |
12 | window.localStorage.setItem(tokenKey, jwt);
13 | }
14 |
15 | export function loginWithJwt(jwt) {
16 | window.localStorage.setItem(tokenKey, jwt);
17 | }
18 |
19 | export function logout() {
20 | window.localStorage.removeItem(tokenKey);
21 | }
22 |
23 | export function getCurrentUser() {
24 | try {
25 | const jwt = window.localStorage.getItem(tokenKey);
26 | return jwtDecode(jwt);
27 | } catch (ex) {
28 | return null;
29 | }
30 | }
31 |
32 | export function getJwt() {
33 | return window.localStorage.getItem(tokenKey);
34 | }
35 |
36 | export default {
37 | login,
38 | loginWithJwt,
39 | logout,
40 | getCurrentUser,
41 | getJwt,
42 | };
43 |
--------------------------------------------------------------------------------
/models/user.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 | import Joi from "joi";
3 | import mongoose from "mongoose";
4 |
5 | const userSchema = new mongoose.Schema({
6 | name: {
7 | type: String,
8 | minlength: 5,
9 | maxlength: 50,
10 | },
11 | email: {
12 | type: String,
13 | minlength: 5,
14 | maxlength: 255,
15 | unique: true,
16 | },
17 | password: {
18 | type: String,
19 | minlength: 5,
20 | maxlength: 1024,
21 | },
22 | });
23 |
24 | userSchema.methods.generateAuthToken = function () {
25 | const token = jwt.sign(
26 | { _id: this._id, isAdmin: this.email === process.env.admin_email },
27 | process.env.jwtPrivateKey
28 | );
29 | return token;
30 | };
31 |
32 | export function validateUser(user) {
33 | const schema = Joi.object({
34 | name: Joi.string().min(5).max(50),
35 | email: Joi.string().min(5).max(255).email(),
36 | password: Joi.string().min(5).max(255),
37 | });
38 |
39 | return schema.validate(user);
40 | }
41 |
42 | export default mongoose.models.User || mongoose.model("User", userSchema);
43 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/api/systems/index.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import db from "../../../utils/db";
3 | import System, { validateSystem } from "../../../models/system";
4 | import withAuth from "../../../utils/auth";
5 |
6 | export default async (req, res) => {
7 | await db();
8 | switch (req.method) {
9 | case "GET":
10 | const systems = await System.find().sort("name");
11 | res.status(200).send(systems);
12 | break;
13 | case "POST":
14 | await withAuth(req, res, async () => {
15 | const { error } = validateSystem(req.body);
16 | if (error) return res.status(400).send(error.details[0].message);
17 |
18 | const system = new System({
19 | name: req.body.name,
20 | url: req.body.url,
21 | type: req.body.type,
22 | status: "up",
23 | });
24 | await system.save();
25 | res.send(system);
26 | });
27 | break;
28 | default:
29 | res.setHeader("Allow", ["GET", "POST"]);
30 | res.status(405).end(`Method Not Allowed`);
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 UpStats
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/utils/db.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const MONGODB_URI = process.env.MONGODB_URI;
4 |
5 | if (!MONGODB_URI) {
6 | throw new Error(
7 | "Please define the MONGODB_URI environment variable inside .env.local"
8 | );
9 | }
10 |
11 | /**
12 | * Global is used here to maintain a cached connection across hot reloads
13 | * in development. This prevents connections growing exponentially
14 | * during API Route usage.
15 | */
16 | let cached = global.mongoose;
17 |
18 | if (!cached) {
19 | cached = global.mongoose = { conn: null, promise: null };
20 | }
21 |
22 | async function dbConnect() {
23 | if (cached.conn) {
24 | return cached.conn;
25 | }
26 |
27 | if (!cached.promise) {
28 | const opts = {
29 | useNewUrlParser: true,
30 | useUnifiedTopology: true,
31 | bufferCommands: false,
32 | bufferMaxEntries: 0,
33 | useFindAndModify: false,
34 | useCreateIndex: true,
35 | };
36 |
37 | cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
38 | return mongoose;
39 | });
40 | }
41 | cached.conn = await cached.promise;
42 | return cached.conn;
43 | }
44 |
45 | export default dbConnect;
46 |
--------------------------------------------------------------------------------
/pages/api/auth.js:
--------------------------------------------------------------------------------
1 | import db from "../../utils/db";
2 | import Joi from "joi";
3 | import bcrypt from "bcrypt";
4 | import _ from "lodash";
5 | import User from "../../models/user";
6 |
7 | export default async (req, res) => {
8 | await db();
9 | if (req.method === "POST") {
10 | const { error } = validate(req.body);
11 | if (error) return res.status(400).send(error.details[0].message);
12 |
13 | let user = await User.findOne({ email: req.body.email });
14 | if (!user) return res.status(400).send("Invalid email or password.");
15 |
16 | const validPassword = await bcrypt.compare(
17 | req.body.password,
18 | user.password
19 | );
20 | if (!validPassword)
21 | return res.status(400).send("Invalid email or password.");
22 |
23 | const token = user.generateAuthToken();
24 |
25 | res.status(200).send(token);
26 | } else {
27 | res.setHeader("Allow", ["POST"]);
28 | res.status(405).end(`Method Not Allowed`);
29 | }
30 | };
31 |
32 | function validate(req) {
33 | const schema = Joi.object({
34 | email: Joi.string().min(5).max(255).required().email(),
35 | password: Joi.string().min(5).max(255).required(),
36 | });
37 |
38 | return schema.validate(req);
39 | }
40 |
--------------------------------------------------------------------------------
/pages/api/config/index.js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import withAuth from "../../../utils/auth";
3 | import Config, { validateConfig } from "../../../models/config";
4 | export default async (req, res) => {
5 | await db();
6 | if (req.method === "GET") {
7 | const configs = await Config.find();
8 | console.log("😀", configs);
9 | res.send(configs[0]);
10 | } else if (req.method === "PUT") {
11 | withAuth(req, res, async () => {
12 | const { error } = validateConfig(req.body);
13 | if (error) return res.status(400).send(error.details[0].message);
14 |
15 | let config = await Config.find();
16 | if (!config.length) {
17 | let config = new Config({
18 | mailing: req.body.mailing,
19 | });
20 | await config.save();
21 | return res.send(config);
22 | }
23 | config[0].mailing = req.body.mailing;
24 | config[0].save();
25 |
26 | if (!config)
27 | return res
28 | .status(404)
29 | .send("The config with the given ID was not found.");
30 |
31 | res.send(config);
32 | });
33 | } else {
34 | res.setHeader("Allow", ["GET", "PUT"]);
35 | res.status(405).end(`Method Not Allowed`);
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/pages/api/users/create.js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import User, { validateUser } from "../../../models/user";
3 | const bcrypt = require("bcrypt");
4 | const _ = require("lodash");
5 |
6 | export default async (req, res) => {
7 | await db();
8 | switch (req.method) {
9 | case "POST":
10 | const { error } = validateUser(req.body);
11 | if (error) return res.status(400).send(error.details[0].message);
12 |
13 | let user = await User.findOne({ email: req.body.email.toLowerCase() });
14 | if (user && user.password)
15 | return res.status(400).send("User already registered.");
16 | else user = new User(_.pick(req.body, ["name", "email", "password"]));
17 | user.password = req.body.password;
18 | user.name = req.body.name;
19 |
20 | const salt = await bcrypt.genSalt(10);
21 | user.password = await bcrypt.hash(user.password, salt);
22 | await user.save();
23 | const token = user.generateAuthToken();
24 |
25 | res
26 | .setHeader("x-auth-token", token)
27 | .send(_.pick(user, ["_id", "name", "email"]));
28 | break;
29 | default:
30 | res.setHeader("Allow", ["POST"]);
31 | res.status(405).end(`Method Not Allowed`);
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/pages/api/users/index.js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import withAuth from "../../../utils/auth";
3 | import admin from "../../../utils/admin";
4 | import User, { validateUser } from "../../../models/user";
5 | const _ = require("lodash");
6 |
7 | export default async (req, res) => {
8 | await db();
9 | switch (req.method) {
10 | case "GET":
11 | withAuth(req, res, async () => {
12 | admin(req, res);
13 | const users = await User.find().sort("name");
14 | res.send(users);
15 | });
16 | break;
17 | case "POST":
18 | withAuth(req, res, async () => {
19 | admin(req, res);
20 | const { error } = validateUser(req.body);
21 | if (error) return res.status(400).send(error.details[0].message);
22 |
23 | let user = await User.findOne({ email: req.body.email.toLowerCase() });
24 | if (user) return res.status(400).send("User already registered.");
25 |
26 | user = new User({ email: req.body.email });
27 | await user.save();
28 |
29 | res.send(_.pick(user, ["_id", "email"]));
30 | });
31 | break;
32 | default:
33 | res.setHeader("Allow", ["GET", "POST"]);
34 | res.status(405).end(`Method Not Allowed`);
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/public/assets/js/main.js:
--------------------------------------------------------------------------------
1 | const showMenu = (headerToggle, navbarId) => {
2 | const toggleBtn = document.getElementById(headerToggle),
3 | nav = document.getElementById(navbarId);
4 |
5 | if (headerToggle && navbarId) {
6 | if (toggleBtn) {
7 | toggleBtn.addEventListener("click", () => {
8 | nav.classList.toggle("show-menu");
9 | toggleBtn.classList.toggle("bx-x");
10 | });
11 | }
12 | }
13 | };
14 | showMenu("header-toggle", "navbar");
15 |
16 | const linkColor = document.querySelectorAll(".nav_link");
17 |
18 | function colorLink() {
19 | linkColor.forEach((l) => l.classList.remove("active"));
20 | this.classList.add("active");
21 | }
22 |
23 | linkColor.forEach((l) => l.addEventListener("click", colorLink));
24 |
25 | const selected = document.querySelector(".selected");
26 | const optionsContainer = document.querySelector(".options-container");
27 |
28 | const optionsList = document.querySelectorAll(".option");
29 | if (selected) {
30 | selected.addEventListener("click", () => {
31 | optionsContainer.classList.toggle("active");
32 | });
33 | }
34 |
35 | optionsList.forEach((o) => {
36 | if (o) {
37 | o.addEventListener("click", () => {
38 | selected.innerHTML = o.querySelector("label").innerHTML;
39 | optionsContainer.classList.remove("active");
40 | });
41 | }
42 | });
43 |
--------------------------------------------------------------------------------
/pages/api/users/[id].js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import withAuth from "../../../utils/auth";
3 | import admin from "../../../utils/admin";
4 | import User, { validateUser } from "../../../models/user";
5 |
6 | export default async (req, res) => {
7 | await db();
8 | switch (req.method) {
9 | case "PUT":
10 | withAuth(req, res, async () => {
11 | admin(req, res);
12 | const { error } = validateUser(req.body);
13 | if (error) return res.status(400).send(error.details[0].message);
14 |
15 | const user = await User.findByIdAndUpdate(req.query.id, {
16 | email: req.body.email.toLowerCase(),
17 | });
18 |
19 | if (!user)
20 | return res
21 | .status(404)
22 | .send("The user with the given ID was not found.");
23 |
24 | res.send(user);
25 | });
26 | break;
27 | case "DELETE":
28 | withAuth(req, res, async () => {
29 | admin(req, res);
30 | const user = await User.findByIdAndRemove(req.query.id);
31 |
32 | if (!user)
33 | return res
34 | .status(404)
35 | .send("The user with the given ID was not found.");
36 |
37 | res.send(user);
38 | });
39 | break;
40 | default:
41 | res.setHeader("Allow", ["DELETE", "PUT"]);
42 | res.status(405).end(`Method Not Allowed`);
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/pages/api/systems/[id].js:
--------------------------------------------------------------------------------
1 | import db from "../../../utils/db";
2 | import System, { validateSystem } from "../../../models/system";
3 | import withAuth from "../../../utils/auth";
4 |
5 | export default async (req, res) => {
6 | await db();
7 | switch (req.method) {
8 | case "GET":
9 | try {
10 | const system = await System.findById(req.query.id);
11 | if (!system)
12 | return res
13 | .status(404)
14 | .send("The system with the given ID was not found.");
15 |
16 | res.status(200).send(system);
17 | } catch {
18 | res.status(400).send("Invalid Object Id");
19 | }
20 | break;
21 | case "PUT":
22 | await withAuth(req, res, async () => {
23 | const { error } = validateSystem(req.body);
24 | if (error) return res.status(400).send(error.details[0].message);
25 |
26 | const system_updated = await System.findByIdAndUpdate(
27 | req.query.id,
28 | {
29 | name: req.body.name,
30 | url: req.body.url,
31 | type: req.body.type,
32 | },
33 | { new: true }
34 | );
35 |
36 | if (!system_updated)
37 | return res
38 | .status(404)
39 | .send("The system with the given ID was not found.");
40 |
41 | res.send(system_updated);
42 | });
43 | break;
44 | case "DELETE":
45 | await withAuth(req, res, async () => {
46 | const system_deleted = await System.findByIdAndRemove(req.query.id);
47 |
48 | if (!system_deleted)
49 | return res
50 | .status(404)
51 | .send("The system with the given ID was not found.");
52 |
53 | res.send(system_deleted);
54 | });
55 | break;
56 | default:
57 | res.setHeader("Allow", ["GET", "DELETE", "PUT"]);
58 | res.status(405).end(`Method Not Allowed`);
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## About UpStats
2 |
3 | There are many great Opensource, Hosted Solutions for Status Page, however, I didn't find one that really suit my needs so we created this enhanced one. I want to create a Status Page so amazing that it'll be the last one you ever need. I think this is the One.
4 |
5 | Here's why:
6 | * Managing your site's right from your Mobile is now easier with Mobile First Dashboard.
7 | * You don't need to do the task, you can appoint others to do your Tasks.
8 | * Let your user to know about your System Status directly via Email.
9 | * Don't let your Telegram Bots simply Down
10 |
11 | Since your needs maybe different.So I'll be adding more in the near future. You may also suggest changes by forking this repo and creating a pull request or opening an issue. Thanks to all the people have have contributed to expanding UpStats!
12 |
13 | ## Built with
14 |
15 | * [Tailwind](https://tailwindcss.com)
16 | * [NextJS](https://nextjs.org/)
17 | * [Typescript](https://www.typescriptlang.org)
18 | * [GramJS](https://github.com/gram-js/gramjs)
19 |
20 | ## Getting Started
21 |
22 | 1. Clone the Repo
23 | ```
24 | git clone https://github.com/UpStats/UpStats.git
25 | ```
26 | 2. Open next.config.js file and replace with your Details
27 | ```
28 | MONGODB_URI - Get from MongoDB.com
29 | jwtPrivateKey - can be Random word like Password, make sure it's not Public
30 | admin_email - Your Email, Required to access Dashboard
31 | ```
32 | 3. Open public/assets/ and replace Favicon.ico
33 | 4. Open public/assets/img/ and replace with your images.
34 | 5. Deploy [UpStats Worker](https://github.com/upstats/worker#getting-started)
35 |
36 | You can deploy UpStats on Vercel
37 |
38 | [](https://vercel.com/new/project?template=https://github.com/Upstats/UpStats)
39 |
40 | ## Demo
41 | 
42 |
UpStats Homepage
43 |
44 | 
45 | UpStats Admin Panel
46 |
47 | ## Contributing
48 |
49 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
50 |
51 | 1. Fork the Project
52 | 2. Create your Feature Branch
53 | 3. Commit your Changes
54 | 4. Push to the Branch
55 | 5. Open a Pull Request
56 | 6. That's it
57 |
58 | ## License
59 |
60 | Distributed under the GNU GPLV3 License. See `LICENSE` for more information.
61 |
62 | ## About US
63 |
64 | UpStats is developed by [ToolsHD](https://gitHub.com/ToolsHD) and [Gowtham](https://github.com/Gowtham2003)
65 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '16 6 * * 1'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
37 | # Learn more:
38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
39 |
40 | steps:
41 | - name: Checkout repository
42 | uses: actions/checkout@v2
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/public/main.css:
--------------------------------------------------------------------------------
1 | main.root {
2 | scroll-behavior: smooth;
3 | font-family: "Open Sans", sans-serif;
4 | }
5 |
6 | /*nav {*/
7 | /*font-family: "Open Sans", sans-serif;*/
8 | /*}*/
9 |
10 | .nav-brand {
11 | display: block;
12 | width: 50px;
13 | height: auto;
14 | border-radius: 50%;
15 | margin: auto;
16 | }
17 |
18 | /*span {*/
19 | /*width: 100%;*/
20 | /*}*/
21 |
22 | #global-status {
23 | width: 90%;
24 | height: 3em;
25 | background: #3747d4;
26 | place-items: center;
27 | color: #ffffff;
28 | font-size: 20px;
29 | font-family: "Open Sans", sans-serif;
30 | letter-spacing: 3px;
31 | margin-top: 0px;
32 | margin-left: 10px;
33 | border-radius: 5px;
34 | border: none;
35 | }
36 |
37 | #status {
38 | width: 90%;
39 | height: 5em;
40 | background-color: white;
41 | color: black;
42 | margin: 30px 0px 10px 11px;
43 | border-radius: 5px;
44 | left: 0;
45 | right: 0;
46 | letter-spacing: 3px;
47 | place-items: center;
48 | }
49 |
50 | div.monitor {
51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 3px 10px 0 rgba(0, 0, 0, 0.19);
52 | }
53 |
54 | .site-status {
55 | padding-left: 50%;
56 | }
57 |
58 | #footer-img {
59 | width: 50px;
60 | border-radius: 50%;
61 | }
62 |
63 | #scroll-to-top {
64 | display: none;
65 | position: fixed;
66 | bottom: 20px;
67 | right: 30px;
68 | z-index: 99;
69 | border: none;
70 | outline: none;
71 | background-color: #3747d4;
72 | color: white;
73 | cursor: pointer;
74 | padding: 15px;
75 | border-radius: 10px;
76 | }
77 |
78 | #scroll-to-top:hover {
79 | background-color: #000000;
80 | }
81 |
82 | @media (min-width: 768px) and (max-width: 1024px) {
83 | .site-status {
84 | padding: 0 0 0 70%;
85 | }
86 |
87 | #global-status,
88 | #status {
89 | margin: 30px 0 0 51px;
90 | }
91 | }
92 |
93 | .email-subscription {
94 | max-width: 400px;
95 | margin: auto;
96 | background: #fff;
97 | text-align: center;
98 | }
99 |
100 | .email-subscription .newsletter {
101 | margin: 20px 27px;
102 | font-family: "Open Sans", sans-serif;
103 | }
104 |
105 | .email-subscription .field {
106 | width: 100%;
107 | height: 45px;
108 | display: flex;
109 | position: relative;
110 | }
111 |
112 | .newsletter .field #email {
113 | width: 100%;
114 | height: 100%;
115 | border: 1px solid lightgrey;
116 | border-radius: 5px;
117 | padding-left: 15px;
118 | font-size: 18px;
119 | outline: none;
120 | }
121 |
122 | #submit {
123 | margin-top: 10px;
124 | width: 100%;
125 | height: 45px;
126 | border: none;
127 | outline: none;
128 | border-radius: 5px;
129 | background-color: #3747d4;
130 | color: #ffffff;
131 | font-size: 18px;
132 | font-weight: 500;
133 | letter-spacing: 1px;
134 | cursor: pointer;
135 | }
136 |
--------------------------------------------------------------------------------
/public/assets/css/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | scroll-behavior: smooth;
3 | font-family: 'Open Sans', sans-serif;
4 | }
5 |
6 | nav {
7 | font-family: 'Open Sans', sans-serif;
8 | }
9 |
10 | .nav-brand {
11 | display: block;
12 | width: 50px;
13 | height: auto;
14 | border-radius: 50%;
15 | margin: auto;
16 | }
17 |
18 | span {
19 | width: 100%;
20 | }
21 |
22 | #global-status {
23 | width: 90%;
24 | height: 3em;
25 | background: #3747D4;
26 | place-items: center;
27 | color: #ffffff;
28 | font-size: 20px;
29 | font-family: 'Open Sans', sans-serif;
30 | letter-spacing: 3px;
31 | margin-top: 0px;
32 | margin-left: 10px;
33 | border-radius: 5px;
34 | border: none;
35 | }
36 |
37 | #status {
38 | width: 90%;
39 | height: 5em;
40 | background-color: white;
41 | color: black;
42 | margin: 30px 0px 10px 11px;
43 | border-radius: 5px;
44 | left: 0;
45 | right: 0;
46 | letter-spacing: 3px;
47 | place-items: center;
48 | }
49 |
50 | div.monitor {
51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 3px 10px 0 rgba(0, 0, 0, 0.19);
52 | }
53 |
54 | .site-status {
55 | padding-left: 50%;
56 | }
57 |
58 | #footer-img {
59 | width: 50px;
60 | border-radius: 50%;
61 | }
62 |
63 | #scroll-to-top {
64 | display: none;
65 | position: fixed;
66 | bottom: 20px;
67 | right: 30px;
68 | z-index: 99;
69 | border: none;
70 | outline: none;
71 | background-color: #3747D4;
72 | color: white;
73 | cursor: pointer;
74 | padding: 15px;
75 | border-radius: 10px;
76 | }
77 |
78 | #scroll-to-top:hover {
79 | background-color: #000000;
80 | }
81 |
82 | @media (min-width: 768px) and (max-width: 1024px) {
83 | .site-status {
84 | padding: 0 0 0 70%;
85 | }
86 |
87 | #global-status,
88 | #status {
89 | margin: 30px 0 0 51px;
90 | }
91 | }
92 |
93 | .email-subscription {
94 | max-width: 400px;
95 | margin: auto;
96 | background: #fff;
97 | text-align: center;
98 | font-family: 'Open Sans', sans-serif;
99 | }
100 |
101 | .email-subscription .newsletter {
102 | margin: 20px 27px;
103 | font-family: 'Open Sans', sans-serif;
104 | }
105 |
106 | .email-subscription .field {
107 | width: 100%;
108 | height: 45px;
109 | display: flex;
110 | position: relative;
111 | }
112 |
113 | .newsletter .field #email {
114 | width: 100%;
115 | height: 100%;
116 | border: 1px solid lightgrey;
117 | border-radius: 5px;
118 | padding-left: 15px;
119 | font-size: 18px;
120 | outline: none;
121 | }
122 |
123 | #submit {
124 | margin-top: 10px;
125 | width: 100%;
126 | height: 45px;
127 | border: none;
128 | outline: none;
129 | border-radius: 5px;
130 | background-color: #3747D4;
131 | color: #ffffff;
132 | font-size: 18px;
133 | font-weight: 500;
134 | letter-spacing: 1px;
135 | cursor: pointer;
136 |
137 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | main.root {
2 | scroll-behavior: smooth;
3 | font-family: "Open Sans", sans-serif;
4 | }
5 |
6 | /*nav {*/
7 | /*font-family: "Open Sans", sans-serif;*/
8 | /*}*/
9 |
10 | .nav-brand {
11 | display: block;
12 | width: 50px;
13 | height: auto;
14 | border-radius: 50%;
15 | margin: auto;
16 | }
17 |
18 | /*span {*/
19 | /*width: 100%;*/
20 | /*}*/
21 |
22 | #global-status {
23 | width: 90%;
24 | height: 3em;
25 | background: #3747d4;
26 | place-items: center;
27 | color: #ffffff;
28 | font-size: 20px;
29 | font-family: "Open Sans", sans-serif;
30 | letter-spacing: 3px;
31 | margin-top: 0px;
32 | margin-left: 10px;
33 | border-radius: 5px;
34 | border: none;
35 | }
36 |
37 | #status {
38 | width: 90%;
39 | height: 5em;
40 | background-color: white;
41 | color: black;
42 | margin: 30px 0px 10px 11px;
43 | border-radius: 5px;
44 | left: 0;
45 | right: 0;
46 | letter-spacing: 3px;
47 | place-items: center;
48 | }
49 |
50 | div.monitor {
51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 3px 10px 0 rgba(0, 0, 0, 0.19);
52 | }
53 |
54 | .site-status {
55 | padding-left: 50%;
56 | }
57 |
58 | #footer-img {
59 | width: 50px;
60 | border-radius: 50%;
61 | }
62 |
63 | #scroll-to-top {
64 | display: none;
65 | position: fixed;
66 | bottom: 20px;
67 | right: 30px;
68 | z-index: 99;
69 | border: none;
70 | outline: none;
71 | background-color: #3747d4;
72 | color: white;
73 | cursor: pointer;
74 | padding: 15px;
75 | border-radius: 10px;
76 | }
77 |
78 | #scroll-to-top:hover {
79 | background-color: #000000;
80 | }
81 |
82 | @media (min-width: 768px) and (max-width: 1024px) {
83 | .site-status {
84 | padding: 0 0 0 70%;
85 | }
86 |
87 | #global-status,
88 | #status {
89 | margin: 30px 0 0 51px;
90 | }
91 | }
92 |
93 | .email-subscription {
94 | max-width: 400px;
95 | margin: auto;
96 | background: #fff;
97 | text-align: center;
98 | }
99 |
100 | .email-subscription .newsletter {
101 | margin: 20px 27px;
102 | font-family: "Open Sans", sans-serif;
103 | }
104 |
105 | .email-subscription .field {
106 | width: 100%;
107 | height: 45px;
108 | display: flex;
109 | position: relative;
110 | }
111 |
112 | .newsletter .field #email {
113 | width: 100%;
114 | height: 100%;
115 | border: 1px solid lightgrey;
116 | border-radius: 5px;
117 | padding-left: 15px;
118 | font-size: 18px;
119 | outline: none;
120 | }
121 |
122 | #submit {
123 | margin-top: 10px;
124 | width: 100%;
125 | height: 45px;
126 | border: none;
127 | outline: none;
128 | border-radius: 5px;
129 | background-color: #3747d4;
130 | color: #ffffff;
131 | font-size: 18px;
132 | font-weight: 500;
133 | letter-spacing: 1px;
134 | cursor: pointer;
135 | }
136 |
--------------------------------------------------------------------------------
/components/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Head from "next/head";
3 |
4 | const Layout = (props) => {
5 | const logout = () => {
6 | window.localStorage.removeItem("token");
7 | window.location = "/login";
8 | };
9 | return (
10 | <>
11 |
12 |
13 |
14 | Dashboard | Site Name
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 | <>
27 |
39 | {/* Nav */}
40 |
41 |
68 |
69 | {props.children}
70 | >
71 |
72 | >
73 | );
74 | };
75 |
76 | export default Layout;
77 |
--------------------------------------------------------------------------------
/pages/login.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import http from "../services/httpService";
3 | const tokenKey = "token";
4 | import { useEffect } from "react";
5 | import {
6 | Input,
7 | useToast,
8 | Text,
9 | Stack,
10 | Alert,
11 | AlertIcon,
12 | } from "@chakra-ui/react";
13 | import { useFormik } from "formik";
14 | import * as Yup from "yup";
15 |
16 | const Login = (props) => {
17 | const toast = useToast();
18 | const login = async (email, password) => {
19 | try {
20 | const { data: jwt } = await http.post("/auth", { email, password });
21 | window.localStorage.setItem(tokenKey, jwt);
22 | window.location = "/admin";
23 | toast({
24 | title: "Success",
25 | description: "Redirecting...",
26 | status: "success",
27 | duration: 9000,
28 | isClosable: true,
29 | });
30 | } catch (ex) {
31 | toast({
32 | title: "Error",
33 | description: "Cannot Login to Account",
34 | status: "error",
35 | duration: 9000,
36 | isClosable: true,
37 | });
38 | }
39 | };
40 | useEffect(() => {
41 | const token = window.localStorage.getItem("token");
42 |
43 | if (token) {
44 | window.location = "/admin";
45 | }
46 | }, []);
47 |
48 | const formik = useFormik({
49 | initialValues: {
50 | email: "",
51 | password: "",
52 | },
53 | validationSchema: Yup.object({
54 | email: Yup.string().email().label("Email").required(),
55 | password: Yup.string().label("Password").required(),
56 | }),
57 | onSubmit: (values) => {
58 | login(values.email, values.password);
59 |
60 | //alert(JSON.stringify(values, null, 2));
61 | },
62 | });
63 | return (
64 |
65 |
66 |
Welcome Back
67 |
Login to continue
68 |
122 |
123 |
124 | );
125 | };
126 |
127 | export default Login;
128 |
--------------------------------------------------------------------------------
/pages/register.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import http from "../services/httpService";
3 | const tokenKey = "token";
4 | import { useEffect } from "react";
5 | import {
6 | Input,
7 | useToast,
8 | Text,
9 | Stack,
10 | Alert,
11 | AlertIcon,
12 | } from "@chakra-ui/react";
13 | import { useFormik } from "formik";
14 | import * as Yup from "yup";
15 |
16 | const Register = (props) => {
17 | const toast = useToast();
18 | const register = async (creds) => {
19 | try {
20 | const { data: jwt } = await http.post("/users/create", { ...creds });
21 | window.localStorage.setItem(tokenKey, jwt);
22 | window.location = "/admin";
23 | toast({
24 | title: "Success",
25 | description: "Redirecting...",
26 | status: "success",
27 | duration: 9000,
28 | isClosable: true,
29 | });
30 | } catch (ex) {
31 | toast({
32 | title: "Error",
33 | description: "Cannot Login to Account",
34 | status: "error",
35 | duration: 9000,
36 | isClosable: true,
37 | });
38 | }
39 | };
40 | useEffect(() => {
41 | const token = window.localStorage.getItem("token");
42 |
43 | if (token) {
44 | window.location = "/admin";
45 | }
46 | }, []);
47 |
48 | const formik = useFormik({
49 | initialValues: {
50 | name: "",
51 | email: "",
52 | password: "",
53 | },
54 | validationSchema: Yup.object({
55 | name: Yup.string().label("Name").required(),
56 | email: Yup.string().email().label("Email").required(),
57 | password: Yup.string().label("Password").required(),
58 | }),
59 | onSubmit: (values) => {
60 | register(values);
61 |
62 | //alert(JSON.stringify(values, null, 2));
63 | },
64 | });
65 | return (
66 |
67 |
68 |
Welcome Back
69 |
Login to continue
70 |
145 |
146 |
147 | );
148 | };
149 |
150 | export default Register;
151 |
--------------------------------------------------------------------------------
/public/assets/css/dashboard.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
2 |
3 | *,
4 | ::before,
5 | ::after {
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | margin: 3.5rem 0 0 0;
11 | padding: 1rem 1rem 0;
12 | font-family: 'Poppins', sans-serif;
13 | font-size: .938rem;
14 | }
15 |
16 | a {
17 | text-decoration: none;
18 | }
19 |
20 | .header {
21 | position: fixed;
22 | top: 0;
23 | left: 0;
24 | width: 100%;
25 | background-color: #FFFFFF;
26 | box-shadow: 0 1px 0 rgba(22, 8, 43, 0.1);
27 | padding: 0 1rem;
28 | z-index: 100;
29 | }
30 |
31 | .header_container {
32 | display: flex;
33 | align-items: center;
34 | height: 3.5rem;
35 | justify-content: space-between;
36 | }
37 |
38 | .header_img {
39 | width: 35px;
40 | height: 35px;
41 | border-radius: 50%;
42 | }
43 |
44 | .header_logo {
45 | color: #19181B;
46 | font-weight: 500;
47 | display: none;
48 | }
49 |
50 | .header_icon,
51 | .header_toggle {
52 | font-size: 1.2rem;
53 | }
54 |
55 | .header_toggle {
56 | color: #19181B;
57 | cursor: pointer;
58 | }
59 |
60 |
61 | .nav {
62 | position: fixed;
63 | top: 0;
64 | left: -100%;
65 | height: 100vh;
66 | padding: 1rem 1rem 0;
67 | background-color: #FFFFFF;
68 | box-shadow: 1px 0 0 rgba(22, 8, 43, 0.1);
69 | z-index: 100;
70 | transition: .4s;
71 | }
72 |
73 | .nav_container {
74 | height: 100%;
75 | display: flex;
76 | flex-direction: column;
77 | justify-content: space-between;
78 | padding-bottom: 3rem;
79 | overflow: auto;
80 | scrollbar-width: none;
81 |
82 | }
83 |
84 | .nav_container::-webkit-scrollbar {
85 | display: none;
86 | }
87 |
88 | .nav_logo {
89 | font-weight: var(--font-semi-bold);
90 | margin-bottom: 2.5rem;
91 | }
92 |
93 | .nav_list,
94 | .nav_items {
95 | display: grid;
96 | }
97 |
98 | .nav_list {
99 | row-gap: 2.5rem;
100 | }
101 |
102 | .nav_items {
103 | row-gap: 1.5rem;
104 | }
105 |
106 | .nav_mainmenu {
107 | font-size: .938rem;
108 | text-transform: uppercase;
109 | letter-spacing: .1rem;
110 | color: #A5A1AA;
111 | font-weight: 900;
112 | }
113 |
114 | .nav_link {
115 | display: flex;
116 | align-items: center;
117 | color: #3747D4;
118 | }
119 |
120 | .nav_link:hover {
121 | color: #000000;
122 | }
123 |
124 | .nav_icon {
125 | font-size: 1.2rem;
126 | margin-right: .5rem;
127 | }
128 |
129 | .nav_name {
130 | font-size: .75rem;
131 | font-weight: 500;
132 | white-space: nowrap;
133 | }
134 |
135 | .nav_logout {
136 | margin-top: 5rem;
137 | }
138 |
139 | .show-menu {
140 | left: 0;
141 | }
142 |
143 | .active {
144 | color: #000000;
145 | }
146 |
147 | @media screen and (min-width: 768px) {
148 | body {
149 | padding: 1rem 3rem 0 6rem;
150 | }
151 |
152 | .header {
153 | padding: 0 3rem 0 6rem;
154 | }
155 |
156 | .header_container {
157 | height: calc(3.5rem + .5rem);
158 | }
159 |
160 | .header_toggle {
161 | display: none;
162 | }
163 |
164 | .header_logo {
165 | display: block;
166 | }
167 |
168 | .header_img {
169 | width: 40px;
170 | height: 40px;
171 | order: 1;
172 | }
173 |
174 | .nav {
175 | left: 0;
176 | padding: 1.2rem 1.5rem 0;
177 | width: 68px;
178 | }
179 |
180 | .nav_items {
181 | row-gap: 1.7rem;
182 | }
183 |
184 | .nav_icon {
185 | font-size: 1.3rem;
186 | }
187 |
188 | .nav_logo-name,
189 | .nav_name,
190 | .nav_mainmenu {
191 | opacity: 0;
192 | transition: .3s;
193 | }
194 |
195 | .nav:hover {
196 | width: 219px;
197 | }
198 |
199 | .nav:hover .nav_logo-name {
200 | opacity: 1;
201 | }
202 |
203 | .nav:hover .nav_mainmenu {
204 | opacity: 1;
205 | }
206 |
207 | .nav:hover .nav_name {
208 | opacity: 1;
209 | }
210 | }
211 |
212 | .users-status {
213 | color: #081DFF;
214 | }
215 |
216 | .status-items-manage {
217 | background: #fff;
218 | margin-top: 20px;
219 | margin-bottom: 3px;
220 |
221 | }
222 |
223 | .status-items-manage .items {
224 | color: black;
225 | display: flex;
226 | padding: 12px 17px;
227 | background: white;
228 | border-radius: 3px;
229 | align-items: center;
230 | border: solid 1px;
231 | justify-content: space-between;
232 | }
233 |
234 | .status-items-manage .items .site-title {
235 | font-size: 18px;
236 | font-weight: 400;
237 | }
238 |
239 | .status-items-manage .items .i {
240 | font-size: 16px;
241 | }
242 |
243 | .bx-trash-alt {
244 | color: red;
245 | }
246 |
247 | .select-box {
248 | display: flex;
249 | width: 250px;
250 | flex-direction: column;
251 | }
252 |
253 | .select-box .options-container {
254 | background: #2f3640;
255 | color: #f5f6fa;
256 | max-height: 0;
257 | width: 100%;
258 | opacity: 0;
259 | transition: all 0.4s;
260 | border-radius: 8px;
261 | overflow: hidden;
262 | order: 1;
263 | }
264 |
265 | .selected {
266 | margin-top: 10px;
267 | background: #2f3640;
268 | border-radius: 8px;
269 | margin-bottom: 8px;
270 | color: #f5f6fa;
271 | position: relative;
272 | order: 0;
273 | }
274 |
275 | .selected::after {
276 | content: "";
277 | background: url("/assets/img/arrow-down.svg");
278 | background-size: contain;
279 | background-repeat: no-repeat;
280 | position: absolute;
281 | height: 100%;
282 | width: 32px;
283 | right: 10px;
284 | top: 5px;
285 | transition: all 0.4s;
286 | }
287 |
288 | .select-box .options-container.active {
289 | max-height: 240px;
290 | opacity: 1;
291 | overflow-y: scroll;
292 | }
293 |
294 | .select-box .options-container.active+.selected::after {
295 | transform: rotateX(180deg);
296 | top: -6px;
297 | }
298 |
299 | .select-box .options-container::-webkit-scrollbar {
300 | width: 8px;
301 | background: #0d141f;
302 | border-radius: 0 8px 8px 0;
303 | }
304 |
305 | .select-box .options-container::-webkit-scrollbar-thumb {
306 | background: #525861;
307 | border-radius: 0 8px 8px 0;
308 | }
309 |
310 | .select-box .option,
311 | .selected {
312 | padding: 12px 24px;
313 | cursor: pointer;
314 | }
315 |
316 | .select-box .option:hover {
317 | background: #414b57;
318 | }
319 |
320 | .select-box label {
321 | cursor: pointer;
322 | }
323 |
324 | .select-box .option .radio {
325 | display: none;
326 | }
327 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import {
3 | Box,
4 | Center,
5 | VStack,
6 | Text,
7 | Flex,
8 | Spacer,
9 | Input,
10 | FormControl,
11 | FormLabel,
12 | useToast,
13 | } from "@chakra-ui/react";
14 | import http from "../services/httpService";
15 |
16 | import { CheckCircleIcon, WarningIcon, EmailIcon } from "@chakra-ui/icons";
17 | import { useState } from "react";
18 | import db from "../utils/db";
19 | import System from "../models/system";
20 | import Config from "../models/config";
21 |
22 | export default function Home({ status, systems, config }) {
23 | console.log(status);
24 | console.log(systems);
25 | const [email, setEmail] = useState("");
26 | const toast = useToast();
27 | const handleSubmit = async (email) => {
28 | try {
29 | await http.post("/subs", { email: email });
30 | toast({
31 | title: "Success",
32 | description: "Successfully Subscribed ",
33 | status: "success",
34 | duration: 9000,
35 | isClosable: true,
36 | });
37 | } catch (ex) {
38 | toast({
39 | title: "Error",
40 | description: "Submit Unsuccessful",
41 | status: "error",
42 | duration: 9000,
43 | isClosable: true,
44 | });
45 | }
46 | };
47 | return (
48 | <>
49 |
50 |
51 |
55 |
56 |
57 | UP Stats
58 |
59 |
60 |
61 |
62 |
68 |
69 |
70 |
71 |
72 | {status.operational
73 | ? "All Systems Operational"
74 | : `${status.outageCount} Systems Outage`}
75 |
76 |
77 |
78 |
79 | {systems.map((system) => (
80 |
89 | {system.name}
90 |
91 | {system.status === "up" && (
92 |
93 | )}
94 |
95 | {system.status === "down" && (
96 |
97 | )}
98 |
99 | ))}
100 |
101 |
102 | {config.mailing ? (
103 |
104 | Want to see Back in action?
105 |
106 | Subscribe via Email and
107 | Get notified about the System Status
108 |
109 |
110 |
111 | Email address
112 | setEmail(e.target.value)}
116 | />
117 | handleSubmit(email)}
127 | >
128 |
129 | Subscribe
130 |
131 |
132 |
133 |
134 | ) : (
135 | ""
136 | )}
137 |
148 |
149 |
165 |
166 |
167 | >
168 | );
169 | }
170 | export async function getStaticProps(context) {
171 | const getSystems = async () => {
172 | const result = await System.find().sort("name");
173 | return result.map((doc) => {
174 | const system = doc.toObject();
175 |
176 | system._id = system._id.toString();
177 | return system;
178 | });
179 | };
180 |
181 | const getStatus = async () => {
182 | let isOperational = false;
183 | let outageCount = 0;
184 | const systems = await System.find().lean();
185 | for (let system of systems) {
186 | isOperational = system.status === "up" ? true : false;
187 | if (!isOperational) outageCount += 1;
188 | }
189 | return {
190 | operational: isOperational,
191 | ...(!isOperational && { outageCount }),
192 | };
193 | };
194 | await db();
195 | const systems = await getSystems();
196 | const status = await getStatus();
197 | const config = await Config.findOne().lean();
198 | config._id = config._id.toString();
199 |
200 | if (!status && !systems) {
201 | return {
202 | notFound: true,
203 | };
204 | }
205 | let data = JSON.stringify({
206 | status: status,
207 | systems: systems,
208 | config: config,
209 | });
210 | return {
211 | props: JSON.parse(data),
212 | revalidate: 1,
213 | };
214 | }
215 |
--------------------------------------------------------------------------------
/pages/admin/manage-admin.jsx:
--------------------------------------------------------------------------------
1 | import Layout from "../../components/Layout";
2 | import {
3 | Input,
4 | Alert,
5 | AlertIcon,
6 | Text,
7 | Stack,
8 | useToast,
9 | } from "@chakra-ui/react";
10 |
11 | import { useEffect, useState } from "react";
12 |
13 | import http from "../../services/httpService";
14 | import { EditIcon, DeleteIcon } from "@chakra-ui/icons";
15 | import { useRouter } from "next/router";
16 | import * as Yup from "yup";
17 | import { useFormik, FormikProvider } from "formik";
18 | import { create } from "apisauce";
19 |
20 | const Test = (props) => {
21 | const api = create({
22 | baseURL: `/api`,
23 | });
24 | const toast = useToast();
25 |
26 | const router = useRouter();
27 | const [users, setUsers] = useState([]);
28 | const [isLoggedIn, setIsLoggedIn] = useState(false);
29 |
30 | const [currentEditUser, setCurrentEditUser] = useState({
31 | email: "",
32 | });
33 | const loadUsers = async () => {
34 | try {
35 | const { data } = await http.get("/users");
36 | setUsers(data);
37 | } catch (e) {
38 | toast({
39 | title: "Error",
40 | description: "Error Loading Users",
41 | status: "error",
42 | duration: 9000,
43 | isClosable: true,
44 | });
45 | }
46 | };
47 | useEffect(() => {
48 | const token = window.localStorage.getItem("token");
49 | http.setJwt(token);
50 |
51 | if (!token) {
52 | setIsLoggedIn(false);
53 | toast({
54 | title: "Error",
55 | description: "Redirecting to Login Page",
56 | status: "warning",
57 | duration: 9000,
58 | isClosable: true,
59 | });
60 | router.push("/login");
61 | } else setIsLoggedIn(true);
62 | }, []);
63 |
64 | useEffect(() => {
65 | loadUsers();
66 | }, []);
67 |
68 | const handleDelete = async (user) => {
69 | const originalUsers = users;
70 | const newUsers = originalUsers.filter((s) => s._id !== user._id);
71 |
72 | setUsers(newUsers);
73 | try {
74 | await http.delete(`/users/${user._id}`);
75 | } catch (ex) {
76 | if (ex.response && ex.response.status === 404)
77 | toast({
78 | title: "Error",
79 | description: "User May be Already Deleted",
80 | status: "error",
81 | duration: 9000,
82 | isClosable: true,
83 | });
84 | setUsers(originalUsers);
85 | }
86 | };
87 | const handleAdd = async (user) => {
88 | try {
89 | const { data } = await api.post("/users", user, {
90 | headers: localStorage.getItem("token"),
91 | });
92 | setUsers([...users, data]);
93 | } catch (ex) {
94 | toast({
95 | title: "Error",
96 | description: "Submit Unsuccessful",
97 | status: "error",
98 | duration: 9000,
99 | isClosable: true,
100 | });
101 | }
102 | };
103 |
104 | const handleEdit = async () => {
105 | const originalUsers = users;
106 | let newUsers = [...users];
107 | const idx = newUsers.findIndex((sys) => sys._id === currentEditUser._id);
108 | newUsers[idx] = { ...currentEditUser };
109 |
110 | setUsers(newUsers);
111 | try {
112 | await http.put(`/users/${currentEditUser._id}`, {
113 | email: currentEditUser.email,
114 | });
115 | setCurrentEditUser({ email: "" });
116 | } catch (ex) {
117 | toast({
118 | title: "Error",
119 | description: "Error Updating The User",
120 | status: "error",
121 | duration: 9000,
122 | isClosable: true,
123 | });
124 | setUsers(originalUsers);
125 | setCurrentEditUser({ email: "" });
126 | }
127 | };
128 | const formik = useFormik({
129 | initialValues: {
130 | email: "",
131 | },
132 | validationSchema: Yup.object({
133 | email: Yup.string().label("Email").email().required("Required"),
134 | }),
135 | onSubmit: (values) => {
136 | handleAdd(values);
137 | },
138 | });
139 | return (
140 |
141 |
142 | <>
143 | {isLoggedIn ? (
144 | <>
145 | {/* CRUD Status List */}
146 |
147 |
New Admin
148 |
183 | {users.map((user) => (
184 |
185 |
186 |
{user.name}
187 |
188 | {
191 | setCurrentEditUser(user);
192 | }}
193 | />
194 | {
198 | handleDelete(user);
199 | }}
200 | />
201 |
202 |
203 |
204 | ))}
205 | {/* End */}
206 | {currentEditUser.email ? (
207 |
208 |
209 |
210 | Edit User
211 |
212 |
213 | Email
214 |
221 | setCurrentEditUser({
222 | ...currentEditUser,
223 | email: e.target.value,
224 | })
225 | }
226 | />
227 |
228 |
229 | {/* Add */}
230 |
231 |
238 |
239 |
240 | ) : (
241 | ""
242 | )}
243 |
244 | >
245 | ) : (
246 | ""
247 | )}
248 | >
249 |
250 |
251 | );
252 | };
253 |
254 | export default Test;
255 |
--------------------------------------------------------------------------------
/pages/admin/index.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Input,
3 | Alert,
4 | AlertIcon,
5 | Text,
6 | Stack,
7 | useToast,
8 | Radio,
9 | RadioGroup,
10 | Switch,
11 | } from "@chakra-ui/react";
12 | import { useEffect, useState } from "react";
13 | import http from "../../services/httpService";
14 | import { EditIcon, DeleteIcon } from "@chakra-ui/icons";
15 | import { useRouter } from "next/router";
16 | import * as Yup from "yup";
17 | import { useFormik, Field, FormikProvider } from "formik";
18 | import React from "react";
19 | import { create } from "apisauce";
20 | import Layout from "../../components/Layout";
21 |
22 | export default function Dashboard() {
23 | const api = create({
24 | baseURL: "/api",
25 | });
26 |
27 | const toast = useToast();
28 |
29 | const router = useRouter();
30 | const [systems, setSystems] = useState([]);
31 | const [isLoggedIn, setIsLoggedIn] = useState(false);
32 | const [mailing, setMailing] = useState(false);
33 |
34 | const [currentEditSystem, setCurrentEditSystem] = useState({
35 | name: "",
36 | url: "",
37 | });
38 | const [subsCount, setSubsCount] = useState(0);
39 | const loadSystems = async () => {
40 | try {
41 | const { data } = await http.get("/systems");
42 | setSystems(data);
43 | } catch (e) {
44 | toast({
45 | title: "Error",
46 | description: "Error Loading Systems",
47 | status: "error",
48 | duration: 9000,
49 | isClosable: true,
50 | });
51 | }
52 | };
53 | const loadConfig = async () => {
54 | try {
55 | const { data } = await http.get("/config");
56 | setMailing(data.mailing);
57 | } catch (e) {
58 | console.log("Error Loading Config");
59 | }
60 | };
61 | const loadCount = async () => {
62 | try {
63 | const { data } = await http.get("/subs");
64 |
65 | setSubsCount(data.length);
66 | } catch (e) {
67 | toast({
68 | title: "Error",
69 | description: "Error Loading Subs Count",
70 | status: "error",
71 | duration: 9000,
72 | isClosable: true,
73 | });
74 | }
75 | };
76 | useEffect(() => {
77 | const token = window.localStorage.getItem("token");
78 | http.setJwt(token);
79 |
80 | if (!token) {
81 | setIsLoggedIn(false);
82 | toast({
83 | title: "Error",
84 | description: "Redirecting to Login Page",
85 | status: "warning",
86 | duration: 9000,
87 | isClosable: true,
88 | });
89 | router.push("/login");
90 | } else setIsLoggedIn(true);
91 | }, []);
92 | useEffect(() => {
93 | loadSystems();
94 | }, []);
95 |
96 | useEffect(() => {
97 | loadCount();
98 | }, []);
99 | useEffect(() => {
100 | loadConfig();
101 | }, []);
102 | const handleDelete = async (system) => {
103 | const originalSystems = systems;
104 | const newSystems = originalSystems.filter((s) => s._id !== system._id);
105 |
106 | setSystems(newSystems);
107 | try {
108 | await http.delete(`/systems/${system._id}`);
109 | } catch (ex) {
110 | if (ex.response && ex.response.status === 404)
111 | toast({
112 | title: "Error",
113 | description: "System May be Already Deleted",
114 | status: "error",
115 | duration: 9000,
116 | isClosable: true,
117 | });
118 | setSystems(originalSystems);
119 | }
120 | };
121 | const handleAdd = async (system) => {
122 | try {
123 | const { data } = await api.post("/systems", system, {
124 | headers: localStorage.getItem("token"),
125 | });
126 | setSystems([...systems, data]);
127 | } catch (ex) {
128 | toast({
129 | title: "Error",
130 | description: "Submit Unsuccessful",
131 | status: "error",
132 | duration: 9000,
133 | isClosable: true,
134 | });
135 | }
136 | };
137 |
138 | const formik = useFormik({
139 | initialValues: {
140 | name: "",
141 | url: "",
142 | type: "web",
143 | },
144 | validationSchema: Yup.object({
145 | name: Yup.string()
146 | .max(15, "Must be 15 characters or less")
147 | .required("Required"),
148 | url: Yup.string().required("Required"),
149 | type: Yup.string(),
150 | }),
151 | onSubmit: (values) => {
152 | handleAdd(values);
153 | //alert(JSON.stringify(values, null, 2));
154 | },
155 | });
156 | const handleEdit = async () => {
157 | const originalSystems = systems;
158 | let newSystems = [...systems];
159 | const idx = newSystems.findIndex(
160 | (sys) => sys._id === currentEditSystem._id
161 | );
162 | newSystems[idx] = { ...currentEditSystem };
163 |
164 | setSystems(newSystems);
165 | try {
166 | await http.put(`/systems/${currentEditSystem._id}`, {
167 | name: currentEditSystem.name,
168 | url: currentEditSystem.url,
169 | type: currentEditSystem.type,
170 | });
171 | setCurrentEditSystem({ name: "", url: "" });
172 | } catch (ex) {
173 | toast({
174 | title: "Error",
175 | description: "Error Updating The System",
176 | status: "error",
177 | duration: 9000,
178 | isClosable: true,
179 | });
180 | setSystems(originalSystems);
181 | setCurrentEditSystem({ name: "", url: "" });
182 | }
183 | };
184 | const handleChangeConfig = async () => {
185 | try {
186 | await http.put(`/config`, {
187 | mailing: mailing,
188 | });
189 | } catch (ex) {
190 | toast({
191 | title: "Error",
192 | description: "Error Updating The Config",
193 | status: "error",
194 | duration: 9000,
195 | isClosable: true,
196 | });
197 | }
198 | };
199 | return (
200 |
201 | <>
202 |
203 | {isLoggedIn ? (
204 | <>
205 |
206 |
207 |
208 |
209 |
222 |
223 | {subsCount}
224 |
225 |
Users Subscribed
226 |
227 |
228 |
229 |
230 | {/* CRUD Status List */}
231 |
232 |
233 | Add New System
234 |
235 |
305 | {/* Status Page List */}
306 | {/* Show Sites here */}
307 | {systems.map((system) => (
308 |
309 |
310 |
{system?.name}
311 |
312 | {
315 | setCurrentEditSystem(system);
316 | }}
317 | />
318 | {
322 | handleDelete(system);
323 | }}
324 | />
325 |
326 |
327 |
328 | ))}
329 | {/* End */}
330 | {currentEditSystem.name ? (
331 |
380 | ) : (
381 | ""
382 | )}
383 |
384 | Configs
385 | Mailing
386 | setMailing(e.target.checked)}
390 | />
391 |
392 |
399 |
400 |
401 |
402 | >
403 | ) : (
404 | ""
405 | )}
406 |
407 | {/* Total No. of Users Subscribed */}
408 |
409 | >
410 |
411 | );
412 | }
413 |
--------------------------------------------------------------------------------
/public/assets/vendor/boxicons.min.css:
--------------------------------------------------------------------------------
1 | @font-face{font-family:'boxicons';font-weight:normal;font-style:normal;src:url('../fonts/boxicons.eot');src:url('../fonts/boxicons.eot') format('embedded-opentype'),url('../fonts/boxicons.woff2') format('woff2'),url('../fonts/boxicons.woff') format('woff'),url('../fonts/boxicons.ttf') format('truetype'),url('../fonts/boxicons.svg?#boxicons') format('svg')}.bx{font-family:'boxicons'!important;font-weight:normal;font-style:normal;font-variant:normal;line-height:1;display:inline-block;text-transform:none;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.bx-ul{margin-left:2em;padding-left:0;list-style:none}.bx-ul>li{position:relative}.bx-ul .bx{font-size:inherit;line-height:inherit;position:absolute;left:-2em;width:2em;text-align:center}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes burst{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}90%{-webkit-transform:scale(1.5);transform:scale(1.5);opacity:0}}@keyframes burst{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}90%{-webkit-transform:scale(1.5);transform:scale(1.5);opacity:0}}@-webkit-keyframes flashing{0%{opacity:1}45%{opacity:0}90%{opacity:1}}@keyframes flashing{0%{opacity:1}45%{opacity:0}90%{opacity:1}}@-webkit-keyframes fade-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}75%{-webkit-transform:translateX(-20px);transform:translateX(-20px);opacity:0}}@keyframes fade-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}75%{-webkit-transform:translateX(-20px);transform:translateX(-20px);opacity:0}}@-webkit-keyframes fade-right{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}75%{-webkit-transform:translateX(20px);transform:translateX(20px);opacity:0}}@keyframes fade-right{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}75%{-webkit-transform:translateX(20px);transform:translateX(20px);opacity:0}}@-webkit-keyframes fade-up{0%{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}75%{-webkit-transform:translateY(-20px);transform:translateY(-20px);opacity:0}}@keyframes fade-up{0%{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}75%{-webkit-transform:translateY(-20px);transform:translateY(-20px);opacity:0}}@-webkit-keyframes fade-down{0%{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}75%{-webkit-transform:translateY(20px);transform:translateY(20px);opacity:0}}@keyframes fade-down{0%{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}75%{-webkit-transform:translateY(20px);transform:translateY(20px);opacity:0}}@-webkit-keyframes tada{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.95,.95,.95) rotate3d(0,0,1,-10deg);transform:scale3d(.95,.95,.95) rotate3d(0,0,1,-10deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1,1,1) rotate3d(0,0,1,10deg);transform:scale3d(1,1,1) rotate3d(0,0,1,10deg)}40%,60%,80%{-webkit-transform:scale3d(1,1,1) rotate3d(0,0,1,-10deg);transform:scale3d(1,1,1) rotate3d(0,0,1,-10deg)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes tada{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.95,.95,.95) rotate3d(0,0,1,-10deg);transform:scale3d(.95,.95,.95) rotate3d(0,0,1,-10deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1,1,1) rotate3d(0,0,1,10deg);transform:scale3d(1,1,1) rotate3d(0,0,1,10deg)}40%,60%,80%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.bx-spin{-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}.bx-spin-hover:hover{-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}.bx-tada{-webkit-animation:tada 1.5s ease infinite;animation:tada 1.5s ease infinite}.bx-tada-hover:hover{-webkit-animation:tada 1.5s ease infinite;animation:tada 1.5s ease infinite}.bx-flashing{-webkit-animation:flashing 1.5s infinite linear;animation:flashing 1.5s infinite linear}.bx-flashing-hover:hover{-webkit-animation:flashing 1.5s infinite linear;animation:flashing 1.5s infinite linear}.bx-burst{-webkit-animation:burst 1.5s infinite linear;animation:burst 1.5s infinite linear}.bx-burst-hover:hover{-webkit-animation:burst 1.5s infinite linear;animation:burst 1.5s infinite linear}.bx-fade-up{-webkit-animation:fade-up 1.5s infinite linear;animation:fade-up 1.5s infinite linear}.bx-fade-up-hover:hover{-webkit-animation:fade-up 1.5s infinite linear;animation:fade-up 1.5s infinite linear}.bx-fade-down{-webkit-animation:fade-down 1.5s infinite linear;animation:fade-down 1.5s infinite linear}.bx-fade-down-hover:hover{-webkit-animation:fade-down 1.5s infinite linear;animation:fade-down 1.5s infinite linear}.bx-fade-left{-webkit-animation:fade-left 1.5s infinite linear;animation:fade-left 1.5s infinite linear}.bx-fade-left-hover:hover{-webkit-animation:fade-left 1.5s infinite linear;animation:fade-left 1.5s infinite linear}.bx-fade-right{-webkit-animation:fade-right 1.5s infinite linear;animation:fade-right 1.5s infinite linear}.bx-fade-right-hover:hover{-webkit-animation:fade-right 1.5s infinite linear;animation:fade-right 1.5s infinite linear}.bx-xs{font-size:1rem!important}.bx-sm{font-size:1.55rem!important}.bx-md{font-size:2.25rem!important}.bx-fw{font-size:1.2857142857em;line-height:.8em;width:1.2857142857em;height:.8em;margin-top:-.2em!important;vertical-align:middle}.bx-lg{font-size:3.0!important}.bx-pull-left{float:left;margin-right:.3em!important}.bx-pull-right{float:right;margin-left:.3em!important}.bx-rotate-90{transform:rotate(90deg);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)'}.bx-rotate-180{transform:rotate(180deg);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)'}.bx-rotate-270{transform:rotate(270deg);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)'}.bx-flip-horizontal{transform:scaleX(-1);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=0,mirror=1)'}.bx-flip-vertical{transform:scaleY(-1);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=2,mirror=1)'}.bx-border{padding:.25em;border:.07em solid rgba(0,0,0,.1);border-radius:.25em}.bx-border-circle{padding:.25em;border:.07em solid rgba(0,0,0,.1);border-radius:50%}.bx-abacus:before{content:"\e900"}.bx-accessibility:before{content:"\e901"}.bx-add-to-queue:before{content:"\e902"}.bx-adjust:before{content:"\e903"}.bx-alarm:before{content:"\e904"}.bx-alarm-add:before{content:"\e905"}.bx-alarm-exclamation:before{content:"\e906"}.bx-alarm-off:before{content:"\e907"}.bx-alarm-snooze:before{content:"\e908"}.bx-album:before{content:"\e909"}.bx-align-justify:before{content:"\e90a"}.bx-align-left:before{content:"\e90b"}.bx-align-middle:before{content:"\e90c"}.bx-align-right:before{content:"\e90d"}.bx-analyse:before{content:"\e90e"}.bx-anchor:before{content:"\e90f"}.bx-angry:before{content:"\e910"}.bx-aperture:before{content:"\e911"}.bx-arch:before{content:"\e912"}.bx-archive:before{content:"\e913"}.bx-archive-in:before{content:"\e914"}.bx-archive-out:before{content:"\e915"}.bx-area:before{content:"\e916"}.bx-arrow-back:before{content:"\e917"}.bx-arrow-from-bottom:before{content:"\e918"}.bx-arrow-from-left:before{content:"\e919"}.bx-arrow-from-right:before{content:"\e91a"}.bx-arrow-from-top:before{content:"\e91b"}.bx-arrow-to-bottom:before{content:"\e91c"}.bx-arrow-to-left:before{content:"\e91d"}.bx-arrow-to-right:before{content:"\e91e"}.bx-arrow-to-top:before{content:"\e91f"}.bx-at:before{content:"\e920"}.bx-atom:before{content:"\e921"}.bx-award:before{content:"\e922"}.bx-badge:before{content:"\e923"}.bx-badge-check:before{content:"\e924"}.bx-ball:before{content:"\e925"}.bx-band-aid:before{content:"\e926"}.bx-bar-chart:before{content:"\e927"}.bx-bar-chart-alt:before{content:"\e928"}.bx-bar-chart-alt-2:before{content:"\e929"}.bx-bar-chart-square:before{content:"\e92a"}.bx-barcode:before{content:"\e92b"}.bx-barcode-reader:before{content:"\e92c"}.bx-baseball:before{content:"\e92d"}.bx-basket:before{content:"\e92e"}.bx-basketball:before{content:"\e92f"}.bx-bath:before{content:"\e930"}.bx-battery:before{content:"\e931"}.bx-bed:before{content:"\e932"}.bx-been-here:before{content:"\e933"}.bx-beer:before{content:"\e934"}.bx-bell:before{content:"\e935"}.bx-bell-minus:before{content:"\e936"}.bx-bell-off:before{content:"\e937"}.bx-bell-plus:before{content:"\e938"}.bx-bible:before{content:"\e939"}.bx-bitcoin:before{content:"\e93a"}.bx-blanket:before{content:"\e93b"}.bx-block:before{content:"\e93c"}.bx-bluetooth:before{content:"\e93d"}.bx-body:before{content:"\e93e"}.bx-bold:before{content:"\e93f"}.bx-bolt-circle:before{content:"\e940"}.bx-bomb:before{content:"\e941"}.bx-bone:before{content:"\e942"}.bx-bong:before{content:"\e943"}.bx-book:before{content:"\e944"}.bx-book-add:before{content:"\e945"}.bx-book-alt:before{content:"\e946"}.bx-book-bookmark:before{content:"\e947"}.bx-book-content:before{content:"\e948"}.bx-book-heart:before{content:"\e949"}.bx-bookmark:before{content:"\e94a"}.bx-bookmark-alt:before{content:"\e94b"}.bx-bookmark-alt-minus:before{content:"\e94c"}.bx-bookmark-alt-plus:before{content:"\e94d"}.bx-bookmark-heart:before{content:"\e94e"}.bx-bookmark-minus:before{content:"\e94f"}.bx-bookmark-plus:before{content:"\e950"}.bx-bookmarks:before{content:"\e951"}.bx-book-open:before{content:"\e952"}.bx-book-reader:before{content:"\e953"}.bx-border-all:before{content:"\e954"}.bx-border-bottom:before{content:"\e955"}.bx-border-inner:before{content:"\e956"}.bx-border-left:before{content:"\e957"}.bx-border-none:before{content:"\e958"}.bx-border-outer:before{content:"\e959"}.bx-border-radius:before{content:"\e95a"}.bx-border-right:before{content:"\e95b"}.bx-border-top:before{content:"\e95c"}.bx-bot:before{content:"\e95d"}.bx-bowling-ball:before{content:"\e95e"}.bx-box:before{content:"\e95f"}.bx-bracket:before{content:"\e960"}.bx-braille:before{content:"\e961"}.bx-brain:before{content:"\e962"}.bx-briefcase:before{content:"\e963"}.bx-briefcase-alt:before{content:"\e964"}.bx-briefcase-alt-2:before{content:"\e965"}.bx-brightness:before{content:"\e966"}.bx-brightness-half:before{content:"\e967"}.bx-broadcast:before{content:"\e968"}.bx-brush:before{content:"\e969"}.bx-brush-alt:before{content:"\e96a"}.bx-bug:before{content:"\e96b"}.bx-bug-alt:before{content:"\e96c"}.bx-building:before{content:"\e96d"}.bx-building-house:before{content:"\e96e"}.bx-buildings:before{content:"\e96f"}.bx-bulb:before{content:"\e970"}.bx-bullseye:before{content:"\e971"}.bx-buoy:before{content:"\e972"}.bx-bus:before{content:"\e973"}.bx-bus-school:before{content:"\e974"}.bx-cabinet:before{content:"\e975"}.bx-cake:before{content:"\e976"}.bx-calculator:before{content:"\e977"}.bx-calendar:before{content:"\e978"}.bx-calendar-alt:before{content:"\e979"}.bx-calendar-check:before{content:"\e97a"}.bx-calendar-edit:before{content:"\e97b"}.bx-calendar-event:before{content:"\e97c"}.bx-calendar-exclamation:before{content:"\e97d"}.bx-calendar-heart:before{content:"\e97e"}.bx-calendar-minus:before{content:"\e97f"}.bx-calendar-plus:before{content:"\e980"}.bx-calendar-star:before{content:"\e981"}.bx-calendar-week:before{content:"\e982"}.bx-calendar-x:before{content:"\e983"}.bx-camera:before{content:"\e984"}.bx-camera-home:before{content:"\e985"}.bx-camera-movie:before{content:"\e986"}.bx-camera-off:before{content:"\e987"}.bx-capsule:before{content:"\e988"}.bx-captions:before{content:"\e989"}.bx-car:before{content:"\e98a"}.bx-card:before{content:"\e98b"}.bx-caret-down:before{content:"\e98c"}.bx-caret-down-circle:before{content:"\e98d"}.bx-caret-down-square:before{content:"\e98e"}.bx-caret-left:before{content:"\e98f"}.bx-caret-left-circle:before{content:"\e990"}.bx-caret-left-square:before{content:"\e991"}.bx-caret-right:before{content:"\e992"}.bx-caret-right-circle:before{content:"\e993"}.bx-caret-right-square:before{content:"\e994"}.bx-caret-up:before{content:"\e995"}.bx-caret-up-circle:before{content:"\e996"}.bx-caret-up-square:before{content:"\e997"}.bx-carousel:before{content:"\e998"}.bx-cart:before{content:"\e999"}.bx-cart-alt:before{content:"\e99a"}.bx-cast:before{content:"\e99b"}.bx-category:before{content:"\e99c"}.bx-category-alt:before{content:"\e99d"}.bx-cctv:before{content:"\e99e"}.bx-certification:before{content:"\e99f"}.bx-chair:before{content:"\e9a0"}.bx-chalkboard:before{content:"\e9a1"}.bx-chart:before{content:"\e9a2"}.bx-chat:before{content:"\e9a3"}.bx-check:before{content:"\e9a4"}.bx-checkbox:before{content:"\e9a5"}.bx-checkbox-checked:before{content:"\e9a6"}.bx-checkbox-square:before{content:"\e9a7"}.bx-check-circle:before{content:"\e9a8"}.bx-check-double:before{content:"\e9a9"}.bx-check-shield:before{content:"\e9aa"}.bx-check-square:before{content:"\e9ab"}.bx-chevron-down:before{content:"\e9ac"}.bx-chevron-down-circle:before{content:"\e9ad"}.bx-chevron-down-square:before{content:"\e9ae"}.bx-chevron-left:before{content:"\e9af"}.bx-chevron-left-circle:before{content:"\e9b0"}.bx-chevron-left-square:before{content:"\e9b1"}.bx-chevron-right:before{content:"\e9b2"}.bx-chevron-right-circle:before{content:"\e9b3"}.bx-chevron-right-square:before{content:"\e9b4"}.bx-chevrons-down:before{content:"\e9b5"}.bx-chevrons-left:before{content:"\e9b6"}.bx-chevrons-right:before{content:"\e9b7"}.bx-chevrons-up:before{content:"\e9b8"}.bx-chevron-up:before{content:"\e9b9"}.bx-chevron-up-circle:before{content:"\e9ba"}.bx-chevron-up-square:before{content:"\e9bb"}.bx-chip:before{content:"\e9bc"}.bx-church:before{content:"\e9bd"}.bx-circle:before{content:"\e9be"}.bx-clinic:before{content:"\e9bf"}.bx-clipboard:before{content:"\e9c0"}.bx-closet:before{content:"\e9c1"}.bx-cloud:before{content:"\e9c2"}.bx-cloud-download:before{content:"\e9c3"}.bx-cloud-drizzle:before{content:"\e9c4"}.bx-cloud-lightning:before{content:"\e9c5"}.bx-cloud-light-rain:before{content:"\e9c6"}.bx-cloud-rain:before{content:"\e9c7"}.bx-cloud-snow:before{content:"\e9c8"}.bx-cloud-upload:before{content:"\e9c9"}.bx-code:before{content:"\e9ca"}.bx-code-alt:before{content:"\e9cb"}.bx-code-block:before{content:"\e9cc"}.bx-code-curly:before{content:"\e9cd"}.bx-coffee:before{content:"\e9ce"}.bx-coffee-togo:before{content:"\e9cf"}.bx-cog:before{content:"\e9d0"}.bx-coin:before{content:"\e9d1"}.bx-coin-stack:before{content:"\e9d2"}.bx-collapse:before{content:"\e9d3"}.bx-collection:before{content:"\e9d4"}.bx-color-fill:before{content:"\e9d5"}.bx-columns:before{content:"\e9d6"}.bx-command:before{content:"\e9d7"}.bx-comment:before{content:"\e9d8"}.bx-comment-add:before{content:"\e9d9"}.bx-comment-check:before{content:"\e9da"}.bx-comment-detail:before{content:"\e9db"}.bx-comment-dots:before{content:"\e9dc"}.bx-comment-edit:before{content:"\e9dd"}.bx-comment-error:before{content:"\e9de"}.bx-comment-minus:before{content:"\e9df"}.bx-comment-x:before{content:"\e9e0"}.bx-compass:before{content:"\e9e1"}.bx-confused:before{content:"\e9e2"}.bx-conversation:before{content:"\e9e3"}.bx-cookie:before{content:"\e9e4"}.bx-cool:before{content:"\e9e5"}.bx-copy:before{content:"\e9e6"}.bx-copy-alt:before{content:"\e9e7"}.bx-copyright:before{content:"\e9e8"}.bx-credit-card:before{content:"\e9e9"}.bx-credit-card-alt:before{content:"\e9ea"}.bx-credit-card-front:before{content:"\e9eb"}.bx-crop:before{content:"\e9ec"}.bx-crosshair:before{content:"\e9ed"}.bx-crown:before{content:"\e9ee"}.bx-cube:before{content:"\e9ef"}.bx-cube-alt:before{content:"\e9f0"}.bx-cuboid:before{content:"\e9f1"}.bx-current-location:before{content:"\e9f2"}.bx-customize:before{content:"\e9f3"}.bx-cut:before{content:"\e9f4"}.bx-cycling:before{content:"\e9f5"}.bx-cylinder:before{content:"\e9f6"}.bx-data:before{content:"\e9f7"}.bx-desktop:before{content:"\e9f8"}.bx-detail:before{content:"\e9f9"}.bx-devices:before{content:"\e9fa"}.bx-dialpad:before{content:"\e9fb"}.bx-dialpad-alt:before{content:"\e9fc"}.bx-diamond:before{content:"\e9fd"}.bx-dice-1:before{content:"\e9fe"}.bx-dice-2:before{content:"\e9ff"}.bx-dice-3:before{content:"\ea00"}.bx-dice-4:before{content:"\ea01"}.bx-dice-5:before{content:"\ea02"}.bx-dice-6:before{content:"\ea03"}.bx-directions:before{content:"\ea04"}.bx-disc:before{content:"\ea05"}.bx-dish:before{content:"\ea06"}.bx-dislike:before{content:"\ea07"}.bx-dizzy:before{content:"\ea08"}.bx-dna:before{content:"\ea09"}.bx-dock-bottom:before{content:"\ea0a"}.bx-dock-left:before{content:"\ea0b"}.bx-dock-right:before{content:"\ea0c"}.bx-dock-top:before{content:"\ea0d"}.bx-dollar:before{content:"\ea0e"}.bx-dollar-circle:before{content:"\ea0f"}.bx-donate-blood:before{content:"\ea10"}.bx-donate-heart:before{content:"\ea11"}.bx-door-open:before{content:"\ea12"}.bx-dots-horizontal:before{content:"\ea13"}.bx-dots-horizontal-rounded:before{content:"\ea14"}.bx-dots-vertical:before{content:"\ea15"}.bx-dots-vertical-rounded:before{content:"\ea16"}.bx-doughnut-chart:before{content:"\ea17"}.bx-down-arrow:before{content:"\ea18"}.bx-down-arrow-alt:before{content:"\ea19"}.bx-down-arrow-circle:before{content:"\ea1a"}.bx-download:before{content:"\ea1b"}.bx-downvote:before{content:"\ea1c"}.bx-drink:before{content:"\ea1d"}.bx-droplet:before{content:"\ea1e"}.bx-dumbbell:before{content:"\ea1f"}.bx-duplicate:before{content:"\ea20"}.bx-edit:before{content:"\ea21"}.bx-edit-alt:before{content:"\ea22"}.bx-envelope:before{content:"\ea23"}.bx-envelope-open:before{content:"\ea24"}.bx-equalizer:before{content:"\ea25"}.bx-eraser:before{content:"\ea26"}.bx-error:before{content:"\ea27"}.bx-error-alt:before{content:"\ea28"}.bx-error-circle:before{content:"\ea29"}.bx-euro:before{content:"\ea2a"}.bx-exclude:before{content:"\ea2b"}.bx-exit:before{content:"\ea2c"}.bx-exit-fullscreen:before{content:"\ea2d"}.bx-expand:before{content:"\ea2e"}.bx-expand-alt:before{content:"\ea2f"}.bx-export:before{content:"\ea30"}.bx-extension:before{content:"\ea31"}.bx-face:before{content:"\ea32"}.bx-fast-forward:before{content:"\ea33"}.bx-fast-forward-circle:before{content:"\ea34"}.bx-female:before{content:"\ea35"}.bx-female-sign:before{content:"\ea36"}.bx-file:before{content:"\ea37"}.bx-file-blank:before{content:"\ea38"}.bx-file-find:before{content:"\ea39"}.bx-film:before{content:"\ea3a"}.bx-filter:before{content:"\ea3b"}.bx-filter-alt:before{content:"\ea3c"}.bx-fingerprint:before{content:"\ea3d"}.bx-first-aid:before{content:"\ea3e"}.bx-first-page:before{content:"\ea3f"}.bx-flag:before{content:"\ea40"}.bx-folder:before{content:"\ea41"}.bx-folder-minus:before{content:"\ea42"}.bx-folder-open:before{content:"\ea43"}.bx-folder-plus:before{content:"\ea44"}.bx-font:before{content:"\ea45"}.bx-font-color:before{content:"\ea46"}.bx-font-family:before{content:"\ea47"}.bx-font-size:before{content:"\ea48"}.bx-food-menu:before{content:"\ea49"}.bx-food-tag:before{content:"\ea4a"}.bx-football:before{content:"\ea4b"}.bx-fridge:before{content:"\ea4c"}.bx-fullscreen:before{content:"\ea4d"}.bx-game:before{content:"\ea4e"}.bx-gas-pump:before{content:"\ea4f"}.bx-ghost:before{content:"\ea50"}.bx-gift:before{content:"\ea51"}.bx-git-branch:before{content:"\ea52"}.bx-git-commit:before{content:"\ea53"}.bx-git-compare:before{content:"\ea54"}.bx-git-merge:before{content:"\ea55"}.bx-git-pull-request:before{content:"\ea56"}.bx-git-repo-forked:before{content:"\ea57"}.bx-glasses:before{content:"\ea58"}.bx-glasses-alt:before{content:"\ea59"}.bx-globe:before{content:"\ea5a"}.bx-globe-alt:before{content:"\ea5b"}.bx-grid:before{content:"\ea5c"}.bx-grid-alt:before{content:"\ea5d"}.bx-grid-horizontal:before{content:"\ea5e"}.bx-grid-small:before{content:"\ea5f"}.bx-grid-vertical:before{content:"\ea60"}.bx-group:before{content:"\ea61"}.bx-handicap:before{content:"\ea62"}.bx-happy:before{content:"\ea63"}.bx-happy-alt:before{content:"\ea64"}.bx-happy-beaming:before{content:"\ea65"}.bx-happy-heart-eyes:before{content:"\ea66"}.bx-hash:before{content:"\ea67"}.bx-hdd:before{content:"\ea68"}.bx-heading:before{content:"\ea69"}.bx-headphone:before{content:"\ea6a"}.bx-health:before{content:"\ea6b"}.bx-heart:before{content:"\ea6c"}.bx-heart-circle:before{content:"\ea6d"}.bx-heart-square:before{content:"\ea6e"}.bx-help-circle:before{content:"\ea6f"}.bx-hide:before{content:"\ea70"}.bx-highlight:before{content:"\ea71"}.bx-history:before{content:"\ea72"}.bx-hive:before{content:"\ea73"}.bx-home:before{content:"\ea74"}.bx-home-alt:before{content:"\ea75"}.bx-home-circle:before{content:"\ea76"}.bx-home-heart:before{content:"\ea77"}.bx-home-smile:before{content:"\ea78"}.bx-horizontal-center:before{content:"\ea79"}.bx-hotel:before{content:"\ea7a"}.bx-hourglass:before{content:"\ea7b"}.bx-id-card:before{content:"\ea7c"}.bx-image:before{content:"\ea7d"}.bx-image-add:before{content:"\ea7e"}.bx-image-alt:before{content:"\ea7f"}.bx-images:before{content:"\ea80"}.bx-import:before{content:"\ea81"}.bx-infinite:before{content:"\ea82"}.bx-info-circle:before{content:"\ea83"}.bx-info-square:before{content:"\ea84"}.bx-intersect:before{content:"\ea85"}.bx-italic:before{content:"\ea86"}.bx-joystick:before{content:"\ea87"}.bx-joystick-alt:before{content:"\ea88"}.bx-joystick-button:before{content:"\ea89"}.bx-key:before{content:"\ea8a"}.bx-label:before{content:"\ea8b"}.bx-landscape:before{content:"\ea8c"}.bx-laptop:before{content:"\ea8d"}.bx-last-page:before{content:"\ea8e"}.bx-laugh:before{content:"\ea8f"}.bx-layer:before{content:"\ea90"}.bx-layer-minus:before{content:"\ea91"}.bx-layer-plus:before{content:"\ea92"}.bx-layout:before{content:"\ea93"}.bx-left-arrow:before{content:"\ea94"}.bx-left-arrow-alt:before{content:"\ea95"}.bx-left-arrow-circle:before{content:"\ea96"}.bx-left-down-arrow-circle:before{content:"\ea97"}.bx-left-indent:before{content:"\ea98"}.bx-left-top-arrow-circle:before{content:"\ea99"}.bx-library:before{content:"\ea9a"}.bx-like:before{content:"\ea9b"}.bx-line-chart:before{content:"\ea9c"}.bx-line-chart-down:before{content:"\ea9d"}.bx-link:before{content:"\ea9e"}.bx-link-alt:before{content:"\ea9f"}.bx-link-external:before{content:"\eaa0"}.bx-lira:before{content:"\eaa1"}.bx-list-check:before{content:"\eaa2"}.bx-list-minus:before{content:"\eaa3"}.bx-list-ol:before{content:"\eaa4"}.bx-list-plus:before{content:"\eaa5"}.bx-list-ul:before{content:"\eaa6"}.bx-loader:before{content:"\eaa7"}.bx-loader-alt:before{content:"\eaa8"}.bx-loader-circle:before{content:"\eaa9"}.bx-location-plus:before{content:"\eaaa"}.bx-lock:before{content:"\eaab"}.bx-lock-alt:before{content:"\eaac"}.bx-lock-open:before{content:"\eaad"}.bx-lock-open-alt:before{content:"\eaae"}.bx-log-in:before{content:"\eaaf"}.bx-log-in-circle:before{content:"\eab0"}.bx-log-out:before{content:"\eab1"}.bx-log-out-circle:before{content:"\eab2"}.bx-low-vision:before{content:"\eab3"}.bx-magnet:before{content:"\eab4"}.bx-mail-send:before{content:"\eab5"}.bx-male:before{content:"\eab6"}.bx-male-sign:before{content:"\eab7"}.bx-map:before{content:"\eab8"}.bx-map-alt:before{content:"\eab9"}.bx-map-pin:before{content:"\eaba"}.bx-mask:before{content:"\eabb"}.bx-medal:before{content:"\eabc"}.bx-meh:before{content:"\eabd"}.bx-meh-alt:before{content:"\eabe"}.bx-meh-blank:before{content:"\eabf"}.bx-memory-card:before{content:"\eac0"}.bx-menu:before{content:"\eac1"}.bx-menu-alt-left:before{content:"\eac2"}.bx-menu-alt-right:before{content:"\eac3"}.bx-merge:before{content:"\eac4"}.bx-message:before{content:"\eac5"}.bx-message-add:before{content:"\eac6"}.bx-message-alt:before{content:"\eac7"}.bx-message-alt-add:before{content:"\eac8"}.bx-message-alt-check:before{content:"\eac9"}.bx-message-alt-detail:before{content:"\eaca"}.bx-message-alt-dots:before{content:"\eacb"}.bx-message-alt-edit:before{content:"\eacc"}.bx-message-alt-error:before{content:"\eacd"}.bx-message-alt-minus:before{content:"\eace"}.bx-message-alt-x:before{content:"\eacf"}.bx-message-check:before{content:"\ead0"}.bx-message-detail:before{content:"\ead1"}.bx-message-dots:before{content:"\ead2"}.bx-message-edit:before{content:"\ead3"}.bx-message-error:before{content:"\ead4"}.bx-message-minus:before{content:"\ead5"}.bx-message-rounded:before{content:"\ead6"}.bx-message-rounded-add:before{content:"\ead7"}.bx-message-rounded-check:before{content:"\ead8"}.bx-message-rounded-detail:before{content:"\ead9"}.bx-message-rounded-dots:before{content:"\eada"}.bx-message-rounded-edit:before{content:"\eadb"}.bx-message-rounded-error:before{content:"\eadc"}.bx-message-rounded-minus:before{content:"\eadd"}.bx-message-rounded-x:before{content:"\eade"}.bx-message-square:before{content:"\eadf"}.bx-message-square-add:before{content:"\eae0"}.bx-message-square-check:before{content:"\eae1"}.bx-message-square-detail:before{content:"\eae2"}.bx-message-square-dots:before{content:"\eae3"}.bx-message-square-edit:before{content:"\eae4"}.bx-message-square-error:before{content:"\eae5"}.bx-message-square-minus:before{content:"\eae6"}.bx-message-square-x:before{content:"\eae7"}.bx-message-x:before{content:"\eae8"}.bx-meteor:before{content:"\eae9"}.bx-microchip:before{content:"\eaea"}.bx-microphone:before{content:"\eaeb"}.bx-microphone-off:before{content:"\eaec"}.bx-minus:before{content:"\eaed"}.bx-minus-back:before{content:"\eaee"}.bx-minus-circle:before{content:"\eaef"}.bx-minus-front:before{content:"\eaf0"}.bx-mobile:before{content:"\eaf1"}.bx-mobile-alt:before{content:"\eaf2"}.bx-mobile-landscape:before{content:"\eaf3"}.bx-mobile-vibration:before{content:"\eaf4"}.bx-money:before{content:"\eaf5"}.bx-moon:before{content:"\eaf6"}.bx-mouse:before{content:"\eaf7"}.bx-mouse-alt:before{content:"\eaf8"}.bx-move:before{content:"\eaf9"}.bx-move-horizontal:before{content:"\eafa"}.bx-move-vertical:before{content:"\eafb"}.bx-movie:before{content:"\eafc"}.bx-movie-play:before{content:"\eafd"}.bx-music:before{content:"\eafe"}.bx-navigation:before{content:"\eaff"}.bx-network-chart:before{content:"\eb00"}.bx-news:before{content:"\eb01"}.bx-no-entry:before{content:"\eb02"}.bx-note:before{content:"\eb03"}.bx-notepad:before{content:"\eb04"}.bx-notification:before{content:"\eb05"}.bx-notification-off:before{content:"\eb06"}.bx-outline:before{content:"\eb07"}.bx-package:before{content:"\eb08"}.bx-paint:before{content:"\eb09"}.bx-paint-roll:before{content:"\eb0a"}.bx-palette:before{content:"\eb0b"}.bx-paperclip:before{content:"\eb0c"}.bx-paper-plane:before{content:"\eb0d"}.bx-paragraph:before{content:"\eb0e"}.bx-paste:before{content:"\eb0f"}.bx-pause:before{content:"\eb10"}.bx-pause-circle:before{content:"\eb11"}.bx-pen:before{content:"\eb12"}.bx-pencil:before{content:"\eb13"}.bx-phone:before{content:"\eb14"}.bx-phone-call:before{content:"\eb15"}.bx-phone-incoming:before{content:"\eb16"}.bx-phone-outgoing:before{content:"\eb17"}.bx-photo-album:before{content:"\eb18"}.bx-pie-chart:before{content:"\eb19"}.bx-pie-chart-alt:before{content:"\eb1a"}.bx-pie-chart-alt-2:before{content:"\eb1b"}.bx-pin:before{content:"\eb1c"}.bx-planet:before{content:"\eb1d"}.bx-play:before{content:"\eb1e"}.bx-play-circle:before{content:"\eb1f"}.bx-plug:before{content:"\eb20"}.bx-plus:before{content:"\eb21"}.bx-plus-circle:before{content:"\eb22"}.bx-plus-medical:before{content:"\eb23"}.bx-pointer:before{content:"\eb24"}.bx-poll:before{content:"\eb25"}.bx-polygon:before{content:"\eb26"}.bx-pound:before{content:"\eb27"}.bx-power-off:before{content:"\eb28"}.bx-printer:before{content:"\eb29"}.bx-pulse:before{content:"\eb2a"}.bx-purchase-tag:before{content:"\eb2b"}.bx-purchase-tag-alt:before{content:"\eb2c"}.bx-pyramid:before{content:"\eb2d"}.bx-question-mark:before{content:"\eb2e"}.bx-radar:before{content:"\eb2f"}.bx-radio:before{content:"\eb30"}.bx-radio-circle:before{content:"\eb31"}.bx-radio-circle-marked:before{content:"\eb32"}.bx-receipt:before{content:"\eb33"}.bx-rectangle:before{content:"\eb34"}.bx-recycle:before{content:"\eb35"}.bx-redo:before{content:"\eb36"}.bx-refresh:before{content:"\eb37"}.bx-rename:before{content:"\eb38"}.bx-repeat:before{content:"\eb39"}.bx-reply:before{content:"\eb3a"}.bx-reply-all:before{content:"\eb3b"}.bx-repost:before{content:"\eb3c"}.bx-reset:before{content:"\eb3d"}.bx-restaurant:before{content:"\eb3e"}.bx-revision:before{content:"\eb3f"}.bx-rewind:before{content:"\eb40"}.bx-rewind-circle:before{content:"\eb41"}.bx-right-arrow:before{content:"\eb42"}.bx-right-arrow-alt:before{content:"\eb43"}.bx-right-arrow-circle:before{content:"\eb44"}.bx-right-down-arrow-circle:before{content:"\eb45"}.bx-right-indent:before{content:"\eb46"}.bx-right-top-arrow-circle:before{content:"\eb47"}.bx-rocket:before{content:"\eb48"}.bx-rotate-left:before{content:"\eb49"}.bx-rotate-right:before{content:"\eb4a"}.bx-rss:before{content:"\eb4b"}.bx-ruble:before{content:"\eb4c"}.bx-ruler:before{content:"\eb4d"}.bx-run:before{content:"\eb4e"}.bx-rupee:before{content:"\eb4f"}.bx-sad:before{content:"\eb50"}.bx-save:before{content:"\eb51"}.bx-scan:before{content:"\eb52"}.bx-screenshot:before{content:"\eb53"}.bx-search:before{content:"\eb54"}.bx-search-alt:before{content:"\eb55"}.bx-search-alt-2:before{content:"\eb56"}.bx-selection:before{content:"\eb57"}.bx-select-multiple:before{content:"\eb58"}.bx-send:before{content:"\eb59"}.bx-server:before{content:"\eb5a"}.bx-shape-circle:before{content:"\eb5b"}.bx-shape-polygon:before{content:"\eb5c"}.bx-shape-square:before{content:"\eb5d"}.bx-shape-triangle:before{content:"\eb5e"}.bx-share:before{content:"\eb5f"}.bx-share-alt:before{content:"\eb60"}.bx-shekel:before{content:"\eb61"}.bx-shield:before{content:"\eb62"}.bx-shield-alt:before{content:"\eb63"}.bx-shield-alt-2:before{content:"\eb64"}.bx-shield-quarter:before{content:"\eb65"}.bx-shield-x:before{content:"\eb66"}.bx-shocked:before{content:"\eb67"}.bx-shopping-bag:before{content:"\eb68"}.bx-show:before{content:"\eb69"}.bx-show-alt:before{content:"\eb6a"}.bx-shuffle:before{content:"\eb6b"}.bx-sidebar:before{content:"\eb6c"}.bx-sitemap:before{content:"\eb6d"}.bx-skip-next:before{content:"\eb6e"}.bx-skip-next-circle:before{content:"\eb6f"}.bx-skip-previous:before{content:"\eb70"}.bx-skip-previous-circle:before{content:"\eb71"}.bx-sleepy:before{content:"\eb72"}.bx-slider:before{content:"\eb73"}.bx-slider-alt:before{content:"\eb74"}.bx-slideshow:before{content:"\eb75"}.bx-smile:before{content:"\eb76"}.bx-sort:before{content:"\eb77"}.bx-sort-alt-2:before{content:"\eb78"}.bx-sort-a-z:before{content:"\eb79"}.bx-sort-down:before{content:"\eb7a"}.bx-sort-up:before{content:"\eb7b"}.bx-sort-z-a:before{content:"\eb7c"}.bx-spa:before{content:"\eb7d"}.bx-space-bar:before{content:"\eb7e"}.bx-spray-can:before{content:"\eb7f"}.bx-spreadsheet:before{content:"\eb80"}.bx-square:before{content:"\eb81"}.bx-square-rounded:before{content:"\eb82"}.bx-star:before{content:"\eb83"}.bx-station:before{content:"\eb84"}.bx-stats:before{content:"\eb85"}.bx-sticker:before{content:"\eb86"}.bx-stop:before{content:"\eb87"}.bx-stop-circle:before{content:"\eb88"}.bx-stopwatch:before{content:"\eb89"}.bx-store:before{content:"\eb8a"}.bx-store-alt:before{content:"\eb8b"}.bx-street-view:before{content:"\eb8c"}.bx-strikethrough:before{content:"\eb8d"}.bx-subdirectory-left:before{content:"\eb8e"}.bx-subdirectory-right:before{content:"\eb8f"}.bx-sun:before{content:"\eb90"}.bx-support:before{content:"\eb91"}.bx-swim:before{content:"\eb92"}.bx-sync:before{content:"\eb93"}.bx-tab:before{content:"\eb94"}.bx-table:before{content:"\eb95"}.bx-tachometer:before{content:"\eb96"}.bx-tag:before{content:"\eb97"}.bx-tag-alt:before{content:"\eb98"}.bx-target-lock:before{content:"\eb99"}.bx-task:before{content:"\eb9a"}.bx-task-x:before{content:"\eb9b"}.bx-taxi:before{content:"\eb9c"}.bx-tennis-ball:before{content:"\eb9d"}.bx-terminal:before{content:"\eb9e"}.bx-test-tube:before{content:"\eb9f"}.bx-text:before{content:"\eba0"}.bx-time:before{content:"\eba1"}.bx-time-five:before{content:"\eba2"}.bx-timer:before{content:"\eba3"}.bx-tired:before{content:"\eba4"}.bx-toggle-left:before{content:"\eba5"}.bx-toggle-right:before{content:"\eba6"}.bx-tone:before{content:"\eba7"}.bx-traffic-cone:before{content:"\eba8"}.bx-train:before{content:"\eba9"}.bx-transfer:before{content:"\ebaa"}.bx-transfer-alt:before{content:"\ebab"}.bx-trash:before{content:"\ebac"}.bx-trash-alt:before{content:"\ebad"}.bx-trending-down:before{content:"\ebae"}.bx-trending-up:before{content:"\ebaf"}.bx-trim:before{content:"\ebb0"}.bx-trip:before{content:"\ebb1"}.bx-trophy:before{content:"\ebb2"}.bx-tv:before{content:"\ebb3"}.bx-underline:before{content:"\ebb4"}.bx-undo:before{content:"\ebb5"}.bx-unite:before{content:"\ebb6"}.bx-unlink:before{content:"\ebb7"}.bx-up-arrow:before{content:"\ebb8"}.bx-up-arrow-alt:before{content:"\ebb9"}.bx-up-arrow-circle:before{content:"\ebba"}.bx-upload:before{content:"\ebbb"}.bx-upside-down:before{content:"\ebbc"}.bx-upvote:before{content:"\ebbd"}.bx-usb:before{content:"\ebbe"}.bx-user:before{content:"\ebbf"}.bx-user-check:before{content:"\ebc0"}.bx-user-circle:before{content:"\ebc1"}.bx-user-minus:before{content:"\ebc2"}.bx-user-pin:before{content:"\ebc3"}.bx-user-plus:before{content:"\ebc4"}.bx-user-voice:before{content:"\ebc5"}.bx-user-x:before{content:"\ebc6"}.bx-vector:before{content:"\ebc7"}.bx-vertical-center:before{content:"\ebc8"}.bx-vial:before{content:"\ebc9"}.bx-video:before{content:"\ebca"}.bx-video-off:before{content:"\ebcb"}.bx-video-plus:before{content:"\ebcc"}.bx-video-recording:before{content:"\ebcd"}.bx-voicemail:before{content:"\ebce"}.bx-volume:before{content:"\ebcf"}.bx-volume-full:before{content:"\ebd0"}.bx-volume-low:before{content:"\ebd1"}.bx-volume-mute:before{content:"\ebd2"}.bx-walk:before{content:"\ebd3"}.bx-wallet:before{content:"\ebd4"}.bx-wallet-alt:before{content:"\ebd5"}.bx-water:before{content:"\ebd6"}.bx-webcam:before{content:"\ebd7"}.bx-wifi:before{content:"\ebd8"}.bx-wifi-0:before{content:"\ebd9"}.bx-wifi-1:before{content:"\ebda"}.bx-wifi-2:before{content:"\ebdb"}.bx-wifi-off:before{content:"\ebdc"}.bx-wind:before{content:"\ebdd"}.bx-window:before{content:"\ebde"}.bx-window-alt:before{content:"\ebdf"}.bx-window-close:before{content:"\ebe0"}.bx-window-open:before{content:"\ebe1"}.bx-windows:before{content:"\ebe2"}.bx-wine:before{content:"\ebe3"}.bx-wink-smile:before{content:"\ebe4"}.bx-wink-tongue:before{content:"\ebe5"}.bx-won:before{content:"\ebe6"}.bx-world:before{content:"\ebe7"}.bx-wrench:before{content:"\ebe8"}.bx-x:before{content:"\ebe9"}.bx-x-circle:before{content:"\ebea"}.bx-yen:before{content:"\ebeb"}.bx-zoom-in:before{content:"\ebec"}.bx-zoom-out:before{content:"\ebed"}.bxs-add-to-queue:before{content:"\ebee"}.bxs-adjust:before{content:"\ebef"}.bxs-adjust-alt:before{content:"\ebf0"}.bxs-alarm:before{content:"\ebf1"}.bxs-alarm-add:before{content:"\ebf2"}.bxs-alarm-exclamation:before{content:"\ebf3"}.bxs-alarm-off:before{content:"\ebf4"}.bxs-alarm-snooze:before{content:"\ebf5"}.bxs-album:before{content:"\ebf6"}.bxs-ambulance:before{content:"\ebf7"}.bxs-analyse:before{content:"\ebf8"}.bxs-angry:before{content:"\ebf9"}.bxs-arch:before{content:"\ebfa"}.bxs-archive:before{content:"\ebfb"}.bxs-archive-in:before{content:"\ebfc"}.bxs-archive-out:before{content:"\ebfd"}.bxs-area:before{content:"\ebfe"}.bxs-arrow-from-bottom:before{content:"\ebff"}.bxs-arrow-from-left:before{content:"\ec00"}.bxs-arrow-from-right:before{content:"\ec01"}.bxs-arrow-from-top:before{content:"\ec02"}.bxs-arrow-to-bottom:before{content:"\ec03"}.bxs-arrow-to-left:before{content:"\ec04"}.bxs-arrow-to-right:before{content:"\ec05"}.bxs-arrow-to-top:before{content:"\ec06"}.bxs-award:before{content:"\ec07"}.bxs-baby-carriage:before{content:"\ec08"}.bxs-backpack:before{content:"\ec09"}.bxs-badge:before{content:"\ec0a"}.bxs-badge-check:before{content:"\ec0b"}.bxs-badge-dollar:before{content:"\ec0c"}.bxs-ball:before{content:"\ec0d"}.bxs-band-aid:before{content:"\ec0e"}.bxs-bank:before{content:"\ec0f"}.bxs-bar-chart-alt-2:before{content:"\ec10"}.bxs-bar-chart-square:before{content:"\ec11"}.bxs-barcode:before{content:"\ec12"}.bxs-baseball:before{content:"\ec13"}.bxs-basket:before{content:"\ec14"}.bxs-basketball:before{content:"\ec15"}.bxs-bath:before{content:"\ec16"}.bxs-battery:before{content:"\ec17"}.bxs-battery-charging:before{content:"\ec18"}.bxs-battery-full:before{content:"\ec19"}.bxs-battery-low:before{content:"\ec1a"}.bxs-bed:before{content:"\ec1b"}.bxs-been-here:before{content:"\ec1c"}.bxs-beer:before{content:"\ec1d"}.bxs-bell:before{content:"\ec1e"}.bxs-bell-minus:before{content:"\ec1f"}.bxs-bell-off:before{content:"\ec20"}.bxs-bell-plus:before{content:"\ec21"}.bxs-bell-ring:before{content:"\ec22"}.bxs-bible:before{content:"\ec23"}.bxs-binoculars:before{content:"\ec24"}.bxs-blanket:before{content:"\ec25"}.bxs-bolt:before{content:"\ec26"}.bxs-bolt-circle:before{content:"\ec27"}.bxs-bomb:before{content:"\ec28"}.bxs-bone:before{content:"\ec29"}.bxs-bong:before{content:"\ec2a"}.bxs-book:before{content:"\ec2b"}.bxs-book-add:before{content:"\ec2c"}.bxs-book-alt:before{content:"\ec2d"}.bxs-book-bookmark:before{content:"\ec2e"}.bxs-book-content:before{content:"\ec2f"}.bxs-book-heart:before{content:"\ec30"}.bxs-bookmark:before{content:"\ec31"}.bxs-bookmark-alt:before{content:"\ec32"}.bxs-bookmark-alt-minus:before{content:"\ec33"}.bxs-bookmark-alt-plus:before{content:"\ec34"}.bxs-bookmark-heart:before{content:"\ec35"}.bxs-bookmark-minus:before{content:"\ec36"}.bxs-bookmark-plus:before{content:"\ec37"}.bxs-bookmarks:before{content:"\ec38"}.bxs-bookmark-star:before{content:"\ec39"}.bxs-book-open:before{content:"\ec3a"}.bxs-book-reader:before{content:"\ec3b"}.bxs-bot:before{content:"\ec3c"}.bxs-bowling-ball:before{content:"\ec3d"}.bxs-box:before{content:"\ec3e"}.bxs-brain:before{content:"\ec3f"}.bxs-briefcase:before{content:"\ec40"}.bxs-briefcase-alt:before{content:"\ec41"}.bxs-briefcase-alt-2:before{content:"\ec42"}.bxs-brightness:before{content:"\ec43"}.bxs-brightness-half:before{content:"\ec44"}.bxs-brush:before{content:"\ec45"}.bxs-brush-alt:before{content:"\ec46"}.bxs-bug:before{content:"\ec47"}.bxs-bug-alt:before{content:"\ec48"}.bxs-building:before{content:"\ec49"}.bxs-building-house:before{content:"\ec4a"}.bxs-buildings:before{content:"\ec4b"}.bxs-bulb:before{content:"\ec4c"}.bxs-bullseye:before{content:"\ec4d"}.bxs-buoy:before{content:"\ec4e"}.bxs-bus:before{content:"\ec4f"}.bxs-business:before{content:"\ec50"}.bxs-bus-school:before{content:"\ec51"}.bxs-cabinet:before{content:"\ec52"}.bxs-cake:before{content:"\ec53"}.bxs-calculator:before{content:"\ec54"}.bxs-calendar:before{content:"\ec55"}.bxs-calendar-alt:before{content:"\ec56"}.bxs-calendar-check:before{content:"\ec57"}.bxs-calendar-edit:before{content:"\ec58"}.bxs-calendar-event:before{content:"\ec59"}.bxs-calendar-exclamation:before{content:"\ec5a"}.bxs-calendar-heart:before{content:"\ec5b"}.bxs-calendar-minus:before{content:"\ec5c"}.bxs-calendar-plus:before{content:"\ec5d"}.bxs-calendar-star:before{content:"\ec5e"}.bxs-calendar-week:before{content:"\ec5f"}.bxs-calendar-x:before{content:"\ec60"}.bxs-camera:before{content:"\ec61"}.bxs-camera-home:before{content:"\ec62"}.bxs-camera-movie:before{content:"\ec63"}.bxs-camera-off:before{content:"\ec64"}.bxs-camera-plus:before{content:"\ec65"}.bxs-capsule:before{content:"\ec66"}.bxs-captions:before{content:"\ec67"}.bxs-car:before{content:"\ec68"}.bxs-car-battery:before{content:"\ec69"}.bxs-car-crash:before{content:"\ec6a"}.bxs-card:before{content:"\ec6b"}.bxs-caret-down-circle:before{content:"\ec6c"}.bxs-caret-down-square:before{content:"\ec6d"}.bxs-caret-left-circle:before{content:"\ec6e"}.bxs-caret-left-square:before{content:"\ec6f"}.bxs-caret-right-circle:before{content:"\ec70"}.bxs-caret-right-square:before{content:"\ec71"}.bxs-caret-up-circle:before{content:"\ec72"}.bxs-caret-up-square:before{content:"\ec73"}.bxs-car-garage:before{content:"\ec74"}.bxs-car-mechanic:before{content:"\ec75"}.bxs-carousel:before{content:"\ec76"}.bxs-cart:before{content:"\ec77"}.bxs-cart-add:before{content:"\ec78"}.bxs-cart-alt:before{content:"\ec79"}.bxs-cart-download:before{content:"\ec7a"}.bxs-car-wash:before{content:"\ec7b"}.bxs-category:before{content:"\ec7c"}.bxs-category-alt:before{content:"\ec7d"}.bxs-cctv:before{content:"\ec7e"}.bxs-certification:before{content:"\ec7f"}.bxs-chalkboard:before{content:"\ec80"}.bxs-chart:before{content:"\ec81"}.bxs-chat:before{content:"\ec82"}.bxs-checkbox:before{content:"\ec83"}.bxs-checkbox-checked:before{content:"\ec84"}.bxs-check-circle:before{content:"\ec85"}.bxs-check-shield:before{content:"\ec86"}.bxs-check-square:before{content:"\ec87"}.bxs-chess:before{content:"\ec88"}.bxs-chevron-down:before{content:"\ec89"}.bxs-chevron-down-circle:before{content:"\ec8a"}.bxs-chevron-down-square:before{content:"\ec8b"}.bxs-chevron-left:before{content:"\ec8c"}.bxs-chevron-left-circle:before{content:"\ec8d"}.bxs-chevron-left-square:before{content:"\ec8e"}.bxs-chevron-right:before{content:"\ec8f"}.bxs-chevron-right-circle:before{content:"\ec90"}.bxs-chevron-right-square:before{content:"\ec91"}.bxs-chevrons-down:before{content:"\ec92"}.bxs-chevrons-left:before{content:"\ec93"}.bxs-chevrons-right:before{content:"\ec94"}.bxs-chevrons-up:before{content:"\ec95"}.bxs-chevron-up:before{content:"\ec96"}.bxs-chevron-up-circle:before{content:"\ec97"}.bxs-chevron-up-square:before{content:"\ec98"}.bxs-chip:before{content:"\ec99"}.bxs-church:before{content:"\ec9a"}.bxs-circle:before{content:"\ec9b"}.bxs-city:before{content:"\ec9c"}.bxs-clinic:before{content:"\ec9d"}.bxs-cloud:before{content:"\ec9e"}.bxs-cloud-download:before{content:"\ec9f"}.bxs-cloud-lightning:before{content:"\eca0"}.bxs-cloud-rain:before{content:"\eca1"}.bxs-cloud-upload:before{content:"\eca2"}.bxs-coffee:before{content:"\eca3"}.bxs-coffee-alt:before{content:"\eca4"}.bxs-coffee-togo:before{content:"\eca5"}.bxs-cog:before{content:"\eca6"}.bxs-coin:before{content:"\eca7"}.bxs-coin-stack:before{content:"\eca8"}.bxs-collection:before{content:"\eca9"}.bxs-color-fill:before{content:"\ecaa"}.bxs-comment:before{content:"\ecab"}.bxs-comment-add:before{content:"\ecac"}.bxs-comment-check:before{content:"\ecad"}.bxs-comment-detail:before{content:"\ecae"}.bxs-comment-dots:before{content:"\ecaf"}.bxs-comment-edit:before{content:"\ecb0"}.bxs-comment-error:before{content:"\ecb1"}.bxs-comment-minus:before{content:"\ecb2"}.bxs-comment-x:before{content:"\ecb3"}.bxs-compass:before{content:"\ecb4"}.bxs-component:before{content:"\ecb5"}.bxs-confused:before{content:"\ecb6"}.bxs-contact:before{content:"\ecb7"}.bxs-conversation:before{content:"\ecb8"}.bxs-cookie:before{content:"\ecb9"}.bxs-cool:before{content:"\ecba"}.bxs-copy:before{content:"\ecbb"}.bxs-copy-alt:before{content:"\ecbc"}.bxs-copyright:before{content:"\ecbd"}.bxs-coupon:before{content:"\ecbe"}.bxs-credit-card:before{content:"\ecbf"}.bxs-credit-card-alt:before{content:"\ecc0"}.bxs-credit-card-front:before{content:"\ecc1"}.bxs-crop:before{content:"\ecc2"}.bxs-crown:before{content:"\ecc3"}.bxs-cube:before{content:"\ecc4"}.bxs-cube-alt:before{content:"\ecc5"}.bxs-cuboid:before{content:"\ecc6"}.bxs-customize:before{content:"\ecc7"}.bxs-cylinder:before{content:"\ecc8"}.bxs-dashboard:before{content:"\ecc9"}.bxs-data:before{content:"\ecca"}.bxs-detail:before{content:"\eccb"}.bxs-devices:before{content:"\eccc"}.bxs-diamond:before{content:"\eccd"}.bxs-dice-1:before{content:"\ecce"}.bxs-dice-2:before{content:"\eccf"}.bxs-dice-3:before{content:"\ecd0"}.bxs-dice-4:before{content:"\ecd1"}.bxs-dice-5:before{content:"\ecd2"}.bxs-dice-6:before{content:"\ecd3"}.bxs-direction-left:before{content:"\ecd4"}.bxs-direction-right:before{content:"\ecd5"}.bxs-directions:before{content:"\ecd6"}.bxs-disc:before{content:"\ecd7"}.bxs-discount:before{content:"\ecd8"}.bxs-dish:before{content:"\ecd9"}.bxs-dislike:before{content:"\ecda"}.bxs-dizzy:before{content:"\ecdb"}.bxs-dock-bottom:before{content:"\ecdc"}.bxs-dock-left:before{content:"\ecdd"}.bxs-dock-right:before{content:"\ecde"}.bxs-dock-top:before{content:"\ecdf"}.bxs-dollar-circle:before{content:"\ece0"}.bxs-donate-blood:before{content:"\ece1"}.bxs-donate-heart:before{content:"\ece2"}.bxs-door-open:before{content:"\ece3"}.bxs-doughnut-chart:before{content:"\ece4"}.bxs-down-arrow:before{content:"\ece5"}.bxs-down-arrow-alt:before{content:"\ece6"}.bxs-down-arrow-circle:before{content:"\ece7"}.bxs-down-arrow-square:before{content:"\ece8"}.bxs-download:before{content:"\ece9"}.bxs-downvote:before{content:"\ecea"}.bxs-drink:before{content:"\eceb"}.bxs-droplet:before{content:"\ecec"}.bxs-droplet-half:before{content:"\eced"}.bxs-dryer:before{content:"\ecee"}.bxs-duplicate:before{content:"\ecef"}.bxs-edit:before{content:"\ecf0"}.bxs-edit-alt:before{content:"\ecf1"}.bxs-edit-location:before{content:"\ecf2"}.bxs-eject:before{content:"\ecf3"}.bxs-envelope:before{content:"\ecf4"}.bxs-envelope-open:before{content:"\ecf5"}.bxs-eraser:before{content:"\ecf6"}.bxs-error:before{content:"\ecf7"}.bxs-error-alt:before{content:"\ecf8"}.bxs-error-circle:before{content:"\ecf9"}.bxs-ev-station:before{content:"\ecfa"}.bxs-exit:before{content:"\ecfb"}.bxs-extension:before{content:"\ecfc"}.bxs-eyedropper:before{content:"\ecfd"}.bxs-face:before{content:"\ecfe"}.bxs-face-mask:before{content:"\ecff"}.bxs-factory:before{content:"\ed00"}.bxs-fast-forward-circle:before{content:"\ed01"}.bxs-file:before{content:"\ed02"}.bxs-file-archive:before{content:"\ed03"}.bxs-file-blank:before{content:"\ed04"}.bxs-file-css:before{content:"\ed05"}.bxs-file-doc:before{content:"\ed06"}.bxs-file-export:before{content:"\ed07"}.bxs-file-find:before{content:"\ed08"}.bxs-file-gif:before{content:"\ed09"}.bxs-file-html:before{content:"\ed0a"}.bxs-file-image:before{content:"\ed0b"}.bxs-file-import:before{content:"\ed0c"}.bxs-file-jpg:before{content:"\ed0d"}.bxs-file-js:before{content:"\ed0e"}.bxs-file-json:before{content:"\ed0f"}.bxs-file-md:before{content:"\ed10"}.bxs-file-pdf:before{content:"\ed11"}.bxs-file-plus:before{content:"\ed12"}.bxs-file-png:before{content:"\ed13"}.bxs-file-txt:before{content:"\ed14"}.bxs-film:before{content:"\ed15"}.bxs-filter-alt:before{content:"\ed16"}.bxs-first-aid:before{content:"\ed17"}.bxs-flag:before{content:"\ed18"}.bxs-flag-alt:before{content:"\ed19"}.bxs-flag-checkered:before{content:"\ed1a"}.bxs-flame:before{content:"\ed1b"}.bxs-flask:before{content:"\ed1c"}.bxs-florist:before{content:"\ed1d"}.bxs-folder:before{content:"\ed1e"}.bxs-folder-minus:before{content:"\ed1f"}.bxs-folder-open:before{content:"\ed20"}.bxs-folder-plus:before{content:"\ed21"}.bxs-food-menu:before{content:"\ed22"}.bxs-fridge:before{content:"\ed23"}.bxs-game:before{content:"\ed24"}.bxs-gas-pump:before{content:"\ed25"}.bxs-ghost:before{content:"\ed26"}.bxs-gift:before{content:"\ed27"}.bxs-graduation:before{content:"\ed28"}.bxs-grid:before{content:"\ed29"}.bxs-grid-alt:before{content:"\ed2a"}.bxs-group:before{content:"\ed2b"}.bxs-guitar-amp:before{content:"\ed2c"}.bxs-hand-down:before{content:"\ed2d"}.bxs-hand-left:before{content:"\ed2e"}.bxs-hand-right:before{content:"\ed2f"}.bxs-hand-up:before{content:"\ed30"}.bxs-happy:before{content:"\ed31"}.bxs-happy-alt:before{content:"\ed32"}.bxs-happy-beaming:before{content:"\ed33"}.bxs-happy-heart-eyes:before{content:"\ed34"}.bxs-hdd:before{content:"\ed35"}.bxs-heart:before{content:"\ed36"}.bxs-heart-circle:before{content:"\ed37"}.bxs-heart-square:before{content:"\ed38"}.bxs-help-circle:before{content:"\ed39"}.bxs-hide:before{content:"\ed3a"}.bxs-home:before{content:"\ed3b"}.bxs-home-circle:before{content:"\ed3c"}.bxs-home-heart:before{content:"\ed3d"}.bxs-home-smile:before{content:"\ed3e"}.bxs-hot:before{content:"\ed3f"}.bxs-hotel:before{content:"\ed40"}.bxs-hourglass:before{content:"\ed41"}.bxs-hourglass-bottom:before{content:"\ed42"}.bxs-hourglass-top:before{content:"\ed43"}.bxs-id-card:before{content:"\ed44"}.bxs-image:before{content:"\ed45"}.bxs-image-add:before{content:"\ed46"}.bxs-image-alt:before{content:"\ed47"}.bxs-inbox:before{content:"\ed48"}.bxs-info-circle:before{content:"\ed49"}.bxs-info-square:before{content:"\ed4a"}.bxs-institution:before{content:"\ed4b"}.bxs-joystick:before{content:"\ed4c"}.bxs-joystick-alt:before{content:"\ed4d"}.bxs-joystick-button:before{content:"\ed4e"}.bxs-key:before{content:"\ed4f"}.bxs-keyboard:before{content:"\ed50"}.bxs-label:before{content:"\ed51"}.bxs-landmark:before{content:"\ed52"}.bxs-landscape:before{content:"\ed53"}.bxs-laugh:before{content:"\ed54"}.bxs-layer:before{content:"\ed55"}.bxs-layer-minus:before{content:"\ed56"}.bxs-layer-plus:before{content:"\ed57"}.bxs-layout:before{content:"\ed58"}.bxs-left-arrow:before{content:"\ed59"}.bxs-left-arrow-alt:before{content:"\ed5a"}.bxs-left-arrow-circle:before{content:"\ed5b"}.bxs-left-arrow-square:before{content:"\ed5c"}.bxs-left-down-arrow-circle:before{content:"\ed5d"}.bxs-left-top-arrow-circle:before{content:"\ed5e"}.bxs-like:before{content:"\ed5f"}.bxs-location-plus:before{content:"\ed60"}.bxs-lock:before{content:"\ed61"}.bxs-lock-alt:before{content:"\ed62"}.bxs-lock-open:before{content:"\ed63"}.bxs-lock-open-alt:before{content:"\ed64"}.bxs-log-in:before{content:"\ed65"}.bxs-log-in-circle:before{content:"\ed66"}.bxs-log-out:before{content:"\ed67"}.bxs-log-out-circle:before{content:"\ed68"}.bxs-low-vision:before{content:"\ed69"}.bxs-magic-wand:before{content:"\ed6a"}.bxs-magnet:before{content:"\ed6b"}.bxs-map:before{content:"\ed6c"}.bxs-map-alt:before{content:"\ed6d"}.bxs-map-pin:before{content:"\ed6e"}.bxs-mask:before{content:"\ed6f"}.bxs-medal:before{content:"\ed70"}.bxs-megaphone:before{content:"\ed71"}.bxs-meh:before{content:"\ed72"}.bxs-meh-alt:before{content:"\ed73"}.bxs-meh-blank:before{content:"\ed74"}.bxs-memory-card:before{content:"\ed75"}.bxs-message:before{content:"\ed76"}.bxs-message-add:before{content:"\ed77"}.bxs-message-alt:before{content:"\ed78"}.bxs-message-alt-add:before{content:"\ed79"}.bxs-message-alt-check:before{content:"\ed7a"}.bxs-message-alt-detail:before{content:"\ed7b"}.bxs-message-alt-dots:before{content:"\ed7c"}.bxs-message-alt-edit:before{content:"\ed7d"}.bxs-message-alt-error:before{content:"\ed7e"}.bxs-message-alt-minus:before{content:"\ed7f"}.bxs-message-alt-x:before{content:"\ed80"}.bxs-message-check:before{content:"\ed81"}.bxs-message-detail:before{content:"\ed82"}.bxs-message-dots:before{content:"\ed83"}.bxs-message-edit:before{content:"\ed84"}.bxs-message-error:before{content:"\ed85"}.bxs-message-minus:before{content:"\ed86"}.bxs-message-rounded:before{content:"\ed87"}.bxs-message-rounded-add:before{content:"\ed88"}.bxs-message-rounded-check:before{content:"\ed89"}.bxs-message-rounded-detail:before{content:"\ed8a"}.bxs-message-rounded-dots:before{content:"\ed8b"}.bxs-message-rounded-edit:before{content:"\ed8c"}.bxs-message-rounded-error:before{content:"\ed8d"}.bxs-message-rounded-minus:before{content:"\ed8e"}.bxs-message-rounded-x:before{content:"\ed8f"}.bxs-message-square:before{content:"\ed90"}.bxs-message-square-add:before{content:"\ed91"}.bxs-message-square-check:before{content:"\ed92"}.bxs-message-square-detail:before{content:"\ed93"}.bxs-message-square-dots:before{content:"\ed94"}.bxs-message-square-edit:before{content:"\ed95"}.bxs-message-square-error:before{content:"\ed96"}.bxs-message-square-minus:before{content:"\ed97"}.bxs-message-square-x:before{content:"\ed98"}.bxs-message-x:before{content:"\ed99"}.bxs-meteor:before{content:"\ed9a"}.bxs-microchip:before{content:"\ed9b"}.bxs-microphone:before{content:"\ed9c"}.bxs-microphone-alt:before{content:"\ed9d"}.bxs-microphone-off:before{content:"\ed9e"}.bxs-minus-circle:before{content:"\ed9f"}.bxs-minus-square:before{content:"\eda0"}.bxs-mobile:before{content:"\eda1"}.bxs-mobile-vibration:before{content:"\eda2"}.bxs-moon:before{content:"\eda3"}.bxs-mouse:before{content:"\eda4"}.bxs-mouse-alt:before{content:"\eda5"}.bxs-movie:before{content:"\eda6"}.bxs-movie-play:before{content:"\eda7"}.bxs-music:before{content:"\eda8"}.bxs-navigation:before{content:"\eda9"}.bxs-network-chart:before{content:"\edaa"}.bxs-news:before{content:"\edab"}.bxs-no-entry:before{content:"\edac"}.bxs-note:before{content:"\edad"}.bxs-notepad:before{content:"\edae"}.bxs-notification:before{content:"\edaf"}.bxs-notification-off:before{content:"\edb0"}.bxs-offer:before{content:"\edb1"}.bxs-package:before{content:"\edb2"}.bxs-paint:before{content:"\edb3"}.bxs-paint-roll:before{content:"\edb4"}.bxs-palette:before{content:"\edb5"}.bxs-paper-plane:before{content:"\edb6"}.bxs-parking:before{content:"\edb7"}.bxs-paste:before{content:"\edb8"}.bxs-pen:before{content:"\edb9"}.bxs-pencil:before{content:"\edba"}.bxs-phone:before{content:"\edbb"}.bxs-phone-call:before{content:"\edbc"}.bxs-phone-incoming:before{content:"\edbd"}.bxs-phone-outgoing:before{content:"\edbe"}.bxs-photo-album:before{content:"\edbf"}.bxs-piano:before{content:"\edc0"}.bxs-pie-chart:before{content:"\edc1"}.bxs-pie-chart-alt:before{content:"\edc2"}.bxs-pie-chart-alt-2:before{content:"\edc3"}.bxs-pin:before{content:"\edc4"}.bxs-pizza:before{content:"\edc5"}.bxs-plane:before{content:"\edc6"}.bxs-plane-alt:before{content:"\edc7"}.bxs-plane-land:before{content:"\edc8"}.bxs-planet:before{content:"\edc9"}.bxs-plane-take-off:before{content:"\edca"}.bxs-playlist:before{content:"\edcb"}.bxs-plug:before{content:"\edcc"}.bxs-plus-circle:before{content:"\edcd"}.bxs-plus-square:before{content:"\edce"}.bxs-pointer:before{content:"\edcf"}.bxs-polygon:before{content:"\edd0"}.bxs-printer:before{content:"\edd1"}.bxs-purchase-tag:before{content:"\edd2"}.bxs-purchase-tag-alt:before{content:"\edd3"}.bxs-pyramid:before{content:"\edd4"}.bxs-quote-alt-left:before{content:"\edd5"}.bxs-quote-alt-right:before{content:"\edd6"}.bxs-quote-left:before{content:"\edd7"}.bxs-quote-right:before{content:"\edd8"}.bxs-quote-single-left:before{content:"\edd9"}.bxs-quote-single-right:before{content:"\edda"}.bxs-radiation:before{content:"\eddb"}.bxs-radio:before{content:"\eddc"}.bxs-receipt:before{content:"\eddd"}.bxs-rectangle:before{content:"\edde"}.bxs-rename:before{content:"\eddf"}.bxs-report:before{content:"\ede0"}.bxs-rewind-circle:before{content:"\ede1"}.bxs-right-arrow:before{content:"\ede2"}.bxs-right-arrow-alt:before{content:"\ede3"}.bxs-right-arrow-circle:before{content:"\ede4"}.bxs-right-arrow-square:before{content:"\ede5"}.bxs-right-down-arrow-circle:before{content:"\ede6"}.bxs-right-top-arrow-circle:before{content:"\ede7"}.bxs-rocket:before{content:"\ede8"}.bxs-ruler:before{content:"\ede9"}.bxs-sad:before{content:"\edea"}.bxs-save:before{content:"\edeb"}.bxs-school:before{content:"\edec"}.bxs-search:before{content:"\eded"}.bxs-search-alt-2:before{content:"\edee"}.bxs-select-multiple:before{content:"\edef"}.bxs-send:before{content:"\edf0"}.bxs-server:before{content:"\edf1"}.bxs-shapes:before{content:"\edf2"}.bxs-share:before{content:"\edf3"}.bxs-share-alt:before{content:"\edf4"}.bxs-shield:before{content:"\edf5"}.bxs-shield-alt-2:before{content:"\edf6"}.bxs-shield-x:before{content:"\edf7"}.bxs-ship:before{content:"\edf8"}.bxs-shocked:before{content:"\edf9"}.bxs-shopping-bag:before{content:"\edfa"}.bxs-shopping-bag-alt:before{content:"\edfb"}.bxs-shopping-bags:before{content:"\edfc"}.bxs-show:before{content:"\edfd"}.bxs-skip-next-circle:before{content:"\edfe"}.bxs-skip-previous-circle:before{content:"\edff"}.bxs-skull:before{content:"\ee00"}.bxs-sleepy:before{content:"\ee01"}.bxs-slideshow:before{content:"\ee02"}.bxs-smile:before{content:"\ee03"}.bxs-sort-alt:before{content:"\ee04"}.bxs-spa:before{content:"\ee05"}.bxs-spray-can:before{content:"\ee06"}.bxs-spreadsheet:before{content:"\ee07"}.bxs-square:before{content:"\ee08"}.bxs-square-rounded:before{content:"\ee09"}.bxs-star:before{content:"\ee0a"}.bxs-star-half:before{content:"\ee0b"}.bxs-sticker:before{content:"\ee0c"}.bxs-stopwatch:before{content:"\ee0d"}.bxs-store:before{content:"\ee0e"}.bxs-store-alt:before{content:"\ee0f"}.bxs-sun:before{content:"\ee10"}.bxs-tachometer:before{content:"\ee11"}.bxs-tag:before{content:"\ee12"}.bxs-tag-alt:before{content:"\ee13"}.bxs-tag-x:before{content:"\ee14"}.bxs-taxi:before{content:"\ee15"}.bxs-tennis-ball:before{content:"\ee16"}.bxs-terminal:before{content:"\ee17"}.bxs-thermometer:before{content:"\ee18"}.bxs-time:before{content:"\ee19"}.bxs-time-five:before{content:"\ee1a"}.bxs-timer:before{content:"\ee1b"}.bxs-tired:before{content:"\ee1c"}.bxs-toggle-left:before{content:"\ee1d"}.bxs-toggle-right:before{content:"\ee1e"}.bxs-tone:before{content:"\ee1f"}.bxs-torch:before{content:"\ee20"}.bxs-to-top:before{content:"\ee21"}.bxs-traffic:before{content:"\ee22"}.bxs-traffic-barrier:before{content:"\ee23"}.bxs-traffic-cone:before{content:"\ee24"}.bxs-train:before{content:"\ee25"}.bxs-trash:before{content:"\ee26"}.bxs-trash-alt:before{content:"\ee27"}.bxs-tree:before{content:"\ee28"}.bxs-trophy:before{content:"\ee29"}.bxs-truck:before{content:"\ee2a"}.bxs-t-shirt:before{content:"\ee2b"}.bxs-tv:before{content:"\ee2c"}.bxs-up-arrow:before{content:"\ee2d"}.bxs-up-arrow-alt:before{content:"\ee2e"}.bxs-up-arrow-circle:before{content:"\ee2f"}.bxs-up-arrow-square:before{content:"\ee30"}.bxs-upside-down:before{content:"\ee31"}.bxs-upvote:before{content:"\ee32"}.bxs-user:before{content:"\ee33"}.bxs-user-account:before{content:"\ee34"}.bxs-user-badge:before{content:"\ee35"}.bxs-user-check:before{content:"\ee36"}.bxs-user-circle:before{content:"\ee37"}.bxs-user-detail:before{content:"\ee38"}.bxs-user-minus:before{content:"\ee39"}.bxs-user-pin:before{content:"\ee3a"}.bxs-user-plus:before{content:"\ee3b"}.bxs-user-rectangle:before{content:"\ee3c"}.bxs-user-voice:before{content:"\ee3d"}.bxs-user-x:before{content:"\ee3e"}.bxs-vector:before{content:"\ee3f"}.bxs-vial:before{content:"\ee40"}.bxs-video:before{content:"\ee41"}.bxs-video-off:before{content:"\ee42"}.bxs-video-plus:before{content:"\ee43"}.bxs-video-recording:before{content:"\ee44"}.bxs-videos:before{content:"\ee45"}.bxs-virus:before{content:"\ee46"}.bxs-virus-block:before{content:"\ee47"}.bxs-volume:before{content:"\ee48"}.bxs-volume-full:before{content:"\ee49"}.bxs-volume-low:before{content:"\ee4a"}.bxs-volume-mute:before{content:"\ee4b"}.bxs-wallet:before{content:"\ee4c"}.bxs-wallet-alt:before{content:"\ee4d"}.bxs-washer:before{content:"\ee4e"}.bxs-watch:before{content:"\ee4f"}.bxs-watch-alt:before{content:"\ee50"}.bxs-webcam:before{content:"\ee51"}.bxs-widget:before{content:"\ee52"}.bxs-window-alt:before{content:"\ee53"}.bxs-wine:before{content:"\ee54"}.bxs-wink-smile:before{content:"\ee55"}.bxs-wink-tongue:before{content:"\ee56"}.bxs-wrench:before{content:"\ee57"}.bxs-x-circle:before{content:"\ee58"}.bxs-x-square:before{content:"\ee59"}.bxs-yin-yang:before{content:"\ee5a"}.bxs-zap:before{content:"\ee5b"}.bxs-zoom-in:before{content:"\ee5c"}.bxs-zoom-out:before{content:"\ee5d"}.bxl-500px:before{content:"\ee5e"}.bxl-adobe:before{content:"\ee5f"}.bxl-airbnb:before{content:"\ee60"}.bxl-algolia:before{content:"\ee61"}.bxl-amazon:before{content:"\ee62"}.bxl-android:before{content:"\ee63"}.bxl-angular:before{content:"\ee64"}.bxl-apple:before{content:"\ee65"}.bxl-audible:before{content:"\ee66"}.bxl-baidu:before{content:"\ee67"}.bxl-behance:before{content:"\ee68"}.bxl-bing:before{content:"\ee69"}.bxl-bitcoin:before{content:"\ee6a"}.bxl-blender:before{content:"\ee6b"}.bxl-blogger:before{content:"\ee6c"}.bxl-bootstrap:before{content:"\ee6d"}.bxl-chrome:before{content:"\ee6e"}.bxl-codepen:before{content:"\ee6f"}.bxl-c-plus-plus:before{content:"\ee70"}.bxl-creative-commons:before{content:"\ee71"}.bxl-css3:before{content:"\ee72"}.bxl-dailymotion:before{content:"\ee73"}.bxl-deviantart:before{content:"\ee74"}.bxl-dev-to:before{content:"\ee75"}.bxl-digg:before{content:"\ee76"}.bxl-digitalocean:before{content:"\ee77"}.bxl-discord:before{content:"\ee78"}.bxl-discourse:before{content:"\ee79"}.bxl-django:before{content:"\ee7a"}.bxl-dribbble:before{content:"\ee7b"}.bxl-dropbox:before{content:"\ee7c"}.bxl-drupal:before{content:"\ee7d"}.bxl-ebay:before{content:"\ee7e"}.bxl-edge:before{content:"\ee7f"}.bxl-etsy:before{content:"\ee80"}.bxl-facebook:before{content:"\ee81"}.bxl-facebook-circle:before{content:"\ee82"}.bxl-facebook-square:before{content:"\ee83"}.bxl-figma:before{content:"\ee84"}.bxl-firebase:before{content:"\ee85"}.bxl-firefox:before{content:"\ee86"}.bxl-flickr:before{content:"\ee87"}.bxl-flickr-square:before{content:"\ee88"}.bxl-foursquare:before{content:"\ee89"}.bxl-git:before{content:"\ee8a"}.bxl-github:before{content:"\ee8b"}.bxl-gitlab:before{content:"\ee8c"}.bxl-google:before{content:"\ee8d"}.bxl-google-cloud:before{content:"\ee8e"}.bxl-google-plus:before{content:"\ee8f"}.bxl-google-plus-circle:before{content:"\ee90"}.bxl-html5:before{content:"\ee91"}.bxl-imdb:before{content:"\ee92"}.bxl-instagram:before{content:"\ee93"}.bxl-instagram-alt:before{content:"\ee94"}.bxl-internet-explorer:before{content:"\ee95"}.bxl-invision:before{content:"\ee96"}.bxl-javascript:before{content:"\ee97"}.bxl-joomla:before{content:"\ee98"}.bxl-jquery:before{content:"\ee99"}.bxl-jsfiddle:before{content:"\ee9a"}.bxl-kickstarter:before{content:"\ee9b"}.bxl-kubernetes:before{content:"\ee9c"}.bxl-less:before{content:"\ee9d"}.bxl-linkedin:before{content:"\ee9e"}.bxl-linkedin-square:before{content:"\ee9f"}.bxl-magento:before{content:"\eea0"}.bxl-mailchimp:before{content:"\eea1"}.bxl-markdown:before{content:"\eea2"}.bxl-mastercard:before{content:"\eea3"}.bxl-medium:before{content:"\eea4"}.bxl-medium-old:before{content:"\eea5"}.bxl-medium-square:before{content:"\eea6"}.bxl-messenger:before{content:"\eea7"}.bxl-microsoft:before{content:"\eea8"}.bxl-microsoft-teams:before{content:"\eea9"}.bxl-nodejs:before{content:"\eeaa"}.bxl-ok-ru:before{content:"\eeab"}.bxl-opera:before{content:"\eeac"}.bxl-patreon:before{content:"\eead"}.bxl-paypal:before{content:"\eeae"}.bxl-periscope:before{content:"\eeaf"}.bxl-pinterest:before{content:"\eeb0"}.bxl-pinterest-alt:before{content:"\eeb1"}.bxl-play-store:before{content:"\eeb2"}.bxl-pocket:before{content:"\eeb3"}.bxl-product-hunt:before{content:"\eeb4"}.bxl-python:before{content:"\eeb5"}.bxl-quora:before{content:"\eeb6"}.bxl-react:before{content:"\eeb7"}.bxl-redbubble:before{content:"\eeb8"}.bxl-reddit:before{content:"\eeb9"}.bxl-redux:before{content:"\eeba"}.bxl-sass:before{content:"\eebb"}.bxl-shopify:before{content:"\eebc"}.bxl-skype:before{content:"\eebd"}.bxl-slack:before{content:"\eebe"}.bxl-slack-old:before{content:"\eebf"}.bxl-snapchat:before{content:"\eec0"}.bxl-soundcloud:before{content:"\eec1"}.bxl-spotify:before{content:"\eec2"}.bxl-spring-boot:before{content:"\eec3"}.bxl-squarespace:before{content:"\eec4"}.bxl-stack-overflow:before{content:"\eec5"}.bxl-stripe:before{content:"\eec6"}.bxl-telegram:before{content:"\eec7"}.bxl-trello:before{content:"\eec8"}.bxl-tumblr:before{content:"\eec9"}.bxl-tux:before{content:"\eeca"}.bxl-twitch:before{content:"\eecb"}.bxl-twitter:before{content:"\eecc"}.bxl-unsplash:before{content:"\eecd"}.bxl-vimeo:before{content:"\eece"}.bxl-visa:before{content:"\eecf"}.bxl-vk:before{content:"\eed0"}.bxl-vuejs:before{content:"\eed1"}.bxl-whatsapp:before{content:"\eed2"}.bxl-whatsapp-square:before{content:"\eed3"}.bxl-wikipedia:before{content:"\eed4"}.bxl-windows:before{content:"\eed5"}.bxl-wix:before{content:"\eed6"}.bxl-wordpress:before{content:"\eed7"}.bxl-yahoo:before{content:"\eed8"}.bxl-yelp:before{content:"\eed9"}.bxl-youtube:before{content:"\eeda"}.bxl-zoom:before{content:"\eedb"}
2 |
--------------------------------------------------------------------------------