mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-12 21:30:35 +01:00
use config file instead of env
This commit is contained in:
parent
6fb569e2cd
commit
d9ae322e2a
19 changed files with 189 additions and 209 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -25,3 +25,4 @@ next-env.d.ts
|
|||
migrations
|
||||
package-lock.json
|
||||
tsconfig.tsbuildinfo
|
||||
config.yml
|
||||
|
|
15
config/config.example.yml
Normal file
15
config/config.example.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
app:
|
||||
name: Pangolin
|
||||
environment: dev
|
||||
base_url: http://localhost:3000
|
||||
log_level: debug
|
||||
save_logs: "false"
|
||||
secure_cookies: "false"
|
||||
|
||||
server:
|
||||
external_port: "3000"
|
||||
internal_port: "3001"
|
||||
|
||||
rate_limit:
|
||||
window_minutes: "1"
|
||||
max_requests: "100"
|
|
@ -1,5 +1,5 @@
|
|||
import { defineConfig } from "drizzle-kit";
|
||||
import environment from "@server/environment";
|
||||
import config, { APP_PATH } from "@server/config";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
|
@ -8,6 +8,6 @@ export default defineConfig({
|
|||
out: path.join("server", "migrations"),
|
||||
verbose: true,
|
||||
dbCredentials: {
|
||||
url: path.join(environment.CONFIG_PATH, "db", "db.sqlite"),
|
||||
url: path.join(APP_PATH, "db", "db.sqlite"),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
"glob": "11.0.0",
|
||||
"helmet": "7.1.0",
|
||||
"http-errors": "2.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"lucia": "3.2.0",
|
||||
"lucide-react": "0.447.0",
|
||||
"moment": "2.30.1",
|
||||
|
@ -62,6 +63,7 @@
|
|||
"@types/cookie-parser": "1.4.7",
|
||||
"@types/cors": "2.8.17",
|
||||
"@types/express": "5.0.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "^20",
|
||||
"@types/nodemailer": "6.4.16",
|
||||
"@types/react": "^18",
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Lucia, TimeSpan } from "lucia";
|
|||
import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle";
|
||||
import db from "@server/db";
|
||||
import { sessions, users } from "@server/db/schema";
|
||||
import config from "@server/config";
|
||||
|
||||
const adapter = new DrizzleSQLiteAdapter(db, sessions, users);
|
||||
|
||||
|
@ -18,19 +19,18 @@ export const lucia = new Lucia(adapter, {
|
|||
dateCreated: attributes.dateCreated,
|
||||
};
|
||||
},
|
||||
// getSessionAttributes: (attributes) => {
|
||||
// return {
|
||||
// country: attributes.country,
|
||||
// };
|
||||
// },
|
||||
sessionCookie: {
|
||||
name: "session",
|
||||
expires: false,
|
||||
attributes: {
|
||||
// secure: environment.ENVIRONMENT === "prod",
|
||||
// sameSite: "strict",
|
||||
secure: false,
|
||||
domain: ".testing123.io",
|
||||
sameSite: "strict",
|
||||
secure: config.app.secure_cookies || false,
|
||||
domain:
|
||||
"." +
|
||||
config.app.external_base_url
|
||||
.split("://")[1]
|
||||
.split(":")[0]
|
||||
.split("/")[0],
|
||||
},
|
||||
},
|
||||
sessionExpiresIn: new TimeSpan(2, "w"),
|
||||
|
@ -42,7 +42,6 @@ declare module "lucia" {
|
|||
interface Register {
|
||||
Lucia: typeof lucia;
|
||||
DatabaseUserAttributes: DatabaseUserAttributes;
|
||||
DatabaseSessionAttributes: DatabaseSessionAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +53,3 @@ interface DatabaseUserAttributes {
|
|||
emailVerified: boolean;
|
||||
dateCreated: string;
|
||||
}
|
||||
|
||||
interface DatabaseSessionAttributes {
|
||||
// country: string;
|
||||
}
|
||||
|
|
85
server/config.ts
Normal file
85
server/config.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
export const APP_PATH = path.join("config");
|
||||
|
||||
const environmentSchema = z.object({
|
||||
app: z.object({
|
||||
name: z.string(),
|
||||
environment: z.enum(["dev", "prod"]),
|
||||
external_base_url: z.string().url(),
|
||||
internal_base_url: z.string().url(),
|
||||
log_level: z.enum(["debug", "info", "warn", "error"]),
|
||||
save_logs: z.string().transform((val) => val === "true"),
|
||||
secure_cookies: z.string().transform((val) => val === "true"),
|
||||
}),
|
||||
server: z.object({
|
||||
external_port: z
|
||||
.string()
|
||||
.transform((val) => parseInt(val, 10))
|
||||
.pipe(z.number()),
|
||||
internal_port: z
|
||||
.string()
|
||||
.transform((val) => parseInt(val, 10))
|
||||
.pipe(z.number()),
|
||||
}),
|
||||
rate_limit: z.object({
|
||||
window_minutes: z
|
||||
.string()
|
||||
.transform((val) => parseInt(val, 10))
|
||||
.pipe(z.number()),
|
||||
max_requests: z
|
||||
.string()
|
||||
.transform((val) => parseInt(val, 10))
|
||||
.pipe(z.number()),
|
||||
}),
|
||||
email: z
|
||||
.object({
|
||||
smtp_host: z.string().optional(),
|
||||
smtp_port: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
return val;
|
||||
})
|
||||
.pipe(z.number().optional()),
|
||||
smtp_user: z.string().optional(),
|
||||
smtp_pass: z.string().optional(),
|
||||
no_reply: z.string().email().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const loadConfig = (configPath: string) => {
|
||||
try {
|
||||
const yamlContent = fs.readFileSync(configPath, "utf8");
|
||||
const config = yaml.load(yamlContent);
|
||||
return config;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(
|
||||
`Error loading configuration file: ${error.message}`,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const configFilePath = path.join(APP_PATH, "config.yml");
|
||||
|
||||
const environment = loadConfig(configFilePath);
|
||||
|
||||
const parsedConfig = environmentSchema.safeParse(environment);
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
const errors = fromError(parsedConfig.error);
|
||||
throw new Error(`Invalid configuration file: ${errors}`);
|
||||
}
|
||||
|
||||
export default parsedConfig.data;
|
|
@ -1,10 +1,10 @@
|
|||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import Database from "better-sqlite3";
|
||||
import * as schema from "@server/db/schema";
|
||||
import environment from "@server/environment";
|
||||
import config, { APP_PATH } from "@server/config";
|
||||
import path from "path";
|
||||
|
||||
const location = path.join(environment.CONFIG_PATH, "db", "db.sqlite");
|
||||
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||
|
||||
const sqlite = new Database(location);
|
||||
export const db = drizzle(sqlite, { schema });
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
export * from "@server/emails/sendEmail";
|
||||
|
||||
import nodemailer from "nodemailer";
|
||||
import environment from "@server/environment";
|
||||
import config from "@server/config";
|
||||
import logger from "@server/logger";
|
||||
|
||||
function createEmailClient() {
|
||||
if (
|
||||
!environment.EMAIL_SMTP_HOST ||
|
||||
!environment.EMAIL_SMTP_PORT ||
|
||||
!environment.EMAIL_SMTP_USER ||
|
||||
!environment.EMAIL_SMTP_PASS
|
||||
!config.email?.smtp_host ||
|
||||
!config.email?.smtp_pass ||
|
||||
!config.email?.smtp_port ||
|
||||
!config.email?.smtp_user
|
||||
) {
|
||||
logger.warn(
|
||||
"Email SMTP configuration is missing. Emails will not be sent.",
|
||||
|
@ -18,12 +18,12 @@ function createEmailClient() {
|
|||
}
|
||||
|
||||
return nodemailer.createTransport({
|
||||
host: environment.EMAIL_SMTP_HOST,
|
||||
port: environment.EMAIL_SMTP_PORT,
|
||||
host: config.email.smtp_host,
|
||||
port: config.email.smtp_port,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: environment.EMAIL_SMTP_USER,
|
||||
pass: environment.EMAIL_SMTP_PASS,
|
||||
user: config.email.smtp_user,
|
||||
pass: config.email.smtp_pass,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import logger from "@server/logger";
|
|||
export async function sendEmail(
|
||||
template: ReactElement,
|
||||
opts: {
|
||||
from: string;
|
||||
to: string;
|
||||
from: string | undefined;
|
||||
to: string | undefined;
|
||||
subject: string;
|
||||
},
|
||||
) {
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import path from "path";
|
||||
|
||||
const environmentSchema = z.object({
|
||||
ENVIRONMENT: z.enum(["dev", "prod"]),
|
||||
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]),
|
||||
SAVE_LOGS: z.string().transform((val) => val === "true"),
|
||||
CONFIG_PATH: z.string().transform((val) => {
|
||||
// validate the path and remove any trailing slashes
|
||||
const resolvedPath = path.resolve(val);
|
||||
return resolvedPath.endsWith(path.sep)
|
||||
? resolvedPath.slice(0, -1)
|
||||
: resolvedPath;
|
||||
}),
|
||||
EXTERNAL_PORT: z
|
||||
.string()
|
||||
.transform((val) => parseInt(val, 10))
|
||||
.pipe(z.number()),
|
||||
INTERNAL_PORT: z
|
||||
.string()
|
||||
.transform((val) => parseInt(val, 10))
|
||||
.pipe(z.number()),
|
||||
RATE_LIMIT_WINDOW_MIN: z
|
||||
.string()
|
||||
.transform((val) => parseInt(val, 10))
|
||||
.pipe(z.number()),
|
||||
RATE_LIMIT_MAX: z
|
||||
.string()
|
||||
.transform((val) => parseInt(val, 10))
|
||||
.pipe(z.number()),
|
||||
APP_NAME: z.string(),
|
||||
EMAIL_SMTP_HOST: z.string().optional(),
|
||||
EMAIL_SMTP_PORT: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
return val;
|
||||
})
|
||||
.pipe(z.number().optional()),
|
||||
EMAIL_SMTP_USER: z.string().optional(),
|
||||
EMAIL_SMTP_PASS: z.string().optional(),
|
||||
EMAIL_NOREPLY: z.string().email().optional(),
|
||||
BASE_URL: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (!val) {
|
||||
return `http://localhost:${environment.EXTERNAL_PORT}`;
|
||||
}
|
||||
return val;
|
||||
})
|
||||
.pipe(z.string().url()),
|
||||
});
|
||||
|
||||
const environment = {
|
||||
ENVIRONMENT: (process.env.ENVIRONMENT as string) || "dev",
|
||||
LOG_LEVEL: (process.env.LOG_LEVEL as string) || "debug",
|
||||
SAVE_LOGS: (process.env.SAVE_LOGS as string) || "false",
|
||||
CONFIG_PATH:
|
||||
(process.env.CONFIG_PATH && path.join(process.env.CONFIG_PATH)) ||
|
||||
path.join("config"),
|
||||
EXTERNAL_PORT: (process.env.EXTERNAL_PORT as string) || "3000",
|
||||
INTERNAL_PORT: (process.env.INTERNAL_PORT as string) || "3001",
|
||||
RATE_LIMIT_WINDOW_MIN: (process.env.RATE_LIMIT_WINDOW_MIN as string) || "1",
|
||||
RATE_LIMIT_MAX: (process.env.RATE_LIMIT_MAX as string) || "100",
|
||||
APP_NAME: (process.env.APP_NAME as string) || "Pangolin",
|
||||
EMAIL_SMTP_HOST: process.env.EMAIL_SMTP_HOST as string,
|
||||
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT as string,
|
||||
EMAIL_SMTP_USER: process.env.EMAIL_SMTP_USER as string,
|
||||
EMAIL_SMTP_PASS: process.env.EMAIL_SMTP_PASS as string,
|
||||
EMAIL_NOREPLY: process.env.EMAIL_NOREPLY as string,
|
||||
BASE_URL: process.env.BASE_URL as string,
|
||||
};
|
||||
|
||||
const parsedConfig = environmentSchema.safeParse(environment);
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
const errors = fromError(parsedConfig.error);
|
||||
throw new Error(`Invalid environment configuration: ${errors}`);
|
||||
}
|
||||
|
||||
export default parsedConfig.data;
|
|
@ -1,7 +1,7 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import next from "next";
|
||||
import { parse } from "url";
|
||||
import environment from "@server/environment";
|
||||
import config from "@server/config";
|
||||
import logger from "@server/logger";
|
||||
import helmet from "helmet";
|
||||
import cors from "cors";
|
||||
|
@ -15,16 +15,16 @@ import { authenticated, unauthenticated } from "@server/routers/external";
|
|||
import cookieParser from "cookie-parser";
|
||||
import { User } from "@server/db/schema";
|
||||
|
||||
const dev = environment.ENVIRONMENT !== "prod";
|
||||
const dev = config.app.environment !== "prod";
|
||||
|
||||
const app = next({ dev });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
const externalPort = environment.EXTERNAL_PORT;
|
||||
const internalPort = environment.INTERNAL_PORT;
|
||||
const externalPort = config.server.external_port;
|
||||
const internalPort = config.server.internal_port;
|
||||
|
||||
app.prepare().then(() => {
|
||||
|
||||
app.prepare().then(() => {
|
||||
|
||||
// External server
|
||||
const externalServer = express();
|
||||
externalServer.set("trust proxy", 1);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import "winston-daily-rotate-file";
|
||||
import environment from "@server/environment";
|
||||
import config, { APP_PATH } from "@server/config";
|
||||
import * as winston from "winston";
|
||||
import path from "path";
|
||||
|
||||
|
@ -24,11 +24,11 @@ const transports: any = [
|
|||
}),
|
||||
];
|
||||
|
||||
if (environment.SAVE_LOGS) {
|
||||
if (config.app.save_logs) {
|
||||
transports.push(
|
||||
new winston.transports.DailyRotateFile({
|
||||
filename: path.join(
|
||||
environment.CONFIG_PATH,
|
||||
APP_PATH,
|
||||
"logs",
|
||||
"pangolin-%DATE%.log",
|
||||
),
|
||||
|
@ -43,7 +43,7 @@ if (environment.SAVE_LOGS) {
|
|||
transports.push(
|
||||
new winston.transports.DailyRotateFile({
|
||||
filename: path.join(
|
||||
environment.CONFIG_PATH,
|
||||
APP_PATH,
|
||||
"logs",
|
||||
".machinelogs-%DATE%.json",
|
||||
),
|
||||
|
@ -63,7 +63,7 @@ if (environment.SAVE_LOGS) {
|
|||
}
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: environment.LOG_LEVEL.toLowerCase(),
|
||||
level: config.app.log_level.toLowerCase(),
|
||||
format: winston.format.combine(
|
||||
winston.format.splat(),
|
||||
winston.format.timestamp(),
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ErrorRequestHandler, NextFunction, Response } from "express";
|
|||
import ErrorResponse from "@server/types/ErrorResponse";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import logger from "@server/logger";
|
||||
import environment from "@server/environment";
|
||||
import config from "@server/config";
|
||||
|
||||
export const errorHandlerMiddleware: ErrorRequestHandler = (
|
||||
error,
|
||||
|
@ -11,7 +11,7 @@ export const errorHandlerMiddleware: ErrorRequestHandler = (
|
|||
next: NextFunction,
|
||||
) => {
|
||||
const statusCode = error.statusCode || HttpCode.INTERNAL_SERVER_ERROR;
|
||||
if (environment.ENVIRONMENT !== "prod") {
|
||||
if (config.app.environment !== "prod") {
|
||||
logger.error(error);
|
||||
}
|
||||
res?.status(statusCode).send({
|
||||
|
@ -20,6 +20,6 @@ export const errorHandlerMiddleware: ErrorRequestHandler = (
|
|||
error: true,
|
||||
message: error.message || "Internal Server Error",
|
||||
status: statusCode,
|
||||
stack: environment.ENVIRONMENT === "prod" ? null : error.stack,
|
||||
stack: config.app.environment === "prod" ? null : error.stack,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import { User, users } from "@server/db/schema";
|
|||
import { eq } from "drizzle-orm";
|
||||
import { verify } from "@node-rs/argon2";
|
||||
import { createTOTPKeyURI } from "oslo/otp";
|
||||
import env from "@server/environment";
|
||||
import config from "@server/config";
|
||||
|
||||
export const requestTotpSecretBody = z.object({
|
||||
password: z.string(),
|
||||
|
@ -65,7 +65,7 @@ export async function requestTotpSecret(
|
|||
|
||||
const hex = crypto.getRandomValues(new Uint8Array(20));
|
||||
const secret = encodeHex(hex);
|
||||
const uri = createTOTPKeyURI(env.APP_NAME, user.email, hex);
|
||||
const uri = createTOTPKeyURI(config.app.name, user.email, hex);
|
||||
|
||||
await db
|
||||
.update(users)
|
||||
|
|
|
@ -5,7 +5,7 @@ import { users, emailVerificationCodes } from "@server/db/schema";
|
|||
import { eq } from "drizzle-orm";
|
||||
import { sendEmail } from "@server/emails";
|
||||
import VerifyEmail from "@server/emails/templates/verifyEmailCode";
|
||||
import env from "@server/environment";
|
||||
import config from "@server/config";
|
||||
|
||||
export async function sendEmailVerificationCode(
|
||||
email: string,
|
||||
|
@ -15,7 +15,7 @@ export async function sendEmailVerificationCode(
|
|||
|
||||
await sendEmail(VerifyEmail({ username: email, verificationCode: code }), {
|
||||
to: email,
|
||||
from: env.EMAIL_NOREPLY!,
|
||||
from: config.email?.no_reply,
|
||||
subject: "Verify your email address",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,8 +24,6 @@ export async function verifyUser(
|
|||
): Promise<any> {
|
||||
const parsedBody = verifyUserBody.safeParse(req.query);
|
||||
|
||||
logger.debug("Parsed body", parsedBody);
|
||||
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
|
@ -40,9 +38,6 @@ export async function verifyUser(
|
|||
try {
|
||||
const { session, user } = await lucia.validateSession(sessionId);
|
||||
|
||||
logger.debug("Session", session);
|
||||
logger.debug("User", user);
|
||||
|
||||
if (!session || !user) {
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "Invalid session"),
|
||||
|
|
|
@ -5,14 +5,13 @@ import { DynamicTraefikConfig } from "./configSchema";
|
|||
import { and, like, eq } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import env from "@server/environment";
|
||||
import environment from "@server/environment";
|
||||
import env from "@server/config";
|
||||
import config from "@server/config";
|
||||
|
||||
export async function traefikConfigProvider(_: Request, res: Response) {
|
||||
try {
|
||||
const targets = await getAllTargets();
|
||||
const traefikConfig = buildTraefikConfig(targets);
|
||||
// logger.debug("Built traefik config");
|
||||
res.status(HttpCode.OK).json(traefikConfig);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to build traefik config: ${e}`);
|
||||
|
@ -32,35 +31,13 @@ export function buildTraefikConfig(
|
|||
}
|
||||
|
||||
const http: DynamicTraefikConfig["http"] = {
|
||||
routers: {
|
||||
"themainwebpage": {
|
||||
"entryPoints": [
|
||||
"http"
|
||||
],
|
||||
"middlewares": [
|
||||
],
|
||||
"service": "service-themainwebpage",
|
||||
"rule": "Host(`testing123.io`)"
|
||||
},
|
||||
},
|
||||
services: {
|
||||
"service-themainwebpage": {
|
||||
"loadBalancer": {
|
||||
"servers": [
|
||||
{
|
||||
"url": `http://${environment.APP_NAME.toLowerCase()}:3000`
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
routers: {},
|
||||
services: {},
|
||||
middlewares: {
|
||||
[middlewareName]: {
|
||||
plugin: {
|
||||
[middlewareName]: {
|
||||
apiBaseUrl: `http://${environment.APP_NAME.toLowerCase()}:3001/api/v1`,
|
||||
// appBaseUrl: env.BASE_URL,
|
||||
appBaseUrl: "http://testing123.io:8081",
|
||||
apiBaseUrl: config.app.internal_base_url,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import axios from "axios";
|
||||
|
||||
// const baseURL = `${window.location.protocol}//${window.location.host}/api/v1`;
|
||||
|
||||
|
||||
export const api = axios.create({
|
||||
baseURL: "http://testing123.io:8081/api/v1",
|
||||
baseURL: process.env.NEXT_PUBLIC_EXTERNAL_API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
@ -12,7 +9,7 @@ export const api = axios.create({
|
|||
});
|
||||
|
||||
export const internal = axios.create({
|
||||
baseURL: "http://pangolin:3000/api/v1",
|
||||
baseURL: process.env.NEXT_PUBLIC_INTERNAL_API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
|
@ -4,49 +4,48 @@
|
|||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 37 100% 100%;
|
||||
--foreground: 37 5% 10%;
|
||||
--card: 37 50% 100%;
|
||||
--card-foreground: 37 5% 15%;
|
||||
--popover: 37 100% 100%;
|
||||
--popover-foreground: 37 100% 10%;
|
||||
--primary: 37 8% 51%;
|
||||
--background: 37 0% 95%;
|
||||
--foreground: 37 0% 10%;
|
||||
--card: 37 0% 90%;
|
||||
--card-foreground: 37 0% 15%;
|
||||
--popover: 37 0% 95%;
|
||||
--popover-foreground: 37 95% 10%;
|
||||
--primary: 37 31% 25%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--secondary: 37 30% 90%;
|
||||
--secondary: 37 10% 74%;
|
||||
--secondary-foreground: 0 0% 0%;
|
||||
--muted: -1 30% 95%;
|
||||
--muted-foreground: 37 5% 40%;
|
||||
--accent: -1 30% 90%;
|
||||
--accent-foreground: 37 5% 15%;
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 37 5% 100%;
|
||||
--border: 37 30% 82%;
|
||||
--input: 37 30% 50%;
|
||||
--ring: 37 8% 51%;
|
||||
--radius: 0rem;
|
||||
--muted: -1 10% 85%;
|
||||
--muted-foreground: 37 0% 40%;
|
||||
--accent: -1 10% 80%;
|
||||
--accent-foreground: 37 0% 15%;
|
||||
--destructive: 0 50% 50%;
|
||||
--destructive-foreground: 37 0% 90%;
|
||||
--border: 37 20% 74%;
|
||||
--input: 37 20% 50%;
|
||||
--ring: 37 31% 25%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 37 50% 10%;
|
||||
--foreground: 37 5% 100%;
|
||||
--card: 37 50% 10%;
|
||||
--card-foreground: 37 5% 100%;
|
||||
--popover: 37 50% 5%;
|
||||
--popover-foreground: 37 5% 100%;
|
||||
--primary: 37 8% 51%;
|
||||
--background: 37 10% 10%;
|
||||
--foreground: 37 0% 90%;
|
||||
--card: 37 0% 10%;
|
||||
--card-foreground: 37 0% 90%;
|
||||
--popover: 37 10% 5%;
|
||||
--popover-foreground: 37 0% 90%;
|
||||
--primary: 37 31% 25%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--secondary: 37 30% 20%;
|
||||
--secondary: 37 10% 20%;
|
||||
--secondary-foreground: 0 0% 100%;
|
||||
--muted: -1 30% 25%;
|
||||
--muted-foreground: 37 5% 65%;
|
||||
--accent: -1 30% 25%;
|
||||
--accent-foreground: 37 5% 95%;
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 37 5% 100%;
|
||||
--border: 37 30% 50%;
|
||||
--input: 37 30% 50%;
|
||||
--ring: 37 8% 51%;
|
||||
--radius: 0rem;
|
||||
--muted: -1 10% 25%;
|
||||
--muted-foreground: 37 0% 65%;
|
||||
--accent: -1 10% 25%;
|
||||
--accent-foreground: 37 0% 90%;
|
||||
--destructive: 0 50% 50%;
|
||||
--destructive-foreground: 37 0% 90%;
|
||||
--border: 37 20% 50%;
|
||||
--input: 37 20% 50%;
|
||||
--ring: 37 31% 25%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,4 +57,4 @@
|
|||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue