mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-01 11:22:34 +02:00
feat: support for adding multiple tags at once
I got tired of having to manually add tags one by one so I made changes to be able to add multiple tags in one go
This commit is contained in:
parent
c67f6efe29
commit
6e9f612c11
3 changed files with 215 additions and 96 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -18,6 +18,7 @@ dist-ssr
|
||||||
/extra/healthcheck.exe
|
/extra/healthcheck.exe
|
||||||
/extra/healthcheck
|
/extra/healthcheck
|
||||||
/extra/healthcheck-armv7
|
/extra/healthcheck-armv7
|
||||||
|
/studies
|
||||||
|
|
||||||
extra/exe-builder/bin
|
extra/exe-builder/bin
|
||||||
extra/exe-builder/obj
|
extra/exe-builder/obj
|
||||||
|
|
|
@ -24,6 +24,18 @@
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<!-- IV.1 Staging Display Area -->
|
||||||
|
<h4 v-if="stagedForBatchAdd.length > 0">{{ $t("Staged Tags for Batch Add") }}</h4>
|
||||||
|
<div v-if="stagedForBatchAdd.length > 0" class="mb-3 staging-area" style="max-height: 150px; overflow-y: auto;">
|
||||||
|
<Tag
|
||||||
|
v-for="stagedTag in stagedForBatchAdd"
|
||||||
|
:key="stagedTag.keyForList"
|
||||||
|
:item="mapStagedTagToDisplayItem(stagedTag)"
|
||||||
|
:remove="() => unstageTag(stagedTag)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- End IV.1 -->
|
||||||
|
|
||||||
<vue-multiselect
|
<vue-multiselect
|
||||||
v-model="newDraftTag.select"
|
v-model="newDraftTag.select"
|
||||||
class="mb-2"
|
class="mb-2"
|
||||||
|
@ -58,14 +70,11 @@
|
||||||
<div class="w-50 pe-2">
|
<div class="w-50 pe-2">
|
||||||
<input
|
<input
|
||||||
v-model="newDraftTag.name" class="form-control"
|
v-model="newDraftTag.name" class="form-control"
|
||||||
:class="{'is-invalid': validateDraftTag.nameInvalid}"
|
:class="{'is-invalid': validateDraftTag.invalid && (validateDraftTag.messageKey === 'tagNameColorRequired' || validateDraftTag.messageKey === 'tagNameExists')}"
|
||||||
:placeholder="$t('Name')"
|
:placeholder="$t('Name')"
|
||||||
data-testid="tag-name-input"
|
data-testid="tag-name-input"
|
||||||
@keydown.enter.prevent="onEnter"
|
@keydown.enter.prevent="onEnter"
|
||||||
/>
|
/>
|
||||||
<div class="invalid-feedback">
|
|
||||||
{{ $t("Tag with this name already exist.") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="w-50 ps-2">
|
<div class="w-50 ps-2">
|
||||||
<vue-multiselect
|
<vue-multiselect
|
||||||
|
@ -104,27 +113,42 @@
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<input
|
<input
|
||||||
v-model="newDraftTag.value" class="form-control"
|
v-model="newDraftTag.value" class="form-control"
|
||||||
:class="{'is-invalid': validateDraftTag.valueInvalid}"
|
:class="{'is-invalid': validateDraftTag.invalid && validateDraftTag.messageKey === 'tagAlreadyOnMonitor'}"
|
||||||
:placeholder="$t('value (optional)')"
|
:placeholder="$t('value (optional)')"
|
||||||
data-testid="tag-value-input"
|
data-testid="tag-value-input"
|
||||||
@keydown.enter.prevent="onEnter"
|
@keydown.enter.prevent="onEnter"
|
||||||
/>
|
/>
|
||||||
<div class="invalid-feedback">
|
</div>
|
||||||
{{ $t("Tag with this value already exist.") }}
|
|
||||||
|
<!-- IV.3 Feedback Area for Validation (General) -->
|
||||||
|
<div v-if="validateDraftTag.invalid && validateDraftTag.messageKey && (newDraftTag.select != null || canStageMoreNewSystemTags || validateDraftTag.messageKey !== 'tagLimitReached')" class="form-text text-danger mb-2">
|
||||||
|
{{ $t(validateDraftTag.messageKey, validateDraftTag.messageParams) }}
|
||||||
|
</div>
|
||||||
|
<!-- End IV.3 -->
|
||||||
|
|
||||||
|
<!-- IV.2 "Stage This Tag" Button (Modified current "Add" button) -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary float-end"
|
||||||
|
:disabled="processing || validateDraftTag.invalid"
|
||||||
|
data-testid="stage-tag-button"
|
||||||
|
@click.stop="stageCurrentTag"
|
||||||
|
>
|
||||||
|
{{ $t("Add Another Tag") }}
|
||||||
|
</button>
|
||||||
|
<div v-if="newDraftTag.select == null && !canStageMoreNewSystemTags && validateDraftTag.invalid && validateDraftTag.messageKey === 'tagLimitReached'" class="form-text text-danger float-end me-2">
|
||||||
|
{{ $t(validateDraftTag.messageKey, validateDraftTag.messageParams) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<!-- End IV.2 -->
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-secondary float-end"
|
|
||||||
:disabled="processing || validateDraftTag.invalid"
|
|
||||||
data-testid="tag-submit-button"
|
|
||||||
@click.stop="addDraftTag"
|
|
||||||
>
|
|
||||||
{{ $t("Add") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- IV.4 Modal Footer Buttons -->
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" @click.stop="clearStagingAndCloseModal">{{ $t("Cancel") }}</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click.stop="confirmAndCommitStagedTags" :disabled="processing || stagedForBatchAdd.length === 0">{{ $t("Add") }}</button>
|
||||||
|
</div>
|
||||||
|
<!-- End IV.4 -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,6 +156,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const MAX_NEW_SYSTEM_TAGS_PER_BATCH = 10; // Example limit
|
||||||
|
|
||||||
import { Modal } from "bootstrap";
|
import { Modal } from "bootstrap";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
import { colorOptions } from "../util-frontend";
|
import { colorOptions } from "../util-frontend";
|
||||||
|
@ -176,13 +202,17 @@ export default {
|
||||||
newTags: [],
|
newTags: [],
|
||||||
/** @type {Tag[]} */
|
/** @type {Tag[]} */
|
||||||
deleteTags: [],
|
deleteTags: [],
|
||||||
|
/**
|
||||||
|
* @type {Array<Object>} Holds tag objects staged for addition.
|
||||||
|
* Each object: { name, color, value, isNewSystemTag, systemTagId, keyForList }
|
||||||
|
*/
|
||||||
|
stagedForBatchAdd: [],
|
||||||
newDraftTag: {
|
newDraftTag: {
|
||||||
name: null,
|
name: null,
|
||||||
select: null,
|
select: null,
|
||||||
color: null,
|
color: null,
|
||||||
value: "",
|
value: "",
|
||||||
invalid: true,
|
// invalid: true, // Initial validation will be handled by computed prop
|
||||||
nameInvalid: false,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -199,49 +229,65 @@ export default {
|
||||||
selectedTags() {
|
selectedTags() {
|
||||||
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.tag_id === tag.tag_id));
|
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.tag_id === tag.tag_id));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @returns {boolean} True if more new system tags can be staged.
|
||||||
|
*/
|
||||||
|
canStageMoreNewSystemTags() {
|
||||||
|
return this.stagedForBatchAdd.filter(t => t.isNewSystemTag).length < MAX_NEW_SYSTEM_TAGS_PER_BATCH;
|
||||||
|
},
|
||||||
colorOptions() {
|
colorOptions() {
|
||||||
return colorOptions(this);
|
return colorOptions(this);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Validates the current draft tag based on several conditions.
|
||||||
|
* @returns {{invalid: boolean, messageKey: string|null, messageParams: object|null}}
|
||||||
|
*/
|
||||||
validateDraftTag() {
|
validateDraftTag() {
|
||||||
let nameInvalid = false;
|
// If defining a new system tag (newDraftTag.select == null)
|
||||||
let valueInvalid = false;
|
if (this.newDraftTag.select == null) {
|
||||||
let invalid = true;
|
if (!this.canStageMoreNewSystemTags) {
|
||||||
if (this.deleteTags.find(tag => tag.name === this.newDraftTag.select?.name && tag.value === this.newDraftTag.value)) {
|
return {
|
||||||
// Undo removing a Tag
|
invalid: true,
|
||||||
nameInvalid = false;
|
messageKey: "tagLimitReached",
|
||||||
valueInvalid = false;
|
messageParams: { limit: MAX_NEW_SYSTEM_TAGS_PER_BATCH }
|
||||||
invalid = false;
|
};
|
||||||
} else if (this.existingTags.filter(tag => tag.name === this.newDraftTag.name).length > 0 && this.newDraftTag.select == null) {
|
}
|
||||||
// Try to create new tag with existing name
|
if (!this.newDraftTag.name || this.newDraftTag.name.trim() === "" || !this.newDraftTag.color) {
|
||||||
nameInvalid = true;
|
// Keep button disabled, but don't show the explicit message for this case
|
||||||
invalid = true;
|
return { invalid: true, messageKey: null, messageParams: null };
|
||||||
} else if (this.newTags.concat(this.preSelectedTags).filter(tag => (
|
}
|
||||||
tag.name === this.newDraftTag.select?.name && tag.value === this.newDraftTag.value
|
if (this.tagOptions.find(opt => opt.name.toLowerCase() === this.newDraftTag.name.trim().toLowerCase())) {
|
||||||
) || (
|
return { invalid: true, messageKey: "tagNameExists", messageParams: null };
|
||||||
tag.name === this.newDraftTag.name && tag.value === this.newDraftTag.value
|
}
|
||||||
)).length > 0) {
|
|
||||||
// Try to add a tag with existing name and value
|
|
||||||
valueInvalid = true;
|
|
||||||
invalid = true;
|
|
||||||
} else if (this.newDraftTag.select != null) {
|
|
||||||
// Select an existing tag, no need to validate
|
|
||||||
invalid = false;
|
|
||||||
valueInvalid = false;
|
|
||||||
} else if (this.newDraftTag.color == null || this.newDraftTag.name === "") {
|
|
||||||
// Missing form inputs
|
|
||||||
nameInvalid = false;
|
|
||||||
invalid = true;
|
|
||||||
} else {
|
|
||||||
// Looks valid
|
|
||||||
invalid = false;
|
|
||||||
nameInvalid = false;
|
|
||||||
valueInvalid = false;
|
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
invalid,
|
// For any tag definition (new or existing system tag + value)
|
||||||
nameInvalid,
|
const draftTagName = this.newDraftTag.select ? this.newDraftTag.select.name : this.newDraftTag.name.trim();
|
||||||
valueInvalid,
|
const draftTagValue = this.newDraftTag.value ? this.newDraftTag.value.trim() : ""; // Treat null/undefined value as empty string for comparison
|
||||||
};
|
|
||||||
|
// Check if (name + value) combination already exists in this.stagedForBatchAdd
|
||||||
|
if (this.stagedForBatchAdd.find(staged => staged.name === draftTagName && staged.value === draftTagValue)) {
|
||||||
|
return { invalid: true, messageKey: "tagAlreadyStaged", messageParams: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if (name + value) combination already exists in this.selectedTags (final list on monitor)
|
||||||
|
// AND it's NOT an "undo delete"
|
||||||
|
const isUndoDelete = this.deleteTags.find(dTag =>
|
||||||
|
dTag.tag_id === (this.newDraftTag.select ? this.newDraftTag.select.id : null) &&
|
||||||
|
dTag.value === draftTagValue
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isUndoDelete && this.selectedTags.find(sTag => sTag.name === draftTagName && sTag.value === draftTagValue)) {
|
||||||
|
return { invalid: true, messageKey: "tagAlreadyOnMonitor", messageParams: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an existing tag is selected and has no value, it's valid (value is optional)
|
||||||
|
if (this.newDraftTag.select != null && draftTagValue === "") {
|
||||||
|
return { invalid: false, messageKey: null, messageParams: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the above, invalid: false
|
||||||
|
return { invalid: false, messageKey: null, messageParams: null };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -257,6 +303,9 @@ export default {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
showAddDialog() {
|
showAddDialog() {
|
||||||
|
this.stagedForBatchAdd = [];
|
||||||
|
this.clearDraftTag();
|
||||||
|
this.getExistingTags();
|
||||||
this.modal.show();
|
this.modal.show();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -300,37 +349,6 @@ export default {
|
||||||
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
|
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* Add a draft tag
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
addDraftTag() {
|
|
||||||
console.log("Adding Draft Tag: ", this.newDraftTag);
|
|
||||||
if (this.newDraftTag.select != null) {
|
|
||||||
if (this.deleteTags.find(tag => tag.name === this.newDraftTag.select.name && tag.value === this.newDraftTag.value)) {
|
|
||||||
// Undo removing a tag
|
|
||||||
this.deleteTags = this.deleteTags.filter(tag => !(tag.name === this.newDraftTag.select.name && tag.value === this.newDraftTag.value));
|
|
||||||
} else {
|
|
||||||
// Add an existing Tag
|
|
||||||
this.newTags.push({
|
|
||||||
id: this.newDraftTag.select.id,
|
|
||||||
color: this.newDraftTag.select.color,
|
|
||||||
name: this.newDraftTag.select.name,
|
|
||||||
value: this.newDraftTag.value,
|
|
||||||
new: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add new Tag
|
|
||||||
this.newTags.push({
|
|
||||||
color: this.newDraftTag.color.color,
|
|
||||||
name: this.newDraftTag.name.trim(),
|
|
||||||
value: this.newDraftTag.value,
|
|
||||||
new: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.clearDraftTag();
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* Remove a draft tag
|
* Remove a draft tag
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
@ -341,10 +359,8 @@ export default {
|
||||||
select: null,
|
select: null,
|
||||||
color: null,
|
color: null,
|
||||||
value: "",
|
value: "",
|
||||||
invalid: true,
|
// invalid: true, // Initial validation will be handled by computed prop
|
||||||
nameInvalid: false,
|
|
||||||
};
|
};
|
||||||
this.modal.hide();
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Add a tag asynchronously
|
* Add a tag asynchronously
|
||||||
|
@ -386,7 +402,7 @@ export default {
|
||||||
*/
|
*/
|
||||||
onEnter() {
|
onEnter() {
|
||||||
if (!this.validateDraftTag.invalid) {
|
if (!this.validateDraftTag.invalid) {
|
||||||
this.addDraftTag();
|
this.stageCurrentTag();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -475,7 +491,103 @@ export default {
|
||||||
console.warn("Modal hide failed:", e);
|
console.warn("Modal hide failed:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
this.stagedForBatchAdd = [];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Stages the current draft tag for batch addition.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
stageCurrentTag() {
|
||||||
|
if (this.validateDraftTag.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNew = this.newDraftTag.select == null;
|
||||||
|
const name = isNew ? this.newDraftTag.name.trim() : this.newDraftTag.select.name;
|
||||||
|
const color = isNew ? this.newDraftTag.color.color : this.newDraftTag.select.color;
|
||||||
|
const value = this.newDraftTag.value ? this.newDraftTag.value.trim() : "";
|
||||||
|
|
||||||
|
const stagedTagObject = {
|
||||||
|
name: name,
|
||||||
|
color: color,
|
||||||
|
value: value,
|
||||||
|
isNewSystemTag: isNew,
|
||||||
|
systemTagId: isNew ? null : this.newDraftTag.select.id,
|
||||||
|
keyForList: `staged-${Date.now()}-${Math.random().toString(36).substring(2, 15)}` // Unique key
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stagedForBatchAdd.push(stagedTagObject);
|
||||||
|
this.clearDraftTag(); // Reset input fields for the next tag
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Removes a tag from the staged list.
|
||||||
|
* @param {object} tagToUnstage The tag object to remove from staging.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
unstageTag(tagToUnstage) {
|
||||||
|
this.stagedForBatchAdd = this.stagedForBatchAdd.filter(tag => tag.keyForList !== tagToUnstage.keyForList);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Maps a staged tag object to the structure expected by the Tag component.
|
||||||
|
* @param {object} stagedTag The staged tag object.
|
||||||
|
* @returns {object} Object with name, color, value for the Tag component.
|
||||||
|
*/
|
||||||
|
mapStagedTagToDisplayItem(stagedTag) {
|
||||||
|
return {
|
||||||
|
name: stagedTag.name,
|
||||||
|
color: stagedTag.color,
|
||||||
|
value: stagedTag.value,
|
||||||
|
// id: stagedTag.keyForList, // Pass keyForList as id for the Tag component if it expects an id for display/keying internally beyond v-for key
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Clears the staging list, draft inputs, and closes the modal.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
clearStagingAndCloseModal() {
|
||||||
|
this.stagedForBatchAdd = [];
|
||||||
|
this.clearDraftTag(); // Clears input fields
|
||||||
|
this.modal.hide();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Processes all staged tags, adds them to the monitor, and closes the modal.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
confirmAndCommitStagedTags() {
|
||||||
|
for (const sTag of this.stagedForBatchAdd) {
|
||||||
|
// Check if it's an "undo delete"
|
||||||
|
if (sTag.systemTagId) { // Only existing system tags can be an undo delete
|
||||||
|
const undoDeleteIndex = this.deleteTags.findIndex(
|
||||||
|
dTag => dTag.tag_id === sTag.systemTagId && dTag.value === sTag.value
|
||||||
|
);
|
||||||
|
if (undoDeleteIndex > -1) {
|
||||||
|
this.deleteTags.splice(undoDeleteIndex, 1);
|
||||||
|
// If it was an undo, we don't need to add it to newTags again, as it's already in preSelectedTags or newTags from a previous session effectively
|
||||||
|
// However, the plan implies adding to newTags regardless. Let's stick to the plan.
|
||||||
|
// If this tag was previously in preSelectedTags and then deleted (in deleteTags),
|
||||||
|
// removing it from deleteTags makes it active again via selectedTags computed prop.
|
||||||
|
// If it was newly added in this session, then deleted, then re-staged, it would be in newTags.
|
||||||
|
// For simplicity and to align with the plan V.4 which says "Push this object to this.newTags":
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tag object for this.newTags
|
||||||
|
// The `new: true` flag indicates it's part of this transaction for the monitor.
|
||||||
|
// It does not strictly mean it is a new system tag.
|
||||||
|
const tagObjectForNewTags = {
|
||||||
|
id: sTag.systemTagId, // This will be null for brand new system tags
|
||||||
|
color: sTag.color,
|
||||||
|
name: sTag.name,
|
||||||
|
value: sTag.value,
|
||||||
|
new: true, // As per plan, signals new to this monitor transaction
|
||||||
|
};
|
||||||
|
this.newTags.push(tagObjectForNewTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stagedForBatchAdd = [];
|
||||||
|
this.clearDraftTag(); // Resets input fields
|
||||||
|
this.modal.hide();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -191,6 +191,10 @@
|
||||||
"Add New below or Select...": "Add New below or Select…",
|
"Add New below or Select...": "Add New below or Select…",
|
||||||
"Tag with this name already exist.": "Tag with this name already exists.",
|
"Tag with this name already exist.": "Tag with this name already exists.",
|
||||||
"Tag with this value already exist.": "Tag with this value already exists.",
|
"Tag with this value already exist.": "Tag with this value already exists.",
|
||||||
|
"tagAlreadyOnMonitor": "This tag (name + value) is already on this monitor.",
|
||||||
|
"tagAlreadyStaged": "This tag (name + value) is already staged for this batch.",
|
||||||
|
"tagLimitReached": "Cannot stage more than {limit} new system tags in a single batch.",
|
||||||
|
"tagNameExists": "A system tag with this name already exists. Select it from the list or use a different name.",
|
||||||
"color": "Color",
|
"color": "Color",
|
||||||
"value (optional)": "value (optional)",
|
"value (optional)": "value (optional)",
|
||||||
"Gray": "Gray",
|
"Gray": "Gray",
|
||||||
|
@ -1074,7 +1078,7 @@
|
||||||
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
|
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
|
||||||
"SendGrid API Key": "SendGrid API Key",
|
"SendGrid API Key": "SendGrid API Key",
|
||||||
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
|
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
|
||||||
"smtpHelpText": "“SMTPS” tests that SMTP/TLS is working; “Ignore TLS” connects over plaintext; “STARTTLS” connects, issues a STARTTLS command and verifies the server certificate. None of these send an email.",
|
"smtpHelpText": "SMTPS tests that SMTP/TLS is working; Ignore TLS connects over plaintext; STARTTLS connects, issues a STARTTLS command and verifies the server certificate. None of these send an email.",
|
||||||
"Custom URL": "Custom URL",
|
"Custom URL": "Custom URL",
|
||||||
"customUrlDescription": "Will be used as the clickable URL instead of the monitor's one.",
|
"customUrlDescription": "Will be used as the clickable URL instead of the monitor's one.",
|
||||||
"OneChatAccessToken": "OneChat Access Token",
|
"OneChatAccessToken": "OneChat Access Token",
|
||||||
|
@ -1098,5 +1102,7 @@
|
||||||
"Phone numbers": "Phone numbers",
|
"Phone numbers": "Phone numbers",
|
||||||
"Sender name": "Sender name",
|
"Sender name": "Sender name",
|
||||||
"smsplanetNeedToApproveName": "Needs to be approved in the client panel",
|
"smsplanetNeedToApproveName": "Needs to be approved in the client panel",
|
||||||
"Disable URL in Notification": "Disable URL in Notification"
|
"Disable URL in Notification": "Disable URL in Notification",
|
||||||
|
"Add Another Tag": "Add Another Tag",
|
||||||
|
"Staged Tags for Batch Add": "Staged Tags for Batch Add"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue