1
0
mirror of https://github.com/tormachris/cf-workers-status-page.git synced 2024-11-23 22:45:43 +01:00

Merge pull request #15 from aexvir/aexvir/tailwind

refactor: switch css framework to tailwind
This commit is contained in:
Adam Janiš 2020-11-21 12:22:04 +01:00 committed by GitHub
commit 35c620f485
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1997 additions and 416 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 450 KiB

1
.gitignore vendored
View File

@ -133,3 +133,4 @@ worker/
.direnv/ .direnv/
out/ out/
package-lock.json package-lock.json
public/style.css

View File

@ -10,7 +10,7 @@ import { processCronTrigger } from './src/functions/cronTrigger'
*/ */
const DEBUG = false const DEBUG = false
addEventListener('fetch', event => { addEventListener('fetch', (event) => {
try { try {
event.respondWith( event.respondWith(
handleEvent(event, require.context('./pages/', true, /\.js$/), DEBUG), handleEvent(event, require.context('./pages/', true, /\.js$/), DEBUG),
@ -27,6 +27,6 @@ addEventListener('fetch', event => {
} }
}) })
addEventListener('scheduled', event => { addEventListener('scheduled', (event) => {
event.waitUntil(processCronTrigger(event)) event.waitUntil(processCronTrigger(event))
}) })

View File

@ -7,21 +7,26 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "flareact dev", "dev": "flareact dev",
"build": "flareact build", "build": "yarn css && flareact build",
"deploy": "flareact publish", "deploy": "yarn build && flareact publish",
"kv-gc": "node ./src/cli/gcMonitors.js", "kv-gc": "node ./src/cli/gcMonitors.js",
"format": "prettier --write '**/*.{js,css,json,md}'" "format": "prettier --write '**/*.{js,css,json,md}'",
"css": "postcss public/tailwind.css -o public/style.css"
}, },
"dependencies": { "dependencies": {
"flareact": "^0.9.0", "flareact": "^0.9.0",
"laco": "^1.2.1", "laco": "^1.2.1",
"laco-react": "^1.1.0", "laco-react": "^1.1.0",
"react": "^16.13.1", "react": "^17.0.1",
"react-dom": "^16.13.1" "react-dom": "^17.0.1"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.0.2",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"prettier": "^1.18.2", "postcss": "^8.1.8",
"postcss-cli": "^8.3.0",
"prettier": "^2.2.0",
"tailwindcss": "^2.0.1",
"yaml-loader": "^0.6.0" "yaml-loader": "^0.6.0"
} }
} }

View File

@ -1,6 +1,6 @@
import { processCronTrigger } from '../../src/functions/cronTrigger' import { processCronTrigger } from '../../src/functions/cronTrigger'
export default async event => { export default async (event) => {
// used only for local debugging // used only for local debugging
//return processCronTrigger(event) //return processCronTrigger(event)
} }

View File

