Compare commits
1 Commits
refactorin
...
v0.0.19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
951860f67e |
@@ -1,26 +0,0 @@
|
||||
shared/node_modules
|
||||
shared/.output
|
||||
|
||||
scripts/node_modules
|
||||
|
||||
lyx-ui/node_modules
|
||||
lyx-ui/.nuxt
|
||||
lyx-ui/.output
|
||||
|
||||
producer/node_modules
|
||||
producer/scripts/start_dev.js
|
||||
producer/ecosystem.config.cjs
|
||||
|
||||
consumer/node_modules
|
||||
consumer/scripts/start_dev.js
|
||||
consumer/ecosystem.config.cjs
|
||||
|
||||
dashboard/node_modules
|
||||
dashboard/.nuxt
|
||||
dashboard/.output
|
||||
dashboard/explains
|
||||
dashboard/tests
|
||||
dashboard/.env
|
||||
dashboard/winston-*.ndjson
|
||||
dashboard/ecosystem.config.cjs
|
||||
|
||||
13
.gitignore
vendored
@@ -1,11 +1,4 @@
|
||||
steps
|
||||
PROCESS_EVENT
|
||||
**/node_modules/
|
||||
docker
|
||||
dev
|
||||
docker-compose.admin.yml
|
||||
full_reload.sh
|
||||
build-all.sh
|
||||
node_modules
|
||||
todo
|
||||
tmp
|
||||
ecosystem.config.js
|
||||
todo
|
||||
cluster
|
||||
|
||||
65
README.md
@@ -4,14 +4,14 @@
|
||||
</p>
|
||||
|
||||
<h4 align="center">
|
||||
📚 <a href="https://docs.litlyx.com">Docs</a> 👾 <a href="https://discord.gg/9cQykjsmWX">Join Discord</a> 🌐 <a href="https://litlyx.com">Website</a> 🔥 <a href="https://dashboard.litlyx.com">Try Litlyx Cloud. It's Free forever.</a>
|
||||
📚 <a href="https://docs.litlyx.com">Docs</a> 👾 <a href="https://discord.gg/9cQykjsmWX">Discord</a> 🌐 <a href="https://litlyx.com">Website</a> 🔥 <a href="https://dashboard.litlyx.com">Cloud</a>
|
||||
</h4>
|
||||
|
||||
#
|
||||
<p align="center">
|
||||
Litlys is a modern, developer-friendly, cookie-free analytics tool.<br>
|
||||
Setup takes less than 30 seconds! Completely self-hostable with docker.<br>
|
||||
Alternative to Google Analytics, Matomo, Umami, Plausible & Simple Analytics.
|
||||
<strong>Litlys is the easiest analytics tool you will ever use. It is fast, modern and completely cookie free.</strong><br>
|
||||
Install in under <strong>30 seconds</strong>. Self host with Docker or use our hosted cloud.<br>
|
||||
A powerful alternative to Google Analytics 4, Posthog and Mixpanel.
|
||||
</p>
|
||||
|
||||
#
|
||||
@@ -19,24 +19,25 @@
|
||||
<br />
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/dashboard-clip.png"/>
|
||||
<img src="assets/d.png"/>
|
||||
</p>
|
||||
|
||||
#
|
||||
|
||||
## Get Started on our Cloud Version
|
||||
|
||||
Sign-up on [Litlyx.com](https://dashboard.litlyx.com) and create a project. Then simply use your `project_id` to connect Litlyx to your website.
|
||||
Sign up on [Litlyx.com](https://dashboard.litlyx.com) and create a project. Then use your `workspace_id` to connect Litlyx to your website.
|
||||
|
||||
## Universal Installation
|
||||
|
||||
```html
|
||||
<script defer data-project="your_project_id" src="https://cdn.jsdelivr.net/gh/litlyx/litlyx-js/browser/litlyx.js"></script>
|
||||
<script defer data-workspace = "workspace_id"
|
||||
src = "https://cdn.jsdelivr.net/npm/litlyx-js@latest/browser/litlyx.js"></script>
|
||||
```
|
||||
|
||||
Importing Litlyx with a direct script instantly starts tracking `Visits`, `Top Pages`, `Bouncing Rate`, `Real-Time Online Users`, `Unique Visitors`, `Countries`, and `Average Session Duration`.
|
||||
This minimal setup is all you need to start tracking visitors on your website or web apps with ease.
|
||||
|
||||
# All Javascript Runtimes
|
||||
# Intergrate with everything
|
||||
|
||||
You can install Litlyx using `npm`, `pnpm` or any modern package managers:
|
||||
|
||||
@@ -44,37 +45,35 @@ You can install Litlyx using `npm`, `pnpm` or any modern package managers:
|
||||
npm i litlyx-js
|
||||
```
|
||||
|
||||
Litlyx natively works with all JavaScript / TypeScript frameworks. You can use Litlyx in all WordPress Websites by injecting JS code using a third party plug-in.
|
||||
Litlyx works with all modern JavaScript and TypeScript frameworks. You can also use Litlyx on any WordPress website by injecting the script with a third party plugin.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/tech.png" />
|
||||
</p>
|
||||
<p align="center"> <img src="assets/icons.png" /> </p>
|
||||
|
||||
# Import using a package manager
|
||||
|
||||
First, Import litlyx-js library into your code:
|
||||
First, import the litlyx-js library:
|
||||
|
||||
```js
|
||||
import { Lit } from 'litlyx-js';
|
||||
```
|
||||
|
||||
Once imported, you need to initialize Litlyx:
|
||||
Then initialize Litlyx:
|
||||
|
||||
```js
|
||||
Lit.init('your_project_id');
|
||||
Lit.init('your_workspace_id');
|
||||
```
|
||||
|
||||
After initialization, Litlyx will automatically track web analytics such as `Page visits`, `Real-Time Online Users`, `Unique Vistors`, and many more.
|
||||
Once initialized, Litlyx automatically tracks page visits, real time users, unique visitors and much more.
|
||||
|
||||
# Track Custom Events (Actions)
|
||||
# Track Custom Events
|
||||
|
||||
You aren't just limited to the built-in KPIs. With Litlyx, you can create your own events to track in your project.
|
||||
You can track your own custom events with Litlyx.
|
||||
|
||||
```js
|
||||
Lit.event('click_on_buy_item');
|
||||
```
|
||||
If you need more detailed information, you can add `metadata`:
|
||||
|
||||
If you want more specific tracking, you can use the `metadata` field, like this:
|
||||
|
||||
```js
|
||||
Lit.event('click_on_buy_item', {
|
||||
@@ -86,18 +85,17 @@ Lit.event('click_on_buy_item', {
|
||||
});
|
||||
```
|
||||
|
||||
Litlyx makes it easy for you to tailor your analytics to your project's needs.
|
||||
|
||||
Litlyx makes it easy to adapt your analytics to your project.
|
||||
|
||||
# Fire Your First Event with cURL
|
||||
|
||||
Want to quickly see how Litlyx works with events? Use the cURL command below to send a test event. Just replace the `project_id` with your actual project ID in your terminal.
|
||||
Use the command below to send a test event. Replace `workspace_id` with your project ID.
|
||||
|
||||
```bash
|
||||
curl -X POST "https://broker.litlyx.com/event" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"pid": "project_id",
|
||||
"pid": "workspace_id",
|
||||
"name": "testEvent1",
|
||||
"metadata": "{\"test\": \"something\"}",
|
||||
"website": "something",
|
||||
@@ -107,21 +105,22 @@ curl -X POST "https://broker.litlyx.com/event" \
|
||||
|
||||
# Self-hosting with docker
|
||||
|
||||
To self-host the Litlyx dashboard, first **clone** this repository. (Litlyx's Docker images are hosted on DockerHub).
|
||||
To self host the Litlyx dashboard, first clone this repository. Litlyx Docker images are hosted on DockerHub.
|
||||
|
||||
Run the following command:
|
||||
|
||||
Then run the following command:
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
at localhost:3000 you will see your own instance of the Litlyx Dashboard.
|
||||
At localhost:3000 you will see your own instance of the Litlyx dashboard.
|
||||
|
||||
## Forward data to your self-hosted instance with script tag
|
||||
|
||||
To forward your data on your self-hosted instance, you need to set up the following variables: `data-host`, `data-port`, `data-secure`(`true` if it is HTTPS or `false` if it is HTTP).
|
||||
|
||||
```html
|
||||
<script defer data-project="your_project_id"
|
||||
<script defer data-project="your_workspace_id"
|
||||
data-host="your-host-name"
|
||||
data-port="your-port"
|
||||
data-secure="true/false"
|
||||
@@ -129,6 +128,12 @@ To forward your data on your self-hosted instance, you need to set up the follow
|
||||
</script>
|
||||
```
|
||||
|
||||
# Unlock the full power of Litlyx self hosting
|
||||
|
||||
Litlyx self hosting gives you the freedom to use the full platform for free.
|
||||
If you want to scale to more client seats, you can choose one of our lifetime plans available in the [pricing](https://litlyx.com/pricing) section.
|
||||
|
||||
|
||||
# Read our docs
|
||||
|
||||
For more info on how to use litlyx read our [documentation](https://docs.litlyx.com).
|
||||
@@ -152,6 +157,8 @@ If you want to contribute to Litlyx's development, reach out to us on [Discord](
|
||||
<img src="https://contrib.rocks/image?repo=litlyx/litlyx" />
|
||||
</a>
|
||||
|
||||
# License
|
||||
# Support or Business Inquiries
|
||||
For any support, write to help@litlyx.com. We are happy to assist you.
|
||||
|
||||
# License
|
||||
Litlyx is licensed under the [Apache 2.0](/LICENSE) license.
|
||||
|
||||
BIN
assets/.DS_Store
vendored
BIN
assets/agent.png
|
Before Width: | Height: | Size: 42 KiB |
BIN
assets/claim.png
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 973 KiB |
BIN
assets/d.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 34 KiB |
BIN
assets/icons.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
assets/tech.png
|
Before Width: | Height: | Size: 7.7 KiB |
4
consumer/.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
ecosystem.config.js
|
||||
dist
|
||||
.gitignore
|
||||
4
consumer/.gitignore
vendored
@@ -1,9 +1,5 @@
|
||||
|
||||
node_modules
|
||||
|
||||
ecosystem.config.cjs
|
||||
ecosystem.config.js
|
||||
|
||||
scripts/start_dev.js
|
||||
scripts/start_dev_prod.js
|
||||
dist
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
|
||||
FROM node:21-alpine as base
|
||||
|
||||
FROM node:22-alpine as builder
|
||||
RUN npm i -g pnpm
|
||||
|
||||
WORKDIR /home/app
|
||||
|
||||
COPY --link ./consumer/package.json ./consumer/pnpm-lock.yaml ./consumer/
|
||||
|
||||
WORKDIR /home/app/consumer
|
||||
COPY ./package*.json .
|
||||
RUN pnpm install
|
||||
COPY . .
|
||||
RUN pnpm run compile-only
|
||||
RUN pnpm run create_db
|
||||
|
||||
COPY --link ../consumer ./
|
||||
FROM node:22-alpine
|
||||
WORKDIR /home/app
|
||||
RUN npm i -g pnpm
|
||||
COPY package*.json ./
|
||||
RUN pnpm install
|
||||
COPY --from=builder /home/app/dist .
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
CMD ["node", "/home/app/consumer/dist/index.js"]
|
||||
CMD ["node", "/home/app/index.js"]
|
||||
@@ -1,31 +1,33 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@trpc/client": "11.2.0",
|
||||
"axios": "^1.7.9",
|
||||
"express": "^4.19.2",
|
||||
"mmdb-lib": "^3.0.1",
|
||||
"mongoose": "^8.9.5",
|
||||
"redis": "^4.7.0",
|
||||
"ua-parser-js": "^1.0.37"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@trpc/server": "11.2.0",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^20.12.13",
|
||||
"@types/node": "^20.16.5",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"name": "consumer",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "node scripts/start_dev.js",
|
||||
"dev_prod": "node scripts/start_dev_prod.js",
|
||||
"compile": "tsc",
|
||||
"dev:test": "ts-node ../scripts/consumer/run_local.ts --testmode",
|
||||
"dev:prod": "ts-node ../scripts/consumer/run_local.ts --production",
|
||||
"compile": "pnpm run shared && tsc",
|
||||
"compile-only": "tsc",
|
||||
"build": "npm run compile && npm run create_db",
|
||||
"create_db": "cd scripts && ts-node create_database.ts",
|
||||
"docker-build": "docker build -t litlyx-consumer -f Dockerfile ../",
|
||||
"docker-inspect": "docker run -it litlyx-consumer sh",
|
||||
"workspace:shared": "ts-node ../scripts/consumer/shared.ts",
|
||||
"workspace:deploy": "ts-node ../scripts/consumer/deploy.ts"
|
||||
"shared": "ts-node ../scripts/consumer/shared.ts",
|
||||
"deploy": "ts-node ../scripts/consumer/deploy.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Emily",
|
||||
|
||||
53
consumer/pnpm-lock.yaml
generated
@@ -8,12 +8,18 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@trpc/client':
|
||||
specifier: 11.2.0
|
||||
version: 11.2.0(@trpc/server@11.2.0(typescript@5.8.3))(typescript@5.8.3)
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.7.9
|
||||
express:
|
||||
specifier: ^4.19.2
|
||||
version: 4.21.2
|
||||
mmdb-lib:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
mongoose:
|
||||
specifier: ^8.9.5
|
||||
version: 8.9.5
|
||||
@@ -24,21 +30,24 @@ importers:
|
||||
specifier: ^1.0.37
|
||||
version: 1.0.39
|
||||
devDependencies:
|
||||
'@trpc/server':
|
||||
specifier: 11.2.0
|
||||
version: 11.2.0(typescript@5.8.3)
|
||||
'@types/express':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
'@types/node':
|
||||
specifier: ^20.12.13
|
||||
specifier: ^20.16.5
|
||||
version: 20.16.5
|
||||
'@types/ua-parser-js':
|
||||
specifier: ^0.7.39
|
||||
version: 0.7.39
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@20.16.5)(typescript@5.6.2)
|
||||
version: 10.9.2(@types/node@20.16.5)(typescript@5.8.3)
|
||||
typescript:
|
||||
specifier: ^5.4.5
|
||||
version: 5.6.2
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
|
||||
packages:
|
||||
|
||||
@@ -88,6 +97,17 @@ packages:
|
||||
peerDependencies:
|
||||
'@redis/client': ^1.0.0
|
||||
|
||||
'@trpc/client@11.2.0':
|
||||
resolution: {integrity: sha512-uGehc0QvK3+UmTZb1KmIwvMLtRW0I5ykiYQHA1Yqa7yHPzpF9zw7mbwna6gqZAgjclt0/S6jwxAMJizoaP8uqQ==}
|
||||
peerDependencies:
|
||||
'@trpc/server': 11.2.0
|
||||
typescript: '>=5.7.2'
|
||||
|
||||
'@trpc/server@11.2.0':
|
||||
resolution: {integrity: sha512-clESXvCT3rTRUavB3wjtmPDlbB+rayVPeaTeXEQVeQNdTrK5o6GnYfCdiJJSdQspUQAwkGgQFRnku5pckuISlw==}
|
||||
peerDependencies:
|
||||
typescript: '>=5.7.2'
|
||||
|
||||
'@tsconfig/node10@1.0.11':
|
||||
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
|
||||
|
||||
@@ -389,6 +409,10 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
mmdb-lib@3.0.1:
|
||||
resolution: {integrity: sha512-dyAyMR+cRykZd1mw5altC9f4vKpCsuywPwo8l/L5fKqDay2zmqT0mF/BvUoXnQiqGn+nceO914rkPKJoyFnGxA==}
|
||||
engines: {node: '>=10', npm: '>=6'}
|
||||
|
||||
mongodb-connection-string-url@3.0.2:
|
||||
resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==}
|
||||
|
||||
@@ -551,8 +575,8 @@ packages:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
typescript@5.6.2:
|
||||
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
|
||||
typescript@5.8.3:
|
||||
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
@@ -638,6 +662,15 @@ snapshots:
|
||||
dependencies:
|
||||
'@redis/client': 1.6.0
|
||||
|
||||
'@trpc/client@11.2.0(@trpc/server@11.2.0(typescript@5.8.3))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@trpc/server': 11.2.0(typescript@5.8.3)
|
||||
typescript: 5.8.3
|
||||
|
||||
'@trpc/server@11.2.0(typescript@5.8.3)':
|
||||
dependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
'@tsconfig/node10@1.0.11': {}
|
||||
|
||||
'@tsconfig/node12@1.0.11': {}
|
||||
@@ -942,6 +975,8 @@ snapshots:
|
||||
|
||||
mime@1.6.0: {}
|
||||
|
||||
mmdb-lib@3.0.1: {}
|
||||
|
||||
mongodb-connection-string-url@3.0.2:
|
||||
dependencies:
|
||||
'@types/whatwg-url': 11.0.5
|
||||
@@ -1102,7 +1137,7 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2):
|
||||
ts-node@10.9.2(@types/node@20.16.5)(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.11
|
||||
@@ -1116,7 +1151,7 @@ snapshots:
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 5.6.2
|
||||
typescript: 5.8.3
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
|
||||
@@ -1125,7 +1160,7 @@ snapshots:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
typescript@5.6.2: {}
|
||||
typescript@5.8.3: {}
|
||||
|
||||
ua-parser-js@1.0.39: {}
|
||||
|
||||
|
||||
BIN
consumer/scripts/GeoLite2-City.mmdb
Normal file
|
After Width: | Height: | Size: 58 MiB |
@@ -1,253 +0,0 @@
|
||||
geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,is_in_european_union
|
||||
49518,en,AF,Africa,RW,Rwanda,0
|
||||
51537,en,AF,Africa,SO,Somalia,0
|
||||
69543,en,AS,Asia,YE,Yemen,0
|
||||
99237,en,AS,Asia,IQ,Iraq,0
|
||||
102358,en,AS,Asia,SA,"Saudi Arabia",0
|
||||
130758,en,AS,Asia,IR,Iran,0
|
||||
146669,en,EU,Europe,CY,Cyprus,1
|
||||
149590,en,AF,Africa,TZ,Tanzania,0
|
||||
163843,en,AS,Asia,SY,Syria,0
|
||||
174982,en,AS,Asia,AM,Armenia,0
|
||||
192950,en,AF,Africa,KE,Kenya,0
|
||||
203312,en,AF,Africa,CD,"DR Congo",0
|
||||
223816,en,AF,Africa,DJ,Djibouti,0
|
||||
226074,en,AF,Africa,UG,Uganda,0
|
||||
239880,en,AF,Africa,CF,"Central African Republic",0
|
||||
241170,en,AF,Africa,SC,Seychelles,0
|
||||
248816,en,AS,Asia,JO,Jordan,0
|
||||
272103,en,AS,Asia,LB,Lebanon,0
|
||||
285570,en,AS,Asia,KW,Kuwait,0
|
||||
286963,en,AS,Asia,OM,Oman,0
|
||||
289688,en,AS,Asia,QA,Qatar,0
|
||||
290291,en,AS,Asia,BH,Bahrain,0
|
||||
290557,en,AS,Asia,AE,"United Arab Emirates",0
|
||||
294640,en,AS,Asia,IL,Israel,0
|
||||
298795,en,AS,Asia,TR,Türkiye,0
|
||||
337996,en,AF,Africa,ET,Ethiopia,0
|
||||
338010,en,AF,Africa,ER,Eritrea,0
|
||||
357994,en,AF,Africa,EG,Egypt,0
|
||||
366755,en,AF,Africa,SD,Sudan,0
|
||||
390903,en,EU,Europe,GR,Greece,1
|
||||
433561,en,AF,Africa,BI,Burundi,0
|
||||
453733,en,EU,Europe,EE,Estonia,1
|
||||
458258,en,EU,Europe,LV,Latvia,1
|
||||
587116,en,AS,Asia,AZ,Azerbaijan,0
|
||||
597427,en,EU,Europe,LT,Lithuania,1
|
||||
607072,en,EU,Europe,SJ,"Svalbard and Jan Mayen",0
|
||||
614540,en,AS,Asia,GE,Georgia,0
|
||||
617790,en,EU,Europe,MD,Moldova,0
|
||||
630336,en,EU,Europe,BY,Belarus,0
|
||||
660013,en,EU,Europe,FI,Finland,1
|
||||
661882,en,EU,Europe,AX,"Åland Islands",1
|
||||
690791,en,EU,Europe,UA,Ukraine,0
|
||||
718075,en,EU,Europe,MK,"North Macedonia",0
|
||||
719819,en,EU,Europe,HU,Hungary,1
|
||||
732800,en,EU,Europe,BG,Bulgaria,1
|
||||
783754,en,EU,Europe,AL,Albania,0
|
||||
798544,en,EU,Europe,PL,Poland,1
|
||||
798549,en,EU,Europe,RO,Romania,1
|
||||
831053,en,EU,Europe,XK,Kosovo,0
|
||||
878675,en,AF,Africa,ZW,Zimbabwe,0
|
||||
895949,en,AF,Africa,ZM,Zambia,0
|
||||
921929,en,AF,Africa,KM,Comoros,0
|
||||
927384,en,AF,Africa,MW,Malawi,0
|
||||
932692,en,AF,Africa,LS,Lesotho,0
|
||||
933860,en,AF,Africa,BW,Botswana,0
|
||||
934292,en,AF,Africa,MU,Mauritius,0
|
||||
934841,en,AF,Africa,SZ,Eswatini,0
|
||||
935317,en,AF,Africa,RE,Réunion,1
|
||||
953987,en,AF,Africa,ZA,"South Africa",0
|
||||
1024031,en,AF,Africa,YT,Mayotte,1
|
||||
1036973,en,AF,Africa,MZ,Mozambique,0
|
||||
1062947,en,AF,Africa,MG,Madagascar,0
|
||||
1149361,en,AS,Asia,AF,Afghanistan,0
|
||||
1168579,en,AS,Asia,PK,Pakistan,0
|
||||
1210997,en,AS,Asia,BD,Bangladesh,0
|
||||
1218197,en,AS,Asia,TM,Turkmenistan,0
|
||||
1220409,en,AS,Asia,TJ,Tajikistan,0
|
||||
1227603,en,AS,Asia,LK,"Sri Lanka",0
|
||||
1252634,en,AS,Asia,BT,Bhutan,0
|
||||
1269750,en,AS,Asia,IN,India,0
|
||||
1282028,en,AS,Asia,MV,Maldives,0
|
||||
1282588,en,AS,Asia,IO,"British Indian Ocean Territory",0
|
||||
1282988,en,AS,Asia,NP,Nepal,0
|
||||
1327865,en,AS,Asia,MM,Myanmar,0
|
||||
1512440,en,AS,Asia,UZ,Uzbekistan,0
|
||||
1522867,en,AS,Asia,KZ,Kazakhstan,0
|
||||
1527747,en,AS,Asia,KG,Kyrgyzstan,0
|
||||
1546748,en,AN,Antarctica,TF,"French Southern Territories",0
|
||||
1547314,en,AN,Antarctica,HM,"Heard and McDonald Islands",0
|
||||
1547376,en,AS,Asia,CC,"Cocos (Keeling) Islands",0
|
||||
1559582,en,OC,Oceania,PW,Palau,0
|
||||
1562822,en,AS,Asia,VN,Vietnam,0
|
||||
1605651,en,AS,Asia,TH,Thailand,0
|
||||
1643084,en,AS,Asia,ID,Indonesia,0
|
||||
1655842,en,AS,Asia,LA,Laos,0
|
||||
1668284,en,AS,Asia,TW,Taiwan,0
|
||||
1694008,en,AS,Asia,PH,Philippines,0
|
||||
1733045,en,AS,Asia,MY,Malaysia,0
|
||||
1814991,en,AS,Asia,CN,China,0
|
||||
1819730,en,AS,Asia,HK,"Hong Kong",0
|
||||
1820814,en,AS,Asia,BN,Brunei,0
|
||||
1821275,en,AS,Asia,MO,Macao,0
|
||||
1831722,en,AS,Asia,KH,Cambodia,0
|
||||
1835841,en,AS,Asia,KR,"South Korea",0
|
||||
1861060,en,AS,Asia,JP,Japan,0
|
||||
1873107,en,AS,Asia,KP,"North Korea",0
|
||||
1880251,en,AS,Asia,SG,Singapore,0
|
||||
1899402,en,OC,Oceania,CK,"Cook Islands",0
|
||||
1966436,en,OC,Oceania,TL,Timor-Leste,0
|
||||
2017370,en,EU,Europe,RU,Russia,0
|
||||
2029969,en,AS,Asia,MN,Mongolia,0
|
||||
2077456,en,OC,Oceania,AU,Australia,0
|
||||
2078138,en,OC,Oceania,CX,"Christmas Island",0
|
||||
2080185,en,OC,Oceania,MH,"Marshall Islands",0
|
||||
2081918,en,OC,Oceania,FM,"Federated States of Micronesia",0
|
||||
2088628,en,OC,Oceania,PG,"Papua New Guinea",0
|
||||
2103350,en,OC,Oceania,SB,"Solomon Islands",0
|
||||
2110297,en,OC,Oceania,TV,Tuvalu,0
|
||||
2110425,en,OC,Oceania,NR,Nauru,0
|
||||
2134431,en,OC,Oceania,VU,Vanuatu,0
|
||||
2139685,en,OC,Oceania,NC,"New Caledonia",0
|
||||
2155115,en,OC,Oceania,NF,"Norfolk Island",0
|
||||
2186224,en,OC,Oceania,NZ,"New Zealand",0
|
||||
2205218,en,OC,Oceania,FJ,Fiji,0
|
||||
2215636,en,AF,Africa,LY,Libya,0
|
||||
2233387,en,AF,Africa,CM,Cameroon,0
|
||||
2245662,en,AF,Africa,SN,Senegal,0
|
||||
2260494,en,AF,Africa,CG,"Congo Republic",0
|
||||
2264397,en,EU,Europe,PT,Portugal,1
|
||||
2275384,en,AF,Africa,LR,Liberia,0
|
||||
2287781,en,AF,Africa,CI,"Ivory Coast",0
|
||||
2300660,en,AF,Africa,GH,Ghana,0
|
||||
2309096,en,AF,Africa,GQ,"Equatorial Guinea",0
|
||||
2328926,en,AF,Africa,NG,Nigeria,0
|
||||
2361809,en,AF,Africa,BF,"Burkina Faso",0
|
||||
2363686,en,AF,Africa,TG,Togo,0
|
||||
2372248,en,AF,Africa,GW,Guinea-Bissau,0
|
||||
2378080,en,AF,Africa,MR,Mauritania,0
|
||||
2395170,en,AF,Africa,BJ,Benin,0
|
||||
2400553,en,AF,Africa,GA,Gabon,0
|
||||
2403846,en,AF,Africa,SL,"Sierra Leone",0
|
||||
2410758,en,AF,Africa,ST,"São Tomé and Príncipe",0
|
||||
2411586,en,EU,Europe,GI,Gibraltar,0
|
||||
2413451,en,AF,Africa,GM,Gambia,0
|
||||
2420477,en,AF,Africa,GN,Guinea,0
|
||||
2434508,en,AF,Africa,TD,Chad,0
|
||||
2440476,en,AF,Africa,NE,Niger,0
|
||||
2453866,en,AF,Africa,ML,Mali,0
|
||||
2461445,en,AF,Africa,EH,"Western Sahara",0
|
||||
2464461,en,AF,Africa,TN,Tunisia,0
|
||||
2510769,en,EU,Europe,ES,Spain,1
|
||||
2542007,en,AF,Africa,MA,Morocco,0
|
||||
2562770,en,EU,Europe,MT,Malta,1
|
||||
2589581,en,AF,Africa,DZ,Algeria,0
|
||||
2622320,en,EU,Europe,FO,"Faroe Islands",0
|
||||
2623032,en,EU,Europe,DK,Denmark,1
|
||||
2629691,en,EU,Europe,IS,Iceland,0
|
||||
2635167,en,EU,Europe,GB,"United Kingdom",0
|
||||
2658434,en,EU,Europe,CH,Switzerland,0
|
||||
2661886,en,EU,Europe,SE,Sweden,1
|
||||
2750405,en,EU,Europe,NL,"The Netherlands",1
|
||||
2782113,en,EU,Europe,AT,Austria,1
|
||||
2802361,en,EU,Europe,BE,Belgium,1
|
||||
2921044,en,EU,Europe,DE,Germany,1
|
||||
2960313,en,EU,Europe,LU,Luxembourg,1
|
||||
2963597,en,EU,Europe,IE,Ireland,1
|
||||
2993457,en,EU,Europe,MC,Monaco,0
|
||||
3017382,en,EU,Europe,FR,France,1
|
||||
3041565,en,EU,Europe,AD,Andorra,0
|
||||
3042058,en,EU,Europe,LI,Liechtenstein,0
|
||||
3042142,en,EU,Europe,JE,Jersey,0
|
||||
3042225,en,EU,Europe,IM,"Isle of Man",0
|
||||
3042362,en,EU,Europe,GG,Guernsey,0
|
||||
3057568,en,EU,Europe,SK,Slovakia,1
|
||||
3077311,en,EU,Europe,CZ,Czechia,1
|
||||
3144096,en,EU,Europe,NO,Norway,0
|
||||
3164670,en,EU,Europe,VA,"Vatican City",0
|
||||
3168068,en,EU,Europe,SM,"San Marino",0
|
||||
3175395,en,EU,Europe,IT,Italy,1
|
||||
3190538,en,EU,Europe,SI,Slovenia,1
|
||||
3194884,en,EU,Europe,ME,Montenegro,0
|
||||
3202326,en,EU,Europe,HR,Croatia,1
|
||||
3277605,en,EU,Europe,BA,"Bosnia and Herzegovina",0
|
||||
3351879,en,AF,Africa,AO,Angola,0
|
||||
3355338,en,AF,Africa,NA,Namibia,0
|
||||
3370751,en,AF,Africa,SH,"Saint Helena",0
|
||||
3371123,en,AN,Antarctica,BV,"Bouvet Island",0
|
||||
3374084,en,NA,"North America",BB,Barbados,0
|
||||
3374766,en,AF,Africa,CV,"Cabo Verde",0
|
||||
3378535,en,SA,"South America",GY,Guyana,0
|
||||
3381670,en,SA,"South America",GF,"French Guiana",1
|
||||
3382998,en,SA,"South America",SR,Suriname,0
|
||||
3424932,en,NA,"North America",PM,"Saint Pierre and Miquelon",0
|
||||
3425505,en,NA,"North America",GL,Greenland,0
|
||||
3437598,en,SA,"South America",PY,Paraguay,0
|
||||
3439705,en,SA,"South America",UY,Uruguay,0
|
||||
3469034,en,SA,"South America",BR,Brazil,0
|
||||
3474414,en,SA,"South America",FK,"Falkland Islands",0
|
||||
3474415,en,AN,Antarctica,GS,"South Georgia and the South Sandwich Islands",0
|
||||
3489940,en,NA,"North America",JM,Jamaica,0
|
||||
3508796,en,NA,"North America",DO,"Dominican Republic",0
|
||||
3562981,en,NA,"North America",CU,Cuba,0
|
||||
3570311,en,NA,"North America",MQ,Martinique,1
|
||||
3572887,en,NA,"North America",BS,Bahamas,0
|
||||
3573345,en,NA,"North America",BM,Bermuda,0
|
||||
3573511,en,NA,"North America",AI,Anguilla,0
|
||||
3573591,en,NA,"North America",TT,"Trinidad and Tobago",0
|
||||
3575174,en,NA,"North America",KN,"St Kitts and Nevis",0
|
||||
3575830,en,NA,"North America",DM,Dominica,0
|
||||
3576396,en,NA,"North America",AG,"Antigua and Barbuda",0
|
||||
3576468,en,NA,"North America",LC,"Saint Lucia",0
|
||||
3576916,en,NA,"North America",TC,"Turks and Caicos Islands",0
|
||||
3577279,en,NA,"North America",AW,Aruba,0
|
||||
3577718,en,NA,"North America",VG,"British Virgin Islands",0
|
||||
3577815,en,NA,"North America",VC,"St Vincent and Grenadines",0
|
||||
3578097,en,NA,"North America",MS,Montserrat,0
|
||||
3578421,en,NA,"North America",MF,"Saint Martin",1
|
||||
3578476,en,NA,"North America",BL,"Saint Barthélemy",0
|
||||
3579143,en,NA,"North America",GP,Guadeloupe,1
|
||||
3580239,en,NA,"North America",GD,Grenada,0
|
||||
3580718,en,NA,"North America",KY,"Cayman Islands",0
|
||||
3582678,en,NA,"North America",BZ,Belize,0
|
||||
3585968,en,NA,"North America",SV,"El Salvador",0
|
||||
3595528,en,NA,"North America",GT,Guatemala,0
|
||||
3608932,en,NA,"North America",HN,Honduras,0
|
||||
3617476,en,NA,"North America",NI,Nicaragua,0
|
||||
3624060,en,NA,"North America",CR,"Costa Rica",0
|
||||
3625428,en,SA,"South America",VE,Venezuela,0
|
||||
3658394,en,SA,"South America",EC,Ecuador,0
|
||||
3686110,en,SA,"South America",CO,Colombia,0
|
||||
3703430,en,NA,"North America",PA,Panama,0
|
||||
3723988,en,NA,"North America",HT,Haiti,0
|
||||
3865483,en,SA,"South America",AR,Argentina,0
|
||||
3895114,en,SA,"South America",CL,Chile,0
|
||||
3923057,en,SA,"South America",BO,Bolivia,0
|
||||
3932488,en,SA,"South America",PE,Peru,0
|
||||
3996063,en,NA,"North America",MX,Mexico,0
|
||||
4030656,en,OC,Oceania,PF,"French Polynesia",0
|
||||
4030699,en,OC,Oceania,PN,"Pitcairn Islands",0
|
||||
4030945,en,OC,Oceania,KI,Kiribati,0
|
||||
4031074,en,OC,Oceania,TK,Tokelau,0
|
||||
4032283,en,OC,Oceania,TO,Tonga,0
|
||||
4034749,en,OC,Oceania,WF,"Wallis and Futuna",0
|
||||
4034894,en,OC,Oceania,WS,Samoa,0
|
||||
4036232,en,OC,Oceania,NU,Niue,0
|
||||
4041468,en,OC,Oceania,MP,"Northern Mariana Islands",0
|
||||
4043988,en,OC,Oceania,GU,Guam,0
|
||||
4566966,en,NA,"North America",PR,"Puerto Rico",0
|
||||
4796775,en,NA,"North America",VI,"U.S. Virgin Islands",0
|
||||
5854968,en,OC,Oceania,UM,"U.S. Outlying Islands",0
|
||||
5880801,en,OC,Oceania,AS,"American Samoa",0
|
||||
6251999,en,NA,"North America",CA,Canada,0
|
||||
6252001,en,NA,"North America",US,"United States",0
|
||||
6254930,en,AS,Asia,PS,Palestine,0
|
||||
6255147,en,AS,Asia,,,0
|
||||
6255148,en,EU,Europe,,,0
|
||||
6290252,en,EU,Europe,RS,Serbia,0
|
||||
6697173,en,AN,Antarctica,AQ,Antarctica,0
|
||||
7609695,en,NA,"North America",SX,"Sint Maarten",0
|
||||
7626836,en,NA,"North America",CW,Curaçao,0
|
||||
7626844,en,NA,"North America",BQ,"Bonaire, Sint Eustatius, and Saba",0
|
||||
7909807,en,AF,Africa,SS,"South Sudan",0
|
||||
|
@@ -32,5 +32,10 @@ function createCountryDatabase() {
|
||||
|
||||
}
|
||||
|
||||
createIpDatabase();
|
||||
createCountryDatabase();
|
||||
|
||||
// createIpDatabaseCities();
|
||||
// createIpDatabase();
|
||||
|
||||
|
||||
|
||||
fs.copyFileSync('./GeoLite2-City.mmdb', '../dist/cities.mmdb');
|
||||
@@ -1,78 +1,53 @@
|
||||
import { ProjectModel } from "./shared/schema/project/ProjectSchema";
|
||||
import { UserModel } from "./shared/schema/UserSchema";
|
||||
import { LimitNotifyModel } from "./shared/schema/broker/LimitNotifySchema";
|
||||
import { EmailService } from './shared/services/EmailService';
|
||||
import { TProjectLimit } from "./shared/schema/project/ProjectsLimits";
|
||||
import { EmailServiceHelper } from "./EmailServiceHelper";
|
||||
import { TUserLimit } from "./shared/schema/UserLimitSchema";
|
||||
|
||||
import { TrcpInstance } from './trpc'
|
||||
|
||||
export async function checkLimitsForEmail(projectCounts: TProjectLimit) {
|
||||
export async function checkLimitsForEmail(projectCounts: TUserLimit) {
|
||||
|
||||
const project_id = projectCounts.project_id;
|
||||
const hasNotifyEntry = await LimitNotifyModel.findOne({ project_id });
|
||||
const user_id = projectCounts.user_id;
|
||||
|
||||
const hasNotifyEntry = await LimitNotifyModel.findOne({ user_id });
|
||||
if (!hasNotifyEntry) {
|
||||
await LimitNotifyModel.create({ project_id, limit1: false, limit2: false, limit3: false })
|
||||
await LimitNotifyModel.create({ user_id, limit1: false, limit2: false, limit3: false })
|
||||
}
|
||||
|
||||
const owner = await UserModel.findById(user_id);
|
||||
if (!owner) return;
|
||||
|
||||
const userName = owner.given_name || owner.name || 'no_name';
|
||||
|
||||
if ((projectCounts.visits + projectCounts.events) >= (projectCounts.limit)) {
|
||||
|
||||
if (hasNotifyEntry.limit3 === true) return;
|
||||
|
||||
const project = await ProjectModel.findById(project_id);
|
||||
if (!project) return;
|
||||
|
||||
const owner = await UserModel.findById(project.owner);
|
||||
if (!owner) return;
|
||||
|
||||
setImmediate(() => {
|
||||
const emailData = EmailService.getEmailServerInfo('limit_max', {
|
||||
target: owner.email,
|
||||
projectName: project.name
|
||||
});
|
||||
EmailServiceHelper.sendEmail(emailData);
|
||||
TrcpInstance.client.email.sendLimitEmail50.mutate({ email: owner.email });
|
||||
});
|
||||
|
||||
await LimitNotifyModel.updateOne({ project_id: projectCounts.project_id }, { limit1: true, limit2: true, limit3: true });
|
||||
await LimitNotifyModel.updateOne({ user_id }, { limit1: true, limit2: true, limit3: true });
|
||||
|
||||
} else if ((projectCounts.visits + projectCounts.events) >= (projectCounts.limit * 0.9)) {
|
||||
|
||||
if (hasNotifyEntry.limit2 === true) return;
|
||||
|
||||
const project = await ProjectModel.findById(project_id);
|
||||
if (!project) return;
|
||||
|
||||
const owner = await UserModel.findById(project.owner);
|
||||
if (!owner) return;
|
||||
|
||||
setImmediate(() => {
|
||||
const emailData = EmailService.getEmailServerInfo('limit_90', {
|
||||
target: owner.email,
|
||||
projectName: project.name
|
||||
});
|
||||
EmailServiceHelper.sendEmail(emailData);
|
||||
TrcpInstance.client.email.sendLimitEmail90.mutate({ email: owner.email });
|
||||
});
|
||||
|
||||
await LimitNotifyModel.updateOne({ project_id: projectCounts.project_id }, { limit1: true, limit2: true, limit3: false });
|
||||
await LimitNotifyModel.updateOne({ user_id }, { limit1: true, limit2: true, limit3: false });
|
||||
|
||||
} else if ((projectCounts.visits + projectCounts.events) >= (projectCounts.limit * 0.5)) {
|
||||
|
||||
if (hasNotifyEntry.limit1 === true) return;
|
||||
|
||||
const project = await ProjectModel.findById(project_id);
|
||||
if (!project) return;
|
||||
|
||||
const owner = await UserModel.findById(project.owner);
|
||||
if (!owner) return;
|
||||
|
||||
setImmediate(() => {
|
||||
const emailData = EmailService.getEmailServerInfo('limit_50', {
|
||||
target: owner.email,
|
||||
projectName: project.name
|
||||
});
|
||||
EmailServiceHelper.sendEmail(emailData);
|
||||
TrcpInstance.client.email.sendLimitEmailMax.mutate({ email: owner.email });
|
||||
});
|
||||
|
||||
await LimitNotifyModel.updateOne({ project_id: projectCounts.project_id }, { limit1: true, limit2: false, limit3: false });
|
||||
|
||||
await LimitNotifyModel.updateOne({ user_id }, { limit1: true, limit2: false, limit3: false });
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
|
||||
import { EmailServerInfo } from './shared/services/EmailService'
|
||||
import axios from 'axios';
|
||||
|
||||
const EMAIL_SECRET = process.env.EMAIL_SECRET;
|
||||
|
||||
export class EmailServiceHelper {
|
||||
static async sendEmail(data: EmailServerInfo) {
|
||||
try {
|
||||
await axios(data.url, {
|
||||
method: 'POST',
|
||||
data: data.body,
|
||||
headers: { ...data.headers, 'x-litlyx-token': EMAIL_SECRET }
|
||||
})
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
|
||||
|
||||
import { ProjectLimitModel } from './shared/schema/project/ProjectsLimits';
|
||||
import { MAX_LOG_LIMIT_PERCENT } from './shared/data/broker/Limits';
|
||||
import { checkLimitsForEmail } from './EmailController';
|
||||
import { UserLimitModel } from './shared/schema/UserLimitSchema';
|
||||
|
||||
export async function checkLimits(project_id: string) {
|
||||
const projectLimits = await ProjectLimitModel.findOne({ project_id });
|
||||
if (!projectLimits) return false;
|
||||
const TOTAL_COUNT = projectLimits.events + projectLimits.visits;
|
||||
const COUNT_LIMIT = projectLimits.limit;
|
||||
export async function checkLimits(user_id: string) {
|
||||
const userLimits = await UserLimitModel.findOne({ user_id });
|
||||
if (!userLimits) return false;
|
||||
const TOTAL_COUNT = userLimits.events + userLimits.visits;
|
||||
const COUNT_LIMIT = userLimits.limit;
|
||||
if ((TOTAL_COUNT) > COUNT_LIMIT * MAX_LOG_LIMIT_PERCENT) return false;
|
||||
await checkLimitsForEmail(projectLimits);
|
||||
await checkLimitsForEmail(userLimits);
|
||||
return true;
|
||||
}
|
||||
@@ -6,15 +6,16 @@ import { ProjectModel } from "./shared/schema/project/ProjectSchema";
|
||||
import { VisitModel } from "./shared/schema/metrics/VisitSchema";
|
||||
import { SessionModel } from "./shared/schema/metrics/SessionSchema";
|
||||
import { EventModel } from "./shared/schema/metrics/EventSchema";
|
||||
import { lookup } from './lookup';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { checkLimits } from './LimitChecker';
|
||||
import express from 'express';
|
||||
|
||||
import { ProjectLimitModel } from './shared/schema/project/ProjectsLimits';
|
||||
import { ProjectCountModel } from './shared/schema/project/ProjectsCounts';
|
||||
import { metricsRouter } from './Metrics';
|
||||
|
||||
import { UserLimitModel } from './shared/schema/UserLimitSchema';
|
||||
import { TrcpInstance } from './trpc';
|
||||
import { PremiumModel } from './shared/schema/PremiumSchema';
|
||||
import { lookupIP } from './lookup';
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -22,17 +23,28 @@ app.use('/metrics', metricsRouter);
|
||||
|
||||
app.listen(process.env.PORT, () => console.log(`Listening on port ${process.env.PORT}`));
|
||||
|
||||
|
||||
TrcpInstance.init(requireEnv('EMAIL_TRPC_URL'), requireEnv('EMAIL_SECRET'));
|
||||
|
||||
connectDatabase(requireEnv('MONGO_CONNECTION_STRING'));
|
||||
main();
|
||||
|
||||
const CONSUMER_NAME = `CONSUMER_${process.env.NODE_APP_INSTANCE || 'DEFAULT'}`
|
||||
|
||||
|
||||
async function getProjectOwner(pid: string) {
|
||||
const ownerData = await ProjectModel.findOne({ _id: pid }, { owner: 1 });
|
||||
return ownerData.owner;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
||||
console.log('Consumer started');
|
||||
|
||||
await RedisStreamService.connect();
|
||||
|
||||
const stream_name = requireEnv('STREAM_NAME');
|
||||
const group_name = requireEnv('GROUP_NAME') as any; // Checks are inside "startReadingLoop"
|
||||
const group_name = requireEnv('GROUP_NAME') as any; // Checks are inside "startReadingLoop"
|
||||
|
||||
await RedisStreamService.startReadingLoop({
|
||||
stream_name, group_name, consumer_name: CONSUMER_NAME
|
||||
@@ -51,22 +63,34 @@ async function processStreamEntry(data: Record<string, string>) {
|
||||
|
||||
const { pid, sessionHash } = data;
|
||||
|
||||
const project = await ProjectModel.exists({ _id: pid });
|
||||
if (!project) return;
|
||||
const owner = await getProjectOwner(pid);
|
||||
if (!owner) return;
|
||||
|
||||
const canLog = await checkLimits(pid);
|
||||
if (!canLog) return;
|
||||
|
||||
const premiumData = await PremiumModel.findOne({ user_id: owner }, { payment_failed: 1, premium_type: 1, created_at: 1 });
|
||||
if (!premiumData) return;
|
||||
if (premiumData.payment_failed === true) return;
|
||||
|
||||
if (premiumData.premium_type !== 7999) {
|
||||
const canLog = await checkLimits(owner.toString());
|
||||
if (!canLog) return;
|
||||
}
|
||||
|
||||
if (premiumData.premium_type === 7999 &&
|
||||
(Date.now() > new Date(premiumData.created_at).getTime() + (1000 * 60 * 60 * 24 * 14) + (1000 * 60 * 60 * 24 * 30))
|
||||
) return;
|
||||
|
||||
if (eventType === 'event') {
|
||||
await process_event(data, sessionHash);
|
||||
await process_event(data, sessionHash, owner.toString());
|
||||
} else if (eventType === 'keep_alive') {
|
||||
await process_keep_alive(data, sessionHash);
|
||||
await process_keep_alive(data, sessionHash, owner.toString());
|
||||
} else if (eventType === 'visit') {
|
||||
await process_visit(data, sessionHash);
|
||||
await process_visit(data, sessionHash, owner.toString());
|
||||
}
|
||||
|
||||
} catch (ex: any) {
|
||||
console.error('ERROR PROCESSING STREAM EVENT', ex.message);
|
||||
console.error(ex);
|
||||
}
|
||||
|
||||
const duration = Date.now() - start;
|
||||
@@ -75,18 +99,30 @@ async function processStreamEntry(data: Record<string, string>) {
|
||||
|
||||
}
|
||||
|
||||
async function process_visit(data: Record<string, string>, sessionHash: string) {
|
||||
async function process_visit(data: Record<string, string>, sessionHash: string, user_id: string) {
|
||||
|
||||
const { pid, ip, website, page, referrer, userAgent, flowHash, timestamp } = data;
|
||||
|
||||
let referrerParsed;
|
||||
try {
|
||||
referrerParsed = new URL(referrer);
|
||||
} catch (ex) {
|
||||
referrerParsed = { hostname: referrer };
|
||||
}
|
||||
const referrerParsed = { hostname: referrer };
|
||||
|
||||
const geoLocation = lookup(ip);
|
||||
let utm_campaign: string | undefined = undefined;
|
||||
let utm_content: string | undefined = undefined;
|
||||
let utm_medium: string | undefined = undefined;
|
||||
let utm_source: string | undefined = undefined;
|
||||
let utm_term: string | undefined = undefined;
|
||||
|
||||
try {
|
||||
const url = new URL(referrer);
|
||||
referrerParsed.hostname = url.hostname
|
||||
utm_campaign = url.searchParams.get('utm_campaign') ?? undefined;
|
||||
utm_content = url.searchParams.get('utm_content') ?? undefined;
|
||||
utm_medium = url.searchParams.get('utm_medium') ?? undefined;
|
||||
utm_source = url.searchParams.get('utm_source') ?? undefined;
|
||||
utm_term = url.searchParams.get('utm_term') ?? undefined;
|
||||
} catch (ex) { }
|
||||
|
||||
|
||||
const geoLocation = lookupIP(ip);
|
||||
|
||||
const userAgentParsed = UAParser(userAgent);
|
||||
|
||||
@@ -100,17 +136,22 @@ async function process_visit(data: Record<string, string>, sessionHash: string)
|
||||
device: device ? device : (userAgentParsed.browser.name ? 'desktop' : undefined),
|
||||
session: sessionHash,
|
||||
flowHash,
|
||||
continent: geoLocation[0],
|
||||
country: geoLocation[1],
|
||||
continent: geoLocation ? geoLocation.continent.code : undefined,
|
||||
country: geoLocation ? geoLocation.country.iso_code : undefined,
|
||||
|
||||
region: (geoLocation && geoLocation.subdivisions && geoLocation.subdivisions.length > 0) ? geoLocation.subdivisions[0]?.iso_code : undefined,
|
||||
city: (geoLocation && geoLocation.subdivisions && geoLocation.subdivisions.length > 1) ? geoLocation.subdivisions[1]?.iso_code : undefined,
|
||||
|
||||
utm_campaign, utm_content, utm_medium, utm_source, utm_term,
|
||||
created_at: new Date(parseInt(timestamp))
|
||||
}),
|
||||
ProjectCountModel.updateOne({ project_id: pid }, { $inc: { 'visits': 1 } }, { upsert: true }),
|
||||
ProjectLimitModel.updateOne({ project_id: pid }, { $inc: { 'visits': 1 } })
|
||||
UserLimitModel.updateOne({ user_id }, { $inc: { 'visits': 1 } })
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
async function process_keep_alive(data: Record<string, string>, sessionHash: string) {
|
||||
async function process_keep_alive(data: Record<string, string>, sessionHash: string, user_id: string) {
|
||||
|
||||
const { pid, instant, flowHash, timestamp, website } = data;
|
||||
|
||||
@@ -137,7 +178,7 @@ async function process_keep_alive(data: Record<string, string>, sessionHash: str
|
||||
|
||||
}
|
||||
|
||||
async function process_event(data: Record<string, string>, sessionHash: string) {
|
||||
async function process_event(data: Record<string, string>, sessionHash: string, user_id: string) {
|
||||
|
||||
const { name, metadata, pid, flowHash, timestamp, website } = data;
|
||||
|
||||
@@ -155,7 +196,7 @@ async function process_event(data: Record<string, string>, sessionHash: string)
|
||||
created_at: new Date(parseInt(timestamp))
|
||||
}),
|
||||
ProjectCountModel.updateOne({ project_id: pid }, { $inc: { 'events': 1 } }, { upsert: true }),
|
||||
ProjectLimitModel.updateOne({ project_id: pid }, { $inc: { 'events': 1 } })
|
||||
UserLimitModel.updateOne({ user_id }, { $inc: { 'events': 1 } })
|
||||
]);
|
||||
|
||||
|
||||
|
||||
@@ -1,42 +1,16 @@
|
||||
import { Reader, CityResponse } from 'mmdb-lib';
|
||||
import fs from 'fs';
|
||||
|
||||
const ipsData = JSON.parse(fs.readFileSync('./dist/ipv4-db.json', 'utf8'));
|
||||
const countriesData = JSON.parse(fs.readFileSync('./dist/countries-db.json', 'utf8'));
|
||||
const dbPath =
|
||||
fs.existsSync('./cities.mmdb') ? './cities.mmdb' :
|
||||
fs.existsSync('./script/GeoLite2-City.mmdb') ? './script/GeoLite2-City.mmdb' :
|
||||
fs.existsSync('./dist/cities.mmdb') ? './dist/cities.mmdb' : ''
|
||||
|
||||
function inRange(ip: string, cidr: string) {
|
||||
const [subnet, mask] = cidr.split('/');
|
||||
const ipBytes = ip.split('.').map(Number);
|
||||
const subnetBytes = subnet.split('.').map(Number);
|
||||
const citiesBuffer = fs.readFileSync(dbPath);
|
||||
|
||||
const ipInt = (ipBytes[0] << 24) | (ipBytes[1] << 16) | (ipBytes[2] << 8) | ipBytes[3];
|
||||
const subnetInt = (subnetBytes[0] << 24) | (subnetBytes[1] << 16) | (subnetBytes[2] << 8) | subnetBytes[3];
|
||||
const reader = new Reader<CityResponse>(citiesBuffer);
|
||||
|
||||
const maskInt = 0xffffffff << (32 - parseInt(mask));
|
||||
|
||||
return (ipInt & maskInt) === (subnetInt & maskInt);
|
||||
}
|
||||
|
||||
function getCountryFromId(id: number) {
|
||||
for (const country of countriesData) {
|
||||
if (country[0] == id) {
|
||||
return country;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function lookup(ip: string) {
|
||||
try {
|
||||
const startPiece = parseInt(ip.split('.')[0]);
|
||||
for (const target of ipsData) {
|
||||
const matchingStartPiece = target[0] == startPiece;
|
||||
if (!matchingStartPiece) continue;
|
||||
if (!inRange(ip, target[1])) continue;
|
||||
const country = getCountryFromId(target[2]);
|
||||
return [country[1], country[2]];
|
||||
}
|
||||
return ['??', '??'];
|
||||
} catch (ex) {
|
||||
console.error('ERROR DURING LOOKUP', ex);
|
||||
return ['??', '??'];
|
||||
}
|
||||
export function lookupIP(address: string) {
|
||||
const res = reader.get(address);
|
||||
return res;
|
||||
}
|
||||
26
consumer/src/trpc.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createTRPCClient, httpBatchLink, TRPCClient } from '@trpc/client';
|
||||
|
||||
//@ts-ignore
|
||||
import type { AppRouter as EmailsAppRouter } from '../../emails/src/index'
|
||||
|
||||
class TRPC {
|
||||
|
||||
public client: TRPCClient<EmailsAppRouter>
|
||||
|
||||
init(url: string, secret: string) {
|
||||
this.client = createTRPCClient<EmailsAppRouter>({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${secret}`
|
||||
}
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const TrcpInstance = new TRPC();
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"target": "ESNext",
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
|
||||
8
dashboard/.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
ecosystem.config.js
|
||||
dist
|
||||
.gitignore
|
||||
.env*
|
||||
.output
|
||||
.nuxt
|
||||
README.md
|
||||
@@ -1,26 +0,0 @@
|
||||
|
||||
MONGO_CONNECTION_STRING=
|
||||
|
||||
REDIS_URL=
|
||||
REDIS_USERNAME=
|
||||
REDIS_PASSWORD=
|
||||
|
||||
AI_ORG=
|
||||
AI_PROJECT=
|
||||
AI_KEY=
|
||||
|
||||
EMAIL_SERVICE=
|
||||
BREVO_API_KEY=
|
||||
|
||||
AUTH_JWT_SECRET=
|
||||
|
||||
AUTH_MODE=
|
||||
|
||||
NOAUTH_USER_EMAIL=
|
||||
NOAUTH_USER_NAME=
|
||||
|
||||
GOOGLE_AUTH_CLIENT_ID=
|
||||
GOOGLE_AUTH_CLIENT_SECRET=
|
||||
|
||||
STRIPE_SECRET=
|
||||
STRIPE_WH_SECRET=
|
||||
19
dashboard/.gitignore
vendored
@@ -12,7 +12,6 @@ node_modules
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
winston-*.ndjson
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
@@ -24,20 +23,6 @@ winston-*.ndjson
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Test reports
|
||||
*.report.txt
|
||||
# Shared files
|
||||
|
||||
# PDF
|
||||
out.pdf
|
||||
|
||||
# TESTS - TO REMOVE
|
||||
tests
|
||||
|
||||
# EXPLAINS MONGODB
|
||||
explains
|
||||
|
||||
#Ecosystem
|
||||
ecosystem.config.cjs
|
||||
ecosystem.config.js
|
||||
|
||||
shared
|
||||
/shared
|
||||
@@ -1,25 +1,14 @@
|
||||
|
||||
FROM node:21-alpine AS base
|
||||
|
||||
FROM base AS build
|
||||
|
||||
FROM node:22-alpine as builder
|
||||
RUN npm i -g pnpm
|
||||
|
||||
WORKDIR /home/app
|
||||
|
||||
COPY --link ./dashboard/package.json ./dashboard/pnpm-lock.yaml ./dashboard/
|
||||
|
||||
WORKDIR /home/app/dashboard
|
||||
COPY ./package*.json .
|
||||
RUN pnpm install
|
||||
COPY . .
|
||||
RUN pnpm run build
|
||||
|
||||
COPY --link ./dashboard ./
|
||||
|
||||
RUN pnpm run build:compose
|
||||
|
||||
FROM node:21-alpine AS production
|
||||
|
||||
FROM node:22-alpine
|
||||
WORKDIR /home/app
|
||||
COPY --from=builder /home/app/.output .
|
||||
|
||||
COPY --from=build /home/app/dashboard/.output /home/app/.output
|
||||
|
||||
CMD ["node", "/home/app/.output/server/index.mjs"]
|
||||
CMD ["node", "/home/app/server/index.mjs"]
|
||||
@@ -1,10 +1,10 @@
|
||||
# Nuxt 3 Minimal Starter
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install the dependencies:
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
@@ -29,7 +29,7 @@ Start the development server on `http://localhost:3000`:
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm run dev
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
@@ -47,7 +47,7 @@ Build the application for production:
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm run build
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
@@ -63,7 +63,7 @@ Locally preview production build:
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm run preview
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
@@ -1,97 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
const { data: onboarding } = useAuthFetch<{ exists: boolean }>('/api/user/onboarding/exists', { key: 'onboarding' });
|
||||
const colorMode = useColorMode()
|
||||
|
||||
import { Lit } from 'litlyx-js';
|
||||
await callOnce('user', async () => {
|
||||
const { loadData } = useAppStart();
|
||||
await loadData();
|
||||
});
|
||||
|
||||
Lit.init('6643cd08a1854e3b81722ab5');
|
||||
|
||||
const debugMode = process.dev;
|
||||
|
||||
const { alerts, closeAlert } = useAlert();
|
||||
|
||||
const { showDialog, closeDialog, dialogComponent, dialogParams, dialogStyle, dialogClosable } = useCustomDialog();
|
||||
|
||||
const { drawerVisible, hideDrawer, drawerClasses } = useDrawer();
|
||||
import { Toaster } from '@/components/ui/sonner'
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="w-dvw h-dvh bg-lyx-lightmode-background-light dark:bg-lyx-background-light relative">
|
||||
<ClientOnly>
|
||||
<Toaster :rich-colors="colorMode.value === 'light' ? true : false" />
|
||||
<GlobalDialog></GlobalDialog>
|
||||
</ClientOnly>
|
||||
|
||||
<Transition name="drawer">
|
||||
<LazyDrawerGeneric @onCloseClick="hideDrawer()" :class="drawerClasses"
|
||||
class="bg-lyx-lightmode-background-light dark:bg-black fixed right-0 top-0 w-full xl:w-[60vw] xl:min-w-[65rem] h-full z-[20]" v-if="drawerVisible">
|
||||
</LazyDrawerGeneric>
|
||||
</Transition>
|
||||
|
||||
|
||||
<div class="fixed top-4 right-8 z-[999] flex flex-col gap-2" v-if="alerts.length > 0">
|
||||
<div v-for="alert of alerts"
|
||||
class="w-[30vw] min-w-[20rem] relative bg-lyx-lightmode-background dark:bg-[#151515] overflow-hidden border-solid border-[2px] border-lyx-lightmode-widget dark:border-[#262626] rounded-lg p-6 drop-shadow-lg">
|
||||
<div class="flex items-start gap-4">
|
||||
<div> <i :class="alert.icon"></i> </div>
|
||||
<div class="grow">
|
||||
<div class="poppins font-semibold">{{ alert.title }}</div>
|
||||
<div class="poppins">
|
||||
{{ alert.text }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<i @click="closeAlert(alert.id)" class="fas fa-close hover:text-[#CCCCCC] cursor-pointer"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div :style="`width: ${Math.floor(100 / alert.ms * alert.remaining)}%; ${alert.transitionStyle}`"
|
||||
class="absolute bottom-0 left-0 h-1 bg-lyx-primary z-100 alert-bar"></div>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
<div v-if="onboarding && onboarding.exists === false && !isSelfhosted()">
|
||||
<Onboarding></Onboarding>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
|
||||
<div v-if="debugMode"
|
||||
class="absolute bottom-8 right-4 bg-red-400 text-white text-[.9rem] font-bold px-4 py-[.2rem] rounded-lg z-[100]">
|
||||
<div class="poppins flex sm:hidden"> XS </div>
|
||||
<div class="poppins hidden sm:max-md:flex"> SM - MOBILE </div>
|
||||
<div class="poppins hidden md:max-lg:flex"> MD - TABLET </div>
|
||||
<div class="poppins hidden lg:max-xl:flex"> LG - LARGE </div>
|
||||
<div class="poppins hidden xl:max-2xl:flex"> XL - EXTRA LARGE </div>
|
||||
<div class="poppins hidden 2xl:flex"> 2XL - WIDE SCREEN </div>
|
||||
</div>
|
||||
|
||||
<div v-if="showDialog"
|
||||
class="custom-dialog w-full h-full flex items-center justify-center lg:pl-32 lg:p-20 p-4 absolute left-0 top-0 z-[100] backdrop-blur-[2px] dark:bg-black/50">
|
||||
<div :style="dialogStyle" class="bg-lyx-lightmode-widget-light outline-lyx-lightmode-widget dark:bg-lyx-widget dark:outline-lyx-widget-lighter rounded-xl relative outline outline-1">
|
||||
<div v-if="dialogClosable" class="flex justify-end absolute z-[100] right-8 top-8">
|
||||
<i @click="closeDialog()" class="fas fa-close text-[1.6rem] hover:text-gray-500 cursor-pointer"></i>
|
||||
</div>
|
||||
<div class="flex items-center justify-center w-full h-full p-4">
|
||||
<component class="w-full" v-if="dialogComponent" v-bind="dialogParams" :is="dialogComponent"></component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<UModals />
|
||||
|
||||
<LazyOnboarding> </LazyOnboarding>
|
||||
|
||||
<NuxtLayout>
|
||||
<NuxtPage></NuxtPage>
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.drawer-enter-active,
|
||||
.drawer-leave-active {
|
||||
transition: all .5s ease-in-out;
|
||||
}
|
||||
|
||||
.drawer-enter-from,
|
||||
.drawer-leave-to {
|
||||
transform: translateX(100%)
|
||||
}
|
||||
|
||||
.drawer-enter-to,
|
||||
.drawer-leave-from {
|
||||
transform: translateX(0)
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
@import './font-awesome/css/all.css';
|
||||
/* Are these many fonts required? For the time being switching to privacy friendly bunny.net for Google fonts. NOTE: No variable font support in bunnet.net yet. */
|
||||
|
||||
@import url('https://fonts.bunny.net/css?family=nunito:300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i');
|
||||
@import url('https://fonts.cdnfonts.com/css/brockmann');
|
||||
@import url('https://fonts.bunny.net/css?family=inter:300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i');
|
||||
@@ -11,4 +8,46 @@
|
||||
@import url('https://fonts.bunny.net/css?family=lato:300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i');
|
||||
@import url('https://fonts.bunny.net/css?family=poppins:300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i');
|
||||
|
||||
@import url('https://cdn.jsdelivr.net/npm/material-symbols@0.28.2/index.css');
|
||||
|
||||
|
||||
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
/* overflow-x: hidden; */
|
||||
overflow-y: hidden;
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
.test {
|
||||
border: 1px solid yellow;
|
||||
}
|
||||
|
||||
.brockmann {
|
||||
font-family: "Brockmann", var(--font-sans) !important;
|
||||
}
|
||||
|
||||
.nunito {
|
||||
font-family: "Nunito", var(--font-sans) !important;
|
||||
}
|
||||
|
||||
.inter {
|
||||
font-family: "Inter", var(--font-sans) !important;
|
||||
}
|
||||
|
||||
.geometric {
|
||||
font-family: "Geometric Sans Serif v1", var(--font-sans) !important;
|
||||
}
|
||||
|
||||
.manrope {
|
||||
font-family: "Manrope", var(--font-sans) !important;
|
||||
}
|
||||
|
||||
.lato {
|
||||
font-family: "Lato", var(--font-sans) !important;
|
||||
}
|
||||
|
||||
.poppins {
|
||||
font-family: "Poppins", var(--font-sans) !important;
|
||||
}
|
||||
167
dashboard/assets/css/tailwind.css
Normal file
@@ -0,0 +1,167 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
|
||||
--color-lyx-text: var(--lyx-text);
|
||||
--color-lyx-text-sub: var(--lyx-text-sub);
|
||||
--color-lyx-test: var(--lyx-test);
|
||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||
@keyframes accordion-down {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
height: var(--reka-accordion-content-height);
|
||||
}
|
||||
}
|
||||
@keyframes accordion-up {
|
||||
from {
|
||||
height: var(--reka-accordion-content-height);
|
||||
}
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.141 0.005 285.823);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||
--primary: oklch(0.21 0.006 285.885);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.967 0.001 286.375);
|
||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
||||
--accent: oklch(0.967 0.001 286.375);
|
||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.92 0.004 286.32);
|
||||
--input: oklch(0.92 0.004 286.32);
|
||||
--ring: oklch(0.705 0.015 286.067);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||
--sidebar-ring: oklch(0.705 0.015 286.067);
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.141 0.005 285.823);
|
||||
|
||||
--lyx-text: #fafafa;
|
||||
--lyx-text-sub: #6b7280;
|
||||
--lyx-test: #0000FF;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.141 0.005 285.823);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
|
||||
--card: oklch(0.141 0.005 285.823);
|
||||
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.141 0.005 285.823);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.985 0 0);
|
||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
||||
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
|
||||
--muted: oklch(0.274 0.006 286.033);
|
||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||
--accent: oklch(0.274 0.006 286.033);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.396 0.141 25.723);
|
||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||
|
||||
--border: oklch(0.274 0.006 286.033);
|
||||
|
||||
--input: oklch(0.274 0.006 286.033);
|
||||
--ring: oklch(0.442 0.017 285.786);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
|
||||
/* --sidebar: oklch(0.21 0.006 285.885); */
|
||||
--sidebar: #09090b;
|
||||
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
|
||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(0.274 0.006 286.033);
|
||||
--sidebar-ring: oklch(0.442 0.017 285.786);
|
||||
|
||||
--lyx-text: #18181b;
|
||||
--lyx-text-sub: #909aa1;
|
||||
--lyx-test: #FF0000;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
27846
dashboard/assets/font-awesome/css/all.css
vendored
12
dashboard/assets/font-awesome/css/all.min.css
vendored
1573
dashboard/assets/font-awesome/css/brands.css
vendored
12841
dashboard/assets/font-awesome/css/duotone.css
vendored
13336
dashboard/assets/font-awesome/css/fontawesome.css
vendored
19
dashboard/assets/font-awesome/css/light.css
vendored
@@ -1,19 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Pro';
|
||||
--fa-font-light: normal 300 1em/1 'Font Awesome 6 Pro'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-light-300.woff2") format("woff2"), url("../webfonts/fa-light-300.ttf") format("truetype"); }
|
||||
|
||||
.fal,
|
||||
.fa-light {
|
||||
font-weight: 300; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-classic:"Font Awesome 6 Pro";--fa-font-light:normal 300 1em/1 "Font Awesome 6 Pro"}@font-face{font-family:"Font Awesome 6 Pro";font-style:normal;font-weight:300;font-display:block;src:url(../webfonts/fa-light-300.woff2) format("woff2"),url(../webfonts/fa-light-300.ttf) format("truetype")}.fa-light,.fal{font-weight:300}
|
||||
19
dashboard/assets/font-awesome/css/regular.css
vendored
@@ -1,19 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Pro';
|
||||
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Pro'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
|
||||
|
||||
.far,
|
||||
.fa-regular {
|
||||
font-weight: 400; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-classic:"Font Awesome 6 Pro";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Pro"}@font-face{font-family:"Font Awesome 6 Pro";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-sharp: 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-light: normal 300 1em/1 'Font Awesome 6 Sharp'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Sharp';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-sharp-light-300.woff2") format("woff2"), url("../webfonts/fa-sharp-light-300.ttf") format("truetype"); }
|
||||
|
||||
.fasl,
|
||||
.fa-light {
|
||||
font-weight: 300; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-sharp:"Font Awesome 6 Sharp";--fa-font-sharp-light:normal 300 1em/1 "Font Awesome 6 Sharp"}@font-face{font-family:"Font Awesome 6 Sharp";font-style:normal;font-weight:300;font-display:block;src:url(../webfonts/fa-sharp-light-300.woff2) format("woff2"),url(../webfonts/fa-sharp-light-300.ttf) format("truetype")}.fa-light,.fasl{font-weight:300}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-sharp: 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-regular: normal 400 1em/1 'Font Awesome 6 Sharp'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Sharp';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-sharp-regular-400.woff2") format("woff2"), url("../webfonts/fa-sharp-regular-400.ttf") format("truetype"); }
|
||||
|
||||
.fasr,
|
||||
.fa-regular {
|
||||
font-weight: 400; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-sharp:"Font Awesome 6 Sharp";--fa-font-sharp-regular:normal 400 1em/1 "Font Awesome 6 Sharp"}@font-face{font-family:"Font Awesome 6 Sharp";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-sharp-regular-400.woff2) format("woff2"),url(../webfonts/fa-sharp-regular-400.ttf) format("truetype")}.fa-regular,.fasr{font-weight:400}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-sharp: 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-solid: normal 900 1em/1 'Font Awesome 6 Sharp'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Sharp';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-sharp-solid-900.woff2") format("woff2"), url("../webfonts/fa-sharp-solid-900.ttf") format("truetype"); }
|
||||
|
||||
.fass,
|
||||
.fa-solid {
|
||||
font-weight: 900; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-sharp:"Font Awesome 6 Sharp";--fa-font-sharp-solid:normal 900 1em/1 "Font Awesome 6 Sharp"}@font-face{font-family:"Font Awesome 6 Sharp";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-sharp-solid-900.woff2) format("woff2"),url(../webfonts/fa-sharp-solid-900.ttf) format("truetype")}.fa-solid,.fass{font-weight:900}
|
||||
19
dashboard/assets/font-awesome/css/sharp-thin.css
vendored
@@ -1,19 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-sharp: 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-thin: normal 100 1em/1 'Font Awesome 6 Sharp'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Sharp';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-sharp-thin-100.woff2") format("woff2"), url("../webfonts/fa-sharp-thin-100.ttf") format("truetype"); }
|
||||
|
||||
.fast,
|
||||
.fa-thin {
|
||||
font-weight: 100; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-sharp:"Font Awesome 6 Sharp";--fa-font-sharp-thin:normal 100 1em/1 "Font Awesome 6 Sharp"}@font-face{font-family:"Font Awesome 6 Sharp";font-style:normal;font-weight:100;font-display:block;src:url(../webfonts/fa-sharp-thin-100.woff2) format("woff2"),url(../webfonts/fa-sharp-thin-100.ttf) format("truetype")}.fa-thin,.fast{font-weight:100}
|
||||
19
dashboard/assets/font-awesome/css/solid.css
vendored
@@ -1,19 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Pro';
|
||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Pro'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Pro';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
.fas,
|
||||
.fa-solid {
|
||||
font-weight: 900; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-classic:"Font Awesome 6 Pro";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Pro"}@font-face{font-family:"Font Awesome 6 Pro";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}
|
||||
640
dashboard/assets/font-awesome/css/svg-with-js.css
vendored
@@ -1,640 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Solid';
|
||||
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Regular';
|
||||
--fa-font-light: normal 300 1em/1 'Font Awesome 6 Light';
|
||||
--fa-font-thin: normal 100 1em/1 'Font Awesome 6 Thin';
|
||||
--fa-font-duotone: normal 900 1em/1 'Font Awesome 6 Duotone';
|
||||
--fa-font-sharp-solid: normal 900 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-regular: normal 400 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-light: normal 300 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-thin: normal 100 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; }
|
||||
|
||||
svg:not(:root).svg-inline--fa, svg:not(:host).svg-inline--fa {
|
||||
overflow: visible;
|
||||
box-sizing: content-box; }
|
||||
|
||||
.svg-inline--fa {
|
||||
display: var(--fa-display, inline-block);
|
||||
height: 1em;
|
||||
overflow: visible;
|
||||
vertical-align: -.125em; }
|
||||
.svg-inline--fa.fa-2xs {
|
||||
vertical-align: 0.1em; }
|
||||
.svg-inline--fa.fa-xs {
|
||||
vertical-align: 0em; }
|
||||
.svg-inline--fa.fa-sm {
|
||||
vertical-align: -0.07143em; }
|
||||
.svg-inline--fa.fa-lg {
|
||||
vertical-align: -0.2em; }
|
||||
.svg-inline--fa.fa-xl {
|
||||
vertical-align: -0.25em; }
|
||||
.svg-inline--fa.fa-2xl {
|
||||
vertical-align: -0.3125em; }
|
||||
.svg-inline--fa.fa-pull-left {
|
||||
margin-right: var(--fa-pull-margin, 0.3em);
|
||||
width: auto; }
|
||||
.svg-inline--fa.fa-pull-right {
|
||||
margin-left: var(--fa-pull-margin, 0.3em);
|
||||
width: auto; }
|
||||
.svg-inline--fa.fa-li {
|
||||
width: var(--fa-li-width, 2em);
|
||||
top: 0.25em; }
|
||||
.svg-inline--fa.fa-fw {
|
||||
width: var(--fa-fw-width, 1.25em); }
|
||||
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0; }
|
||||
|
||||
.fa-layers-text, .fa-layers-counter {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
text-align: center; }
|
||||
|
||||
.fa-layers {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
vertical-align: -.125em;
|
||||
width: 1em; }
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center; }
|
||||
|
||||
.fa-layers-text {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center; }
|
||||
|
||||
.fa-layers-counter {
|
||||
background-color: var(--fa-counter-background-color, #ff253a);
|
||||
border-radius: var(--fa-counter-border-radius, 1em);
|
||||
box-sizing: border-box;
|
||||
color: var(--fa-inverse, #fff);
|
||||
line-height: var(--fa-counter-line-height, 1);
|
||||
max-width: var(--fa-counter-max-width, 5em);
|
||||
min-width: var(--fa-counter-min-width, 1.5em);
|
||||
overflow: hidden;
|
||||
padding: var(--fa-counter-padding, 0.25em 0.5em);
|
||||
right: var(--fa-right, 0);
|
||||
text-overflow: ellipsis;
|
||||
top: var(--fa-top, 0);
|
||||
-webkit-transform: scale(var(--fa-counter-scale, 0.25));
|
||||
transform: scale(var(--fa-counter-scale, 0.25));
|
||||
-webkit-transform-origin: top right;
|
||||
transform-origin: top right; }
|
||||
|
||||
.fa-layers-bottom-right {
|
||||
bottom: var(--fa-bottom, 0);
|
||||
right: var(--fa-right, 0);
|
||||
top: auto;
|
||||
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
-webkit-transform-origin: bottom right;
|
||||
transform-origin: bottom right; }
|
||||
|
||||
.fa-layers-bottom-left {
|
||||
bottom: var(--fa-bottom, 0);
|
||||
left: var(--fa-left, 0);
|
||||
right: auto;
|
||||
top: auto;
|
||||
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
-webkit-transform-origin: bottom left;
|
||||
transform-origin: bottom left; }
|
||||
|
||||
.fa-layers-top-right {
|
||||
top: var(--fa-top, 0);
|
||||
right: var(--fa-right, 0);
|
||||
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
-webkit-transform-origin: top right;
|
||||
transform-origin: top right; }
|
||||
|
||||
.fa-layers-top-left {
|
||||
left: var(--fa-left, 0);
|
||||
right: auto;
|
||||
top: var(--fa-top, 0);
|
||||
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left; }
|
||||
|
||||
.fa-1x {
|
||||
font-size: 1em; }
|
||||
|
||||
.fa-2x {
|
||||
font-size: 2em; }
|
||||
|
||||
.fa-3x {
|
||||
font-size: 3em; }
|
||||
|
||||
.fa-4x {
|
||||
font-size: 4em; }
|
||||
|
||||
.fa-5x {
|
||||
font-size: 5em; }
|
||||
|
||||
.fa-6x {
|
||||
font-size: 6em; }
|
||||
|
||||
.fa-7x {
|
||||
font-size: 7em; }
|
||||
|
||||
.fa-8x {
|
||||
font-size: 8em; }
|
||||
|
||||
.fa-9x {
|
||||
font-size: 9em; }
|
||||
|
||||
.fa-10x {
|
||||
font-size: 10em; }
|
||||
|
||||
.fa-2xs {
|
||||
font-size: 0.625em;
|
||||
line-height: 0.1em;
|
||||
vertical-align: 0.225em; }
|
||||
|
||||
.fa-xs {
|
||||
font-size: 0.75em;
|
||||
line-height: 0.08333em;
|
||||
vertical-align: 0.125em; }
|
||||
|
||||
.fa-sm {
|
||||
font-size: 0.875em;
|
||||
line-height: 0.07143em;
|
||||
vertical-align: 0.05357em; }
|
||||
|
||||
.fa-lg {
|
||||
font-size: 1.25em;
|
||||
line-height: 0.05em;
|
||||
vertical-align: -0.075em; }
|
||||
|
||||
.fa-xl {
|
||||
font-size: 1.5em;
|
||||
line-height: 0.04167em;
|
||||
vertical-align: -0.125em; }
|
||||
|
||||
.fa-2xl {
|
||||
font-size: 2em;
|
||||
line-height: 0.03125em;
|
||||
vertical-align: -0.1875em; }
|
||||
|
||||
.fa-fw {
|
||||
text-align: center;
|
||||
width: 1.25em; }
|
||||
|
||||
.fa-ul {
|
||||
list-style-type: none;
|
||||
margin-left: var(--fa-li-margin, 2.5em);
|
||||
padding-left: 0; }
|
||||
.fa-ul > li {
|
||||
position: relative; }
|
||||
|
||||
.fa-li {
|
||||
left: calc(var(--fa-li-width, 2em) * -1);
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: var(--fa-li-width, 2em);
|
||||
line-height: inherit; }
|
||||
|
||||
.fa-border {
|
||||
border-color: var(--fa-border-color, #eee);
|
||||
border-radius: var(--fa-border-radius, 0.1em);
|
||||
border-style: var(--fa-border-style, solid);
|
||||
border-width: var(--fa-border-width, 0.08em);
|
||||
padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); }
|
||||
|
||||
.fa-pull-left {
|
||||
float: left;
|
||||
margin-right: var(--fa-pull-margin, 0.3em); }
|
||||
|
||||
.fa-pull-right {
|
||||
float: right;
|
||||
margin-left: var(--fa-pull-margin, 0.3em); }
|
||||
|
||||
.fa-beat {
|
||||
-webkit-animation-name: fa-beat;
|
||||
animation-name: fa-beat;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out);
|
||||
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
|
||||
|
||||
.fa-bounce {
|
||||
-webkit-animation-name: fa-bounce;
|
||||
animation-name: fa-bounce;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1));
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); }
|
||||
|
||||
.fa-fade {
|
||||
-webkit-animation-name: fa-fade;
|
||||
animation-name: fa-fade;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
|
||||
|
||||
.fa-beat-fade {
|
||||
-webkit-animation-name: fa-beat-fade;
|
||||
animation-name: fa-beat-fade;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
|
||||
|
||||
.fa-flip {
|
||||
-webkit-animation-name: fa-flip;
|
||||
animation-name: fa-flip;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out);
|
||||
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
|
||||
|
||||
.fa-shake {
|
||||
-webkit-animation-name: fa-shake;
|
||||
animation-name: fa-shake;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, linear);
|
||||
animation-timing-function: var(--fa-animation-timing, linear); }
|
||||
|
||||
.fa-spin {
|
||||
-webkit-animation-name: fa-spin;
|
||||
animation-name: fa-spin;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 2s);
|
||||
animation-duration: var(--fa-animation-duration, 2s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, linear);
|
||||
animation-timing-function: var(--fa-animation-timing, linear); }
|
||||
|
||||
.fa-spin-reverse {
|
||||
--fa-animation-direction: reverse; }
|
||||
|
||||
.fa-pulse,
|
||||
.fa-spin-pulse {
|
||||
-webkit-animation-name: fa-spin;
|
||||
animation-name: fa-spin;
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, steps(8));
|
||||
animation-timing-function: var(--fa-animation-timing, steps(8)); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fa-beat,
|
||||
.fa-bounce,
|
||||
.fa-fade,
|
||||
.fa-beat-fade,
|
||||
.fa-flip,
|
||||
.fa-pulse,
|
||||
.fa-shake,
|
||||
.fa-spin,
|
||||
.fa-spin-pulse {
|
||||
-webkit-animation-delay: -1ms;
|
||||
animation-delay: -1ms;
|
||||
-webkit-animation-duration: 1ms;
|
||||
animation-duration: 1ms;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
animation-iteration-count: 1;
|
||||
-webkit-transition-delay: 0s;
|
||||
transition-delay: 0s;
|
||||
-webkit-transition-duration: 0s;
|
||||
transition-duration: 0s; } }
|
||||
|
||||
@-webkit-keyframes fa-beat {
|
||||
0%, 90% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
45% {
|
||||
-webkit-transform: scale(var(--fa-beat-scale, 1.25));
|
||||
transform: scale(var(--fa-beat-scale, 1.25)); } }
|
||||
|
||||
@keyframes fa-beat {
|
||||
0%, 90% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
45% {
|
||||
-webkit-transform: scale(var(--fa-beat-scale, 1.25));
|
||||
transform: scale(var(--fa-beat-scale, 1.25)); } }
|
||||
|
||||
@-webkit-keyframes fa-bounce {
|
||||
0% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
10% {
|
||||
-webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
|
||||
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
|
||||
30% {
|
||||
-webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
|
||||
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
|
||||
50% {
|
||||
-webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
|
||||
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
|
||||
57% {
|
||||
-webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
|
||||
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
|
||||
64% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
100% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); } }
|
||||
|
||||
@keyframes fa-bounce {
|
||||
0% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
10% {
|
||||
-webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
|
||||
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
|
||||
30% {
|
||||
-webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
|
||||
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
|
||||
50% {
|
||||
-webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
|
||||
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
|
||||
57% {
|
||||
-webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
|
||||
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
|
||||
64% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
100% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); } }
|
||||
|
||||
@-webkit-keyframes fa-fade {
|
||||
50% {
|
||||
opacity: var(--fa-fade-opacity, 0.4); } }
|
||||
|
||||
@keyframes fa-fade {
|
||||
50% {
|
||||
opacity: var(--fa-fade-opacity, 0.4); } }
|
||||
|
||||
@-webkit-keyframes fa-beat-fade {
|
||||
0%, 100% {
|
||||
opacity: var(--fa-beat-fade-opacity, 0.4);
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
50% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(var(--fa-beat-fade-scale, 1.125));
|
||||
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
|
||||
|
||||
@keyframes fa-beat-fade {
|
||||
0%, 100% {
|
||||
opacity: var(--fa-beat-fade-opacity, 0.4);
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
50% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(var(--fa-beat-fade-scale, 1.125));
|
||||
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
|
||||
|
||||
@-webkit-keyframes fa-flip {
|
||||
50% {
|
||||
-webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
|
||||
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
|
||||
|
||||
@keyframes fa-flip {
|
||||
50% {
|
||||
-webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
|
||||
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
|
||||
|
||||
@-webkit-keyframes fa-shake {
|
||||
0% {
|
||||
-webkit-transform: rotate(-15deg);
|
||||
transform: rotate(-15deg); }
|
||||
4% {
|
||||
-webkit-transform: rotate(15deg);
|
||||
transform: rotate(15deg); }
|
||||
8%, 24% {
|
||||
-webkit-transform: rotate(-18deg);
|
||||
transform: rotate(-18deg); }
|
||||
12%, 28% {
|
||||
-webkit-transform: rotate(18deg);
|
||||
transform: rotate(18deg); }
|
||||
16% {
|
||||
-webkit-transform: rotate(-22deg);
|
||||
transform: rotate(-22deg); }
|
||||
20% {
|
||||
-webkit-transform: rotate(22deg);
|
||||
transform: rotate(22deg); }
|
||||
32% {
|
||||
-webkit-transform: rotate(-12deg);
|
||||
transform: rotate(-12deg); }
|
||||
36% {
|
||||
-webkit-transform: rotate(12deg);
|
||||
transform: rotate(12deg); }
|
||||
40%, 100% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); } }
|
||||
|
||||
@keyframes fa-shake {
|
||||
0% {
|
||||
-webkit-transform: rotate(-15deg);
|
||||
transform: rotate(-15deg); }
|
||||
4% {
|
||||
-webkit-transform: rotate(15deg);
|
||||
transform: rotate(15deg); }
|
||||
8%, 24% {
|
||||
-webkit-transform: rotate(-18deg);
|
||||
transform: rotate(-18deg); }
|
||||
12%, 28% {
|
||||
-webkit-transform: rotate(18deg);
|
||||
transform: rotate(18deg); }
|
||||
16% {
|
||||
-webkit-transform: rotate(-22deg);
|
||||
transform: rotate(-22deg); }
|
||||
20% {
|
||||
-webkit-transform: rotate(22deg);
|
||||
transform: rotate(22deg); }
|
||||
32% {
|
||||
-webkit-transform: rotate(-12deg);
|
||||
transform: rotate(-12deg); }
|
||||
36% {
|
||||
-webkit-transform: rotate(12deg);
|
||||
transform: rotate(12deg); }
|
||||
40%, 100% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); } }
|
||||
|
||||
@-webkit-keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg); } }
|
||||
|
||||
@keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg); } }
|
||||
|
||||
.fa-rotate-90 {
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg); }
|
||||
|
||||
.fa-rotate-180 {
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg); }
|
||||
|
||||
.fa-rotate-270 {
|
||||
-webkit-transform: rotate(270deg);
|
||||
transform: rotate(270deg); }
|
||||
|
||||
.fa-flip-horizontal {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1); }
|
||||
|
||||
.fa-flip-vertical {
|
||||
-webkit-transform: scale(1, -1);
|
||||
transform: scale(1, -1); }
|
||||
|
||||
.fa-flip-both,
|
||||
.fa-flip-horizontal.fa-flip-vertical {
|
||||
-webkit-transform: scale(-1, -1);
|
||||
transform: scale(-1, -1); }
|
||||
|
||||
.fa-rotate-by {
|
||||
-webkit-transform: rotate(var(--fa-rotate-angle, none));
|
||||
transform: rotate(var(--fa-rotate-angle, none)); }
|
||||
|
||||
.fa-stack {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 2em;
|
||||
position: relative;
|
||||
width: 2.5em; }
|
||||
|
||||
.fa-stack-1x,
|
||||
.fa-stack-2x {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: var(--fa-stack-z-index, auto); }
|
||||
|
||||
.svg-inline--fa.fa-stack-1x {
|
||||
height: 1em;
|
||||
width: 1.25em; }
|
||||
|
||||
.svg-inline--fa.fa-stack-2x {
|
||||
height: 2em;
|
||||
width: 2.5em; }
|
||||
|
||||
.fa-inverse {
|
||||
color: var(--fa-inverse, #fff); }
|
||||
|
||||
.sr-only,
|
||||
.fa-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0; }
|
||||
|
||||
.sr-only-focusable:not(:focus),
|
||||
.fa-sr-only-focusable:not(:focus) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0; }
|
||||
|
||||
.svg-inline--fa .fa-primary {
|
||||
fill: var(--fa-primary-color, currentColor);
|
||||
opacity: var(--fa-primary-opacity, 1); }
|
||||
|
||||
.svg-inline--fa .fa-secondary {
|
||||
fill: var(--fa-secondary-color, currentColor);
|
||||
opacity: var(--fa-secondary-opacity, 0.4); }
|
||||
|
||||
.svg-inline--fa.fa-swap-opacity .fa-primary {
|
||||
opacity: var(--fa-secondary-opacity, 0.4); }
|
||||
|
||||
.svg-inline--fa.fa-swap-opacity .fa-secondary {
|
||||
opacity: var(--fa-primary-opacity, 1); }
|
||||
|
||||
.svg-inline--fa mask .fa-primary,
|
||||
.svg-inline--fa mask .fa-secondary {
|
||||
fill: black; }
|
||||
|
||||
.fad.fa-inverse,
|
||||
.fa-duotone.fa-inverse {
|
||||
color: var(--fa-inverse, #fff); }
|
||||
19
dashboard/assets/font-awesome/css/thin.css
vendored
@@ -1,19 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Pro';
|
||||
--fa-font-thin: normal 100 1em/1 'Font Awesome 6 Pro'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Pro';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-thin-100.woff2") format("woff2"), url("../webfonts/fa-thin-100.ttf") format("truetype"); }
|
||||
|
||||
.fat,
|
||||
.fa-thin {
|
||||
font-weight: 100; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-classic:"Font Awesome 6 Pro";--fa-font-thin:normal 100 1em/1 "Font Awesome 6 Pro"}@font-face{font-family:"Font Awesome 6 Pro";font-style:normal;font-weight:100;font-display:block;src:url(../webfonts/fa-thin-100.woff2) format("woff2"),url(../webfonts/fa-thin-100.ttf) format("truetype")}.fa-thin,.fat{font-weight:100}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype");
|
||||
unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype");
|
||||
unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a}
|
||||
2194
dashboard/assets/font-awesome/css/v4-shims.css
vendored
@@ -1,34 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Brands';
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Pro';
|
||||
font-display: block;
|
||||
font-weight: 900;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Pro';
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Pro';
|
||||
font-display: block;
|
||||
font-weight: 300;
|
||||
src: url("../webfonts/fa-light-300.woff2") format("woff2"), url("../webfonts/fa-light-300.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Duotone';
|
||||
font-display: block;
|
||||
font-weight: 900;
|
||||
src: url("../webfonts/fa-duotone-900.woff2") format("woff2"), url("../webfonts/fa-duotone-900.ttf") format("truetype"); }
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Pro";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Pro";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Pro";font-display:block;font-weight:300;src:url(../webfonts/fa-light-300.woff2) format("woff2"),url(../webfonts/fa-light-300.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Duotone";font-display:block;font-weight:900;src:url(../webfonts/fa-duotone-900.woff2) format("woff2"),url(../webfonts/fa-duotone-900.ttf) format("truetype")}
|
||||
@@ -1,4 +0,0 @@
|
||||
:root {
|
||||
--card-color: #1d1d1f;
|
||||
--bg-color: #151517;
|
||||
}
|
||||
@@ -1,113 +1 @@
|
||||
@use './utilities.scss';
|
||||
@use './colors.scss';
|
||||
|
||||
:root{
|
||||
--font-sans: "SF Pro Text","SF Pro Icons", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", "Google Sans", "Helvetica Neue", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist";
|
||||
src: url("../fonts/GeistVF.ttf");
|
||||
}
|
||||
|
||||
.actionable-visits-color-checkbox {
|
||||
color: #5655d7;
|
||||
}
|
||||
|
||||
.actionable-sessions-color-checkbox {
|
||||
color: #4abde8;
|
||||
}
|
||||
.actionable-events-color-checkbox {
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.geist {
|
||||
font-family: "Geist", var(--font-sans);
|
||||
}
|
||||
|
||||
|
||||
.fas,
|
||||
.far,
|
||||
.fat {
|
||||
font-family: "Font Awesome 6 Pro" !important;
|
||||
}
|
||||
|
||||
.fab {
|
||||
font-family: "Font Awesome 6 Brands" !important;
|
||||
}
|
||||
|
||||
.brockmann {
|
||||
font-family: "Brockmann", var(--font-sans)!important;
|
||||
}
|
||||
|
||||
.nunito {
|
||||
font-family: "Nunito",var(--font-sans)!important;
|
||||
}
|
||||
|
||||
.inter {
|
||||
font-family: "Inter", var(--font-sans)!important;
|
||||
}
|
||||
|
||||
.geometric {
|
||||
font-family: "Geometric Sans Serif v1", var(--font-sans)!important;
|
||||
}
|
||||
|
||||
.manrope {
|
||||
font-family: "Manrope", var(--font-sans)!important;
|
||||
}
|
||||
|
||||
.lato {
|
||||
font-family: "Lato", var(--font-sans)!important;
|
||||
}
|
||||
|
||||
.poppins {
|
||||
font-family: "Poppins", var(--font-sans)!important;
|
||||
}
|
||||
|
||||
.poppins-childs {
|
||||
font-family: "Poppins", var(--font-sans)!important;
|
||||
|
||||
* {
|
||||
font-family: "Poppins", var(--font-sans)!important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.hide-scrollbars {
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
scrollbar-width: none;
|
||||
|
||||
/* Firefox */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* Chrome, Safari and Opera */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.card-shadow {
|
||||
box-shadow: 0 0 18px #00000033;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
progress::-moz-progress-bar {
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100dvw;
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: 'Nunito', var(--font-sans);
|
||||
}
|
||||
@use './scrollbars.scss';
|
||||
24
dashboard/assets/scss/scrollbars.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #888;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #f1f1f1;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
.test0 {
|
||||
@apply border-gray-400/20 border;
|
||||
}
|
||||
|
||||
.test {
|
||||
border: 1px solid yellow !important;
|
||||
}
|
||||
|
||||
.test2 {
|
||||
border: 2px solid blue !important;
|
||||
}
|
||||
|
||||
.test3 {
|
||||
border: 3px solid green !important;
|
||||
}
|
||||
|
||||
|
||||
.bgtest {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.bgtest2 {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
.bgtest3 {
|
||||
background-color: green;
|
||||
}
|
||||
18
dashboard/auth.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
declare module '#auth-utils' {
|
||||
|
||||
interface User {
|
||||
email: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
interface UserSession {
|
||||
v: string
|
||||
}
|
||||
|
||||
interface SecureSessionData {
|
||||
user_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export { }
|
||||
20
dashboard/components.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "new-york",
|
||||
"typescript": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "assets/css/tailwind.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"composables": "@/composables",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChartData, ChartOptions } from 'chart.js';
|
||||
import { useBarChart, BarChart } from 'vue-chart-3';
|
||||
registerChartComponents();
|
||||
|
||||
const props = defineProps<{
|
||||
data: any[],
|
||||
labels: string[]
|
||||
color: string,
|
||||
}>();
|
||||
|
||||
const chartOptions = ref<ChartOptions<'bar'>>({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
includeInvisible: true
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
},
|
||||
},
|
||||
x: {
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right',
|
||||
},
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleFont: { size: 16, weight: 'bold' },
|
||||
bodyFont: { size: 14 },
|
||||
padding: 10,
|
||||
cornerRadius: 4,
|
||||
boxPadding: 10,
|
||||
caretPadding: 20,
|
||||
yAlign: 'bottom',
|
||||
xAlign: 'center',
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const chartData = ref<ChartData<'bar'>>({
|
||||
labels: props.labels,
|
||||
datasets: [
|
||||
{
|
||||
data: props.data,
|
||||
backgroundColor: [props.color + '77'],
|
||||
borderColor: props.color,
|
||||
borderWidth: 4,
|
||||
hoverBackgroundColor: props.color,
|
||||
hoverBorderColor: 'white',
|
||||
hoverBorderWidth: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
const { barChartProps, barChartRef } = useBarChart({ chartData: chartData, options: chartOptions });
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
// const c = document.createElement('canvas');
|
||||
// const ctx = c.getContext("2d");
|
||||
// let gradient: any = `${props.color}22`;
|
||||
// if (ctx) {
|
||||
// gradient = ctx.createLinearGradient(0, 25, 0, 300);
|
||||
// gradient.addColorStop(0, `${props.color}99`);
|
||||
// gradient.addColorStop(0.35, `${props.color}66`);
|
||||
// gradient.addColorStop(1, `${props.color}22`);
|
||||
// } else {
|
||||
// console.warn('Cannot get context for gradient');
|
||||
// }
|
||||
|
||||
// chartData.value.datasets[0].backgroundColor = [gradient];
|
||||
|
||||
watch(props, () => {
|
||||
console.log('UPDATE')
|
||||
chartData.value.labels = props.labels;
|
||||
chartData.value.datasets[0].data = props.data;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<BarChart v-bind="barChartProps"> </BarChart>
|
||||
</template>
|
||||
@@ -1,110 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChartData, ChartOptions } from 'chart.js';
|
||||
import { useLineChart, LineChart } from 'vue-chart-3';
|
||||
registerChartComponents();
|
||||
|
||||
const props = defineProps<{
|
||||
data: any[],
|
||||
labels: string[]
|
||||
color: string,
|
||||
}>();
|
||||
|
||||
const chartOptions = ref<ChartOptions<'line'>>({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
includeInvisible: true
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: true,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
// borderDash: [5, 10]
|
||||
},
|
||||
},
|
||||
x: {
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: true,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleFont: { size: 16, weight: 'bold' },
|
||||
bodyFont: { size: 14 },
|
||||
padding: 10,
|
||||
cornerRadius: 4,
|
||||
boxPadding: 10,
|
||||
caretPadding: 20,
|
||||
yAlign: 'bottom',
|
||||
xAlign: 'center',
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const chartData = ref<ChartData<'line'>>({
|
||||
labels: props.labels,
|
||||
datasets: [
|
||||
{
|
||||
data: props.data,
|
||||
backgroundColor: [props.color + '77'],
|
||||
borderColor: props.color,
|
||||
borderWidth: 4,
|
||||
fill: true,
|
||||
tension: 0.45,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 10,
|
||||
hoverBackgroundColor: props.color,
|
||||
hoverBorderColor: 'white',
|
||||
hoverBorderWidth: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
const { lineChartProps, lineChartRef } = useLineChart({ chartData: chartData, options: chartOptions });
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
const c = document.createElement('canvas');
|
||||
const ctx = c.getContext("2d");
|
||||
let gradient: any = `${props.color}22`;
|
||||
if (ctx) {
|
||||
gradient = ctx.createLinearGradient(0, 25, 0, 300);
|
||||
gradient.addColorStop(0, `${props.color}99`);
|
||||
gradient.addColorStop(0.35, `${props.color}66`);
|
||||
gradient.addColorStop(1, `${props.color}22`);
|
||||
} else {
|
||||
console.warn('Cannot get context for gradient');
|
||||
}
|
||||
|
||||
chartData.value.datasets[0].backgroundColor = [gradient];
|
||||
|
||||
watch(props, () => {
|
||||
chartData.value.labels = props.labels;
|
||||
chartData.value.datasets[0].data = props.data;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<LineChart ref="lineChartRef" v-bind="lineChartProps"> </LineChart>
|
||||
</template>
|
||||
@@ -1,116 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChartData, ChartOptions } from 'chart.js';
|
||||
import { useBarChart, BarChart } from 'vue-chart-3';
|
||||
registerChartComponents();
|
||||
|
||||
const props = defineProps<{
|
||||
datasets: any[],
|
||||
labels: string[],
|
||||
}>();
|
||||
|
||||
const chartOptions = ref<ChartOptions<'bar'>>({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
includeInvisible: true
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
},
|
||||
},
|
||||
x: {
|
||||
stacked: true,
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleFont: { size: 16, weight: 'bold' },
|
||||
bodyFont: { size: 14 },
|
||||
padding: 10,
|
||||
cornerRadius: 4,
|
||||
boxPadding: 10,
|
||||
caretPadding: 20,
|
||||
yAlign: 'bottom',
|
||||
xAlign: 'center',
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const chartData = ref<ChartData<'bar'>>({
|
||||
labels: props.labels,
|
||||
datasets: props.datasets.map(e => {
|
||||
return {
|
||||
data: e.data,
|
||||
label: e.label || '?',
|
||||
backgroundColor: [e.color],
|
||||
borderWidth: 0,
|
||||
borderRadius: 0
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
const { barChartProps, barChartRef } = useBarChart({ chartData: chartData, options: chartOptions });
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
// const c = document.createElement('canvas');
|
||||
// const ctx = c.getContext("2d");
|
||||
// let gradient: any = `${props.color}22`;
|
||||
// if (ctx) {
|
||||
// gradient = ctx.createLinearGradient(0, 25, 0, 300);
|
||||
// gradient.addColorStop(0, `${props.color}99`);
|
||||
// gradient.addColorStop(0.35, `${props.color}66`);
|
||||
// gradient.addColorStop(1, `${props.color}22`);
|
||||
// } else {
|
||||
// console.warn('Cannot get context for gradient');
|
||||
// }
|
||||
|
||||
// chartData.value.datasets[0].backgroundColor = [gradient];
|
||||
|
||||
watch(props, () => {
|
||||
console.log('UPDATE')
|
||||
chartData.value.labels = props.labels;
|
||||
chartData.value.datasets.length = 0;
|
||||
chartData.value.datasets = props.datasets.map(e => {
|
||||
return {
|
||||
data: e.data,
|
||||
label: e.label || '?',
|
||||
backgroundColor: [e.color],
|
||||
borderWidth: 0,
|
||||
borderRadius: 8
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<BarChart v-bind="barChartProps"> </BarChart>
|
||||
</template>
|
||||
133
dashboard/components/AppSidebar.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<script setup lang="ts">
|
||||
import type { SidebarProps } from '@/components/ui/sidebar'
|
||||
|
||||
import NavMain from './NavMain.vue'
|
||||
import NavProjects from './NavProjects.vue'
|
||||
import NavUser from './NavUser.vue'
|
||||
import TeamSwitcher from './ProjectSwitcher.vue'
|
||||
|
||||
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader } from '@/components/ui/sidebar'
|
||||
import SidebarData from './SidebarData.vue'
|
||||
import { Box } from 'lucide-vue-next'
|
||||
|
||||
const { plan } = usePremiumStore();
|
||||
|
||||
const props = withDefaults(defineProps<SidebarProps>(), { collapsible: 'icon' });
|
||||
|
||||
const { user } = useUserSession();
|
||||
|
||||
const userLogo = true;
|
||||
|
||||
const userData = computed(() => {
|
||||
return {
|
||||
name: 'noname',
|
||||
avatar: '',
|
||||
email: user.value?.email || 'nomail'
|
||||
}
|
||||
})
|
||||
|
||||
const debugMode = false;//process.dev;
|
||||
|
||||
const projectStore = useProjectStore();
|
||||
const colorMode = useColorMode()
|
||||
|
||||
|
||||
async function leaveProject() {
|
||||
await useAuthFetchSync('/api/members/leave');
|
||||
await projectStore.fetchProjects();
|
||||
}
|
||||
|
||||
async function acceptInvite(project_id: string) {
|
||||
await useCatch({
|
||||
toast: true,
|
||||
toastTitle: 'Error accepting invite',
|
||||
async action() {
|
||||
await useAuthFetchSync('/api/members/accept', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: { project_id }
|
||||
})
|
||||
},
|
||||
async onSuccess() {
|
||||
await projectStore.fetchProjects();
|
||||
await projectStore.fetchPendingInvites();
|
||||
const newActive = projectStore.projects.at(-1)?._id.toString();
|
||||
if (newActive) await projectStore.setActive(newActive);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function declineInvite(project_id: string) {
|
||||
await useCatch({
|
||||
toast: true,
|
||||
toastTitle: 'Error declining invite',
|
||||
async action() {
|
||||
await useAuthFetchSync('/api/members/decline', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: { project_id }
|
||||
})
|
||||
},
|
||||
onSuccess() {
|
||||
projectStore.fetchPendingInvites();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sidebar v-bind="props" variant="sidebar">
|
||||
<SidebarHeader class="px-0">
|
||||
<div class="border-b-2 ">
|
||||
<div class="px-2 flex items-center justify-center my-4 gap-4">
|
||||
<NuxtLink to="/"><img class="h-6" :src="colorMode.value === 'dark' ? '/logo-white.svg' : '/logo-black.svg'"></NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <ProjectSwitcher /> -->
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarData></SidebarData>
|
||||
<div v-if="debugMode"
|
||||
class="bg-red-500/70 text-white text-[.8rem] flex font-bold mx-4 p-2 px-4 rounded-md z-[100]">
|
||||
<div class="poppins mr-4"> DEV </div>
|
||||
<div class="poppins flex sm:hidden"> XS </div>
|
||||
<div class="poppins hidden sm:max-md:flex"> SM - MOBILE </div>
|
||||
<div class="poppins hidden md:max-lg:flex"> MD - TABLET </div>
|
||||
<div class="poppins hidden lg:max-xl:flex"> LG - LARGE </div>
|
||||
<div class="poppins hidden xl:max-2xl:flex"> XL - EXTRA LARGE </div>
|
||||
<div class="poppins hidden 2xl:flex"> 2XL - WIDE SCREEN </div>
|
||||
</div>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
|
||||
<SidebarBanner
|
||||
v-if="plan && ((plan.premium_type === 7006 || plan.premium_type === 0) || plan.payment_failed || plan.canceled) && projectStore.isOwner"
|
||||
class="w-full">
|
||||
</SidebarBanner>
|
||||
|
||||
<div class="border border-violet-500/50 dark:bg-violet-500/10 rounded-lg py-2 px-4 flex flex-col gap-4 mt-4"
|
||||
v-if="projectStore.pendingInvites.length > 0">
|
||||
<div class="text-[.9rem]">
|
||||
You have been invited to
|
||||
<b>{{ projectStore.pendingInvites[0].project_name }} </b>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-between">
|
||||
<Button @click="declineInvite(projectStore.pendingInvites[0].project_id)" size="sm" variant="ghost">
|
||||
Decline </Button>
|
||||
<Button @click="acceptInvite(projectStore.pendingInvites[0].project_id)" size="sm"> Accept </Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button @click="leaveProject()" class="my-4" v-if="!projectStore.isOwner" variant="outline">
|
||||
Leave project
|
||||
</Button>
|
||||
|
||||
<NavUser :user="userData" />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
</template>
|
||||
@@ -1,175 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
|
||||
export type IconProvider = (e: { _id: string, count: string } & any) => ['img' | 'icon', string] | undefined;
|
||||
|
||||
|
||||
type Props = {
|
||||
data: { _id: string, count: number }[],
|
||||
iconProvider?: IconProvider,
|
||||
elementTextTransformer?: (text: string) => string,
|
||||
label: string,
|
||||
subLabel: string,
|
||||
desc: string,
|
||||
loading?: boolean,
|
||||
interactive?: boolean,
|
||||
isDetailView?: boolean,
|
||||
rawButton?: boolean,
|
||||
hideShowMore?: boolean,
|
||||
customIconStyle?: string,
|
||||
showLink?: boolean
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
const emits = defineEmits<{
|
||||
(e: 'dataReload'): void,
|
||||
(e: 'showDetails', id: string): void,
|
||||
(e: 'showRawData'): void,
|
||||
(e: 'showGeneral'): void,
|
||||
(e: 'showMore'): void,
|
||||
}>();
|
||||
|
||||
const maxData = computed(() => {
|
||||
const counts = props.data.map(e => e.count);
|
||||
return Math.max(...counts);
|
||||
});
|
||||
|
||||
function reloadData() {
|
||||
emits('dataReload');
|
||||
}
|
||||
|
||||
function showDetails(id: string) {
|
||||
emits('showDetails', id);
|
||||
}
|
||||
|
||||
function openExternalLink(link: string) {
|
||||
if (link === 'self') return;
|
||||
return window.open('https://' + link, '_blank');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<LyxUiCard class="w-full h-full p-4 flex flex-col gap-8 relative">
|
||||
<div class="flex justify-between mb-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex gap-4 items-center">
|
||||
<div class="poppins font-semibold text-[1.4rem] text-lyx-lightmode-text dark:text-lyx-text">
|
||||
{{ label }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i @click="reloadData()"
|
||||
class="hover:rotate-[50deg] transition-all duration-100 fas fa-refresh text-[1.2rem] cursor-pointer"></i>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="poppins text-[1rem] text-lyx-ligtmode-text-darker dark:text-text-sub/90">
|
||||
{{ desc }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="rawButton" class="hidden lg:flex">
|
||||
|
||||
<LyxUiButton @click="$emit('showRawData')" type="primary" class="h-fit">
|
||||
<div class="flex gap-1 items-center justify-center ">
|
||||
<div> Show raw data </div>
|
||||
<div class="flex items-center"> <i class="fas fa-arrow-up-right"></i> </div>
|
||||
</div>
|
||||
</LyxUiButton>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-full flex flex-col">
|
||||
<div
|
||||
class="flex justify-between font-bold lyx-text-lightmode-text-dark dark:text-text-sub/80 text-[1.1rem] mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="isDetailView" class="flex items-center justify-center">
|
||||
<i @click="$emit('showGeneral')"
|
||||
class="fas fa-arrow-left text-[.9rem] hover:text-text cursor-pointer"></i>
|
||||
</div>
|
||||
<div> {{ subLabel }} </div>
|
||||
</div>
|
||||
<div> Count </div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
|
||||
<div v-if="props.data.length > 0" class="flex justify-between items-center"
|
||||
v-for="element of props.data">
|
||||
|
||||
<div class="flex items-center gap-2 w-10/12 relative">
|
||||
|
||||
<div v-if="showLink">
|
||||
<i @click="openExternalLink(element._id)"
|
||||
class="fas fa-link text-gray-300 hover:text-gray-400 cursor-pointer"></i>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1 items-center" @click="showDetails(element._id)"
|
||||
:class="{ 'cursor-pointer line-active': interactive }">
|
||||
|
||||
<div class="absolute rounded-sm w-full h-full bg-[#6f829c38] dark:bg-[#92abcf38]"
|
||||
:style="'width:' + 100 / maxData * element.count + '%;'"></div>
|
||||
|
||||
<div class="flex px-2 py-1 relative items-center gap-4">
|
||||
<div v-if="iconProvider && iconProvider(element) != undefined"
|
||||
class="flex items-center h-[1.3rem]">
|
||||
|
||||
<img v-if="iconProvider(element)?.[0] == 'img'" class="h-full"
|
||||
:style="customIconStyle" :src="iconProvider(element)?.[1]">
|
||||
|
||||
<i v-else :class="iconProvider(element)?.[1]"></i>
|
||||
</div>
|
||||
<span
|
||||
class="text-ellipsis line-clamp-1 ui-font z-[19] text-[.95rem] text-lyx-lightmode-text-dark dark:text-text/70">
|
||||
{{ elementTextTransformer?.(element._id) || element._id }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-lyx-lightmode-text dark:text-lyx-text font-semibold text-[.9rem] md:text-[1rem] manrope">
|
||||
{{
|
||||
formatNumberK(element.count) }} </div>
|
||||
</div>
|
||||
<div v-if="props.data.length == 0" class="flex justify-center text-text-sub font-light text-[1.1rem]">
|
||||
No data yet
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hideShowMore" class="flex justify-center mt-4 text-text-sub/90 items-end grow">
|
||||
|
||||
<LyxUiButton type="outline" @click="$emit('showMore')">
|
||||
Show more
|
||||
</LyxUiButton>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading"
|
||||
class="backdrop-blur-[1px] z-[20] left-0 top-0 w-full h-full flex items-center justify-center font-bold rockmann absolute">
|
||||
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||
</div>
|
||||
</LyxUiCard>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.line-active:hover {
|
||||
.absolute {
|
||||
@apply bg-accent/20
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ui-font {
|
||||
font-feature-settings: normal;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
font-variation-settings: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4
|
||||
}
|
||||
</style>
|
||||