refactor: Recorder code to vue3

This commit is contained in:
Shariq Ansari 2022-09-29 14:24:34 +05:30
parent 6bd95516ba
commit 60d9335d38
9 changed files with 384 additions and 369 deletions

View file

@ -22,7 +22,7 @@ class Recorder {
}
show() {
if (!this.view || this.view.$route.name == "recorder-detail") return;
this.view.$router.replace({ name: "recorder-detail" });
if (!this.route || this.route.name == "RecorderDetail") return;
this.router?.replace({ name: "RecorderDetail" });
}
}

View file

@ -5,15 +5,24 @@
<div class="tag-filters-area">
<div class="active-tag-filters">
<button class="btn btn-default btn-xs add-filter text-muted">
{{ __("Add Filter") }}
{{ translated_string("Add Filter") }}
</button>
</div>
</div>
<div class="filter-edit-area"></div>
<div class="sort-selector">
<div class="dropdown"><a class="text-muted dropdown-toggle small" data-toggle="dropdown"><span class="dropdown-text">{{ columns.filter(c => c.slug == query.sort)[0].label }}</span></a>
<div class="dropdown">
<a class="text-muted dropdown-toggle small" data-toggle="dropdown">
<span class="dropdown-text">
{{ columns.filter(c => c.slug == query.sort)[0].label }}
</span>
</a>
<ul class="dropdown-menu">
<li v-for="(column, index) in columns.filter(c => c.sortable)" :key="index" @click="query.sort = column.slug"><a class="option">{{ column.label }}</a></li>
<li v-for="(column, index) in columns.filter(c => c.sortable)" :key="index" @click="query.sort = column.slug">
<a class="option">
{{ column.label }}
</a>
</li>
</ul>
</div>
<button class="btn btn-default btn-xs btn-order">
@ -71,13 +80,17 @@
</div>
<div v-if="requests.length == 0" class="no-result text-muted flex justify-center align-center" style="">
<div class="msg-box no-border" v-if="status.status == 'Inactive'" >
<p><button class="btn btn-primary btn-sm btn-new-doc" @click="start()">{{ __("Start Recording") }}</button></p>
<p>{{ __("Recorder is Inactive.") }}</p>
<p>{{ __("Start recording or drag & drop a previously exported data file to view it.") }}</p>
<p>
<button class="btn btn-primary btn-sm btn-new-doc" @click="start()">
{{ translated_string("Start Recording") }}
</button>
</p>
<p>{{ translated_string("Recorder is Inactive.") }}</p>
<p>{{ translated_string("Start recording or drag & drop a previously exported data file to view it.") }}</p>
</div>
<div class="msg-box no-border" v-if="status.status == 'Active'" >
<p>{{ __("No Requests found") }}</p>
<p>{{ __("Go make some noise") }}</p>
<p>{{ translated_string("No Requests found") }}</p>
<p>{{ translated_string("Go make some noise") }}</p>
</div>
</div>
<div v-else class="list-paging-area">
@ -102,181 +115,193 @@
</div>
</template>
<script>
export default {
name: "RecorderDetail",
data() {
return {
requests: [],
columns: [
{label: __("Path"), slug: "path"},
{label: __("Duration (ms)"), slug: "duration", sortable: true, number: true},
{label: __("Time in Queries (ms)"), slug: "time_queries", sortable: true, number: true},
{label: __("Queries"), slug: "queries", sortable: true, number: true},
{label: __("Method"), slug: "method"},
{label: __("Time"), slug: "time", sortable: true},
],
query: {
sort: "duration",
order: "desc",
filters: {},
pagination: {
limit: 20,
page: 1,
total: 0,
}
},
status: {
color: "grey",
status: "Unknown",
},
};
},
created() {
let route = frappe.get_route();
if (route[2]) {
this.$router.push({name: 'request-detail', params: {id: route[2]}});
}
},
mounted() {
this.fetch_status();
this.refresh();
this.$root.page.set_secondary_action(__("Clear"), () => {
frappe.set_route("recorder");
this.clear();
});
this.$root.page.add_menu_item("Export data", () => this.export_data());
},
computed: {
pages: function() {
const current_page = this.query.pagination.page;
const total_pages = this.query.pagination.total;
return [{
label: __("First"),
number: 1,
status: (current_page == 1) ? "disabled" : "",
},{
label: __("Previous"),
number: Math.max(current_page - 1, 1),
status: (current_page == 1) ? "disabled" : "",
}, {
label: current_page,
number: current_page,
status: "btn-info",
}, {
label: __("Next"),
number: Math.min(current_page + 1, total_pages),
status: (current_page == total_pages) ? "disabled" : "",
}, {
label: __("Last"),
number: total_pages,
status: (current_page == total_pages) ? "disabled" : "",
}];
}
},
methods: {
filtered: function(requests) {
requests = requests.slice();
const filters = Object.entries(this.query.filters);
requests = requests.filter(
(r) => filters.map((f) => (r[f[0]] || "").match(f[1])).every(Boolean)
);
this.query.pagination.total = Math.ceil(requests.length / this.query.pagination.limit);
return requests;
},
paginated: function(requests) {
requests = requests.slice();
const begin = (this.query.pagination.page - 1) * (this.query.pagination.limit);
const end = begin + this.query.pagination.limit;
return requests.slice(begin, end);
},
sorted: function(requests) {
requests = requests.slice();
const order = (this.query.order == "asc") ? 1 : -1;
const sort = this.query.sort;
return requests.sort((a,b) => (a[sort] > b[sort]) ? order : -order);
},
refresh: function() {
frappe.call("frappe.recorder.get").then( r => this.requests = r.message);
},
update: function(message) {
this.requests.push(JSON.parse(message));
},
clear: function() {
frappe.call("frappe.recorder.delete").then(r => this.refresh());
},
start: function() {
frappe.call("frappe.recorder.start").then(r => this.fetch_status());
},
stop: function() {
frappe.call("frappe.recorder.stop").then(r => this.fetch_status());
},
fetch_status: function() {
frappe.call("frappe.recorder.status").then(r => this.update_status(r.message));
},
update_status: function(result) {
if(result) {
this.status = {status: "Active", color: "green"}
} else {
this.status = {status: "Inactive", color: "red"}
}
this.$root.page.set_indicator(this.status.status, this.status.color);
if(this.status.status == "Active") {
frappe.realtime.on("recorder-dump-event", this.update);
} else {
frappe.realtime.off("recorder-dump-event", this.update);
}
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { translated_string } from './utils'
this.update_buttons();
},
update_buttons: function() {
if(this.status.status == "Active") {
this.$root.page.set_primary_action(__("Stop"), () => {
this.stop();
});
} else {
this.$root.page.set_primary_action(__("Start"), () => {
this.start();
});
}
},
route_to_request_detail(request) {
this.$router.push({name: 'request-detail', params: {request, id: request.uuid}});
},
export_data: function() {
if (!this.requests) {
return;
}
frappe.call("frappe.recorder.export_data")
.then((r) => {
const data = r.message;
const filename = `${data[0]['uuid']}..${data[data.length -1]['uuid']}.json`
// variables
let router = ref(useRouter());
let requests = ref([]);
let page = frappe.pages['recorder'].page;
const el = document.createElement('a');
el.setAttribute('href', 'data:application/json,' + encodeURIComponent(JSON.stringify(data)));
el.setAttribute('download', filename);
el.click();
});
},
import_data: function(e) {
if (this.requests.length > 0) {
// don't replace existing capture
return;
}
const request_file = e.dataTransfer.files[0];
let columns = [
{label: __("Path"), slug: "path"},
{label: __("Duration (ms)"), slug: "duration", sortable: true, number: true},
{label: __("Time in Queries (ms)"), slug: "time_queries", sortable: true, number: true},
{label: __("Queries"), slug: "queries", sortable: true, number: true},
{label: __("Method"), slug: "method"},
{label: __("Time"), slug: "time", sortable: true},
];
const file_reader = new FileReader();
file_reader.readAsText(request_file, 'UTF-8');
file_reader.onload = ({target: {result}}) => {
this.requests = JSON.parse(result);
}
}
let query = ref({
sort: "duration",
order: "desc",
filters: {},
pagination: {
limit: 20,
page: 1,
total: 0,
}
};
</script>
<style>
.list-row .level-left {
flex: 8;
width: 100%;
});
let status = ref({
color: "grey",
status: "Unknown",
});
// Started
frappe.recorder.router = router.value;
let route = frappe.get_route();
if (route[2]) {
router.value.push({name: 'RequestDetail', params: {id: route[2]}});
}
// Methods
function filtered(reqs) {
reqs = reqs.slice();
const filters = Object.entries(query.value.filters);
reqs = reqs.filter(
(r) => filters.map((f) => (r[f[0]] || "").match(f[1])).every(Boolean)
);
query.value.pagination.total = Math.ceil(reqs.length / query.value.pagination.limit);
return reqs;
}
function paginated(reqs) {
reqs = reqs.slice();
const begin = (query.value.pagination.page - 1) * (query.value.pagination.limit);
const end = begin + query.value.pagination.limit;
return reqs.slice(begin, end);
}
function sorted(reqs) {
reqs = reqs.slice();
const order = (query.value.order == "asc") ? 1 : -1;
const sort = query.value.sort;
return reqs.sort((a,b) => (a[sort] > b[sort]) ? order : -order);
}
function refresh() {
frappe.call("frappe.recorder.get").then( r => requests.value = r.message);
}
function update(message) {
requests.value.push(JSON.parse(message));
}
function clear() {
frappe.call("frappe.recorder.delete").then(r => refresh());
}
function start() {
frappe.call("frappe.recorder.start").then(r => fetch_status());
}
function stop() {
frappe.call("frappe.recorder.stop").then(r => fetch_status());
}
function fetch_status() {
frappe.call("frappe.recorder.status").then(r => update_status(r.message));
}
function update_status(result) {
if(result) {
status.value = {status: "Active", color: "green"}
} else {
status.value = {status: "Inactive", color: "red"}
}
page.set_indicator(status.value.status, status.value.color);
if(status.value.status == "Active") {
frappe.realtime.on("recorder-dump-event", update);
} else {
frappe.realtime.off("recorder-dump-event", update);
}
update_buttons();
}
function update_buttons() {
if(status.value.status == "Active") {
page.set_primary_action(__("Stop"), () => {
stop();
});
} else {
page.set_primary_action(__("Start"), () => {
start();
});
}
}
function route_to_request_detail(request) {
router.value.beforeEach(async to => {
if (to.meta.shouldFetch) {
to.meta.request = await request
}
});
router.value.push({name: 'RequestDetail', params: {id: request.uuid}});
}
function export_data() {
if (!requests.value) {
return;
}
frappe.call("frappe.recorder.export_data")
.then((r) => {
const data = r.message;
const filename = `${data[0]['uuid']}..${data[data.length -1]['uuid']}.json`
const el = document.createElement('a');
el.setAttribute('href', 'data:application/json,' + encodeURIComponent(JSON.stringify(data)));
el.setAttribute('download', filename);
el.click();
});
}
function import_data(e) {
if (requests.value.length > 0) {
// don't replace existing capture
return;
}
const request_file = e.dataTransfer.files[0];
const file_reader = new FileReader();
file_reader.readAsText(request_file, 'UTF-8');
file_reader.onload = ({target: {result}}) => {
requests.value = JSON.parse(result);
}
}
// Mounted
onMounted(() => {
fetch_status();
refresh();
page.set_secondary_action(__("Clear"), () => {
frappe.set_route("recorder");
clear();
});
page.add_menu_item("Export data", () => export_data());
});
// Computed
let pages = computed(() => {
const current_page = query.value.pagination.page;
const total_pages = query.value.pagination.total;
return [{
label: __("First"),
number: 1,
status: (current_page == 1) ? "disabled" : "",
},{
label: __("Previous"),
number: Math.max(current_page - 1, 1),
status: (current_page == 1) ? "disabled" : "",
}, {
label: current_page,
number: current_page,
status: "btn-info",
}, {
label: __("Next"),
number: Math.min(current_page + 1, total_pages),
status: (current_page == total_pages) ? "disabled" : "",
}, {
label: __("Last"),
number: total_pages,
status: (current_page == total_pages) ? "disabled" : "",
}];
});
</script>
<style>
.list-row .level-left {
flex: 8;
width: 100%;
}
</style>

View file

@ -1,17 +1,20 @@
<template>
<keep-alive include="RecorderDetail">
<router-view/>
</keep-alive>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</router-view>
</template>
<script>
export default {
name: "RecorderRoot",
watch: {
$route() {
frappe.router.current_route = frappe.router.parse();
frappe.breadcrumbs.update();
}
}
};
<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'
let route = useRoute();
watch(route, () => {
frappe.router.current_route = frappe.router.parse();
frappe.breadcrumbs.update();
frappe.recorder.route = route;
});
</script>

View file

@ -16,7 +16,7 @@
</div>
<div class="row form-section visible-section">
<div class="col-sm-10">
<h6 class="form-section-heading uppercase">{{ __("SQL Queries") }}</h6>
<h6 class="form-section-heading uppercase">{{ translated_string("SQL Queries") }}</h6>
</div>
<div class="col-sm-2 filter-list">
<div class="sort-selector">
@ -37,7 +37,7 @@
<div class="checkbox">
<label>
<span class="input-area"><input type="checkbox" class="input-with-feedback bold" data-fieldtype="Check" v-model="group_duplicates"></span>
<span class="label-area small">{{ __("Group Duplicate Queries") }}</span>
<span class="label-area small">{{ translated_string("Group Duplicate Queries") }}</span>
</label>
</div>
</div>
@ -48,15 +48,15 @@
<div class="grid-row">
<div class="data-row row">
<div class="row-index col col-xs-1">
<span>{{ __("Index") }}</span></div>
<span>{{ translated_string("Index") }}</span></div>
<div class="col grid-static-col col-xs-6">
<div class="static-area ellipsis">{{ __("Query") }}</div>
<div class="static-area ellipsis">{{ translated_string("Query") }}</div>
</div>
<div class="col grid-static-col col-xs-2">
<div class="static-area ellipsis text-right">{{ __("Duration (ms)") }}</div>
<div class="static-area ellipsis text-right">{{ translated_string("Duration (ms)") }}"</div>
</div>
<div class="col grid-static-col col-xs-2">
<div class="static-area ellipsis text-right">{{ __("Exact Copies") }}</div>
<div class="static-area ellipsis text-right">{{ translated_string("Exact Copies") }}</div>
</div>
</div>
</div>
@ -82,7 +82,7 @@
<div class="recorder-form-in-grid" v-if="showing == call.index">
<div class="grid-form-heading" @click="showing = null">
<div class="toolbar grid-header-toolbar">
<span class="panel-title">{{ __("SQL Query") }} #<span class="grid-form-row-index">{{ call.index }}</span></span>
<span class="panel-title">{{ translated_string("SQL Query") }} #<span class="grid-form-row-index">{{ call.index }}</span></span>
</div>
</div>
<div class="grid-form-body">
@ -95,25 +95,25 @@
<form>
<div class="frappe-control">
<div class="form-group">
<div class="clearfix"><label class="control-label">{{ __("Query") }}</label></div>
<div class="clearfix"><label class="control-label">{{ translated_string("Query") }}</label></div>
<div class="control-value like-disabled-input for-description"><pre>{{ call.query }}</pre></div>
</div>
</div>
<div class="frappe-control input-max-width">
<div class="form-group">
<div class="clearfix"><label class="control-label">{{ __("Duration (ms)") }}</label></div>
<div class="clearfix"><label class="control-label">{{ translated_string("Duration (ms)") }}"</label></div>
<div class="control-value like-disabled-input">{{ call.duration }}</div>
</div>
</div>
<div class="frappe-control input-max-width">
<div class="form-group">
<div class="clearfix"><label class="control-label">{{ __("Exact Copies") }}</label></div>
<div class="clearfix"><label class="control-label">{{ translated_string("Exact Copies") }}</label></div>
<div class="control-value like-disabled-input">{{ call.exact_copies }}</div>
</div>
</div>
<div class="frappe-control">
<div class="form-group">
<div class="clearfix"><label class="control-label">{{ __("Stack Trace") }}</label></div>
<div class="clearfix"><label class="control-label">{{ translated_string("Stack Trace") }}</label></div>
<div class="control-value like-disabled-input for-description" style="overflow:auto">
<table class="table table-striped">
<thead>
@ -122,11 +122,9 @@
</tr>
</thead>
<tbody>
<template v-for="(row, index) in call.stack">
<tr :key="index">
<td v-for="key in ['filename', 'lineno', 'function']" :key="key">{{ row[key] }}</td>
</tr>
</template>
<tr v-for="(row, index) in call.stack" :key="index">
<td v-for="key in ['filename', 'lineno', 'function']" :key="key">{{ row[key] }}</td>
</tr>
</tbody>
</table>
</div>
@ -134,7 +132,7 @@
</div>
<div class="frappe-control" v-if="call.explain_result[0]">
<div class="form-group">
<div class="clearfix"><label class="control-label">{{ __("SQL Explain") }}</label></div>
<div class="clearfix"><label class="control-label">{{ translated_string("SQL Explain") }}</label></div>
<div class="control-value like-disabled-input for-description" style="overflow:auto">
<table class="table table-striped">
<thead>
@ -162,7 +160,7 @@
</div>
</div>
</div>
<div v-if="request.calls.length == 0" class="grid-empty text-center">{{ __("No Data") }}</div>
<div v-if="request.calls.length == 0" class="grid-empty text-center">{{ translated_string("No Data") }}</div>
</div>
</div>
</div>
@ -192,112 +190,114 @@
</div>
</template>
<script>
export default {
name: "RequestDetail",
data() {
return {
columns: [
{label: __("Path"), slug: "path", type: "Data", class: "col-sm-6"},
{label: __("CMD"), slug: "cmd", type: "Data", class: "col-sm-6"},
{label: __("Time"), slug: "time", type: "Time", class: "col-sm-6"},
{label: __("Duration (ms)"), slug: "duration", type: "Float", class: "col-sm-6"},
{label: __("Number of Queries"), slug: "queries", type: "Int", class: "col-sm-6"},
{label: __("Time in Queries (ms)"), slug: "time_queries", type: "Float", class: "col-sm-6"},
{label: __("Request Headers"), slug: "headers", type: "Small Text", formatter: value => `<pre class="for-description like-disabled-input">${JSON.stringify(value, null, 4)}</pre>`, class: "col-sm-12"},
{label: __("Form Dict"), slug: "form_dict", type: "Small Text", formatter: value => `<pre class="for-description like-disabled-input">${JSON.stringify(value, null, 4)}</pre>`, class: "col-sm-12"},
],
table_columns: [
{label: __("Execution Order"), slug: "index", sortable: true},
{label: __("Duration (ms)"), slug: "duration", sortable: true},
{label: __("Exact Copies"), slug: "exact_copies", sortable: true},
],
query: {
sort: "duration",
order: "desc",
pagination: {
limit: 20,
page: 1,
total: 0,
}
},
group_duplicates: false,
showing: null,
request: {
calls: [],
},
};
},
computed: {
pages: function() {
const current_page = this.query.pagination.page;
const total_pages = this.query.pagination.total;
return [{
label: __("First"),
number: 1,
status: (current_page == 1) ? "disabled" : "",
},{
label: __("Previous"),
number: Math.max(current_page - 1, 1),
status: (current_page == 1) ? "disabled" : "",
}, {
label: current_page,
number: current_page,
status: "btn-info",
}, {
label: __("Next"),
number: Math.min(current_page + 1, total_pages),
status: (current_page == total_pages) ? "disabled" : "",
}, {
label: __("Last"),
number: total_pages,
status: (current_page == total_pages) ? "disabled" : "",
}];
}
},
methods: {
paginated: function(calls) {
calls = calls.slice();
this.query.pagination.total = Math.ceil(calls.length / this.query.pagination.limit);
const begin = (this.query.pagination.page - 1) * (this.query.pagination.limit);
const end = begin + this.query.pagination.limit;
return calls.slice(begin, end);
},
sorted: function(calls) {
calls = calls.slice();
const order = (this.query.order == "asc") ? 1 : -1;
const sort = this.query.sort;
return calls.sort((a,b) => (a[sort] > b[sort]) ? order : -order);
},
grouped: function(calls) {
if(this.group_duplicates) {
calls = calls.slice();
return calls.uniqBy(call => call["query"]);
}
return calls
},
},
mounted() {
frappe.breadcrumbs.add({
type: 'Custom',
label: __('Recorder'),
route: '/app/recorder'
});
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { translated_string } from './utils'
// variables
let route = ref(useRoute());
let columns = [
{label: __("Path"), slug: "path", type: "Data", class: "col-sm-6"},
{label: __("CMD"), slug: "cmd", type: "Data", class: "col-sm-6"},
{label: __("Time"), slug: "time", type: "Time", class: "col-sm-6"},
{label: __("Duration (ms)"), slug: "duration", type: "Float", class: "col-sm-6"},
{label: __("Number of Queries"), slug: "queries", type: "Int", class: "col-sm-6"},
{label: __("Time in Queries (ms)"), slug: "time_queries", type: "Float", class: "col-sm-6"},
{label: __("Request Headers"), slug: "headers", type: "Small Text", formatter: value => `<pre class="for-description like-disabled-input">${JSON.stringify(value, null, 4)}</pre>`, class: "col-sm-12"},
{label: __("Form Dict"), slug: "form_dict", type: "Small Text", formatter: value => `<pre class="for-description like-disabled-input">${JSON.stringify(value, null, 4)}</pre>`, class: "col-sm-12"},
];
let table_columns = [
{label: __("Execution Order"), slug: "index", sortable: true},
{label: __("Duration (ms)"), slug: "duration", sortable: true},
{label: __("Exact Copies"), slug: "exact_copies", sortable: true},
];
let query = ref({
sort: "duration",
order: "desc",
pagination: {
limit: 20,
page: 1,
total: 0,
}
});
let group_duplicates = ref(false);
let showing = ref(null);
let request = ref({
calls: [],
});
// Methods
function paginated(calls) {
calls = calls.slice();
query.value.pagination.total = Math.ceil(calls.length / query.value.pagination.limit);
const begin = (query.value.pagination.page - 1) * (query.value.pagination.limit);
const end = begin + query.value.pagination.limit;
return calls.slice(begin, end);
}
function sorted(calls) {
calls = calls.slice();
const order = (query.value.order == "asc") ? 1 : -1;
const sort = query.value.sort;
return calls.sort((a,b) => (a[sort] > b[sort]) ? order : -order);
}
function grouped(calls) {
if(group_duplicates.value) {
calls = calls.slice();
return calls.uniqBy(call => call["query"]);
}
return calls
}
// Computed
let pages = computed(() => {
const current_page = query.value.pagination.page;
const total_pages = query.value.pagination.total;
return [{
label: __("First"),
number: 1,
status: (current_page == 1) ? "disabled" : "",
},{
label: __("Previous"),
number: Math.max(current_page - 1, 1),
status: (current_page == 1) ? "disabled" : "",
}, {
label: current_page,
number: current_page,
status: "btn-info",
}, {
label: __("Next"),
number: Math.min(current_page + 1, total_pages),
status: (current_page == total_pages) ? "disabled" : "",
}, {
label: __("Last"),
number: total_pages,
status: (current_page == total_pages) ? "disabled" : "",
}];
});
// Mounted
onMounted(async () => {
frappe.breadcrumbs.add({
type: 'Custom',
label: __('Recorder'),
route: '/app/recorder'
});
const req = route.value.meta.request;
const id = route.value.params.id;
if (req && (req.headers || req.form_dict || req.calls)) {
// complete request data passed as parameter.
request.value = req;
} else {
let r = await frappe.call({
method: "frappe.recorder.get",
args: {
uuid: req?.uuid || id,
}
});
request.value = r.message;
}
});
const request = this.$route.params.request;
if (request.headers || request.form_dict || request.calls) {
// complete request data passed as parameter.
this.request = request;
} else {
frappe.call({
method: "frappe.recorder.get",
args: {
uuid: request.uuid
}
}).then( r => {
this.request = r.message
});
}
},
};
</script>

View file

@ -0,0 +1,5 @@
import { createApp } from "vue";
import RecorderRoot from "./RecorderRoot.vue";
import router from "./router.js";
frappe.recorder.view = createApp(RecorderRoot).use(router).mount(".recorder-container");

View file

@ -1,48 +0,0 @@
import Vue from "vue/dist/vue.js";
import VueRouter from "vue-router/dist/vue-router.js";
import RecorderRoot from "./RecorderRoot.vue";
import RecorderDetail from "./RecorderDetail.vue";
import RequestDetail from "./RequestDetail.vue";
Vue.prototype.__ = window.__;
Vue.prototype.frappe = window.frappe;
Vue.use(VueRouter);
const routes = [
{
name: "recorder-detail",
path: "/detail",
component: RecorderDetail,
},
{
name: "request-detail",
path: "/request/:id",
component: RequestDetail,
},
{
path: "/",
redirect: {
name: "recorder-detail",
},
},
];
const router = new VueRouter({
mode: "history",
base: "/app/recorder/",
routes: routes,
});
frappe.recorder.view = new Vue({
el: ".recorder-container",
router: router,
data: {
page: frappe.pages["recorder"].page,
},
template: "<recorder-root/>",
components: {
RecorderRoot,
},
});

View file

@ -0,0 +1,28 @@
import { createWebHistory, createRouter } from "vue-router";
import RecorderDetail from "./RecorderDetail.vue";
import RequestDetail from "./RequestDetail.vue";
const routes = [
{
path: "/detail",
name: "RecorderDetail",
component: RecorderDetail,
},
{
path: "/request/:id",
name: "RequestDetail",
component: RequestDetail,
meta: { shouldFetch: true },
},
{
path: "/",
redirect: "/detail",
},
];
const router = createRouter({
history: createWebHistory("/app/recorder/"),
routes,
});
export default router;

View file

@ -0,0 +1,3 @@
export function translated_string(string) {
return __(string);
}

View file

@ -1 +0,0 @@
import "./frappe/recorder/recorder";