committed by
							
								
								GitHub
							
						
					
				
				 31 changed files with 599 additions and 179 deletions
			
			
		@ -0,0 +1,26 @@ | 
				
			|||
# DON'T UPDATE TO alpine3.13, 1.14, see #41. | 
				
			|||
FROM node:14-alpine3.12 AS release | 
				
			|||
WORKDIR /app | 
				
			|||
 | 
				
			|||
# split the sqlite install here, so that it can caches the arm prebuilt | 
				
			|||
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \ | 
				
			|||
            ln -s /usr/bin/python3 /usr/bin/python && \ | 
				
			|||
            npm install mapbox/node-sqlite3#593c9d && \ | 
				
			|||
            apk del .build-deps && \ | 
				
			|||
            rm -f /usr/bin/python | 
				
			|||
 | 
				
			|||
# Install apprise | 
				
			|||
RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib | 
				
			|||
RUN pip3 --no-cache-dir install apprise && \ | 
				
			|||
            rm -rf /root/.cache | 
				
			|||
 | 
				
			|||
COPY . . | 
				
			|||
RUN npm install --legacy-peer-deps && npm run build && npm prune | 
				
			|||
 | 
				
			|||
EXPOSE 3001 | 
				
			|||
VOLUME ["/app/data"] | 
				
			|||
HEALTHCHECK --interval=600s --timeout=130s --start-period=300s CMD node extra/healthcheck.js | 
				
			|||
CMD ["node", "server/server.js"] | 
				
			|||
 | 
				
			|||
FROM release AS nightly | 
				
			|||
RUN npm run mark-as-nightly | 
				
			|||
@ -1,28 +0,0 @@ | 
				
			|||
# DON'T UPDATE TO alpine3.13, 1.14, see #41. | 
				
			|||
FROM node:14-bullseye AS release | 
				
			|||
WORKDIR /app | 
				
			|||
 | 
				
			|||
RUN apt update | 
				
			|||
RUN apt --yes install python3 python3-pip python3-dev git g++ make | 
				
			|||
RUN ln -s /usr/bin/python3 /usr/bin/python | 
				
			|||
 | 
				
			|||
# split the sqlite install here, so that it can caches the arm prebuilt | 
				
			|||
RUN npm install mapbox/node-sqlite3#593c9d | 
				
			|||
 | 
				
			|||
# Install apprise | 
				
			|||
RUN apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib | 
				
			|||
RUN pip3 --no-cache-dir install apprise && \ | 
				
			|||
            rm -rf /root/.cache | 
				
			|||
 | 
				
			|||
RUN apt --yes install iputils-ping | 
				
			|||
 | 
				
			|||
COPY . . | 
				
			|||
RUN npm install --legacy-peer-deps && npm run build && npm prune | 
				
			|||
 | 
				
			|||
EXPOSE 3001 | 
				
			|||
VOLUME ["/app/data"] | 
				
			|||
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js | 
				
			|||
CMD ["node", "server/server.js"] | 
				
			|||
 | 
				
			|||
FROM release AS nightly | 
				
			|||
RUN npm run mark-as-nightly | 
				
			|||
@ -1,19 +1,31 @@ | 
				
			|||
let http = require("http"); | 
				
			|||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; | 
				
			|||
 | 
				
			|||
let client; | 
				
			|||
 | 
				
			|||
