Skip to content

Commit a9eeb1b

Browse files
author
scinorandex
committed
Initial support for dashboard
1 parent 61d482f commit a9eeb1b

File tree

12 files changed

+1053
-29
lines changed

12 files changed

+1053
-29
lines changed

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"license": "GPL-3.0",
3131
"devDependencies": {
3232
"@types/asciichart": "^1.5.4",
33+
"@types/blessed": "^0.1.17",
3334
"@types/express": "^4.17.11",
3435
"@types/minimist": "^1.2.1",
3536
"@types/morgan": "^1.9.2",
@@ -43,10 +44,13 @@
4344
"dependencies": {
4445
"asciichart": "^1.5.25",
4546
"axios": "^0.21.1",
47+
"blessed": "^0.1.81",
48+
"blessed-contrib": "^4.8.21",
4649
"colors": "^1.4.0",
4750
"express": "^4.17.1",
4851
"minimist": "^1.2.5",
4952
"morgan": "^1.10.0",
50-
"typescript": "^4.2.3"
53+
"typescript": "^4.2.3",
54+
"world-countries": "^4.0.0"
5155
}
5256
}

src/api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import express from "express";
22
import morgan from "morgan";
3+
import { dashboardRouter } from "./api/dashboardRouter";
34
import { errorHandler } from "./api/errorHandler";
45
import { plainRouter } from "./api/plainRouter";
56
import { regularRouter } from "./api/regularRouter";
@@ -10,8 +11,12 @@ const port = parseInt(process.env.PORT!) || 7070;
1011

1112
const app = express();
1213
app.use(morgan("common"));
14+
app.use("/history/web/charts", dashboardRouter);
15+
1316
app.use(userAgentMiddleware);
1417

18+
app.use("/history/charts", dashboardRouter);
19+
1520
/**
1621
* Plain CMD/Basic routes have both quiet and full modes
1722
* Same with regular / routes with ansi color codes

src/api/dashboardRouter.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Request, Router } from "express";
2+
import {
3+
globalDashboard,
4+
DashboardSize,
5+
countryDashboard,
6+
} from "../utils/routes/dashboard/dashboardHandlers";
7+
import handleAsync from "./handleAsync";
8+
import { isTerminal } from "./userAgent";
9+
10+
export const dashboardRouter = Router({ mergeParams: true });
11+
12+
/**
13+
*
14+
* @param req Express request
15+
* @returns True if the request is from a not from wget, curl or httpie
16+
*/
17+
const isWeb: (req: Request) => boolean = (req) => {
18+
// Check if the link is asking for web version of dashboard
19+
const link = req.baseUrl.startsWith("/history/web/charts");
20+
// Check if the user agent is NOT coming from terminal based application
21+
const isNotTerminal = !isTerminal(req.headers["user-agent"]);
22+
return link && isNotTerminal;
23+
};
24+
25+
dashboardRouter.get(
26+
"/:size?",
27+
handleAsync(async (req, res, next) => {
28+
// Get parameters from request
29+
let size = req.params.size as DashboardSize;
30+
31+
// Set default size and check then check if size var matches
32+
if (size === undefined) size = "sm";
33+
if (!["sm", "md", "lg"].includes(size)) return next();
34+
35+
let response = await globalDashboard(size, isWeb(req));
36+
res.send(response);
37+
})
38+
);
39+
40+
dashboardRouter.get(
41+
"/:country/:size?",
42+
handleAsync(async (req, res, next) => {
43+
// Get parameters from request
44+
let country = req.params.country;
45+
let size = req.params.size as DashboardSize;
46+
47+
// Set default size and check then check if size var matches
48+
if (size === undefined) size = "sm";
49+
if (!["sm", "md", "lg"].includes(size)) return next();
50+
51+
let response = await countryDashboard(country, size, isWeb(req));
52+
res.send(response);
53+
})
54+
);

