mirror of
				https://github.com/tormachris/cf-workers-status-page.git
				synced 2025-11-04 04:46:24 +01:00 
			
		
		
		
	init
This commit is contained in:
		
							
								
								
									
										49
									
								
								src/components/monitorHistogram.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/components/monitorHistogram.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
import config from '../../config.yaml'
 | 
			
		||||
 | 
			
		||||
export default function MonitorHistogram({ kvMonitorsDaysMap, monitor }) {
 | 
			
		||||
  let date = new Date()
 | 
			
		||||
  date.setDate(date.getDate() - config.settings.daysInHistory)
 | 
			
		||||
 | 
			
		||||
  if (typeof window !== 'undefined') {
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        key={`${monitor.id}-histogram`}
 | 
			
		||||
        className="horizontal flex histogram"
 | 
			
		||||
      >
 | 
			
		||||
        {Array.from(Array(config.settings.daysInHistory).keys()).map(key => {
 | 
			
		||||
          date.setDate(date.getDate() + 1)
 | 
			
		||||
          const dayInHistory = date.toISOString().split('T')[0]
 | 
			
		||||
          const dayInHistoryKey = 'h_' + monitor.id + '_' + dayInHistory
 | 
			
		||||
 | 
			
		||||
          let bg = ''
 | 
			
		||||
          let dayInHistoryStatus = 'No data'
 | 
			
		||||
 | 
			
		||||
          if (typeof kvMonitorsDaysMap[dayInHistoryKey] !== 'undefined') {
 | 
			
		||||
            bg = kvMonitorsDaysMap[dayInHistoryKey] ? 'green' : 'orange'
 | 
			
		||||
            dayInHistoryStatus = kvMonitorsDaysMap[dayInHistoryKey]
 | 
			
		||||
              ? 'No outages'
 | 
			
		||||
              : 'Some outages'
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return (
 | 
			
		||||
            <div key={key} className="hitbox">
 | 
			
		||||
              <div
 | 
			
		||||
                className={`${bg} bar`}
 | 
			
		||||
                data-tooltip={`${dayInHistory} - ${dayInHistoryStatus}`}
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          )
 | 
			
		||||
        })}
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  } else {
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        key={`${monitor.id}-histogram`}
 | 
			
		||||
        className="horizontal flex histogram"
 | 
			
		||||
      >
 | 
			
		||||
        <div className="grey-text">Loading histogram ...</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								src/components/monitorStatusLabel.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/components/monitorStatusLabel.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
export default function MonitorStatusLabel({ kvMonitorsMap, monitor }) {
 | 
			
		||||
  let labelColor = 'grey'
 | 
			
		||||
  let labelText = 'No data'
 | 
			
		||||
 | 
			
		||||
  if (typeof kvMonitorsMap[monitor.id] !== 'undefined') {
 | 
			
		||||
    if (kvMonitorsMap[monitor.id].operational) {
 | 
			
		||||
      labelColor = 'green'
 | 
			
		||||
      labelText = 'Operational'
 | 
			
		||||
    } else {
 | 
			
		||||
      labelColor = 'orange'
 | 
			
		||||
      labelText = 'Not great not terrible'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return <div className={`ui ${labelColor} horizontal label`}>{labelText}</div>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								src/css/index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/css/index.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
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 !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								src/functions/cronTrigger.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/functions/cronTrigger.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
import config from '../../config.yaml'
 | 
			
		||||
 | 
			
		||||
import { setKV, getKV, getKVWithMetadata, gcMonitors } from './helpers'
 | 
			
		||||
 | 
			
		||||
export async function processCronTrigger(event) {
 | 
			
		||||
  for (const monitor of config.monitors) {
 | 
			
		||||
    console.log(`Checking ${monitor.name} ...`)
 | 
			
		||||
 | 
			
		||||
    const init = {
 | 
			
		||||
      method: monitor.method || 'GET',
 | 
			
		||||
      redirect: monitor.followRedirect ? 'follow' : 'manual',
 | 
			
		||||
      headers: {
 | 
			
		||||
        'User-Agent': 'cf-worker-status-page',
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const response = await fetch(monitor.url, init)
 | 
			
		||||
    const monitorOperational = response.status === (monitor.expectStatus || 200)
 | 
			
		||||
    const kvMonitor = await getKVWithMetadata('s_' + monitor.id)
 | 
			
		||||
 | 
			
		||||
    // metadata from monitor settings
 | 
			
		||||
    const metadata = {
 | 
			
		||||
      operational: monitorOperational,
 | 
			
		||||
      statusCode: response.status,
 | 
			
		||||
      id: monitor.id,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // write current status if status changed or for first time
 | 
			
		||||
    if (
 | 
			
		||||
      !kvMonitor.metadata ||
 | 
			
		||||
      kvMonitor.metadata.operational !== monitorOperational
 | 
			
		||||
    ) {
 | 
			
		||||
      console.log('saving new results..')
 | 
			
		||||
 | 
			
		||||
      if (typeof SECRET_SLACK_WEBHOOK !== 'undefined') {
 | 
			
		||||
        await notifySlack(metadata)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await setKV('s_' + monitor.id, null, metadata)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // check day status, write only on not operational or for first time
 | 
			
		||||
    const kvDayStatusKey =
 | 
			
		||||
      'h_' + monitor.id + '_' + new Date().toISOString().split('T')[0]
 | 
			
		||||
    //console.log(kvDayStatusKey)
 | 
			
		||||
    const kvDayStatus = await getKV(kvDayStatusKey)
 | 
			
		||||
 | 
			
		||||
    if (!kvDayStatus || (kvDayStatus && !monitorOperational)) {
 | 
			
		||||
      await setKV(kvDayStatusKey, null, metadata)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await setKV('lastUpdate', Date.now())
 | 
			
		||||
  }
 | 
			
		||||
  await gcMonitors(config)
 | 
			
		||||
 | 
			
		||||
  return new Response('OK')
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								src/functions/helpers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/functions/helpers.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
export async function getMonitors() {
 | 
			
		||||
  const monitors = await listKV('s_')
 | 
			
		||||
  return monitors.keys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getMonitorsHistory() {
 | 
			
		||||
  const monitorsHistory = await listKV('h_', 600)
 | 
			
		||||
  return monitorsHistory.keys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getLastUpdate() {
 | 
			
		||||
  return await getKV('lastUpdate')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function listKV(prefix = '', cacheTtl = false) {
 | 
			
		||||
  const cacheKey = 'list_' + prefix + '_' + process.env.BUILD_ID
 | 
			
		||||
  const cachedResponse = await getKV(cacheKey)
 | 
			
		||||
 | 
			
		||||
  if (cacheTtl && cachedResponse) {
 | 
			
		||||
    return JSON.parse(cachedResponse)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let list = []
 | 
			
		||||
  let cursor = null
 | 
			
		||||
  let res = {}
 | 
			
		||||
  do {
 | 
			
		||||
    res = await KV_STATUS_PAGE.list({ prefix: prefix, cursor })
 | 
			
		||||
    list = list.concat(res.keys)
 | 
			
		||||
    cursor = res.cursor
 | 
			
		||||
  } while (!res.list_complete)
 | 
			
		||||
 | 
			
		||||
  if (cacheTtl) {
 | 
			
		||||
    await setKV(cacheKey, JSON.stringify({ keys: list }), null, 600)
 | 
			
		||||
  }
 | 
			
		||||
  return { keys: list }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function setKV(key, value, metadata, expirationTtl) {
 | 
			
		||||
  return KV_STATUS_PAGE.put(key, value, { metadata, expirationTtl })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getKV(key, type = 'text') {
 | 
			
		||||
  return KV_STATUS_PAGE.get(key, type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getKVWithMetadata(key) {
 | 
			
		||||
  return KV_STATUS_PAGE.getWithMetadata(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function deleteKV(key) {
 | 
			
		||||
  return KV_STATUS_PAGE.delete(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function gcMonitors(config) {
 | 
			
		||||
  const checkKvPrefix = 's_'
 | 
			
		||||
 | 
			
		||||
  const monitors = config.monitors.map(key => {
 | 
			
		||||
    return key.id
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const kvMonitors = await listKV(checkKvPrefix)
 | 
			
		||||
  const kvState = kvMonitors.keys.map(key => {
 | 
			
		||||
    return key.metadata.id
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const keysForRemoval = kvState.filter(x => !monitors.includes(x))
 | 
			
		||||
 | 
			
		||||
  keysForRemoval.forEach(key => {
 | 
			
		||||
    console.log('gc: deleting ' + checkKvPrefix + key)
 | 
			
		||||
    deleteKV(checkKvPrefix + key)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function notifySlack(monitor, metadata) {
 | 
			
		||||
  const blocks = [
 | 
			
		||||
    {
 | 
			
		||||
      type: 'section',
 | 
			
		||||
      text: {
 | 
			
		||||
        type: 'mrkdwn',
 | 
			
		||||
        text: `Some monitor is now in :this: status`,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
  return fetch(SECRET_SLACK_WEBHOOK_URL, {
 | 
			
		||||
    body: JSON.stringify({ blocks }),
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
    headers: { 'Content-Type': 'application/json' },
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user