mirror of
https://github.com/jpros/tacticalrmm-web.git
synced 2026-02-27 14:51:21 +00:00
223 lines
6.4 KiB
Vue
223 lines
6.4 KiB
Vue
<template>
|
|
<div v-if="!selectedAgentPk">No agent selected</div>
|
|
<div v-else class="q-pa-xs">
|
|
<q-btn size="sm" color="grey-5" icon="fas fa-plus" label="Add Note" text-color="black" @click="addNote" />
|
|
<q-btn dense flat push @click="refreshNotes" icon="refresh" />
|
|
<q-btn class="q-ml-xl" size="sm" color="primary" icon-right="archive" label="Export to csv" @click="exportTable" />
|
|
<template v-if="notes === undefined || notes.length === 0">
|
|
<p>No Notes</p>
|
|
</template>
|
|
<template v-else>
|
|
<q-table
|
|
grid
|
|
class="tabs-tbl-sticky"
|
|
:style="{ 'max-height': tabsTableHeight }"
|
|
:data="notes"
|
|
:columns="columns"
|
|
:visible-columns="visibleColumns"
|
|
:pagination.sync="pagination"
|
|
row-key="id"
|
|
:rows-per-page-options="[0]"
|
|
hide-bottom
|
|
>
|
|
<template v-slot:top-right></template>
|
|
<template v-slot:item="props">
|
|
<q-card class="notes-card q-pa-none q-ma-xs" bordered>
|
|
<q-card-section>
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="text-subtitle2">{{ props.row.entry_time }}</div>
|
|
<div class="text-caption">{{ props.row.username }}</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn color="grey-7" round flat icon="more_vert">
|
|
<q-menu cover auto-close>
|
|
<q-list>
|
|
<q-btn color="primary" push flat icon="edit" label="Edit" @click="editNote(props.row.id)" />
|
|
<br />
|
|
<q-btn
|
|
color="negative"
|
|
push
|
|
flat
|
|
icon="delete"
|
|
label="Delete"
|
|
@click="deleteNote(props.row.id)"
|
|
/>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
<q-card-section style="max-height: 20vh" class="scroll">
|
|
<pre>{{ props.row.note }}</pre>
|
|
</q-card-section>
|
|
</q-card>
|
|
</template>
|
|
</q-table>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapState, mapGetters } from "vuex";
|
|
import mixins from "@/mixins/mixins";
|
|
import { exportFile } from "quasar";
|
|
|
|
function wrapCsvValue(val, formatFn) {
|
|
let formatted = formatFn !== void 0 ? formatFn(val) : val;
|
|
|
|
formatted = formatted === void 0 || formatted === null ? "" : String(formatted);
|
|
|
|
formatted = formatted.split('"').join('""').split("\n").join("\\n").split("\r").join("\\r");
|
|
|
|
return `"${formatted}"`;
|
|
}
|
|
|
|
export default {
|
|
name: "NotesTab",
|
|
mixins: [mixins],
|
|
data() {
|
|
return {
|
|
note: {},
|
|
pagination: {
|
|
rowsPerPage: 0,
|
|
sortBy: "id",
|
|
descending: false,
|
|
},
|
|
columns: [
|
|
{ name: "id", label: "ID", field: "id" },
|
|
{
|
|
name: "entry_time",
|
|
label: "Date",
|
|
field: "entry_time",
|
|
},
|
|
{
|
|
name: "username",
|
|
label: "User",
|
|
field: "username",
|
|
},
|
|
{
|
|
name: "note",
|
|
label: "Note",
|
|
field: "note",
|
|
},
|
|
],
|
|
visibleColumns: ["entry_time", "username", "note"],
|
|
};
|
|
},
|
|
methods: {
|
|
addNote() {
|
|
this.$q
|
|
.dialog({
|
|
title: "Add Note",
|
|
prompt: {
|
|
type: "textarea",
|
|
},
|
|
style: "width: 30vw; max-width: 50vw;",
|
|
ok: { label: "Add" },
|
|
})
|
|
.onOk(data => {
|
|
this.$axios
|
|
.post(`/agents/${this.selectedAgentPk}/notes/`, { note: data })
|
|
.then(r => {
|
|
this.refreshNotes();
|
|
this.notifySuccess(r.data);
|
|
})
|
|
.catch(() => this.notifyError("Something went wrong"));
|
|
});
|
|
},
|
|
editNote(pk) {
|
|
this.$axios
|
|
.get(`/agents/${pk}/note/`)
|
|
.then(r => {
|
|
this.note = r.data;
|
|
this.$q
|
|
.dialog({
|
|
title: "Edit Note",
|
|
prompt: {
|
|
model: this.note.note,
|
|
type: "textarea",
|
|
},
|
|
style: "width: 30vw; max-width: 50vw;",
|
|
ok: { label: "Save" },
|
|
cancel: true,
|
|
})
|
|
.onOk(data => {
|
|
this.note.note = data;
|
|
this.$axios
|
|
.patch(`/agents/${pk}/note/`, this.note)
|
|
.then(r => {
|
|
this.refreshNotes();
|
|
this.notifySuccess(r.data);
|
|
})
|
|
.catch(() => this.notifyError("Something went wrong"));
|
|
})
|
|
.onDismiss(() => {
|
|
this.note = {};
|
|
});
|
|
})
|
|
.catch(() => this.notifyError("Something went wrong"));
|
|
},
|
|
deleteNote(pk) {
|
|
this.$q
|
|
.dialog({
|
|
title: "Delete note?",
|
|
cancel: true,
|
|
ok: { label: "Delete", color: "negative" },
|
|
})
|
|
.onOk(() => {
|
|
this.$axios
|
|
.delete(`/agents/${pk}/note/`)
|
|
.then(r => {
|
|
this.refreshNotes();
|
|
this.notifySuccess(r.data);
|
|
})
|
|
.catch(() => this.notifyError("Something went wrong"));
|
|
});
|
|
},
|
|
refreshNotes() {
|
|
this.$store.dispatch("loadNotes", this.selectedAgentPk);
|
|
},
|
|
exportTable() {
|
|
// naive encoding to csv format
|
|
const content = [this.columns.map(col => wrapCsvValue(col.label))]
|
|
.concat(
|
|
this.notes.map(row =>
|
|
this.columns
|
|
.map(col =>
|
|
wrapCsvValue(
|
|
typeof col.field === "function" ? col.field(row) : row[col.field === void 0 ? col.name : col.field],
|
|
col.format
|
|
)
|
|
)
|
|
.join(",")
|
|
)
|
|
)
|
|
.join("\r\n");
|
|
|
|
const status = exportFile(`${this.agentHostname}-notes.csv`, content, "text/csv");
|
|
|
|
if (status !== true) {
|
|
this.$q.notify({
|
|
message: "Browser denied file download...",
|
|
color: "negative",
|
|
icon: "warning",
|
|
});
|
|
}
|
|
},
|
|
},
|
|
computed: {
|
|
...mapState({
|
|
notes: state => state.notes,
|
|
}),
|
|
...mapGetters(["selectedAgentPk", "tabsTableHeight", "agentHostname"]),
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="sass" scoped>
|
|
.notes-card
|
|
width: 100%
|
|
max-width: 20vw
|
|
</style> |