@ -1,115 +1,97 @@
import Head from 'flareact/head'
import MonitorHistogram from '../src/components/monitorHistogram'
import {
getMonitors,
useKeyPress,
} from '../src/functions/helpers'
import config from '../config.yaml'
import MonitorStatusLabel from '../src/components/monitorStatusLabel'
import MonitorStatusHeader from '../src/components/monitorStatusHeader'
import MonitorFilter from '../src/components/monitorFilter'
import { Store } from 'laco' import { Store } from 'laco'
import { useStore } from 'laco-react' import { useStore } from 'laco-react'
import Head from 'flareact/head'
const MonitorStore = new Store( import { getMonitors, useKeyPress, switchTheme } from '../src/functions/helpers'
{ import config from '../config.yaml'
monitors: config.monitors, import MonitorCard from '../src/components/monitorCard'
visible: config.monitors, import MonitorFilter from '../src/components/monitorFilter'
activeFilter: false import MonitorStatusHeader from '../src/components/monitorStatusHeader'
} import ThemeSwitcher from '../src/components/themeSwitcher'
)
const filterByTerm = (term) => MonitorStore.set( const MonitorStore = new Store({
state => ({ visible: state.monitors.filter((monitor) => monitor.name.toLowerCase().includes(term)) }) monitors: config.monitors,
) visible: config.monitors,
activeFilter: false,
})
const filterByTerm = (term) =>
MonitorStore.set((state) => ({
visible: state.monitors.filter((monitor) =>
monitor.name.toLowerCase().includes(term),
),
}))
export async function getEdgeProps() { export async function getEdgeProps() {
// get KV data // get KV data
const {value: kvMonitors, metadata: kvMonitorsMetadata } = await getMonitors() const {
value: kvMonitors,
metadata: kvMonitorsMetadata,
} = await getMonitors()
return { return {
props: { props: {
config, config,
kvMonitors: kvMonitors || {}, kvMonitors: kvMonitors || {},
kvMonitorsMetadata: kvMonitorsMetadata || {} kvMonitorsMetadata: kvMonitorsMetadata || {},
}, },
// Revalidate these props once every x seconds // Revalidate these props once every x seconds
revalidate: 5, revalidate: 5,
} }
} }
export default function Index({ export default function Index({ config, kvMonitors, kvMonitorsMetadata }) {
config,
kvMonitors,
kvMonitorsMetadata,
}) {
const state = useStore(MonitorStore) const state = useStore(MonitorStore)
const slash = useKeyPress('/') const slash = useKeyPress('/')
return ( return (
<div> <div className="min-h-screen">
<Head> <Head>
<title>{config.settings.title}</title> <title>{config.settings.title}</title>
<link <link rel="stylesheet" href="./style.css" />
rel="stylesheet" <script>
href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/semantic.min.css" {`
crossOrigin="anonymous" function setTheme(theme) {
/> document.documentElement.classList.remove("dark", "light")
<link rel="stylesheet" href="./main.css" /> document.documentElement.classList.add(theme)
localStorage.theme = theme
}
(() => {
const query = window.matchMedia("(prefers-color-scheme: dark)")
query.addListener(() => {
setTheme(query.matches ? "dark" : "light")
})
if (["dark", "light"].includes(localStorage.theme)) {
setTheme(localStorage.theme)
} else {
setTheme(query.matches ? "dark" : "light")
}
})()
`}
</script>
</Head> </Head>
<div className="ui basic segment container"> <div className="container mx-auto px-4">
<div className="horizontal flex between"> <div className="flex flex-row justify-between items-center p-4">
<h1 className="ui huge marginless title header"> <div className="flex flex-row items-center">
<img <img className="h-8 w-auto" src={config.settings.logo} />
className="ui middle aligned tiny image" <h1 className="ml-4 text-3xl">{config.settings.title}</h1>
src={config.settings.logo} </div>
/> <div className="flex flex-row items-center">
{config.settings.title} {typeof window !== 'undefined' && <ThemeSwitcher />}
</h1> <MonitorFilter active={slash} callback={filterByTerm} />
<MonitorFilter </div>
active={slash}
callback={filterByTerm}
/>
</div> </div>
<MonitorStatusHeader <MonitorStatusHeader kvMonitorsMetadata={kvMonitorsMetadata} />
kvMonitorsMetadata={kvMonitorsMetadata}
/>
{state.visible.map((monitor, key) => { {state.visible.map((monitor, key) => {
return ( return (
<div key={key} className="ui segment"> <MonitorCard
<div key={key}
className="ui horizontal flex between" monitor={monitor}
style={{ marginBottom: '8px' }} data={kvMonitors[monitor.id]}
> />
<div className="ui marginless header">
{monitor.description && (
<span data-tooltip={monitor.description}>
<i className="blue small info circle icon" />
</span>
)}
<div className="content">{monitor.name}</div>
</div>
<MonitorStatusLabel
kvMonitor={kvMonitors[monitor.id]}
/>
</div>
<MonitorHistogram
monitorId={monitor.id}
kvMonitor={kvMonitors[monitor.id]}
/>
<div className="horizontal flex between grey-text">
<div>{config.settings.daysInHistogram} days ago</div>
<div>Today</div>
</div>
</div>
) )
})} })}
<div className="horizontal flex between grey-text"> <div className="flex flex-row justify-between mt-4 text-sm">
<div> <div>
Powered by{' '} Powered by{' '}
<a href="https://workers.cloudflare.com/" target="_blank"> <a href="https://workers.cloudflare.com/" target="_blank">
@ -129,6 +111,15 @@ export default function Index({
</a> </a>
</div> </div>
</div> </div>
<script>
{`
function setTheme(theme) {
document.documentElement.classList.remove("dark", "light")
document.documentElement.classList.add(theme)
localStorage.theme = theme
}
`}
</script>
</div> </div>
</div> </div>
) )

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,68 +0,0 @@
body {
background: #eeeeee;
}
.flex {
display: flex;
justify-content: center;
align-content: center;
align-items: center;
}
.flex.horizontal {
flex-direction: row;
}
.flex.vertical {
flex-direction: column;
}
.flex.between {
justify-content: space-between;
}
.marginless {
margin: 0 !important;
}
.paddingless {
padding: 0 !important;
}
.black-text {
color: #000 !important;
}
.grey-text {
color: #a0a0a0 !important;
}
.white-text {
color: #fff !important;
}
.histogram {
height: 24px;
width: 100%;
margin: 0 auto;
}
.hitbox {
align-items: flex-end;
box-sizing: border-box;
height: 100%;
width: 100%;
padding: 1px;
border-radius: 3.75px;
}
.bar {
background: #dcddde;
padding-bottom: 1px;
height: 100%;
width: 85%;
border-radius: 100px;
}
.bar.green {
background: #21ba45;
}
.bar.red {
background: #db2828;
}
.bar.orange {
background: #f2711c;
}
span i.icon {
margin: 0 .25em .25em 0 !important;
}
.ui.title.header .ui.image {
margin-top: -.5em !important;
}

68
public/tailwind.css Normal file
View File

@ -0,0 +1,68 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-50;
}
a {
@apply text-blue-500 dark:text-blue-400;
}
}
@layer components {
.card {
@apply p-4 bg-white border border-gray-200 dark:bg-gray-700 dark:border-gray-600 shadow rounded-lg p-4 mb-2;
}
.pill {
@apply px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full;
}
.histogram {
@apply h-6 w-full mx-auto;
}
.hitbox {
align-items: flex-end;
box-sizing: border-box;
height: 100%;
width: 100%;
padding: 1px;
border-radius: 3.75px;
}
.bar {
@apply bg-gray-300 dark:bg-gray-600;
padding-bottom: 1px;
height: 100%;
width: 85%;
border-radius: 100px;
}
.bar.green {
@apply bg-green-400 dark:bg-green-700;
}
.bar.red {
@apply bg-red-400 dark:bg-red-700;
}
.bar.yellow {
@apply bg-yellow-400 dark:bg-yellow-700;
}
.tooltip {
@apply relative;
}
.tooltip .content {
@apply invisible absolute z-50 inline-block;
@apply rounded-full py-1 px-2 bg-gray-100 dark:bg-gray-800 shadow;
@apply opacity-0 transition-all duration-200 scale-50;
}
.tooltip:hover .content {
@apply visible opacity-100 scale-100;
}
}

View File

@ -9,7 +9,9 @@ const apiToken = process.env.CF_API_TOKEN
const kvPrefix = 's_' const kvPrefix = 's_'
if (!accountId || !namespaceId || !apiToken) { if (!accountId || !namespaceId || !apiToken) {
console.error("Missing required environment variables: CF_ACCOUNT_ID, KV_NAMESPACE_ID, CF_API_TOKEN") console.error(
'Missing required environment variables: CF_ACCOUNT_ID, KV_NAMESPACE_ID, CF_API_TOKEN',
)
process.exit(0) process.exit(0)
} }
@ -51,24 +53,26 @@ function loadConfig() {
return JSON.parse(config) return JSON.parse(config)
} }
getKvMonitors(kvPrefix).then(async kvMonitors => { getKvMonitors(kvPrefix)
const config = loadConfig() .then(async (kvMonitors) => {
const monitors = config.monitors.map(key => { const config = loadConfig()
return key.id const monitors = config.monitors.map((key) => {
}) return key.id
const kvState = kvMonitors.map(key => { })
return key.name const kvState = kvMonitors.map((key) => {
}) return key.name
const keysForRemoval = kvState.filter( })
x => !monitors.includes(x.replace(kvPrefix, '')), const keysForRemoval = kvState.filter(
) (x) => !monitors.includes(x.replace(kvPrefix, '')),
if (keysForRemoval.length > 0) {
console.log(
`Removing following keys from KV storage as they are no longer in the config: ${keysForRemoval.join(
', ',
)}`,
) )
await deleteKvBulk(keysForRemoval)
} if (keysForRemoval.length > 0) {
}).catch(e => console.log(e)) console.log(
`Removing following keys from KV storage as they are no longer in the config: ${keysForRemoval.join(
', ',
)}`,
)
await deleteKvBulk(keysForRemoval)
}
})
.catch((e) => console.log(e))

View File

@ -0,0 +1,46 @@
import config from '../../config.yaml'
import MonitorStatusLabel from './monitorStatusLabel'
import MonitorHistogram from './monitorHistogram'
const infoIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-5 mr-2 mx-auto text-blue-500 dark:text-blue-400"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clipRule="evenodd"
/>
</svg>
)
export default function MonitorCard({ key, monitor, data }) {
return (
<div key={key} className="card">
<div className="flex flex-row justify-between items-center mb-2">
<div className="flex flex-row items-center align-center">
{monitor.description && (
<div className="tooltip">
{infoIcon}
<div className="content text-center transform -translate-y-1/2 top-1/2 ml-8 w-72 text-sm object-left">
{monitor.description}
</div>
</div>
)}
<div className="text-xl">{monitor.name}</div>
</div>
<MonitorStatusLabel kvMonitor={data} />
</div>
<MonitorHistogram monitorId={monitor.id} kvMonitor={data} />
<div className="flex flex-row justify-between items-center text-gray-400 text-sm">
<div>{config.settings.daysInHistogram} days ago</div>
<div>Today</div>
</div>
</div>
)
}

View File

@ -1,6 +1,21 @@
import config from '../../config.yaml'
import { useState } from 'react' import { useState } from 'react'
const searchIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
className="h-7 mx-auto text-gray-300 dark:text-gray-600"
fill="currentColor"
>
<path d="M9 9a2 2 0 114 0 2 2 0 01-4 0z" />
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a4 4 0 00-3.446 6.032l-2.261 2.26a1 1 0 101.414 1.415l2.261-2.261A4 4 0 1011 5z"
clipRule="evenodd"
/>
</svg>
)
export default function MonitorFilter({ active, callback }) { export default function MonitorFilter({ active, callback }) {
const [input, setInput] = useState('') const [input, setInput] = useState('')
@ -21,21 +36,19 @@ export default function MonitorFilter({ active, callback }) {
} }
return ( return (
<div className="ui search"> <div className="col-span-6 sm:col-span-3 relative">
<div className="ui icon input"> <input
<input className="block w-full py-2 px-3 border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-full shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
className="prompt" type="text"
type="text" value={input}
value={input} onInput={handleInput}
onInput={handleInput} onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown} placeholder="Tap '/' to search"
placeholder="Tap '/' to search" tabIndex={0}
tabIndex={0} ref={(e) => e && active && e.focus()}
ref={ />
(e) => e && active && e.focus() <div className="absolute inset-y-1 right-1 flex z-1 items-center">
} {searchIcon}
/>
<i className="search icon"></i>
</div> </div>
</div> </div>
) )

View File

@ -1,56 +1,54 @@
import config from '../../config.yaml' import config from '../../config.yaml'
export default function MonitorHistogram({ export default function MonitorHistogram({ monitorId, kvMonitor }) {
monitorId,
kvMonitor,
}) {
// create date and set date - daysInHistogram for the first day of the histogram // create date and set date - daysInHistogram for the first day of the histogram
let date = new Date() let date = new Date()
date.setDate(date.getDate() - config.settings.daysInHistogram) date.setDate(date.getDate() - config.settings.daysInHistogram)
let content = null
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
return ( content = Array.from(Array(config.settings.daysInHistogram).keys()).map(
<div (key) => {
key={`${monitorId}-histogram`} date.setDate(date.getDate() + 1)
className="horizontal flex histogram" const dayInHistogram = date.toISOString().split('T')[0]
>
{Array.from(Array(config.settings.daysInHistogram).keys()).map(key => {
date.setDate(date.getDate() + 1)
const dayInHistogram = date.toISOString().split('T')[0]
let bg = '' let bg = ''
let dayInHistogramLabel = config.settings.dayInHistogramNoData let dayInHistogramLabel = config.settings.dayInHistogramNoData
// filter all dates before first check, check the rest // filter all dates before first check, check the rest
if (kvMonitor && kvMonitor.firstCheck <= dayInHistogram) { if (kvMonitor && kvMonitor.firstCheck <= dayInHistogram) {
if (!kvMonitor.failedDays.includes(dayInHistogram)) { if (!kvMonitor.failedDays.includes(dayInHistogram)) {
bg = 'green' bg = 'green'
dayInHistogramLabel = config.settings.dayInHistogramOperational dayInHistogramLabel = config.settings.dayInHistogramOperational
} else { } else {
bg = 'orange' bg = 'yellow'
dayInHistogramLabel = config.settings.dayInHistogramNotOperational dayInHistogramLabel = config.settings.dayInHistogramNotOperational
}
} }
}
return ( return (
<div key={key} className="hitbox"> <div key={key} className="hitbox tooltip">
<div <div className={`${bg} bar`} />
className={`${bg} bar`} <div className="content text-center py-1 px-2 mt-2 left-1/2 -ml-20 w-40 text-xs">
data-tooltip={`${dayInHistogram} - ${dayInHistogramLabel}`} {dayInHistogram}
/> <br />
<span className="font-semibold text-sm">
{dayInHistogramLabel}
</span>
</div> </div>
) </div>
})} )
</div> },
)
} else {
return (
<div
key={`${monitorId}-histogram`}
className="horizontal flex histogram"
>
<div className="grey-text">Loading histogram ...</div>
</div>
) )
} }
return (
<div
key={`${monitorId}-histogram`}
className="flex flex-row items-center histogram"
>
{content}
</div>
)
} }

View File

@ -1,28 +1,34 @@
import config from '../../config.yaml' import config from '../../config.yaml'
export default function MonitorStatusHeader({kvMonitorsMetadata}) { const classes = {
let backgroundColor = 'green' green:
let headerText = config.settings.allmonitorsOperational 'bg-green-200 text-green-700 dark:bg-green-700 dark:text-green-200 border-green-300 dark:border-green-600',
let textColor = 'black' yellow:
'bg-yellow-200 text-yellow-700 dark:bg-yellow-700 dark:text-yellow-200 border-yellow-300 dark:border-yellow-600',
}
export default function MonitorStatusHeader({ kvMonitorsMetadata }) {
let color = 'green'
let text = config.settings.allmonitorsOperational
if (!kvMonitorsMetadata.monitorsOperational) { if (!kvMonitorsMetadata.monitorsOperational) {
backgroundColor = 'yellow' color = 'yellow'
headerText = config.settings.notAllmonitorsOperational text = config.settings.notAllmonitorsOperational
} }
return ( return (
<div className={`ui inverted segment ${backgroundColor}`}> <div className={`card mb-4 font-semibold ${classes[color]}`}>
<div className="horizontal flex between"> <div className="flex flex-row justify-between items-center">
<div className={`ui marginless header ${textColor}-text`}> <div>{text}</div>
{headerText} {kvMonitorsMetadata.lastUpdate && typeof window !== 'undefined' && (
</div> <div className="text-xs font-light">
{ checked{' '}
kvMonitorsMetadata.lastUpdate && typeof window !== 'undefined' && ( {Math.round(
<div className={`${textColor}-text`}> (Date.now() - kvMonitorsMetadata.lastUpdate.time) / 1000,
checked {Math.round((Date.now() - kvMonitorsMetadata.lastUpdate.time) / 1000)} sec ago (from {kvMonitorsMetadata.lastUpdate.loc}) )}{' '}
sec ago (from {kvMonitorsMetadata.lastUpdate.loc})
</div> </div>
) )}
}
</div> </div>
</div> </div>
) )

View File

@ -1,18 +1,25 @@
import config from '../../config.yaml' import config from '../../config.yaml'
const classes = {
gray: 'bg-gray-200 text-gray-800 dark:bg-gray-800 dark:text-gray-200',
green: 'bg-green-200 text-green-800 dark:bg-green-800 dark:text-green-200',
yellow:
'bg-yellow-200 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-200',
}
export default function MonitorStatusLabel({ kvMonitor }) { export default function MonitorStatusLabel({ kvMonitor }) {
let labelColor = 'grey' let color = 'gray'
let labelText = 'No data' let text = 'No data'
if (typeof kvMonitor !== 'undefined') { if (typeof kvMonitor !== 'undefined') {
if (kvMonitor.operational) { if (kvMonitor.operational) {
labelColor = 'green' color = 'green'
labelText = config.settings.monitorLabelOperational text = config.settings.monitorLabelOperational
} else { } else {
labelColor = 'orange' color = 'yellow'
labelText = config.settings.monitorLabelNotOperational text = config.settings.monitorLabelNotOperational
} }
} }
return <div className={`ui ${labelColor} horizontal label`}>{labelText}</div> return <div className={`pill leading-5 ${classes[color]}`}>{text}</div>
} }

View File

@ -0,0 +1,58 @@
import { useEffect, useState } from 'react'
const moonIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="h-5 mx-auto"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
)
const sunIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="h-5 mx-auto"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
)
export default function ThemeSwitcher() {
const [darkmode, setDark] = useState(localStorage.getItem('theme') === 'dark')
useEffect(() => {
setTheme(darkmode ? 'dark' : 'light')
}, [darkmode])
const changeTheme = () => {
setDark(!darkmode)
}
const buttonColor = darkmode ? 'bg-gray-700' : 'bg-gray-200'
return (
<button
className={`${buttonColor} rounded-full h-7 w-7 mr-4`}
onClick={changeTheme}
>
{darkmode ? sunIcon : moonIcon}
</button>
)
}

View File

@ -1,10 +1,6 @@
import config from '../../config.yaml' import config from '../../config.yaml'
import { import { setKV, getKVWithMetadata, notifySlack } from './helpers'
setKV,
getKVWithMetadata,
notifySlack,
} from './helpers'
function getDate() { function getDate() {
return new Date().toISOString().split('T')[0] return new Date().toISOString().split('T')[0]
@ -12,7 +8,10 @@ function getDate() {
export async function processCronTrigger(event) { export async function processCronTrigger(event) {
// Get monitors state from KV // Get monitors state from KV
let {value: monitorsState, metadata: monitorsStateMetadata} = await getKVWithMetadata('monitors_data', 'json') let {
value: monitorsState,
metadata: monitorsStateMetadata,
} = await getKVWithMetadata('monitors_data', 'json')
// Create empty state objects if not exists in KV storage yet // Create empty state objects if not exists in KV storage yet
if (!monitorsState) { if (!monitorsState) {
@ -28,7 +27,7 @@ export async function processCronTrigger(event) {
for (const monitor of config.monitors) { for (const monitor of config.monitors) {
// Create default monitor state if does not exist yet // Create default monitor state if does not exist yet
if (typeof monitorsState[monitor.id] === 'undefined') { if (typeof monitorsState[monitor.id] === 'undefined') {
monitorsState[monitor.id] = {failedDays: []} monitorsState[monitor.id] = { failedDays: [] }
} }
console.log(`Checking ${monitor.name} ...`) console.log(`Checking ${monitor.name} ...`)
@ -43,15 +42,22 @@ export async function processCronTrigger(event) {
} }
const checkResponse = await fetch(monitor.url, init) const checkResponse = await fetch(monitor.url, init)
const monitorOperational = checkResponse.status === (monitor.expectStatus || 200) const monitorOperational =
checkResponse.status === (monitor.expectStatus || 200)
// Send Slack message on monitor change // Send Slack message on monitor change
if (monitorsState[monitor.id].operational !== monitorOperational && typeof SECRET_SLACK_WEBHOOK_URL !== 'undefined' && SECRET_SLACK_WEBHOOK_URL !== 'default-gh-action-secret') { if (
event.waitUntil(notifySlack(monitor, monitorOperational)) monitorsState[monitor.id].operational !== monitorOperational &&
typeof SECRET_SLACK_WEBHOOK_URL !== 'undefined' &&
SECRET_SLACK_WEBHOOK_URL !== 'default-gh-action-secret'
) {
event.waitUntil(notifySlack(monitor, monitorOperational))
} }
monitorsState[monitor.id].operational = checkResponse.status === (monitor.expectStatus || 200) monitorsState[monitor.id].operational =
monitorsState[monitor.id].firstCheck = monitorsState[monitor.id].firstCheck || getDate() checkResponse.status === (monitor.expectStatus || 200)
monitorsState[monitor.id].firstCheck =
monitorsState[monitor.id].firstCheck || getDate()
// Set monitorsOperational and push current day to failedDays // Set monitorsOperational and push current day to failedDays
if (!monitorOperational) { if (!monitorOperational) {
@ -72,11 +78,15 @@ export async function processCronTrigger(event) {
const loc = res.headers.get('cf-ray').split('-')[1] const loc = res.headers.get('cf-ray').split('-')[1]
monitorsStateMetadata.lastUpdate = { monitorsStateMetadata.lastUpdate = {
loc, loc,
time: Date.now() time: Date.now(),
} }
// Save monitorsState and monitorsStateMetadata to KV storage // Save monitorsState and monitorsStateMetadata to KV storage
await setKV('monitors_data', JSON.stringify(monitorsState), monitorsStateMetadata) await setKV(
'monitors_data',
JSON.stringify(monitorsState),
monitorsStateMetadata,
)
return new Response('OK') return new Response('OK')
} }

View File

@ -1,8 +1,8 @@
import config from '../../config.yaml' import config from '../../config.yaml'
import {useEffect, useState} from 'react' import { useEffect, useState } from 'react'
export async function getMonitors() { export async function getMonitors() {
return await getKVWithMetadata('monitors_data', "json") return await getKVWithMetadata('monitors_data', 'json')
} }
export async function setKV(key, value, metadata, expirationTtl) { export async function setKV(key, value, metadata, expirationTtl) {
@ -23,11 +23,10 @@ export async function notifySlack(monitor, operational) {
type: 'section', type: 'section',
text: { text: {
type: 'mrkdwn', type: 'mrkdwn',
text: `Monitor *${monitor.name}* changed status to *${ text: `Monitor *${monitor.name}* changed status to *${operational
operational
? config.settings.monitorLabelOperational ? config.settings.monitorLabelOperational
: config.settings.monitorLabelNotOperational : config.settings.monitorLabelNotOperational
}*`, }*`,
}, },
}, },
{ {
@ -35,11 +34,9 @@ export async function notifySlack(monitor, operational) {
elements: [ elements: [
{ {
type: 'mrkdwn', type: 'mrkdwn',
text: `${ text: `${operational ? ':white_check_mark:' : ':x:'} \`${monitor.method ? monitor.method : 'GET'
operational ? ':white_check_mark:' : ':x:' } ${monitor.url}\` - :eyes: <${config.settings.url
} \`${monitor.method ? monitor.method : "GET"} ${monitor.url}\` - :eyes: <${ }|Status Page>`,
config.settings.url
}|Status Page>`,
}, },
], ],
}, },
@ -59,24 +56,24 @@ export function useKeyPress(targetKey) {
function downHandler({ key }) { function downHandler({ key }) {
if (key === targetKey) { if (key === targetKey) {
setKeyPressed(true); setKeyPressed(true)
} }
} }
const upHandler = ({ key }) => { const upHandler = ({ key }) => {
if (key === targetKey) { if (key === targetKey) {
setKeyPressed(false); setKeyPressed(false)
} }
} }
useEffect(() => { useEffect(() => {
window.addEventListener('keydown', downHandler); window.addEventListener('keydown', downHandler)
window.addEventListener('keyup', upHandler); window.addEventListener('keyup', upHandler)
return () => { return () => {
window.removeEventListener('keydown', downHandler); window.removeEventListener('keydown', downHandler)
window.removeEventListener('keyup', upHandler); window.removeEventListener('keyup', upHandler)
}; }
}, []) }, [])
return keyPressed return keyPressed

918
tailwind.config.js Normal file
View File

@ -0,0 +1,918 @@
const colors = require('tailwindcss/colors')
module.exports = {
purge: {
content: ['./src/**/*.js', './pages/**/*.js', './public/tailwind.css'],
},
presets: [],
darkMode: 'class', // or 'media' or 'class'
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
gray: colors.coolGray,
red: colors.red,
yellow: colors.yellow,
green: colors.green,
blue: colors.lightBlue,
indigo: colors.indigo,
purple: colors.violet,
pink: colors.pink,
},
spacing: {
px: '1px',
0: '0px',
0.5: '0.125rem',
1: '0.25rem',
1.5: '0.375rem',
2: '0.5rem',
2.5: '0.625rem',
3: '0.75rem',
3.5: '0.875rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
11: '2.75rem',
12: '3rem',
14: '3.5rem',
16: '4rem',
20: '5rem',
24: '6rem',
28: '7rem',
32: '8rem',
36: '9rem',
40: '10rem',
44: '11rem',
48: '12rem',
52: '13rem',
56: '14rem',
60: '15rem',
64: '16rem',
72: '18rem',
80: '20rem',
96: '24rem',
},
animation: {
none: 'none',
spin: 'spin 1s linear infinite',
ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
bounce: 'bounce 1s infinite',
},
backgroundColor: (theme) => theme('colors'),
backgroundImage: {
none: 'none',
'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))',
'gradient-to-tr':
'linear-gradient(to top right, var(--tw-gradient-stops))',
'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))',
'gradient-to-br':
'linear-gradient(to bottom right, var(--tw-gradient-stops))',
'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))',
'gradient-to-bl':
'linear-gradient(to bottom left, var(--tw-gradient-stops))',
'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))',
'gradient-to-tl':
'linear-gradient(to top left, var(--tw-gradient-stops))',
},
backgroundOpacity: (theme) => theme('opacity'),
backgroundPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top',
},
backgroundSize: {
auto: 'auto',
cover: 'cover',
contain: 'contain',
},
borderColor: (theme) => ({
...theme('colors'),
DEFAULT: theme('colors.gray.200', 'currentColor'),
}),
borderOpacity: (theme) => theme('opacity'),
borderRadius: {
none: '0px',
sm: '0.125rem',
DEFAULT: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
'2xl': '1rem',
'3xl': '1.5rem',
full: '9999px',
},
borderWidth: {
DEFAULT: '1px',
0: '0px',
2: '2px',
4: '4px',
8: '8px',
},
boxShadow: {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
DEFAULT:
'0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md:
'0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg:
'0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl:
'0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
none: 'none',
},
container: {},
cursor: {
auto: 'auto',
default: 'default',
pointer: 'pointer',
wait: 'wait',
text: 'text',
move: 'move',
'not-allowed': 'not-allowed',
},
divideColor: (theme) => theme('borderColor'),
divideOpacity: (theme) => theme('borderOpacity'),
divideWidth: (theme) => theme('borderWidth'),
fill: { current: 'currentColor' },
flex: {
1: '1 1 0%',
auto: '1 1 auto',
initial: '0 1 auto',
none: 'none',
},
flexGrow: {
0: '0',
DEFAULT: '1',
},
flexShrink: {
0: '0',
DEFAULT: '1',
},
fontFamily: {
sans: [
'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"',
],
serif: [
'ui-serif',
'Georgia',
'Cambria',
'"Times New Roman"',
'Times',
'serif',
],
mono: [
'ui-monospace',
'SFMono-Regular',
'Menlo',
'Monaco',
'Consolas',
'"Liberation Mono"',
'"Courier New"',
'monospace',
],
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
'5xl': ['3rem', { lineHeight: '1' }],
'6xl': ['3.75rem', { lineHeight: '1' }],
'7xl': ['4.5rem', { lineHeight: '1' }],
'8xl': ['6rem', { lineHeight: '1' }],
'9xl': ['8rem', { lineHeight: '1' }],
},
fontWeight: {
thin: '100',
extralight: '200',
light: '300',
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900',
},
gap: (theme) => theme('spacing'),
gradientColorStops: (theme) => theme('colors'),
gridAutoColumns: {
auto: 'auto',
min: 'min-content',
max: 'max-content',
fr: 'minmax(0, 1fr)',
},
gridAutoRows: {
auto: 'auto',
min: 'min-content',
max: 'max-content',
fr: 'minmax(0, 1fr)',
},
gridColumn: {
auto: 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
'span-7': 'span 7 / span 7',
'span-8': 'span 8 / span 8',
'span-9': 'span 9 / span 9',
'span-10': 'span 10 / span 10',
'span-11': 'span 11 / span 11',
'span-12': 'span 12 / span 12',
'span-full': '1 / -1',
},
gridColumnEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
},
gridColumnStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
},
gridRow: {
auto: 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
'span-full': '1 / -1',
},
gridRowStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
},
gridRowEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
},
transformOrigin: {
center: 'center',
top: 'top',
'top-right': 'top right',
right: 'right',
'bottom-right': 'bottom right',
bottom: 'bottom',
'bottom-left': 'bottom left',
left: 'left',
'top-left': 'top left',
},
gridTemplateColumns: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
7: 'repeat(7, minmax(0, 1fr))',
8: 'repeat(8, minmax(0, 1fr))',
9: 'repeat(9, minmax(0, 1fr))',
10: 'repeat(10, minmax(0, 1fr))',
11: 'repeat(11, minmax(0, 1fr))',
12: 'repeat(12, minmax(0, 1fr))',
},
gridTemplateRows: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
},
height: (theme) => ({
auto: 'auto',
...theme('spacing'),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.666667%',
'2/6': '33.333333%',
'3/6': '50%',
'4/6': '66.666667%',
'5/6': '83.333333%',
full: '100%',
screen: '100vh',
}),
inset: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing')),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
full: '100%',
'-1/2': '-50%',
'-1/3': '-33.333333%',
'-2/3': '-66.666667%',
'-1/4': '-25%',
'-2/4': '-50%',
'-3/4': '-75%',
'-full': '-100%',
}),
keyframes: {
spin: {
to: {
transform: 'rotate(360deg)',
},
},
ping: {
'75%, 100%': {
transform: 'scale(2)',
opacity: '0',
},
},
pulse: {
'50%': {
opacity: '.5',
},
},
bounce: {
'0%, 100%': {
transform: 'translateY(-25%)',
animationTimingFunction: 'cubic-bezier(0.8,0,1,1)',
},
'50%': {
transform: 'none',
animationTimingFunction: 'cubic-bezier(0,0,0.2,1)',
},
},
},
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0em',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
lineHeight: {
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
3: '.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
},
listStyleType: {
none: 'none',
disc: 'disc',
decimal: 'decimal',
},
margin: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing')),
}),
maxHeight: (theme) => ({
...theme('spacing'),
full: '100%',
screen: '100vh',
}),
maxWidth: (theme, { breakpoints }) => ({
none: 'none',
0: '0rem',
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
'7xl': '80rem',
full: '100%',
min: 'min-content',
max: 'max-content',
prose: '65ch',
...breakpoints(theme('screens')),
}),
minHeight: {
0: '0px',
full: '100%',
screen: '100vh',
},
minWidth: {
0: '0px',
full: '100%',
min: 'min-content',
max: 'max-content',
},
objectPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top',
},
opacity: {
0: '0',
5: '0.05',
10: '0.1',
20: '0.2',
25: '0.25',
30: '0.3',
40: '0.4',
50: '0.5',
60: '0.6',
70: '0.7',
75: '0.75',
80: '0.8',
90: '0.9',
95: '0.95',
100: '1',
},
order: {
first: '-9999',
last: '9999',
none: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
},
outline: {
none: ['2px solid transparent', '2px'],
white: ['2px dotted white', '2px'],
black: ['2px dotted black', '2px'],
},
padding: (theme) => theme('spacing'),
placeholderColor: (theme) => theme('colors'),
placeholderOpacity: (theme) => theme('opacity'),
ringColor: (theme) => ({
DEFAULT: theme('colors.blue.500', '#3b82f6'),
...theme('colors'),
}),
ringOffsetColor: (theme) => theme('colors'),
ringOffsetWidth: {
0: '0px',
1: '1px',
2: '2px',
4: '4px',
8: '8px',
},
ringOpacity: (theme) => ({
DEFAULT: '0.5',
...theme('opacity'),
}),
ringWidth: {
DEFAULT: '3px',
0: '0px',
1: '1px',
2: '2px',
4: '4px',
8: '8px',
},
rotate: {
'-180': '-180deg',
'-90': '-90deg',
'-45': '-45deg',
'-12': '-12deg',
'-6': '-6deg',
'-3': '-3deg',
'-2': '-2deg',
'-1': '-1deg',
0: '0deg',
1: '1deg',
2: '2deg',
3: '3deg',
6: '6deg',
12: '12deg',
45: '45deg',
90: '90deg',
180: '180deg',
},
scale: {
0: '0',
50: '.5',
75: '.75',
90: '.9',
95: '.95',
100: '1',
105: '1.05',
110: '1.1',
125: '1.25',
150: '1.5',
},
skew: {
'-12': '-12deg',
'-6': '-6deg',
'-3': '-3deg',
'-2': '-2deg',
'-1': '-1deg',
0: '0deg',
1: '1deg',
2: '2deg',
3: '3deg',
6: '6deg',
12: '12deg',
},
space: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing')),
}),
stroke: {
current: 'currentColor',
},
strokeWidth: {
0: '0',
1: '1',
2: '2',
},
textColor: (theme) => theme('colors'),
textOpacity: (theme) => theme('opacity'),
transitionDuration: {
DEFAULT: '150ms',
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms',
},
transitionDelay: {
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms',
},
transitionProperty: {
none: 'none',
all: 'all',
DEFAULT:
'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',
colors: 'background-color, border-color, color, fill, stroke',
opacity: 'opacity',
shadow: 'box-shadow',
transform: 'transform',
},
transitionTimingFunction: {
DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)',
linear: 'linear',
in: 'cubic-bezier(0.4, 0, 1, 1)',
out: 'cubic-bezier(0, 0, 0.2, 1)',
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
},
translate: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing')),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
full: '100%',
'-1/2': '-50%',
'-1/3': '-33.333333%',
'-2/3': '-66.666667%',
'-1/4': '-25%',
'-2/4': '-50%',
'-3/4': '-75%',
'-full': '-100%',
}),
width: (theme) => ({
auto: 'auto',
...theme('spacing'),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.666667%',
'2/6': '33.333333%',
'3/6': '50%',
'4/6': '66.666667%',
'5/6': '83.333333%',
'1/12': '8.333333%',
'2/12': '16.666667%',
'3/12': '25%',
'4/12': '33.333333%',
'5/12': '41.666667%',
'6/12': '50%',
'7/12': '58.333333%',
'8/12': '66.666667%',
'9/12': '75%',
'10/12': '83.333333%',
'11/12': '91.666667%',
full: '100%',
screen: '100vw',
min: 'min-content',
max: 'max-content',
}),
zIndex: {
auto: 'auto',
0: '0',
10: '10',
20: '20',
30: '30',
40: '40',
50: '50',
},
},
variantOrder: [
'first',
'last',
'odd',
'even',
'visited',
'checked',
'group-hover',
'group-focus',
'focus-within',
'hover',
'focus',
'focus-visible',
'active',
'disabled',
],
variants: {
accessibility: ['responsive', 'focus-within', 'focus'],
alignContent: ['responsive'],
alignItems: ['responsive'],
alignSelf: ['responsive'],
animation: ['responsive'],
appearance: ['responsive'],
backgroundAttachment: ['responsive'],
backgroundClip: ['responsive'],
backgroundColor: [
'responsive',
'dark',
'group-hover',
'focus-within',
'hover',
'focus',
],
backgroundImage: ['responsive'],
backgroundOpacity: [
'responsive',
'group-hover',
'focus-within',
'hover',
'focus',
],
backgroundPosition: ['responsive'],
backgroundRepeat: ['responsive'],
backgroundSize: ['responsive'],
borderCollapse: ['responsive'],
borderColor: [
'responsive',
'dark',
'group-hover',
'focus-within',
'hover',
'focus',
],
borderOpacity: [
'responsive',
'group-hover',
'focus-within',
'hover',
'focus',
],
borderRadius: ['responsive'],
borderStyle: ['responsive'],
borderWidth: ['responsive'],
boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
boxSizing: ['responsive'],
clear: ['responsive'],
container: ['responsive'],
cursor: ['responsive'],
display: ['responsive'],
divideColor: ['responsive', 'dark'],
divideOpacity: ['responsive'],
divideStyle: ['responsive'],
divideWidth: ['responsive'],
fill: ['responsive'],
flex: ['responsive'],
flexDirection: ['responsive'],
flexGrow: ['responsive'],
flexShrink: ['responsive'],
flexWrap: ['responsive'],
float: ['responsive'],
fontFamily: ['responsive'],
fontSize: ['responsive'],
fontSmoothing: ['responsive'],
fontStyle: ['responsive'],
fontVariantNumeric: ['responsive'],
fontWeight: ['responsive'],
gap: ['responsive'],
gradientColorStops: ['responsive', 'dark', 'hover', 'focus'],
gridAutoColumns: ['responsive'],
gridAutoFlow: ['responsive'],
gridAutoRows: ['responsive'],
gridColumn: ['responsive'],
gridColumnEnd: ['responsive'],
gridColumnStart: ['responsive'],
gridRow: ['responsive'],
gridRowEnd: ['responsive'],
gridRowStart: ['responsive'],
gridTemplateColumns: ['responsive'],
gridTemplateRows: ['responsive'],
height: ['responsive'],
inset: ['responsive'],
justifyContent: ['responsive'],
justifyItems: ['responsive'],
justifySelf: ['responsive'],
letterSpacing: ['responsive'],
lineHeight: ['responsive'],
listStylePosition: ['responsive'],
listStyleType: ['responsive'],
margin: ['responsive'],
maxHeight: ['responsive'],
maxWidth: ['responsive'],
minHeight: ['responsive'],
minWidth: ['responsive'],
objectFit: ['responsive'],
objectPosition: ['responsive'],
opacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
order: ['responsive'],
outline: ['responsive', 'focus-within', 'focus'],
overflow: ['responsive'],
overscrollBehavior: ['responsive'],
padding: ['responsive'],
placeContent: ['responsive'],
placeItems: ['responsive'],
placeSelf: ['responsive'],
placeholderColor: ['responsive', 'dark', 'focus'],
placeholderOpacity: ['responsive', 'focus'],
pointerEvents: ['responsive'],
position: ['responsive'],
resize: ['responsive'],
ringColor: ['responsive', 'dark', 'focus-within', 'focus'],
ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'],
ringOffsetWidth: ['responsive', 'focus-within', 'focus'],
ringOpacity: ['responsive', 'focus-within', 'focus'],
ringWidth: ['responsive', 'focus-within', 'focus'],
rotate: ['responsive', 'hover', 'focus'],
scale: ['responsive', 'hover', 'focus'],
skew: ['responsive', 'hover', 'focus'],
space: ['responsive'],
stroke: ['responsive'],
strokeWidth: ['responsive'],
tableLayout: ['responsive'],
textAlign: ['responsive'],
textColor: [
'responsive',
'dark',
'group-hover',
'focus-within',
'hover',
'focus',
],
textDecoration: [
'responsive',
'group-hover',
'focus-within',
'hover',
'focus',
],
textOpacity: [
'responsive',
'group-hover',
'focus-within',
'hover',
'focus',
],
textOverflow: ['responsive'],
textTransform: ['responsive'],
transform: ['responsive'],
transformOrigin: ['responsive'],
transitionDelay: ['responsive'],
transitionDuration: ['responsive'],
transitionProperty: ['responsive'],
transitionTimingFunction: ['responsive'],
translate: ['responsive', 'hover', 'focus'],
userSelect: ['responsive'],
verticalAlign: ['responsive'],
visibility: ['responsive'],
whitespace: ['responsive'],
width: ['responsive'],
wordBreak: ['responsive'],
zIndex: ['responsive', 'focus-within', 'focus'],
},
plugins: [],
}

769
yarn.lock

File diff suppressed because it is too large Load Diff