mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-30 18:24:03 +00:00
Merge branch 'master' into clean-mobile-table
This commit is contained in:
commit
3f12afce28
11 changed files with 412 additions and 10479 deletions
|
@ -2,10 +2,10 @@
|
||||||
FROM node:14-alpine3.12 AS release
|
FROM node:14-alpine3.12 AS release
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# split the sqlite install here, so that it can caches the arm prebuilt
|
# split the sqlite install here, so that it can caches the prebuilt
|
||||||
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \
|
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \
|
||||||
ln -s /usr/bin/python3 /usr/bin/python && \
|
ln -s /usr/bin/python3 /usr/bin/python && \
|
||||||
npm install @louislam/sqlite3@5.0.3 bcrypt@5.0.1 && \
|
npm install better-sqlite3@7.4.3 bcrypt@5.0.1 && \
|
||||||
apk del .build-deps && \
|
apk del .build-deps && \
|
||||||
rm -f /usr/bin/python
|
rm -f /usr/bin/python
|
||||||
|
|
||||||
|
|
10724
package-lock.json
generated
10724
package-lock.json
generated
File diff suppressed because it is too large
Load diff
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.3.1",
|
"version": "1.3.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -17,10 +17,10 @@
|
||||||
"update": "",
|
"update": "",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"vite-preview-dist": "vite preview --host",
|
"vite-preview-dist": "vite preview --host",
|
||||||
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.3.1 --target release . --push",
|
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.3.2 --target release . --push",
|
||||||
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||||
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push",
|
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||||
"setup": "git checkout 1.3.1 && npm install && npm run build",
|
"setup": "git checkout 1.3.2 && npm install && npm run build",
|
||||||
"update-version": "node extra/update-version.js",
|
"update-version": "node extra/update-version.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
|
@ -36,11 +36,11 @@
|
||||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.0-4",
|
"@fortawesome/vue-fontawesome": "^3.0.0-4",
|
||||||
"@louislam/sqlite3": "^5.0.3",
|
|
||||||
"@popperjs/core": "^2.9.3",
|
"@popperjs/core": "^2.9.3",
|
||||||
"args-parser": "^1.3.0",
|
"args-parser": "^1.3.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
|
"better-sqlite3": "^7.4.3",
|
||||||
"bootstrap": "^5.1.0",
|
"bootstrap": "^5.1.0",
|
||||||
"chart.js": "^3.5.0",
|
"chart.js": "^3.5.0",
|
||||||
"chartjs-adapter-dayjs": "^1.0.0",
|
"chartjs-adapter-dayjs": "^1.0.0",
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
"password-hash": "^1.2.2",
|
"password-hash": "^1.2.2",
|
||||||
"prom-client": "^13.2.0",
|
"prom-client": "^13.2.0",
|
||||||
"prometheus-api-metrics": "^3.2.0",
|
"prometheus-api-metrics": "^3.2.0",
|
||||||
"redbean-node": "0.0.21",
|
"redbean-node": "0.1.1",
|
||||||
"socket.io": "^4.1.3",
|
"socket.io": "^4.1.3",
|
||||||
"socket.io-client": "^4.1.3",
|
"socket.io-client": "^4.1.3",
|
||||||
"tcp-ping": "^0.1.1",
|
"tcp-ping": "^0.1.1",
|
||||||
|
|
|
@ -28,7 +28,7 @@ exports.startInterval = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
check();
|
check();
|
||||||
interval = setInterval(check, 3600 * 48);
|
interval = setInterval(check, 3600 * 1000 * 48);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.enableCheckUpdate = async (value) => {
|
exports.enableCheckUpdate = async (value) => {
|
||||||
|
|
|
@ -3,7 +3,6 @@ const { sleep, debug, isDev } = require("../src/util");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setSetting, setting } = require("./util-server");
|
const { setSetting, setting } = require("./util-server");
|
||||||
const knex = require("knex");
|
const knex = require("knex");
|
||||||
const sqlite3 = require("@louislam/sqlite3");
|
|
||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
|
|
||||||
|
@ -14,48 +13,23 @@ class Database {
|
||||||
static sqliteInstance = null;
|
static sqliteInstance = null;
|
||||||
|
|
||||||
static async connect() {
|
static async connect() {
|
||||||
|
const acquireConnectionTimeout = 120 * 1000;
|
||||||
|
|
||||||
if (! this.sqliteInstance) {
|
R.useBetterSQLite3 = true;
|
||||||
this.sqliteInstance = new sqlite3.Database(Database.path);
|
R.betterSQLite3Options.timeout = acquireConnectionTimeout;
|
||||||
this.sqliteInstance.run("PRAGMA journal_mode = WAL");
|
|
||||||
}
|
|
||||||
|
|
||||||
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
R.setup("sqlite", {
|
||||||
Dialect.prototype._driver = () => sqlite3;
|
filename: Database.path,
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
Dialect.prototype.acquireConnectionOrg = Dialect.prototype.acquireConnection;
|
|
||||||
|
|
||||||
Dialect.prototype.acquireConnection = async function () {
|
|
||||||
let a = await this.acquireConnectionOrg();
|
|
||||||
debug("acquired Connection");
|
|
||||||
return a;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always return same connection.
|
|
||||||
Dialect.prototype.acquireRawConnection = async function () {
|
|
||||||
return Database.sqliteInstance;
|
|
||||||
};
|
|
||||||
|
|
||||||
Dialect.prototype.destroyRawConnection = async () => { }
|
|
||||||
|
|
||||||
const knexInstance = knex({
|
|
||||||
client: Dialect,
|
|
||||||
connection: { }, // Do not remove, Leave it empty is ok
|
|
||||||
useNullAsDefault: true,
|
useNullAsDefault: true,
|
||||||
pool: {
|
acquireConnectionTimeout: acquireConnectionTimeout,
|
||||||
|
}, {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 5,
|
max: 1,
|
||||||
idleTimeoutMillis: 30000,
|
idleTimeoutMillis: 120 * 1000,
|
||||||
}
|
propagateCreateError: false,
|
||||||
|
acquireTimeoutMillis: acquireConnectionTimeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log( knexInstance.pool)
|
|
||||||
console.log("pool size")
|
|
||||||
|
|
||||||
R.setup(knexInstance);
|
|
||||||
|
|
||||||
if (process.env.SQL_LOG === "1") {
|
if (process.env.SQL_LOG === "1") {
|
||||||
R.debug(true);
|
R.debug(true);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +37,10 @@ class Database {
|
||||||
// Auto map the model to a bean object
|
// Auto map the model to a bean object
|
||||||
R.freeze(true)
|
R.freeze(true)
|
||||||
await R.autoloadModels("./server/model");
|
await R.autoloadModels("./server/model");
|
||||||
|
|
||||||
|
// Change to WAL
|
||||||
|
await R.exec("PRAGMA journal_mode = WAL");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async patch() {
|
static async patch() {
|
||||||
|
|
|
@ -110,10 +110,9 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.type === "http" || this.type === "keyword") {
|
if (this.type === "http" || this.type === "keyword") {
|
||||||
|
// Do not do any queries/high loading things before the "bean.ping"
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
// Use Custom agent to disable session reuse
|
|
||||||
// https://github.com/nodejs/node/issues/3940
|
|
||||||
let res = await axios.get(this.url, {
|
let res = await axios.get(this.url, {
|
||||||
timeout: this.interval * 1000 * 0.8,
|
timeout: this.interval * 1000 * 0.8,
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -121,7 +120,7 @@ class Monitor extends BeanModel {
|
||||||
"User-Agent": "Uptime-Kuma/" + version,
|
"User-Agent": "Uptime-Kuma/" + version,
|
||||||
},
|
},
|
||||||
httpsAgent: new https.Agent({
|
httpsAgent: new https.Agent({
|
||||||
maxCachedSessions: 0,
|
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||||
rejectUnauthorized: ! this.getIgnoreTls(),
|
rejectUnauthorized: ! this.getIgnoreTls(),
|
||||||
}),
|
}),
|
||||||
maxRedirects: this.maxredirects,
|
maxRedirects: this.maxredirects,
|
||||||
|
@ -241,7 +240,8 @@ class Monitor extends BeanModel {
|
||||||
try {
|
try {
|
||||||
await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON())
|
await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Cannot send notification to " + notification.name)
|
console.error("Cannot send notification to " + notification.name);
|
||||||
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,22 +258,22 @@ class Monitor extends BeanModel {
|
||||||
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
prometheus.update(bean, tlsInfo)
|
|
||||||
|
|
||||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||||
|
|
||||||
await R.store(bean)
|
|
||||||
Monitor.sendStats(io, this.id, this.user_id)
|
Monitor.sendStats(io, this.id, this.user_id)
|
||||||
|
|
||||||
|
await R.store(bean);
|
||||||
|
prometheus.update(bean, tlsInfo);
|
||||||
|
|
||||||
previousBeat = bean;
|
previousBeat = bean;
|
||||||
|
|
||||||
|
this.heartbeatInterval = setTimeout(beat, this.interval * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
beat();
|
beat();
|
||||||
this.heartbeatInterval = setInterval(beat, this.interval * 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
clearInterval(this.heartbeatInterval)
|
clearTimeout(this.heartbeatInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -150,7 +150,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||||
if (user) {
|
if (user) {
|
||||||
debug("afterLogin")
|
debug("afterLogin")
|
||||||
|
|
||||||
await afterLogin(socket, user)
|
afterLogin(socket, user)
|
||||||
|
|
||||||
debug("afterLogin ok")
|
debug("afterLogin ok")
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||||
let user = await login(data.username, data.password)
|
let user = await login(data.username, data.password)
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
await afterLogin(socket, user)
|
afterLogin(socket, user)
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -561,7 +561,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||||
debug("check auto login")
|
debug("check auto login")
|
||||||
if (await setting("disableAuth")) {
|
if (await setting("disableAuth")) {
|
||||||
console.log("Disabled Auth: auto login to admin")
|
console.log("Disabled Auth: auto login to admin")
|
||||||
await afterLogin(socket, await R.findOne("user"))
|
afterLogin(socket, await R.findOne("user"))
|
||||||
socket.emit("autoLogin")
|
socket.emit("autoLogin")
|
||||||
} else {
|
} else {
|
||||||
debug("need auth")
|
debug("need auth")
|
||||||
|
@ -621,6 +621,8 @@ async function sendMonitorList(socket) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendNotificationList(socket) {
|
async function sendNotificationList(socket) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
let result = [];
|
let result = [];
|
||||||
let list = await R.find("notification", " user_id = ? ", [
|
let list = await R.find("notification", " user_id = ? ", [
|
||||||
socket.userID,
|
socket.userID,
|
||||||
|
@ -631,6 +633,9 @@ async function sendNotificationList(socket) {
|
||||||
}
|
}
|
||||||
|
|
||||||
io.to(socket.userID).emit("notificationList", result)
|
io.to(socket.userID).emit("notificationList", result)
|
||||||
|
|
||||||
|
timeLogger.print("Send Notification List");
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,24 +644,27 @@ async function afterLogin(socket, user) {
|
||||||
socket.join(user.id)
|
socket.join(user.id)
|
||||||
|
|
||||||
let monitorList = await sendMonitorList(socket)
|
let monitorList = await sendMonitorList(socket)
|
||||||
|
|
||||||
sendNotificationList(socket)
|
sendNotificationList(socket)
|
||||||
|
|
||||||
// Delay a bit, so that it let the main page to query the data first, since SQLite can process one sql at the same time only.
|
await sleep(500);
|
||||||
// For example, query the edit data first.
|
|
||||||
setTimeout(async () => {
|
|
||||||
for (let monitorID in monitorList) {
|
for (let monitorID in monitorList) {
|
||||||
sendHeartbeatList(socket, monitorID);
|
await sendHeartbeatList(socket, monitorID);
|
||||||
sendImportantHeartbeatList(socket, monitorID);
|
}
|
||||||
Monitor.sendStats(io, monitorID, user.id)
|
|
||||||
|
for (let monitorID in monitorList) {
|
||||||
|
await sendImportantHeartbeatList(socket, monitorID);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let monitorID in monitorList) {
|
||||||
|
await Monitor.sendStats(io, monitorID, user.id)
|
||||||
}
|
}
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMonitorJSONList(userID) {
|
async function getMonitorJSONList(userID) {
|
||||||
let result = {};
|
let result = {};
|
||||||
|
|
||||||
let monitorList = await R.find("monitor", " user_id = ? ", [
|
let monitorList = await R.find("monitor", " user_id = ? ORDER BY weight DESC, name", [
|
||||||
userID,
|
userID,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -786,6 +794,8 @@ async function sendHeartbeatList(socket, monitorID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit("heartbeatList", monitorID, result)
|
socket.emit("heartbeatList", monitorID, result)
|
||||||
|
|
||||||
|
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendImportantHeartbeatList(socket, monitorID) {
|
async function sendImportantHeartbeatList(socket, monitorID) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div>
|
<div v-if="monitor">
|
||||||
<h1> {{ monitor.name }}</h1>
|
<h1> {{ monitor.name }}</h1>
|
||||||
<p class="url">
|
<p class="url">
|
||||||
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank">{{ monitor.url }}</a>
|
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank">{{ monitor.url }}</a>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
|
<transition name="slide-fade" appear>
|
||||||
<MonitorList />
|
<MonitorList />
|
||||||
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
24
src/util.ts
24
src/util.ts
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck
|
|
||||||
// Common Util for frontend and backend
|
// Common Util for frontend and backend
|
||||||
// Backend uses the compiled file util.js
|
// Backend uses the compiled file util.js
|
||||||
// Frontend uses util.ts
|
// Frontend uses util.ts
|
||||||
|
@ -13,7 +12,7 @@ export const DOWN = 0;
|
||||||
export const UP = 1;
|
export const UP = 1;
|
||||||
export const PENDING = 2;
|
export const PENDING = 2;
|
||||||
|
|
||||||
export function flipStatus(s) {
|
export function flipStatus(s: number) {
|
||||||
if (s === UP) {
|
if (s === UP) {
|
||||||
return DOWN;
|
return DOWN;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +24,7 @@ export function flipStatus(s) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sleep(ms) {
|
export function sleep(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,8 +32,8 @@ export function sleep(ms) {
|
||||||
* PHP's ucfirst
|
* PHP's ucfirst
|
||||||
* @param str
|
* @param str
|
||||||
*/
|
*/
|
||||||
export function ucfirst(str) {
|
export function ucfirst(str: string) {
|
||||||
if (! str) {
|
if (!str) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,12 +41,15 @@ export function ucfirst(str) {
|
||||||
return firstLetter.toUpperCase() + str.substr(1);
|
return firstLetter.toUpperCase() + str.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function debug(msg) {
|
export function debug(msg: any) {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
declare global { interface String { replaceAll(str: string, newStr: string): string; } }
|
||||||
|
|
||||||
export function polyfill() {
|
export function polyfill() {
|
||||||
/**
|
/**
|
||||||
* String.prototype.replaceAll() polyfill
|
* String.prototype.replaceAll() polyfill
|
||||||
|
@ -56,7 +58,7 @@ export function polyfill() {
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
if (!String.prototype.replaceAll) {
|
if (!String.prototype.replaceAll) {
|
||||||
String.prototype.replaceAll = function (str, newStr) {
|
String.prototype.replaceAll = function (str: string, newStr: string) {
|
||||||
|
|
||||||
// If a regex pattern
|
// If a regex pattern
|
||||||
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
|
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
|
||||||
|
@ -71,11 +73,13 @@ export function polyfill() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimeLogger {
|
export class TimeLogger {
|
||||||
|
startTime: number;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.startTime = dayjs().valueOf();
|
this.startTime = dayjs().valueOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
print(name) {
|
print(name: string) {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms")
|
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms")
|
||||||
}
|
}
|
||||||
|
@ -85,7 +89,7 @@ export class TimeLogger {
|
||||||
/**
|
/**
|
||||||
* Returns a random number between min (inclusive) and max (exclusive)
|
* Returns a random number between min (inclusive) and max (exclusive)
|
||||||
*/
|
*/
|
||||||
export function getRandomArbitrary(min, max) {
|
export function getRandomArbitrary(min: number, max: number) {
|
||||||
return Math.random() * (max - min) + min;
|
return Math.random() * (max - min) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +102,7 @@ export function getRandomArbitrary(min, max) {
|
||||||
* lower than max if max isn't an integer).
|
* lower than max if max isn't an integer).
|
||||||
* Using Math.round() will give you a non-uniform distribution!
|
* Using Math.round() will give you a non-uniform distribution!
|
||||||
*/
|
*/
|
||||||
export function getRandomInt(min, max) {
|
export function getRandomInt(min: number, max: number) {
|
||||||
min = Math.ceil(min);
|
min = Math.ceil(min);
|
||||||
max = Math.floor(max);
|
max = Math.floor(max);
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
"target": "es2018",
|
"target": "es2018",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2020"
|
"es2020",
|
||||||
|
"DOM",
|
||||||
],
|
],
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"files.insertFinalNewline": true
|
"strict": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"./server/util.ts"
|
"./src/util.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue