1 Commits

Author SHA1 Message Date
antonio
951860f67e new selfhosted version 2025-11-28 16:49:20 +01:00
1046 changed files with 72586 additions and 574750 deletions

View File

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

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

View File

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 973 KiB

BIN
assets/d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/icons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

4
consumer/.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
ecosystem.config.js
dist
.gitignore

4
consumer/.gitignore vendored
View File

@@ -1,9 +1,5 @@
node_modules
ecosystem.config.cjs
ecosystem.config.js
scripts/start_dev.js
scripts/start_dev_prod.js
dist

View File

@@ -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"]

View File

@@ -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",

View File

@@ -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: {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 MiB

File diff suppressed because it is too large Load Diff

View File

@@ -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
1 geoname_id locale_code continent_code continent_name country_iso_code country_name is_in_european_union
2 49518 en AF Africa RW Rwanda 0
3 51537 en AF Africa SO Somalia 0
4 69543 en AS Asia YE Yemen 0
5 99237 en AS Asia IQ Iraq 0
6 102358 en AS Asia SA Saudi Arabia 0
7 130758 en AS Asia IR Iran 0
8 146669 en EU Europe CY Cyprus 1
9 149590 en AF Africa TZ Tanzania 0
10 163843 en AS Asia SY Syria 0
11 174982 en AS Asia AM Armenia 0
12 192950 en AF Africa KE Kenya 0
13 203312 en AF Africa CD DR Congo 0
14 223816 en AF Africa DJ Djibouti 0
15 226074 en AF Africa UG Uganda 0
16 239880 en AF Africa CF Central African Republic 0
17 241170 en AF Africa SC Seychelles 0
18 248816 en AS Asia JO Jordan 0
19 272103 en AS Asia LB Lebanon 0
20 285570 en AS Asia KW Kuwait 0
21 286963 en AS Asia OM Oman 0
22 289688 en AS Asia QA Qatar 0
23 290291 en AS Asia BH Bahrain 0
24 290557 en AS Asia AE United Arab Emirates 0
25 294640 en AS Asia IL Israel 0
26 298795 en AS Asia TR Türkiye 0
27 337996 en AF Africa ET Ethiopia 0
28 338010 en AF Africa ER Eritrea 0
29 357994 en AF Africa EG Egypt 0
30 366755 en AF Africa SD Sudan 0
31 390903 en EU Europe GR Greece 1
32 433561 en AF Africa BI Burundi 0
33 453733 en EU Europe EE Estonia 1
34 458258 en EU Europe LV Latvia 1
35 587116 en AS Asia AZ Azerbaijan 0
36 597427 en EU Europe LT Lithuania 1
37 607072 en EU Europe SJ Svalbard and Jan Mayen 0
38 614540 en AS Asia GE Georgia 0
39 617790 en EU Europe MD Moldova 0
40 630336 en EU Europe BY Belarus 0
41 660013 en EU Europe FI Finland 1
42 661882 en EU Europe AX Åland Islands 1
43 690791 en EU Europe UA Ukraine 0
44 718075 en EU Europe MK North Macedonia 0
45 719819 en EU Europe HU Hungary 1
46 732800 en EU Europe BG Bulgaria 1
47 783754 en EU Europe AL Albania 0
48 798544 en EU Europe PL Poland 1
49 798549 en EU Europe RO Romania 1
50 831053 en EU Europe XK Kosovo 0
51 878675 en AF Africa ZW Zimbabwe 0
52 895949 en AF Africa ZM Zambia 0
53 921929 en AF Africa KM Comoros 0
54 927384 en AF Africa MW Malawi 0
55 932692 en AF Africa LS Lesotho 0
56 933860 en AF Africa BW Botswana 0
57 934292 en AF Africa MU Mauritius 0
58 934841 en AF Africa SZ Eswatini 0
59 935317 en AF Africa RE Réunion 1
60 953987 en AF Africa ZA South Africa 0
61 1024031 en AF Africa YT Mayotte 1
62 1036973 en AF Africa MZ Mozambique 0
63 1062947 en AF Africa MG Madagascar 0
64 1149361 en AS Asia AF Afghanistan 0
65 1168579 en AS Asia PK Pakistan 0
66 1210997 en AS Asia BD Bangladesh 0
67 1218197 en AS Asia TM Turkmenistan 0
68 1220409 en AS Asia TJ Tajikistan 0
69 1227603 en AS Asia LK Sri Lanka 0
70 1252634 en AS Asia BT Bhutan 0
71 1269750 en AS Asia IN India 0
72 1282028 en AS Asia MV Maldives 0
73 1282588 en AS Asia IO British Indian Ocean Territory 0
74 1282988 en AS Asia NP Nepal 0
75 1327865 en AS Asia MM Myanmar 0
76 1512440 en AS Asia UZ Uzbekistan 0
77 1522867 en AS Asia KZ Kazakhstan 0
78 1527747 en AS Asia KG Kyrgyzstan 0
79 1546748 en AN Antarctica TF French Southern Territories 0
80 1547314 en AN Antarctica HM Heard and McDonald Islands 0
81 1547376 en AS Asia CC Cocos (Keeling) Islands 0
82 1559582 en OC Oceania PW Palau 0
83 1562822 en AS Asia VN Vietnam 0
84 1605651 en AS Asia TH Thailand 0
85 1643084 en AS Asia ID Indonesia 0
86 1655842 en AS Asia LA Laos 0
87 1668284 en AS Asia TW Taiwan 0
88 1694008 en AS Asia PH Philippines 0
89 1733045 en AS Asia MY Malaysia 0
90 1814991 en AS Asia CN China 0
91 1819730 en AS Asia HK Hong Kong 0
92 1820814 en AS Asia BN Brunei 0
93 1821275 en AS Asia MO Macao 0
94 1831722 en AS Asia KH Cambodia 0
95 1835841 en AS Asia KR South Korea 0
96 1861060 en AS Asia JP Japan 0
97 1873107 en AS Asia KP North Korea 0
98 1880251 en AS Asia SG Singapore 0
99 1899402 en OC Oceania CK Cook Islands 0
100 1966436 en OC Oceania TL Timor-Leste 0
101 2017370 en EU Europe RU Russia 0
102 2029969 en AS Asia MN Mongolia 0
103 2077456 en OC Oceania AU Australia 0
104 2078138 en OC Oceania CX Christmas Island 0
105 2080185 en OC Oceania MH Marshall Islands 0
106 2081918 en OC Oceania FM Federated States of Micronesia 0
107 2088628 en OC Oceania PG Papua New Guinea 0
108 2103350 en OC Oceania SB Solomon Islands 0
109 2110297 en OC Oceania TV Tuvalu 0
110 2110425 en OC Oceania NR Nauru 0
111 2134431 en OC Oceania VU Vanuatu 0
112 2139685 en OC Oceania NC New Caledonia 0
113 2155115 en OC Oceania NF Norfolk Island 0
114 2186224 en OC Oceania NZ New Zealand 0
115 2205218 en OC Oceania FJ Fiji 0
116 2215636 en AF Africa LY Libya 0
117 2233387 en AF Africa CM Cameroon 0
118 2245662 en AF Africa SN Senegal 0
119 2260494 en AF Africa CG Congo Republic 0
120 2264397 en EU Europe PT Portugal 1
121 2275384 en AF Africa LR Liberia 0
122 2287781 en AF Africa CI Ivory Coast 0
123 2300660 en AF Africa GH Ghana 0
124 2309096 en AF Africa GQ Equatorial Guinea 0
125 2328926 en AF Africa NG Nigeria 0
126 2361809 en AF Africa BF Burkina Faso 0
127 2363686 en AF Africa TG Togo 0
128 2372248 en AF Africa GW Guinea-Bissau 0
129 2378080 en AF Africa MR Mauritania 0
130 2395170 en AF Africa BJ Benin 0
131 2400553 en AF Africa GA Gabon 0
132 2403846 en AF Africa SL Sierra Leone 0
133 2410758 en AF Africa ST São Tomé and Príncipe 0
134 2411586 en EU Europe GI Gibraltar 0
135 2413451 en AF Africa GM Gambia 0
136 2420477 en AF Africa GN Guinea 0
137 2434508 en AF Africa TD Chad 0
138 2440476 en AF Africa NE Niger 0
139 2453866 en AF Africa ML Mali 0
140 2461445 en AF Africa EH Western Sahara 0
141 2464461 en AF Africa TN Tunisia 0
142 2510769 en EU Europe ES Spain 1
143 2542007 en AF Africa MA Morocco 0
144 2562770 en EU Europe MT Malta 1
145 2589581 en AF Africa DZ Algeria 0
146 2622320 en EU Europe FO Faroe Islands 0
147 2623032 en EU Europe DK Denmark 1
148 2629691 en EU Europe IS Iceland 0
149 2635167 en EU Europe GB United Kingdom 0
150 2658434 en EU Europe CH Switzerland 0
151 2661886 en EU Europe SE Sweden 1
152 2750405 en EU Europe NL The Netherlands 1
153 2782113 en EU Europe AT Austria 1
154 2802361 en EU Europe BE Belgium 1
155 2921044 en EU Europe DE Germany 1
156 2960313 en EU Europe LU Luxembourg 1
157 2963597 en EU Europe IE Ireland 1
158 2993457 en EU Europe MC Monaco 0
159 3017382 en EU Europe FR France 1
160 3041565 en EU Europe AD Andorra 0
161 3042058 en EU Europe LI Liechtenstein 0
162 3042142 en EU Europe JE Jersey 0
163 3042225 en EU Europe IM Isle of Man 0
164 3042362 en EU Europe GG Guernsey 0
165 3057568 en EU Europe SK Slovakia 1
166 3077311 en EU Europe CZ Czechia 1
167 3144096 en EU Europe NO Norway 0
168 3164670 en EU Europe VA Vatican City 0
169 3168068 en EU Europe SM San Marino 0
170 3175395 en EU Europe IT Italy 1
171 3190538 en EU Europe SI Slovenia 1
172 3194884 en EU Europe ME Montenegro 0
173 3202326 en EU Europe HR Croatia 1
174 3277605 en EU Europe BA Bosnia and Herzegovina 0
175 3351879 en AF Africa AO Angola 0
176 3355338 en AF Africa NA Namibia 0
177 3370751 en AF Africa SH Saint Helena 0
178 3371123 en AN Antarctica BV Bouvet Island 0
179 3374084 en NA North America BB Barbados 0
180 3374766 en AF Africa CV Cabo Verde 0
181 3378535 en SA South America GY Guyana 0
182 3381670 en SA South America GF French Guiana 1
183 3382998 en SA South America SR Suriname 0
184 3424932 en NA North America PM Saint Pierre and Miquelon 0
185 3425505 en NA North America GL Greenland 0
186 3437598 en SA South America PY Paraguay 0
187 3439705 en SA South America UY Uruguay 0
188 3469034 en SA South America BR Brazil 0
189 3474414 en SA South America FK Falkland Islands 0
190 3474415 en AN Antarctica GS South Georgia and the South Sandwich Islands 0
191 3489940 en NA North America JM Jamaica 0
192 3508796 en NA North America DO Dominican Republic 0
193 3562981 en NA North America CU Cuba 0
194 3570311 en NA North America MQ Martinique 1
195 3572887 en NA North America BS Bahamas 0
196 3573345 en NA North America BM Bermuda 0
197 3573511 en NA North America AI Anguilla 0
198 3573591 en NA North America TT Trinidad and Tobago 0
199 3575174 en NA North America KN St Kitts and Nevis 0
200 3575830 en NA North America DM Dominica 0
201 3576396 en NA North America AG Antigua and Barbuda 0
202 3576468 en NA North America LC Saint Lucia 0
203 3576916 en NA North America TC Turks and Caicos Islands 0
204 3577279 en NA North America AW Aruba 0
205 3577718 en NA North America VG British Virgin Islands 0
206 3577815 en NA North America VC St Vincent and Grenadines 0
207 3578097 en NA North America MS Montserrat 0
208 3578421 en NA North America MF Saint Martin 1
209 3578476 en NA North America BL Saint Barthélemy 0
210 3579143 en NA North America GP Guadeloupe 1
211 3580239 en NA North America GD Grenada 0
212 3580718 en NA North America KY Cayman Islands 0
213 3582678 en NA North America BZ Belize 0
214 3585968 en NA North America SV El Salvador 0
215 3595528 en NA North America GT Guatemala 0
216 3608932 en NA North America HN Honduras 0
217 3617476 en NA North America NI Nicaragua 0
218 3624060 en NA North America CR Costa Rica 0
219 3625428 en SA South America VE Venezuela 0
220 3658394 en SA South America EC Ecuador 0
221 3686110 en SA South America CO Colombia 0
222 3703430 en NA North America PA Panama 0
223 3723988 en NA North America HT Haiti 0
224 3865483 en SA South America AR Argentina 0
225 3895114 en SA South America CL Chile 0
226 3923057 en SA South America BO Bolivia 0
227 3932488 en SA South America PE Peru 0
228 3996063 en NA North America MX Mexico 0
229 4030656 en OC Oceania PF French Polynesia 0
230 4030699 en OC Oceania PN Pitcairn Islands 0
231 4030945 en OC Oceania KI Kiribati 0
232 4031074 en OC Oceania TK Tokelau 0
233 4032283 en OC Oceania TO Tonga 0
234 4034749 en OC Oceania WF Wallis and Futuna 0
235 4034894 en OC Oceania WS Samoa 0
236 4036232 en OC Oceania NU Niue 0
237 4041468 en OC Oceania MP Northern Mariana Islands 0
238 4043988 en OC Oceania GU Guam 0
239 4566966 en NA North America PR Puerto Rico 0
240 4796775 en NA North America VI U.S. Virgin Islands 0
241 5854968 en OC Oceania UM U.S. Outlying Islands 0
242 5880801 en OC Oceania AS American Samoa 0
243 6251999 en NA North America CA Canada 0
244 6252001 en NA North America US United States 0
245 6254930 en AS Asia PS Palestine 0
246 6255147 en AS Asia 0
247 6255148 en EU Europe 0
248 6290252 en EU Europe RS Serbia 0
249 6697173 en AN Antarctica AQ Antarctica 0
250 7609695 en NA North America SX Sint Maarten 0
251 7626836 en NA North America CW Curaçao 0
252 7626844 en NA North America BQ Bonaire, Sint Eustatius, and Saba 0
253 7909807 en AF Africa SS South Sudan 0

View File

@@ -32,5 +32,10 @@ function createCountryDatabase() {
}
createIpDatabase();
createCountryDatabase();
// createIpDatabaseCities();
// createIpDatabase();
fs.copyFileSync('./GeoLite2-City.mmdb', '../dist/cities.mmdb');

View File

@@ -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 });
}

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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 } })
]);

View File

@@ -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
View 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();

View File

@@ -2,7 +2,8 @@
"compilerOptions": {
"module": "NodeNext",
"target": "ESNext",
"outDir": "dist"
"outDir": "dist",
"skipLibCheck": true
},
"include": [
"src/**/*.ts"

8
dashboard/.dockerignore Normal file
View File

@@ -0,0 +1,8 @@
node_modules
ecosystem.config.js
dist
.gitignore
.env*
.output
.nuxt
README.md

View File

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

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

View File

@@ -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"]

View File

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

View File

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

View File

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -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}

View File

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

View File

@@ -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}

View File

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

View File

@@ -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}

View File

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

View File

@@ -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}

View File

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

View File

@@ -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}

View File

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

View File

@@ -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}

View File

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

View File

@@ -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}

View File

@@ -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); }

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -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}

View File

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

View File

@@ -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}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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"); }

View File

@@ -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")}

Binary file not shown.

View File

@@ -1,4 +0,0 @@
:root {
--card-color: #1d1d1f;
--bg-color: #151517;
}

View File

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

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

View File

@@ -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
View 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
View 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"
}

View File

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

View File

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

View File

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

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

View File

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

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