Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/incident/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const HomeIncidentHeader = ({
) : null}
</div>
<div className="navigation-header-buttons">
{isEditor ? (
{isEditor && incident ? (
<>
<div
onClick={() =>
Expand Down
32 changes: 18 additions & 14 deletions app/components/monitor/status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,25 @@ const MonitorStatus = ({
{monitor.uptimePercentage || 0}%
</div>
</div>
<div className="monitor-status-content">
<div className="monitor-status-title">
{t('home.monitor.headers.cert_expiry')}
</div>
<div className="monitor-status-subtitle">
({t('home.monitor.headers.days_left')})
{monitor.type === 'ping' ||
monitor.type === 'push' ||
monitor.type === 'docker' ? null : (
<div className="monitor-status-content">
<div className="monitor-status-title">
{t('home.monitor.headers.cert_expiry')}
</div>
<div className="monitor-status-subtitle">
({t('home.monitor.headers.days_left')})
</div>
<div className="monitor-status-text">
{monitor.url?.startsWith('http://')
? t('common.invalid')
: monitor.cert?.isValid
? `${monitor.cert.daysRemaining}`
: t('common.expired')}
</div>
</div>
<div className="monitor-status-text">
{monitor.url?.startsWith('http://')
? t('common.invalid')
: monitor.cert?.isValid
? `${monitor.cert.daysRemaining}`
: t('common.expired')}
</div>
</div>
)}
</div>
);
};
Expand Down
4 changes: 4 additions & 0 deletions app/components/navigation/info/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@
display: flex;
align-items: center;
}

> div:last-child {
word-break: break-all;
}
}
2 changes: 1 addition & 1 deletion app/components/navigation/info/monitor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const NavigationMonitorInfo = ({ monitor }: { monitor: MonitorProps }) => {
<div className="navigation-info-item">
<div>{t('home.info.url')}</div>
<div>
{monitor.type === 'http'
{monitor.type !== 'tcp'
? monitor.url
: `${monitor.url}:${monitor.port}`}
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/status/configure/layout/customHTML.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { FaTrashCan } from '../../../icons';
import useStatusPageContext from '../../../../context/status-page';

const StatusConfigureLayoutCustomHTML = ({ componentId }) => {
const { getComponent, setComponentValue, removeComponent } =
const { getComponent, setComponentValue, removeComponent, layoutItems } =
useStatusPageContext();

const { isMinimized, data } = useMemo(
Expand Down
4 changes: 4 additions & 0 deletions app/styles/pages/incidents.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
display: flex;
gap: pxToRem(10);
padding-bottom: pxToRem(10);
margin: pxToRem(10) 0;
}

.icm-title {
Expand All @@ -33,6 +34,9 @@
display: flex;
flex-direction: column;
gap: pxToRem(5);
max-height: pxToRem(350);
overflow-y: auto;
margin: pxToRem(10) 0;
}

.icml-item {
Expand Down
2 changes: 1 addition & 1 deletion app/types/context/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface ContextMonitorProps {
body: Record<string, any>;
valid_status_codes: string[];
email: string;
type: 'http' | 'json' | 'ping' | 'tcp';
type: 'docker' | 'http' | 'json' | 'ping' | 'push' | 'tcp';
notificationId: string;
notificationType: string;
uptimePercentage: number;
Expand Down
1 change: 1 addition & 0 deletions app/types/monitor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface MonitorProps {
createdAt: string;
cert: CertificateProps;
heartbeats: HeartbeatProps[];
statusChanged: HeartbeatProps[];
icon: { id: string; name: string; url: string };
[key: string]: any;
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lunalytics",
"version": "0.10.20",
"version": "0.10.21",
"description": "Open source Node.js server/website monitoring tool",
"private": true,
"author": "KSJaay <ksjaay@gmail.com>",
Expand Down
46 changes: 46 additions & 0 deletions scripts/migrations/0-10-21.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// import local files
import SQLite from '../../server/database/sqlite/setup.js';
import logger from '../../server/utils/logger.js';

const infomation = {
title: 'Dropdown redundant indexes',
description:
'Drops various redundant indexes from the database and adds improved ones.',
version: '0.10.21',
};

const migrate = async () => {
const client = await SQLite.connect();

await client.raw(`DROP INDEX IF EXISTS heartbeat_monitorid_index;`);
await client.raw(`DROP INDEX IF EXISTS hourly_heartbeat_monitorid_index;`);

client.raw(
`CREATE INDEX IF NOT EXISTS heartbeat_monitorid_date_index ON heartbeat (monitorId, date DESC);`
);

client.raw(
`CREATE INDEX IF NOT EXISTS hourly_heartbeat_monitorid_date_index ON hourly_heartbeat (monitorId, date DESC);`
);

const redundantIndexes = [
'api_token_token_index',
'heartbeat_monitorid_index',
'hourly_heartbeat_monitorid_index',
'incident_incidentid_index',
'invite_token_index',
'status_page_statusid_index',
'status_page_statusurl_index',
'user_session_sessionid_index',
'user_email_index',
];

for (const indexName of redundantIndexes) {
await client.raw(`DROP INDEX IF EXISTS ${indexName};`);
}

logger.info('Migrations', { message: '0.10.21 has been applied' });
return;
};

export { infomation, migrate };
2 changes: 2 additions & 0 deletions scripts/migrations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { migrate as migrateMonitorJson } from './0-9-7.js';
import { migrate as migrateUiOverhaul } from './0-10-0.js';
import { migrate as migrateMonitorParent } from './0-10-13.js';
import { migrate as migrateUserSettings } from './0-10-16.js';
import { migrate as migrateIndexes } from './0-10-21.js';

const migrationList = {
'0.4.0': migrateTcpUpdate,
Expand All @@ -29,6 +30,7 @@ const migrationList = {
'0.10.0': migrateUiOverhaul,
'0.10.13': migrateMonitorParent,
'0.10.16': migrateUserSettings,
'0.10.21': migrateIndexes,
};

export default migrationList;
1 change: 1 addition & 0 deletions server/class/monitor/docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const clean = ({ heartbeats = [], ...monitor }, includeHeartbeats = true) => ({
ignoreTls: monitor.ignoreTls == '1',
createdAt: monitor.createdAt,
heartbeats: includeHeartbeats ? heartbeats : undefined,
statusChanged: includeHeartbeats ? monitor.statusChanged : undefined,
icon: parseJsonOrArray(monitor.icon),
});

Expand Down
1 change: 1 addition & 0 deletions server/class/monitor/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const clean = (
createdAt: monitor.createdAt,
cert: includeCert ? cleanCertificate(cert) : undefined,
heartbeats: includeHeartbeats ? heartbeats : undefined,
statusChanged: includeHeartbeats ? monitor.statusChanged : undefined,
icon: parseJsonOrArray(monitor.icon),
});

Expand Down
1 change: 1 addition & 0 deletions server/class/monitor/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const clean = (
createdAt: monitor.createdAt,
cert: includeCert ? cleanCertificate(cert) : undefined,
heartbeats: includeHeartbeats ? heartbeats : undefined,
statusChanged: includeHeartbeats ? monitor.statusChanged : undefined,
icon: parseJsonOrArray(monitor.icon),
});

Expand Down
1 change: 1 addition & 0 deletions server/class/monitor/ping.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const clean = (
paused: monitor.paused == '1',
createdAt: monitor.createdAt,
heartbeats: includeHeartbeats ? heartbeats : undefined,
statusChanged: includeHeartbeats ? monitor.statusChanged : undefined,
icon: parseJsonOrArray(monitor.icon),
});

Expand Down
1 change: 1 addition & 0 deletions server/class/monitor/tcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const clean = ({ heartbeats = [], ...monitor }, includeHeartbeats = true) => ({
paused: monitor.paused == '1',
createdAt: monitor.createdAt,
heartbeats: includeHeartbeats ? heartbeats : undefined,
statusChanged: includeHeartbeats ? monitor.statusChanged : undefined,
icon: parseJsonOrArray(monitor.icon),
});

Expand Down
21 changes: 21 additions & 0 deletions server/database/queries/heartbeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ export const fetchHeartbeats = async (monitorId, limit = 168) => {
return heartbeats;
};

export const fetchStatusChangeHeartbeats = async (monitorId, limit = 20) => {
const rawQuery = `
SELECT id, status, latency, date, isDown, message
FROM (
SELECT *, LAG(isDown) OVER (PARTITION BY monitorId ORDER BY date DESC) AS prevIsDown
FROM (
SELECT * FROM heartbeat
WHERE monitorId = ?
ORDER BY date DESC
LIMIT 1000
)
)
WHERE isDown != prevIsDown OR prevIsDown IS NULL
ORDER BY date DESC
LIMIT ?;
`;
const statusChanges = await SQLite.client.raw(rawQuery, [monitorId, limit]);

return statusChanges;
};

export const isMonitorDown = async (monitorId, limit = 1) => {
const newLimit = limit || 1;

Expand Down
3 changes: 3 additions & 0 deletions server/database/sqlite/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export class SQLite {
// Need to enable this for foreign key to work
await this.client.raw('PRAGMA foreign_keys = ON');

await this.client.raw('PRAGMA journal_mode = WAL'); // Better concurrency
await this.client.raw('PRAGMA cache_size = -12000'); // 12 MB cache

logger.info('SQLite', {
message: 'Connected to SQLite database',
});
Expand Down
1 change: 0 additions & 1 deletion server/database/sqlite/tables/api_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export const apiTokenTable = async (client) => {
table.string('email').notNullable();
table.datetime('createdAt');

table.index('token');
table.index('email');
});
}
Expand Down
7 changes: 4 additions & 3 deletions server/database/sqlite/tables/heartbeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ export const heartbeatTable = async (client) => {
table.datetime('date').notNullable();
table.boolean('isDown').defaultTo(false);
table.text('message').notNullable();

table.index('monitorId');
table.index(['monitorId', 'date']);
});

client.raw(
`CREATE INDEX IF NOT EXISTS heartbeat_monitorid_date_index ON heartbeat (monitorId, date DESC);`
);
}
};
7 changes: 4 additions & 3 deletions server/database/sqlite/tables/hourly_heartbeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ export const hourlyHeartbeatTable = async (client) => {
table.integer('status').notNullable();
table.integer('latency').notNullable();
table.datetime('date').notNullable();

table.index('monitorId');
table.index(['monitorId', 'date']);
});

client.raw(
`CREATE INDEX IF NOT EXISTS hourly_heartbeat_monitorid_date_index ON hourly_heartbeat (monitorId, date DESC);`
);
}
};
1 change: 0 additions & 1 deletion server/database/sqlite/tables/incident.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export const incidentTable = async (client) => {
table.datetime('completedAt');
table.boolean('isClosed').defaultTo(false);

table.index('incidentId');
table.index('createdAt');
table.index('completedAt');
table.index('isClosed');
Expand Down
1 change: 0 additions & 1 deletion server/database/sqlite/tables/invite.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export const inviteTable = async (client) => {
table.integer('uses').defaultTo(0);

table.index('email');
table.index('token');
});
}
};
4 changes: 1 addition & 3 deletions server/database/sqlite/tables/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const monitorTable = async (client) => {
if (!monitorExists) {
await client.schema.createTable('monitor', (table) => {
table.increments('id');
table.string('monitorId').notNullable().primary();
table.string('monitorId').notNullable().primary().unique();
table.string('parentId').defaultTo(null);
table.string('name').notNullable();
table.string('url').notNullable();
Expand All @@ -30,8 +30,6 @@ export const monitorTable = async (client) => {
name: 'Lunalytics',
url: 'https://cdn.jsdelivr.net/gh/selfhst/icons/svg/lunalytics.svg',
});

table.index('monitorId');
});
}
};
4 changes: 1 addition & 3 deletions server/database/sqlite/tables/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const notificationsTable = async (client) => {

if (!notificationExists) {
await client.schema.createTable('notifications', (table) => {
table.string('id').notNullable().primary();
table.string('id').notNullable().primary().unique();
table.string('platform').notNullable();
table.string('messageType').notNullable();
table.text('token').notNullable();
Expand All @@ -13,8 +13,6 @@ export const notificationsTable = async (client) => {
table.string('friendlyName');
table.text('data');
table.datetime('createdAt');

table.index('id');
});
}
};
2 changes: 1 addition & 1 deletion server/database/sqlite/tables/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const providersTable = async (client) => {
table.boolean('enabled').defaultTo(true);
table.json('data').defaultTo({});

table.index(['provider']);
table.index('provider');
});
}
};
3 changes: 0 additions & 3 deletions server/database/sqlite/tables/status_page.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ export const statusPageTable = async (client) => {
table.json('layout').notNullable();
table.string('email').notNullable();
table.datetime('createdAt');

table.index('statusId');
table.index('statusUrl');
});
}
};
1 change: 0 additions & 1 deletion server/database/sqlite/tables/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export const userTable = async (client) => {
table.datetime('createdAt');
table.jsonb('settings').defaultTo(JSON.stringify({}));

table.index('email');
table.index('isVerified');
});
}
Expand Down
1 change: 0 additions & 1 deletion server/database/sqlite/tables/user_session.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export const userSessionTable = async (client) => {
table.jsonb('data');
table.datetime('createdAt');

table.index('sessionId');
table.index('email');
});
}
Expand Down
Loading