src/api/userAgent.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ export type Handler = (
1313
* @param userAgent The user agent of the requester
1414
* @returns A boolean that is true of the user agent provided is from curl / wget / httpie
1515
*/
16-
const isTerminal: (userAgent: string | undefined) => boolean = (userAgent) => {
16+
export const isTerminal: (userAgent: string | undefined) => boolean = (
17+
userAgent
18+
) => {
1719
if (userAgent === undefined) return false;
1820
if (/curl|wget|httpie/i.test(userAgent)) return true;
1921
return false;

src/utils/getInformation.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ axios.defaults.baseURL = "https://disease.sh/v3/covid-19";
1111
export const getAllInfo: () => Promise<{
1212
updated: number;
1313
data: {
14+
active: number;
1415
cases: number;
1516
deaths: number;
1617
recovered: number;
@@ -19,13 +20,14 @@ export const getAllInfo: () => Promise<{
1920
};
2021
}> = async () => {
2122
let { data: globalData } = await axios.get("/all");
22-
let { cases, deaths, recovered, updated } = globalData;
23+
let { cases, deaths, recovered, updated, active } = globalData;
2324
let deathRate = (deaths / cases) * 100;
2425
let recoveryRate = (recovered / cases) * 100;
2526

2627
return {
2728
updated,
2829
data: {
30+
active,
2931
cases,
3032
deaths,
3133
recovered,
@@ -131,9 +133,10 @@ export async function getHistorical(
131133
const dates = Object.keys(mode === "all" ? chartData["cases"] : chartData);
132134

133135
// Label for chart
134-
const date = `${
135-
mode.charAt(0).toUpperCase() + mode.slice(1)
136-
} from ${dates.shift()} to ${dates.pop()}`;
136+
const informationType =
137+
mode === "all" ? "Data" : mode.charAt(0).toUpperCase() + mode.slice(1);
138+
139+
const date = `${informationType} from ${dates.shift()} to ${dates.pop()}`;
137140

138141
if (mode === "all") {
139142
return {

src/utils/libs/columnizeData.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
*
3+
* @param data An object containing your keys and values
4+
* @returns A 2 row column containing your keys and values
5+
*/
6+
export const columnizeData: (
7+
data: { [key: string]: string },
8+
padding?: number
9+
) => string = (data, padding) => {
10+
// Generate table
11+
let table = "";
12+
13+
// Create columns
14+
let normalizedArray: string[] = [];
15+
Object.keys(data).forEach((key) => {
16+
let value = data[key];
17+
let line = `${key.padEnd(15, " ")}| ${value.padEnd(13, " ")}`; // create a line with length 30;
18+
normalizedArray.push(line);
19+
});
20+
21+
while (normalizedArray.length > 0) {
22+
let left = normalizedArray.shift();
23+
let right = normalizedArray.shift();
24+
25+
//right may be undefined, so default to empty string
26+
if (right === undefined) right = "";
27+
28+
table += `${left}${right}`;
29+
if (normalizedArray.length !== 0) table += `\n`; // do not add whitespace at the end of the table
30+
}
31+
32+
if (padding !== undefined) {
33+
table = table
34+
.split("\n")
35+
.map((line) => " ".repeat(padding) + line)
36+
.join("\n");
37+
}
38+
39+
return table;
40+
};
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { DashboardSize } from "./dashboardHandlers";
2+
3+
interface BlessedSizeConfiguration {
4+
// MOCKSTDOUT
5+
// Touching the arguments here will mess up with the screenshot function
6+
// Generally, making the numbers bigger will affect the response string because the output will take up more space
7+
// However, making the numbers smaller is just going to add invisible padding on the bottom and the right
8+
mockStdout: number[];
9+
screenshot: number[];
10+
11+
// position: number[]
12+
// grid.set(a, b, c, d);
13+
// a = starting position across the y axis
14+
// b = starting position across the x axis
15+
// c = span across the y axis
16+
// d = span across the x axis
17+
header: {
18+
position: number[];
19+
};
20+
map: {
21+
position: number[];
22+
};
23+
table: {
24+
position: number[];
25+
tableXOffset: number;
26+
};
27+
bar: {
28+
position: number[];
29+
barXOffset: number;
30+
};
31+
donut: {
32+
position: number[];
33+
};
34+
line: {
35+
position: number[];
36+
};
37+
}
38+
39+
// To the future person who has to touch this, may god have mercy on your soul
40+
export const blessedConfig: {
41+
[key in DashboardSize]: BlessedSizeConfiguration;
42+
} = {
43+
sm: {
44+
mockStdout: [180, 40],
45+
screenshot: [90, 36],
46+
header: {
47+
position: [0, 0, 4, 4],
48+
},
49+
map: {
50+
position: [0, 4, 4, 5],
51+
},
52+
table: {
53+
position: [4, 0, 4, 9],
54+
tableXOffset: 15,
55+
},
56+
bar: {
57+
position: [8, 0, 5, 5],
58+
barXOffset: 3,
59+
},
60+
donut: {
61+
position: [8, 5, 5, 4],
62+
},
63+
line: {
64+
position: [13, 0, 5, 9],
65+
},
66+
},
67+
md: {
68+
mockStdout: [340, 50],
69+
screenshot: [180, 34],
70+
header: {
71+
position: [0, 0, 4, 3],
72+
},
73+
map: {
74+
position: [4, 0, 5, 3],
75+
},
76+
table: {
77+
position: [0, 3, 4, 6],
78+
tableXOffset: 28,
79+
},
80+
bar: {
81+
position: [4, 3, 5, 3],
82+
barXOffset: 6,
83+
},
84+
donut: {
85+
position: [4, 6, 5, 3],
86+
},
87+
line: {
88+
position: [9, 0, 5, 9],
89+
},
90+
},
91+
lg: {
92+
mockStdout: [360, 50],
93+
screenshot: [180, 40],
94+
header: {
95+
position: [0, 0, 2, 9],
96+
},
97+
map: {
98+
position: [6, 0, 5, 3],
99+
},
100+
table: {
101+
position: [2, 0, 4, 9],
102+
tableXOffset: 60,
103+
},
104+
bar: {
105+
position: [6, 3, 5, 3],
106+
barXOffset: 8,
107+
},
108+
donut: {
109+
position: [6, 6, 5, 3],
110+
},
111+
line: {
112+
position: [11, 0, 5, 9],
113+
},
114+
},
115+
};

0 commit comments

Comments
 (0)