mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-24 23:34:03 +00:00
6fc34e44d9
I've been having an issue with trying to use an IPAddress Host:Port combination in monitoring my GRPC instances. This was because the input type was set to url instead of text. Even if the pattern passes the match test, the url would block as it requires a fully qualified domain name with HTTP and this would fail to submit
1096 lines
54 KiB
Vue
1096 lines
54 KiB
Vue
<template>
|
|
<transition name="slide-fade" appear>
|
|
<div>
|
|
<h1 class="mb-3">{{ pageName }}</h1>
|
|
<form @submit.prevent="submit">
|
|
<div class="shadow-box">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h2 class="mb-2">{{ $t("General") }}</h2>
|
|
|
|
<div class="my-3">
|
|
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
|
|
<select id="type" v-model="monitor.type" class="form-select">
|
|
<optgroup :label="$t('General Monitor Type')">
|
|
<option value="http">
|
|
HTTP(s)
|
|
</option>
|
|
<option value="port">
|
|
TCP Port
|
|
</option>
|
|
<option value="ping">
|
|
Ping
|
|
</option>
|
|
<option value="keyword">
|
|
HTTP(s) - {{ $t("Keyword") }}
|
|
</option>
|
|
<option value="grpc-keyword">
|
|
gRPC(s) - {{ $t("Keyword") }}
|
|
</option>
|
|
<option value="dns">
|
|
DNS
|
|
</option>
|
|
<option value="docker">
|
|
{{ $t("Docker Container") }}
|
|
</option>
|
|
</optgroup>
|
|
|
|
<optgroup :label="$t('Passive Monitor Type')">
|
|
<option value="push">
|
|
Push
|
|
</option>
|
|
</optgroup>
|
|
|
|
<optgroup :label="$t('Specific Monitor Type')">
|
|
<option value="steam">
|
|
{{ $t("Steam Game Server") }}
|
|
</option>
|
|
<option value="gamedig">
|
|
GameDig
|
|
</option>
|
|
<option value="mqtt">
|
|
MQTT
|
|
</option>
|
|
<option value="sqlserver">
|
|
Microsoft SQL Server
|
|
</option>
|
|
<option value="postgres">
|
|
PostgreSQL
|
|
</option>
|
|
<option value="mysql">
|
|
MySQL/MariaDB
|
|
</option>
|
|
<option value="mongodb">
|
|
MongoDB
|
|
</option>
|
|
<option value="radius">
|
|
Radius
|
|
</option>
|
|
<option value="redis">
|
|
Redis
|
|
</option>
|
|
</optgroup>
|
|
|
|
<!--
|
|
Hidden for now: Reason refer to Setting.vue
|
|
<optgroup :label="$t('Custom Monitor Type')">
|
|
<option value="browser">
|
|
(Beta) HTTP(s) - Browser Engine (Chrome/Firefox)
|
|
</option>
|
|
</optgroup>
|
|
</select>
|
|
-->
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Friendly Name -->
|
|
<div class="my-3">
|
|
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
|
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
|
</div>
|
|
|
|
<!-- URL -->
|
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'browser' " class="my-3">
|
|
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
|
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
|
|
</div>
|
|
|
|
<!-- gRPC URL -->
|
|
<div v-if="monitor.type === 'grpc-keyword' " class="my-3">
|
|
<label for="grpc-url" class="form-label">{{ $t("URL") }}</label>
|
|
<input id="grpc-url" v-model="monitor.grpcUrl" type="text" class="form-control" pattern="[^\:]+:[0-9]{5}" required>
|
|
</div>
|
|
|
|
<!-- Push URL -->
|
|
<div v-if="monitor.type === 'push' " class="my-3">
|
|
<label for="push-url" class="form-label">{{ $t("PushUrl") }}</label>
|
|
<CopyableInput id="push-url" v-model="pushURL" type="url" disabled="disabled" />
|
|
<div class="form-text">
|
|
{{ $t("needPushEvery", [monitor.interval]) }}<br />
|
|
{{ $t("pushOptionalParams", ["status, msg, ping"]) }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Keyword -->
|
|
<div v-if="monitor.type === 'keyword' || monitor.type === 'grpc-keyword'" class="my-3">
|
|
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
|
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
|
<div class="form-text">
|
|
{{ $t("keywordDescription") }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Game -->
|
|
<!-- GameDig only -->
|
|
<div v-if="monitor.type === 'gamedig'" class="my-3">
|
|
<label for="game" class="form-label"> {{ $t("Game") }} </label>
|
|
<select id="game" v-model="monitor.game" class="form-select" required>
|
|
<option v-for="game in gameList" :key="game.keys[0]" :value="game.keys[0]">
|
|
{{ game.pretty }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Hostname -->
|
|
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius only -->
|
|
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' ||monitor.type === 'mqtt' || monitor.type === 'radius'" class="my-3">
|
|
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
|
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : ipOrHostnameRegexPattern}`" required>
|
|
</div>
|
|
|
|
<!-- Port -->
|
|
<!-- For TCP Port / Steam / MQTT / Radius Type -->
|
|
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius'" class="my-3">
|
|
<label for="port" class="form-label">{{ $t("Port") }}</label>
|
|
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
|
|
</div>
|
|
|
|
<!-- DNS Resolver Server -->
|
|
<!-- For DNS Type -->
|
|
<template v-if="monitor.type === 'dns'">
|
|
<div class="my-3">
|
|
<label for="dns_resolve_server" class="form-label">{{ $t("Resolver Server") }}</label>
|
|
<input id="dns_resolve_server" v-model="monitor.dns_resolve_server" type="text" class="form-control" :pattern="ipRegex" required>
|
|
<div class="form-text">
|
|
{{ $t("resolverserverDescription") }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Port -->
|
|
<div class="my-3">
|
|
<label for="port" class="form-label">{{ $t("Port") }}</label>
|
|
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
|
|
<div class="form-text">
|
|
{{ $t("dnsPortDescription") }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="dns_resolve_type" class="form-label">{{ $t("Resource Record Type") }}</label>
|
|
|
|
<!-- :allow-empty="false" is not working, set a default value instead https://github.com/shentao/vue-multiselect/issues/336 -->
|
|
<VueMultiselect
|
|
id="dns_resolve_type"
|
|
v-model="monitor.dns_resolve_type"
|
|
:options="dnsresolvetypeOptions"
|
|
:multiple="false"
|
|
:close-on-select="true"
|
|
:clear-on-select="false"
|
|
:preserve-search="false"
|
|
:placeholder="$t('Pick a RR-Type...')"
|
|
:preselect-first="false"
|
|
:max-height="500"
|
|
:taggable="false"
|
|
></VueMultiselect>
|
|
|
|
<div class="form-text">
|
|
{{ $t("rrtypeDescription") }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Docker Container Name / ID -->
|
|
<!-- For Docker Type -->
|
|
<div v-if="monitor.type === 'docker'" class="my-3">
|
|
<label for="docker_container" class="form-label">{{ $t("Container Name / ID") }}</label>
|
|
<input id="docker_container" v-model="monitor.docker_container" type="text" class="form-control" required>
|
|
</div>
|
|
|
|
<!-- Docker Host -->
|
|
<!-- For Docker Type -->
|
|
<div v-if="monitor.type === 'docker'" class="my-3">
|
|
<h2 class="mb-2">{{ $t("Docker Host") }}</h2>
|
|
<p v-if="$root.dockerHostList.length === 0">
|
|
{{ $t("Not available, please setup.") }}
|
|
</p>
|
|
|
|
<div v-else class="mb-3">
|
|
<label for="docker-host" class="form-label">{{ $t("Docker Host") }}</label>
|
|
<select id="docket-host" v-model="monitor.docker_host" class="form-select">
|
|
<option v-for="host in $root.dockerHostList" :key="host.id" :value="host.id">{{ host.name }}</option>
|
|
</select>
|
|
<a href="#" @click="$refs.dockerHostDialog.show(monitor.docker_host)">{{ $t("Edit") }}</a>
|
|
</div>
|
|
|
|
<button class="btn btn-primary me-2" type="button" @click="$refs.dockerHostDialog.show()">
|
|
{{ $t("Setup Docker Host") }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- MQTT -->
|
|
<!-- For MQTT Type -->
|
|
<template v-if="monitor.type === 'mqtt'">
|
|
<div class="my-3">
|
|
<label for="mqttUsername" class="form-label">MQTT {{ $t("Username") }}</label>
|
|
<input id="mqttUsername" v-model="monitor.mqttUsername" type="text" class="form-control">
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="mqttPassword" class="form-label">MQTT {{ $t("Password") }}</label>
|
|
<input id="mqttPassword" v-model="monitor.mqttPassword" type="password" class="form-control">
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="mqttTopic" class="form-label">MQTT {{ $t("Topic") }}</label>
|
|
<input id="mqttTopic" v-model="monitor.mqttTopic" type="text" class="form-control" required>
|
|
<div class="form-text">
|
|
{{ $t("topicExplanation") }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="mqttSuccessMessage" class="form-label">MQTT {{ $t("successMessage") }}</label>
|
|
<input id="mqttSuccessMessage" v-model="monitor.mqttSuccessMessage" type="text" class="form-control">
|
|
<div class="form-text">
|
|
{{ $t("successMessageExplanation") }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-if="monitor.type === 'radius'">
|
|
<div class="my-3">
|
|
<label for="radius_username" class="form-label">Radius {{ $t("Username") }}</label>
|
|
<input id="radius_username" v-model="monitor.radiusUsername" type="text" class="form-control" required />
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="radius_password" class="form-label">Radius {{ $t("Password") }}</label>
|
|
<input id="radius_password" v-model="monitor.radiusPassword" type="password" class="form-control" required />
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="radius_secret" class="form-label">{{ $t("RadiusSecret") }}</label>
|
|
<input id="radius_secret" v-model="monitor.radiusSecret" type="password" class="form-control" required />
|
|
<div class="form-text"> {{ $t( "RadiusSecretDescription") }} </div>
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="radius_called_station_id" class="form-label">{{ $t("RadiusCalledStationId") }}</label>
|
|
<input id="radius_called_station_id" v-model="monitor.radiusCalledStationId" type="text" class="form-control" required />
|
|
<div class="form-text"> {{ $t( "RadiusCalledStationIdDescription") }} </div>
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="radius_calling_station_id" class="form-label">{{ $t("RadiusCallingStationId") }}</label>
|
|
<input id="radius_calling_station_id" v-model="monitor.radiusCallingStationId" type="text" class="form-control" required />
|
|
<div class="form-text"> {{ $t( "RadiusCallingStationIdDescription") }} </div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- SQL Server / PostgreSQL / MySQL -->
|
|
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql'">
|
|
<div class="my-3">
|
|
<label for="sqlConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
|
|
|
<template v-if="monitor.type === 'sqlserver'">
|
|
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="Server=<hostname>,<port>;Database=<your database>;User Id=<your user id>;Password=<your password>;Encrypt=<true/false>;TrustServerCertificate=<Yes/No>;Connection Timeout=<int>">
|
|
</template>
|
|
<template v-if="monitor.type === 'postgres'">
|
|
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="postgres://username:password@host:port/database">
|
|
</template>
|
|
<template v-if="monitor.type === 'mysql'">
|
|
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="mysql://username:password@host:port/database">
|
|
</template>
|
|
</div>
|
|
<div class="my-3">
|
|
<label for="sqlQuery" class="form-label">{{ $t("Query") }}</label>
|
|
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" placeholder="Example: select getdate()"></textarea>
|
|
</div>
|
|
</template>
|
|
<!-- Redis -->
|
|
<template v-if="monitor.type === 'redis'">
|
|
<div class="my-3">
|
|
<label for="redisConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
|
<input id="redisConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="redis://user:password@host:port">
|
|
</div>
|
|
</template>
|
|
|
|
<!-- MongoDB -->
|
|
<template v-if="monitor.type === 'mongodb'">
|
|
<div class="my-3">
|
|
<label for="sqlConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
|
|
|
<template v-if="monitor.type === 'mongodb'">
|
|
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="mongodb://username:password@host:port/database">
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Interval -->
|
|
<div class="my-3">
|
|
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
|
<input id="interval" v-model="monitor.interval" type="number" class="form-control" required :min="minInterval" step="1" :max="maxInterval">
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="maxRetries" class="form-label">{{ $t("Retries") }}</label>
|
|
<input id="maxRetries" v-model="monitor.maxretries" type="number" class="form-control" required min="0" step="1">
|
|
<div class="form-text">
|
|
{{ $t("retriesDescription") }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="retry-interval" class="form-label">
|
|
{{ $t("Heartbeat Retry Interval") }}
|
|
<span>({{ $t("retryCheckEverySecond", [ monitor.retryInterval ]) }})</span>
|
|
</label>
|
|
<input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required :min="minInterval" step="1">
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="resend-interval" class="form-label">
|
|
{{ $t("Resend Notification if Down X times consecutively") }}
|
|
<span v-if="monitor.resendInterval > 0">({{ $t("resendEveryXTimes", [ monitor.resendInterval ]) }})</span>
|
|
<span v-else>({{ $t("resendDisabled") }})</span>
|
|
</label>
|
|
<input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1">
|
|
</div>
|
|
|
|
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
|
|
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
|
|
<input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox">
|
|
<label class="form-check-label" for="expiry-notification">
|
|
{{ $t("Certificate Expiry Notification") }}
|
|
</label>
|
|
<div class="form-text">
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
|
|
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
|
|
<label class="form-check-label" for="ignore-tls">
|
|
{{ $t("ignoreTLSError") }}
|
|
</label>
|
|
</div>
|
|
|
|
<div class="my-3 form-check">
|
|
<input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox">
|
|
<label class="form-check-label" for="upside-down">
|
|
{{ $t("Upside Down Mode") }}
|
|
</label>
|
|
<div class="form-text">
|
|
{{ $t("upsideDownModeDescription") }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ping packet size -->
|
|
<div v-if="monitor.type === 'ping'" class="my-3">
|
|
<label for="packet-size" class="form-label">{{ $t("Packet Size") }}</label>
|
|
<input id="packet-size" v-model="monitor.packetSize" type="number" class="form-control" required min="1" max="65500" step="1">
|
|
</div>
|
|
|
|
<!-- HTTP / Keyword only -->
|
|
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'grpc-keyword' ">
|
|
<div class="my-3">
|
|
<label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
|
|
<input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
|
|
<div class="form-text">
|
|
{{ $t("maxRedirectDescription") }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="acceptedStatusCodes" class="form-label">{{ $t("Accepted Status Codes") }}</label>
|
|
|
|
<VueMultiselect
|
|
id="acceptedStatusCodes"
|
|
v-model="monitor.accepted_statuscodes"
|
|
:options="acceptedStatusCodeOptions"
|
|
:multiple="true"
|
|
:close-on-select="false"
|
|
:clear-on-select="false"
|
|
:preserve-search="true"
|
|
:placeholder="$t('Pick Accepted Status Codes...')"
|
|
:preselect-first="false"
|
|
:max-height="600"
|
|
:taggable="true"
|
|
></VueMultiselect>
|
|
|
|
<div class="form-text">
|
|
{{ $t("acceptedStatusCodesDescription") }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Description -->
|
|
<div class="my-3">
|
|
<label for="description" class="form-label">{{ $t("Description") }}</label>
|
|
<input id="description" v-model="monitor.description" type="text" class="form-control">
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<tags-manager ref="tagsManager" :pre-selected-tags="monitor.tags"></tags-manager>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div v-if="$root.isMobile" class="mt-3" />
|
|
|
|
<!-- Notifications -->
|
|
<h2 class="mb-2">{{ $t("Notifications") }}</h2>
|
|
<p v-if="$root.notificationList.length === 0">
|
|
{{ $t("Not available, please setup.") }}
|
|
</p>
|
|
|
|
<div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch my-3">
|
|
<input :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id]" class="form-check-input" type="checkbox">
|
|
|
|
<label class="form-check-label" :for=" 'notification' + notification.id">
|
|
{{ notification.name }}
|
|
<a href="#" @click="$refs.notificationDialog.show(notification.id)">{{ $t("Edit") }}</a>
|
|
</label>
|
|
|
|
<span v-if="notification.isDefault == true" class="badge bg-primary ms-2">{{ $t("Default") }}</span>
|
|
</div>
|
|
|
|
<button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
|
|
{{ $t("Setup Notification") }}
|
|
</button>
|
|
|
|
<!-- Proxies -->
|
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword'">
|
|
<h2 class="mt-5 mb-2">{{ $t("Proxy") }}</h2>
|
|
<p v-if="$root.proxyList.length === 0">
|
|
{{ $t("Not available, please setup.") }}
|
|
</p>
|
|
|
|
<div v-if="$root.proxyList.length > 0" class="form-check my-3">
|
|
<input id="proxy-disable" v-model="monitor.proxyId" :value="null" name="proxy" class="form-check-input" type="radio">
|
|
<label class="form-check-label" for="proxy-disable">{{ $t("No Proxy") }}</label>
|
|
</div>
|
|
|
|
<div v-for="proxy in $root.proxyList" :key="proxy.id" class="form-check my-3">
|
|
<input :id="`proxy-${proxy.id}`" v-model="monitor.proxyId" :value="proxy.id" name="proxy" class="form-check-input" type="radio">
|
|
|
|
<label class="form-check-label" :for="`proxy-${proxy.id}`">
|
|
{{ proxy.host }}:{{ proxy.port }} ({{ proxy.protocol }})
|
|
<a href="#" @click="$refs.proxyDialog.show(proxy.id)">{{ $t("Edit") }}</a>
|
|
</label>
|
|
|
|
<span v-if="proxy.default === true" class="badge bg-primary ms-2">{{ $t("default") }}</span>
|
|
</div>
|
|
|
|
<button class="btn btn-primary me-2" type="button" @click="$refs.proxyDialog.show()">
|
|
{{ $t("Setup Proxy") }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- HTTP Options -->
|
|
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
|
|
<h2 class="mt-5 mb-2">{{ $t("HTTP Options") }}</h2>
|
|
|
|
<!-- Method -->
|
|
<div class="my-3">
|
|
<label for="method" class="form-label">{{ $t("Method") }}</label>
|
|
<select id="method" v-model="monitor.method" class="form-select">
|
|
<option value="GET">
|
|
GET
|
|
</option>
|
|
<option value="POST">
|
|
POST
|
|
</option>
|
|
<option value="PUT">
|
|
PUT
|
|
</option>
|
|
<option value="PATCH">
|
|
PATCH
|
|
</option>
|
|
<option value="DELETE">
|
|
DELETE
|
|
</option>
|
|
<option value="HEAD">
|
|
HEAD
|
|
</option>
|
|
<option value="OPTIONS">
|
|
OPTIONS
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Encoding -->
|
|
<div class="my-3">
|
|
<label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
|
|
<select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
|
|
<option value="json">JSON</option>
|
|
<option value="xml">XML</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="my-3">
|
|
<label for="body" class="form-label">{{ $t("Body") }}</label>
|
|
<textarea id="body" v-model="monitor.body" class="form-control" :placeholder="bodyPlaceholder"></textarea>
|
|
</div>
|
|
|
|
<!-- Headers -->
|
|
<div class="my-3">
|
|
<label for="headers" class="form-label">{{ $t("Headers") }}</label>
|
|
<textarea id="headers" v-model="monitor.headers" class="form-control" :placeholder="headersPlaceholder"></textarea>
|
|
</div>
|
|
|
|
<!-- HTTP Auth -->
|
|
<h4 class="mt-5 mb-2">{{ $t("Authentication") }}</h4>
|
|
|
|
<!-- Method -->
|
|
<div class="my-3">
|
|
<label for="method" class="form-label">{{ $t("Method") }}</label>
|
|
<select id="method" v-model="monitor.authMethod" class="form-select">
|
|
<option :value="null">
|
|
{{ $t("None") }}
|
|
</option>
|
|
<option value="basic">
|
|
{{ $t("HTTP Basic Auth") }}
|
|
</option>
|
|
<option value="ntlm">
|
|
NTLM
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<template v-if="monitor.authMethod && monitor.authMethod !== null ">
|
|
<div class="my-3">
|
|
<label for="basicauth" class="form-label">{{ $t("Username") }}</label>
|
|
<input id="basicauth-user" v-model="monitor.basic_auth_user" type="text" class="form-control" :placeholder="$t('Username')">
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="basicauth" class="form-label">{{ $t("Password") }}</label>
|
|
<input id="basicauth-pass" v-model="monitor.basic_auth_pass" type="password" autocomplete="new-password" class="form-control" :placeholder="$t('Password')">
|
|
</div>
|
|
<template v-if="monitor.authMethod === 'ntlm' ">
|
|
<div class="my-3">
|
|
<label for="basicauth" class="form-label">{{ $t("Domain") }}</label>
|
|
<input id="basicauth-domain" v-model="monitor.authDomain" type="text" class="form-control" :placeholder="$t('Domain')">
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<label for="basicauth" class="form-label">{{ $t("Workstation") }}</label>
|
|
<input id="basicauth-workstation" v-model="monitor.authWorkstation" type="text" class="form-control" :placeholder="$t('Workstation')">
|
|
</div>
|
|
</template>
|
|
</template>
|
|
</template>
|
|
|
|
<!-- gRPC Options -->
|
|
<template v-if="monitor.type === 'grpc-keyword' ">
|
|
<!-- Proto service enable TLS -->
|
|
<h2 class="mt-5 mb-2">{{ $t("GRPC Options") }}</h2>
|
|
<div class="my-3 form-check">
|
|
<input id="grpc-enable-tls" v-model="monitor.grpcEnableTls" class="form-check-input" type="checkbox" value="">
|
|
<label class="form-check-label" for="grpc-enable-tls">
|
|
{{ $t("Enable TLS") }}
|
|
</label>
|
|
<div class="form-text">
|
|
{{ $t("enableGRPCTls") }}
|
|
</div>
|
|
</div>
|
|
<!-- Proto service name data -->
|
|
<div class="my-3">
|
|
<label for="protobuf" class="form-label">{{ $t("Proto Service Name") }}</label>
|
|
<input id="name" v-model="monitor.grpcServiceName" type="text" class="form-control" :placeholder="protoServicePlaceholder" required>
|
|
</div>
|
|
|
|
<!-- Proto method data -->
|
|
<div class="my-3">
|
|
<label for="protobuf" class="form-label">{{ $t("Proto Method") }}</label>
|
|
<input id="name" v-model="monitor.grpcMethod" type="text" class="form-control" :placeholder="protoMethodPlaceholder" required>
|
|
<div class="form-text">
|
|
{{ $t("grpcMethodDescription") }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Proto data -->
|
|
<div class="my-3">
|
|
<label for="protobuf" class="form-label">{{ $t("Proto Content") }}</label>
|
|
<textarea id="protobuf" v-model="monitor.grpcProtobuf" class="form-control" :placeholder="protoBufDataPlaceholder"></textarea>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="my-3">
|
|
<label for="body" class="form-label">{{ $t("Body") }}</label>
|
|
<textarea id="body" v-model="monitor.grpcBody" class="form-control" :placeholder="bodyPlaceholder"></textarea>
|
|
</div>
|
|
|
|
<!-- Metadata: temporary disable waiting for next PR allow to send gRPC with metadata -->
|
|
<template v-if="false">
|
|
<div class="my-3">
|
|
<label for="metadata" class="form-label">{{ $t("Metadata") }}</label>
|
|
<textarea id="metadata" v-model="monitor.grpcMetadata" class="form-control" :placeholder="headersPlaceholder"></textarea>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="fixed-bottom-bar p-3">
|
|
<button id="monitor-submit-btn" class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<NotificationDialog ref="notificationDialog" @added="addedNotification" />
|
|
<DockerHostDialog ref="dockerHostDialog" @added="addedDockerHost" />
|
|
<ProxyDialog ref="proxyDialog" @added="addedProxy" />
|
|
</div>
|
|
</transition>
|
|
</template>
|
|
|
|
<script>
|
|
import VueMultiselect from "vue-multiselect";
|
|
import { useToast } from "vue-toastification";
|
|
import CopyableInput from "../components/CopyableInput.vue";
|
|
import NotificationDialog from "../components/NotificationDialog.vue";
|
|
import DockerHostDialog from "../components/DockerHostDialog.vue";
|
|
import ProxyDialog from "../components/ProxyDialog.vue";
|
|
import TagsManager from "../components/TagsManager.vue";
|
|
import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } from "../util.ts";
|
|
import { hostNameRegexPattern } from "../util-frontend";
|
|
|
|
const toast = useToast();
|
|
|
|
export default {
|
|
components: {
|
|
ProxyDialog,
|
|
CopyableInput,
|
|
NotificationDialog,
|
|
DockerHostDialog,
|
|
TagsManager,
|
|
VueMultiselect,
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
minInterval: MIN_INTERVAL_SECOND,
|
|
maxInterval: MAX_INTERVAL_SECOND,
|
|
processing: false,
|
|
monitor: {
|
|
notificationIDList: {},
|
|
// Do not add default value here, please check init() method
|
|
},
|
|
acceptedStatusCodeOptions: [],
|
|
dnsresolvetypeOptions: [],
|
|
ipOrHostnameRegexPattern: hostNameRegexPattern(),
|
|
mqttIpOrHostnameRegexPattern: hostNameRegexPattern(true),
|
|
gameList: null,
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
|
|
ipRegex() {
|
|
|
|
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
|
if (! isDev) {
|
|
return this.ipRegexPattern;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
pageName() {
|
|
let name = "Add New Monitor";
|
|
if (this.isClone) {
|
|
name = "Clone Monitor";
|
|
} else if (this.isEdit) {
|
|
name = "Edit";
|
|
}
|
|
return this.$t(name);
|
|
},
|
|
|
|
isAdd() {
|
|
return this.$route.path === "/add";
|
|
},
|
|
|
|
isClone() {
|
|
return this.$route.path.startsWith("/clone");
|
|
},
|
|
|
|
isEdit() {
|
|
return this.$route.path.startsWith("/edit");
|
|
},
|
|
|
|
pushURL() {
|
|
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?status=up&msg=OK&ping=";
|
|
},
|
|
|
|
protoServicePlaceholder() {
|
|
return this.$t("Example:", [ "Health" ]);
|
|
},
|
|
|
|
protoMethodPlaceholder() {
|
|
return this.$t("Example:", [ "check" ]);
|
|
},
|
|
|
|
protoBufDataPlaceholder() {
|
|
return this.$t("Example:", [ `
|
|
syntax = "proto3";
|
|
|
|
package grpc.health.v1;
|
|
|
|
service Health {
|
|
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
|
|
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
|
|
}
|
|
|
|
message HealthCheckRequest {
|
|
string service = 1;
|
|
}
|
|
|
|
message HealthCheckResponse {
|
|
enum ServingStatus {
|
|
UNKNOWN = 0;
|
|
SERVING = 1;
|
|
NOT_SERVING = 2;
|
|
SERVICE_UNKNOWN = 3; // Used only by the Watch method.
|
|
}
|
|
ServingStatus status = 1;
|
|
}
|
|
` ]);
|
|
},
|
|
bodyPlaceholder() {
|
|
if (this.monitor && this.monitor.httpBodyEncoding && this.monitor.httpBodyEncoding === "xml") {
|
|
return this.$t("Example:", [ `
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
<soap:Body>
|
|
<Uptime>Kuma</Uptime>
|
|
</soap:Body>
|
|
</soap:Envelope>` ]);
|
|
}
|
|
return this.$t("Example:", [ `
|
|
{
|
|
"key": "value"
|
|
}` ]);
|
|
},
|
|
|
|
headersPlaceholder() {
|
|
return this.$t("Example:", [ `
|
|
{
|
|
"HeaderName": "HeaderValue"
|
|
}` ]);
|
|
},
|
|
|
|
currentGameObject() {
|
|
if (this.gameList) {
|
|
for (let game of this.gameList) {
|
|
if (game.keys[0] === this.monitor.game) {
|
|
return game;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
},
|
|
watch: {
|
|
"$root.proxyList"() {
|
|
if (this.isAdd) {
|
|
if (this.$root.proxyList && !this.monitor.proxyId) {
|
|
const proxy = this.$root.proxyList.find(proxy => proxy.default);
|
|
|
|
if (proxy) {
|
|
this.monitor.proxyId = proxy.id;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
"$route.fullPath"() {
|
|
this.init();
|
|
},
|
|
|
|
"monitor.interval"(value, oldValue) {
|
|
// Link interval and retryInterval if they are the same value.
|
|
if (this.monitor.retryInterval === oldValue) {
|
|
this.monitor.retryInterval = value;
|
|
}
|
|
},
|
|
|
|
"monitor.type"() {
|
|
if (this.monitor.type === "push") {
|
|
if (! this.monitor.pushToken) {
|
|
this.monitor.pushToken = genSecret(10);
|
|
}
|
|
}
|
|
|
|
// Set default port for DNS if not already defined
|
|
if (! this.monitor.port || this.monitor.port === "53" || this.monitor.port === "1812") {
|
|
if (this.monitor.type === "dns") {
|
|
this.monitor.port = "53";
|
|
} else if (this.monitor.type === "radius") {
|
|
this.monitor.port = "1812";
|
|
} else {
|
|
this.monitor.port = undefined;
|
|
}
|
|
}
|
|
|
|
// Get the game list from server
|
|
if (this.monitor.type === "gamedig") {
|
|
this.$root.getSocket().emit("getGameList", (res) => {
|
|
if (res.ok) {
|
|
this.gameList = res.gameList;
|
|
} else {
|
|
toast.error(res.msg);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
currentGameObject(newGameObject, previousGameObject) {
|
|
if (!this.monitor.port || (previousGameObject && previousGameObject.options.port === this.monitor.port)) {
|
|
this.monitor.port = newGameObject.options.port;
|
|
}
|
|
this.monitor.game = newGameObject.keys[0];
|
|
}
|
|
|
|
},
|
|
mounted() {
|
|
this.init();
|
|
|
|
let acceptedStatusCodeOptions = [
|
|
"100-199",
|
|
"200-299",
|
|
"300-399",
|
|
"400-499",
|
|
"500-599",
|
|
];
|
|
|
|
let dnsresolvetypeOptions = [
|
|
"A",
|
|
"AAAA",
|
|
"CAA",
|
|
"CNAME",
|
|
"MX",
|
|
"NS",
|
|
"PTR",
|
|
"SOA",
|
|
"SRV",
|
|
"TXT",
|
|
];
|
|
|
|
for (let i = 100; i <= 999; i++) {
|
|
acceptedStatusCodeOptions.push(i.toString());
|
|
}
|
|
|
|
this.acceptedStatusCodeOptions = acceptedStatusCodeOptions;
|
|
this.dnsresolvetypeOptions = dnsresolvetypeOptions;
|
|
},
|
|
methods: {
|
|
/** Initialize the edit monitor form */
|
|
init() {
|
|
if (this.isAdd) {
|
|
|
|
this.monitor = {
|
|
type: "http",
|
|
name: "",
|
|
url: "https://",
|
|
method: "GET",
|
|
interval: 60,
|
|
retryInterval: this.interval,
|
|
resendInterval: 0,
|
|
maxretries: 0,
|
|
notificationIDList: {},
|
|
ignoreTls: false,
|
|
upsideDown: false,
|
|
packetSize: 56,
|
|
expiryNotification: false,
|
|
maxredirects: 10,
|
|
accepted_statuscodes: [ "200-299" ],
|
|
dns_resolve_type: "A",
|
|
dns_resolve_server: "1.1.1.1",
|
|
docker_container: "",
|
|
docker_host: null,
|
|
proxyId: null,
|
|
mqttUsername: "",
|
|
mqttPassword: "",
|
|
mqttTopic: "",
|
|
mqttSuccessMessage: "",
|
|
authMethod: null,
|
|
httpBodyEncoding: "json"
|
|
};
|
|
|
|
if (this.$root.proxyList && !this.monitor.proxyId) {
|
|
const proxy = this.$root.proxyList.find(proxy => proxy.default);
|
|
|
|
if (proxy) {
|
|
this.monitor.proxyId = proxy.id;
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
|
if (this.$root.notificationList[i].isDefault === true) {
|
|
this.monitor.notificationIDList[this.$root.notificationList[i].id] = true;
|
|
}
|
|
}
|
|
} else if (this.isEdit || this.isClone) {
|
|
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
|
|
if (res.ok) {
|
|
this.monitor = res.monitor;
|
|
|
|
if (this.isClone) {
|
|
/*
|
|
* Cloning a monitor will include properties that can not be posted to backend
|
|
* as they are not valid columns in the SQLite table.
|
|
*/
|
|
this.monitor.id = undefined; // Remove id when cloning as we want a new id
|
|
this.monitor.includeSensitiveData = undefined;
|
|
this.monitor.maintenance = undefined;
|
|
this.monitor.name = this.$t("cloneOf", [ this.monitor.name ]);
|
|
this.monitor.tags = undefined; // FIXME: Cloning tags does not work yet
|
|
}
|
|
|
|
// Handling for monitors that are created before 1.7.0
|
|
if (this.monitor.retryInterval === 0) {
|
|
this.monitor.retryInterval = this.monitor.interval;
|
|
}
|
|
} else {
|
|
toast.error(res.msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* Validate form input
|
|
* @returns {boolean} Is the form input valid?
|
|
*/
|
|
isInputValid() {
|
|
if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) {
|
|
try {
|
|
JSON.parse(this.monitor.body);
|
|
} catch (err) {
|
|
toast.error(this.$t("BodyInvalidFormat") + err.message);
|
|
return false;
|
|
}
|
|
}
|
|
if (this.monitor.headers) {
|
|
try {
|
|
JSON.parse(this.monitor.headers);
|
|
} catch (err) {
|
|
toast.error(this.$t("HeadersInvalidFormat") + err.message);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Submit the form data for processing
|
|
* @returns {void}
|
|
*/
|
|
async submit() {
|
|
|
|
this.processing = true;
|
|
|
|
if (!this.isInputValid()) {
|
|
this.processing = false;
|
|
return;
|
|
}
|
|
|
|
// Beautify the JSON format (only if httpBodyEncoding is not set or === json)
|
|
if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) {
|
|
this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4);
|
|
}
|
|
|
|
if (this.monitor.type && this.monitor.type !== "http" && this.monitor.type !== "keyword") {
|
|
this.monitor.httpBodyEncoding = null;
|
|
}
|
|
|
|
if (this.monitor.headers) {
|
|
this.monitor.headers = JSON.stringify(JSON.parse(this.monitor.headers), null, 4);
|
|
}
|
|
|
|
if (this.monitor.hostname) {
|
|
this.monitor.hostname = this.monitor.hostname.trim();
|
|
}
|
|
|
|
if (this.monitor.url) {
|
|
this.monitor.url = this.monitor.url.trim();
|
|
}
|
|
|
|
if (this.isAdd || this.isClone) {
|
|
this.$root.add(this.monitor, async (res) => {
|
|
|
|
if (res.ok) {
|
|
await this.$refs.tagsManager.submit(res.monitorID);
|
|
|
|
toast.success(res.msg);
|
|
this.processing = false;
|
|
this.$root.getMonitorList();
|
|
this.$router.push("/dashboard/" + res.monitorID);
|
|
} else {
|
|
toast.error(res.msg);
|
|
this.processing = false;
|
|
}
|
|
|
|
});
|
|
} else {
|
|
await this.$refs.tagsManager.submit(this.monitor.id);
|
|
|
|
this.$root.getSocket().emit("editMonitor", this.monitor, (res) => {
|
|
this.processing = false;
|
|
this.$root.toastRes(res);
|
|
this.init();
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Added a Notification Event
|
|
* Enable it if the notification is added in EditMonitor.vue
|
|
* @param {number} id ID of notification to add
|
|
*/
|
|
addedNotification(id) {
|
|
this.monitor.notificationIDList[id] = true;
|
|
},
|
|
|
|
/**
|
|
* Added a Proxy Event
|
|
* Enable it if the proxy is added in EditMonitor.vue
|
|
* @param {number} id ID of proxy to add
|
|
*/
|
|
addedProxy(id) {
|
|
this.monitor.proxyId = id;
|
|
},
|
|
|
|
// Added a Docker Host Event
|
|
// Enable it if the Docker Host is added in EditMonitor.vue
|
|
addedDockerHost(id) {
|
|
this.monitor.docker_host = id;
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "../assets/vars.scss";
|
|
|
|
$padding: 20px;
|
|
|
|
.shadow-box {
|
|
padding-top: $padding;
|
|
padding-bottom: 0;
|
|
padding-right: $padding;
|
|
padding-left: $padding;
|
|
}
|
|
|
|
textarea {
|
|
min-height: 200px;
|
|
}
|
|
|
|
.fixed-bottom-bar {
|
|
position: sticky;
|
|
bottom: 0;
|
|
margin-left: -$padding;
|
|
margin-right: -$padding;
|
|
z-index: 100;
|
|
background-color: rgba(white, 0.2);
|
|
backdrop-filter: blur(2px);
|
|
border-radius: 0 0 10px 10px;
|
|
|
|
.dark & {
|
|
background-color: rgba($dark-header-bg, 0.9);
|
|
}
|
|
}
|
|
</style>
|