if (process.env.SSL_KEY && process.env.SSL_CERT) { | 
				
			|||
    client = require("https"); | 
				
			|||
} else { | 
				
			|||
    client = require("http"); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
let options = { | 
				
			|||
    host: "localhost", | 
				
			|||
    port: "3001", | 
				
			|||
    timeout: 2000, | 
				
			|||
    host: process.env.HOST || "127.0.0.1", | 
				
			|||
    port: parseInt(process.env.PORT) || 3001, | 
				
			|||
    timeout: 120 * 1000, | 
				
			|||
}; | 
				
			|||
let request = http.request(options, (res) => { | 
				
			|||
    console.log(`STATUS: ${res.statusCode}`); | 
				
			|||
    if (res.statusCode == 200) { | 
				
			|||
 | 
				
			|||
let request = client.request(options, (res) => { | 
				
			|||
    console.log(`Health Check OK [Res Code: ${res.statusCode}]`); | 
				
			|||
    if (res.statusCode === 200) { | 
				
			|||
        process.exit(0); | 
				
			|||
    } else { | 
				
			|||
        process.exit(1); | 
				
			|||
    } | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
request.on("error", function (err) { | 
				
			|||
    console.log("ERROR"); | 
				
			|||
    console.error("Health Check ERROR"); | 
				
			|||
    process.exit(1); | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
request.end(); | 
				
			|||
 | 
				
			|||
@ -0,0 +1,91 @@ | 
				
			|||
/* | 
				
			|||
 * For Client Socket | 
				
			|||
 */ | 
				
			|||
const { TimeLogger } = require("../src/util"); | 
				
			|||
const { R } = require("redbean-node"); | 
				
			|||
const { io } = require("./server"); | 
				
			|||
 | 
				
			|||
async function sendNotificationList(socket) { | 
				
			|||
    const timeLogger = new TimeLogger(); | 
				
			|||
 | 
				
			|||
    let result = []; | 
				
			|||
    let list = await R.find("notification", " user_id = ? ", [ | 
				
			|||
        socket.userID, | 
				
			|||
    ]); | 
				
			|||
 | 
				
			|||
    for (let bean of list) { | 
				
			|||
        result.push(bean.export()) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    io.to(socket.userID).emit("notificationList", result) | 
				
			|||
 | 
				
			|||
    timeLogger.print("Send Notification List"); | 
				
			|||
 | 
				
			|||
    return list; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * Send Heartbeat History list to socket | 
				
			|||
 * @param toUser  True = send to all browsers with the same user id, False = send to the current browser only | 
				
			|||
 * @param overwrite Overwrite client-side's heartbeat list | 
				
			|||
 */ | 
				
			|||
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { | 
				
			|||
    const timeLogger = new TimeLogger(); | 
				
			|||
 | 
				
			|||
    let list = await R.find("heartbeat", ` | 
				
			|||
        monitor_id = ? | 
				
			|||
        ORDER BY time DESC | 
				
			|||
        LIMIT 100 | 
				
			|||
    `, [
 | 
				
			|||
        monitorID, | 
				
			|||
    ]) | 
				
			|||
 | 
				
			|||
    let result = []; | 
				
			|||
 | 
				
			|||
    for (let bean of list) { | 
				
			|||
        result.unshift(bean.toJSON()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if (toUser) { | 
				
			|||
        io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite); | 
				
			|||
    } else { | 
				
			|||
        socket.emit("heartbeatList", monitorID, result, overwrite); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 *  Important Heart beat list (aka event list) | 
				
			|||
 * @param socket | 
				
			|||
 * @param monitorID | 
				
			|||
 * @param toUser  True = send to all browsers with the same user id, False = send to the current browser only | 
				
			|||
 * @param overwrite Overwrite client-side's heartbeat list | 
				
			|||
 */ | 
				
			|||
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { | 
				
			|||
    const timeLogger = new TimeLogger(); | 
				
			|||
 | 
				
			|||
    let list = await R.find("heartbeat", ` | 
				
			|||
        monitor_id = ? | 
				
			|||
        AND important = 1 | 
				
			|||
        ORDER BY time DESC | 
				
			|||
        LIMIT 500 | 
				
			|||
    `, [
 | 
				
			|||
        monitorID, | 
				
			|||
    ]) | 
				
			|||
 | 
				
			|||
    timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`); | 
				
			|||
 | 
				
			|||
    if (toUser) { | 
				
			|||
        io.to(socket.userID).emit("importantHeartbeatList", monitorID, list, overwrite); | 
				
			|||
    } else { | 
				
			|||
        socket.emit("importantHeartbeatList", monitorID, list, overwrite); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = { | 
				
			|||
    sendNotificationList, | 
				
			|||
    sendImportantHeartbeatList, | 
				
			|||
    sendHeartbeatList, | 
				
			|||
} | 
				
			|||
@ -0,0 +1,102 @@ | 
				
			|||
<template> | 
				
			|||
    <div class="input-group mb-3"> | 
				
			|||
        <!-- | 
				
			|||
        Hack - Disable Chrome save password | 
				
			|||
        readonly + onfocus | 
				
			|||
        https://stackoverflow.com/questions/41217019/how-to-prevent-a-browser-from-storing-passwords | 
				
			|||
       --> | 
				
			|||
        <input | 
				
			|||
            v-model="model" | 
				
			|||
            :type="visibility" | 
				
			|||
            class="form-control" | 
				
			|||
            :placeholder="placeholder" | 
				
			|||
            :maxlength="maxlength" | 
				
			|||
            :autocomplete="autocomplete" | 
				
			|||
            :required="required" | 
				
			|||
            :readonly="isReadOnly" | 
				
			|||
            @focus="removeReadOnly" | 
				
			|||
        > | 
				
			|||
 | 
				
			|||
        <a v-if="visibility == 'password'" class="btn btn-outline-primary" @click="showInput()"> | 
				
			|||
            <font-awesome-icon icon="eye" /> | 
				
			|||
        </a> | 
				
			|||
        <a v-if="visibility == 'text'" class="btn btn-outline-primary" @click="hideInput()"> | 
				
			|||
            <font-awesome-icon icon="eye-slash" /> | 
				
			|||
        </a> | 
				
			|||
    </div> | 
				
			|||
</template> | 
				
			|||
 | 
				
			|||
<script> | 
				
			|||
export default { | 
				
			|||
    props: { | 
				
			|||
        modelValue: { | 
				
			|||
            type: String, | 
				
			|||
            default: "" | 
				
			|||
        }, | 
				
			|||
        placeholder: { | 
				
			|||
            type: String, | 
				
			|||
            default: "" | 
				
			|||
        }, | 
				
			|||
        maxlength: { | 
				
			|||
            type: Number, | 
				
			|||
            default: 255 | 
				
			|||
        }, | 
				
			|||
        autocomplete: { | 
				
			|||
            type: Boolean, | 
				
			|||
        }, | 
				
			|||
        required: { | 
				
			|||
            type: Boolean | 
				
			|||
        }, | 
				
			|||
        readonly: { | 
				
			|||
            type: Boolean, | 
				
			|||
            default: false, | 
				
			|||
        }, | 
				
			|||
    }, | 
				
			|||
    data() { | 
				
			|||
        return { | 
				
			|||
            visibility: "password", | 
				
			|||
            readOnlyValue: false, | 
				
			|||
        } | 
				
			|||
    }, | 
				
			|||
    computed: { | 
				
			|||
        model: { | 
				
			|||
            get() { | 
				
			|||
                return this.modelValue | 
				
			|||
            }, | 
				
			|||
            set(value) { | 
				
			|||
                this.$emit("update:modelValue", value) | 
				
			|||
            } | 
				
			|||
        }, | 
				
			|||
        isReadOnly() { | 
				
			|||
            // Actually readonly from prop | 
				
			|||
            if (this.readonly) { | 
				
			|||
                return true; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // Hack - Disable Chrome save password | 
				
			|||
            return this.readOnlyValue; | 
				
			|||
        } | 
				
			|||
    }, | 
				
			|||
    created() { | 
				
			|||
        // Hack - Disable Chrome save password | 
				
			|||
        if (this.autocomplete) { | 
				
			|||
            this.readOnlyValue = "readonly"; | 
				
			|||
        } | 
				
			|||
    }, | 
				
			|||
    methods: { | 
				
			|||
        showInput() { | 
				
			|||
            this.visibility = "text"; | 
				
			|||
        }, | 
				
			|||
        hideInput() { | 
				
			|||
            this.visibility = "password"; | 
				
			|||
        }, | 
				
			|||
 | 
				
			|||
        // Hack - Disable Chrome save password | 
				
			|||
        removeReadOnly() { | 
				
			|||
            if (this.autocomplete) { | 
				
			|||
                this.readOnlyValue = false; | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
</script> | 
				
			|||
@ -1,10 +1,10 @@ | 
				
			|||
import { library } from "@fortawesome/fontawesome-svg-core" | 
				
			|||
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp } from "@fortawesome/free-solid-svg-icons" | 
				
			|||
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons" | 
				
			|||
//import { fa } from '@fortawesome/free-regular-svg-icons'
 | 
				
			|||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" | 
				
			|||
 | 
				
			|||
// Add Free Font Awesome Icons here
 | 
				
			|||
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
 | 
				
			|||
library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp); | 
				
			|||
library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash); | 
				
			|||
 | 
				
			|||
export { FontAwesomeIcon } | 
				
			|||
 | 
				
			|||
					Loading…
					
					
				
		Reference in new issue