Data browser: cosmetics, reformat
This commit is contained in:
@@ -3,91 +3,91 @@ const GET = (url) => axios.get('/-data'+url)
|
|||||||
const storageGet = (key, def) => localStorage.getItem('data-viewer:'+key) || def
|
const storageGet = (key, def) => localStorage.getItem('data-viewer:'+key) || def
|
||||||
const storageSet = (key, val) => localStorage.setItem('data-viewer:'+key, val)
|
const storageSet = (key, val) => localStorage.setItem('data-viewer:'+key, val)
|
||||||
|
|
||||||
const viewer = new Vue ({
|
const vue = new Vue ({
|
||||||
|
|
||||||
el:'#app',
|
el:'#app',
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
dataSource: storageGet('data-source', 'db'),
|
dataSource: storageGet('data-source', 'db'),
|
||||||
skip: storageGet('skip', 0),
|
skip: storageGet('skip', 0),
|
||||||
top: storageGet('top', 20),
|
top: storageGet('top', 20),
|
||||||
entity: storageGet('entity') ? JSON.parse(storageGet('entity')) : undefined,
|
entity: storageGet('entity') ? JSON.parse(storageGet('entity')) : undefined,
|
||||||
entities: [],
|
entities: [],
|
||||||
columns: [],
|
columns: [],
|
||||||
data: [],
|
data: [],
|
||||||
rowDetails: {},
|
rowDetails: {},
|
||||||
rowKey: storageGet('rowKey')
|
rowKey: storageGet('rowKey')
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
dataSource: (v) => { storageSet('data-source', v); vue.fetchEntities() },
|
||||||
|
skip: (v) => { storageSet('skip', v); if (vue.entity) vue.fetchData() },
|
||||||
|
top: (v) => { storageSet('top', v); if (vue.entity) vue.fetchData() },
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
async fetchEntities () {
|
||||||
|
let url = `/Entities`
|
||||||
|
if (vue.dataSource === 'db') url += `?dataSource=db`
|
||||||
|
const {data} = await GET(url)
|
||||||
|
vue.entities = data.value
|
||||||
|
const entity = vue.entity && vue.entities.find(e => e.name === vue.entity.name)
|
||||||
|
if (entity) { // restore selection from previous fetch
|
||||||
|
vue.columns = entity.columns
|
||||||
|
await vue.fetchData(entity)
|
||||||
|
} else {
|
||||||
|
vue.entity = undefined
|
||||||
|
vue.columns = []
|
||||||
|
vue.data = []
|
||||||
|
vue.rowDetails = {}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
async inspectEntity (eve) {
|
||||||
dataSource: (v) => { storageSet('data-source', v); viewer.fetchEntities() },
|
const entity = vue.entity = vue.entities [eve.currentTarget.rowIndex-1]
|
||||||
skip: (v) => { storageSet('skip', v); if (viewer.entity) viewer.fetchData() },
|
storageSet('entity', JSON.stringify(entity))
|
||||||
top: (v) => { storageSet('top', v); if (viewer.entity) viewer.fetchData() },
|
vue.columns = vue.entities.find(e => e.name === entity.name).columns
|
||||||
|
return await this.fetchData()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
async fetchData () {
|
||||||
|
let url = `/Data?entity=${vue.entity.name}&$skip=${vue.skip}&$top=${vue.top}`
|
||||||
|
if (vue.dataSource === 'db') url += `&dataSource=db`
|
||||||
|
const {data} = await GET(url)
|
||||||
|
vue.data = data.value.map(d => d.record.map(r => r.data))
|
||||||
|
const row = vue.data.find(data => vue._makeRowKey(data) === vue.rowKey)
|
||||||
|
if (row) vue._setRowDetails(row)
|
||||||
|
else vue.rowDetails = {}
|
||||||
|
},
|
||||||
|
|
||||||
async fetchEntities () {
|
inspectRow (eve) {
|
||||||
let url = `/Entities`
|
vue.rowDetails = {}
|
||||||
if (viewer.dataSource === 'db') url += `?dataSource=db`
|
const selectedRow = eve.currentTarget.rowIndex-1
|
||||||
const {data} = await GET(url)
|
vue.rowKey = vue._makeRowKey(vue.data[selectedRow])
|
||||||
viewer.entities = data.value
|
storageSet('rowKey', vue.rowKey)
|
||||||
const entity = viewer.entity && viewer.entities.find(e => e.name === viewer.entity.name)
|
vue._setRowDetails(vue.data[selectedRow])
|
||||||
if (entity) { // restore selection from previous fetch
|
},
|
||||||
viewer.columns = entity.columns
|
|
||||||
await viewer.fetchData(entity)
|
|
||||||
} else {
|
|
||||||
viewer.entity = undefined
|
|
||||||
viewer.columns = []
|
|
||||||
viewer.data = []
|
|
||||||
viewer.rowDetails = {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async inspectEntity (eve) {
|
_setRowDetails(row) {
|
||||||
const entity = viewer.entity = viewer.entities [eve.currentTarget.rowIndex-1]
|
vue.rowDetails = {}
|
||||||
storageSet('entity', JSON.stringify(entity))
|
row.forEach((line, colIndex) => {
|
||||||
viewer.columns = viewer.entities.find(e => e.name === entity.name).columns
|
vue.rowDetails[vue.columns[colIndex].name] = line
|
||||||
return await this.fetchData()
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchData () {
|
_makeRowKey(row) {
|
||||||
let url = `/Data?entity=${viewer.entity.name}&$skip=${viewer.skip}&$top=${viewer.top}`
|
// to identify a row, build a key string out of all key columns' values
|
||||||
if (viewer.dataSource === 'db') url += `&dataSource=db`
|
return row
|
||||||
const {data} = await GET(url)
|
.filter((_, colIndex) => vue.columns[colIndex] && vue.columns[colIndex].isKey)
|
||||||
viewer.data = data.value.map(d => d.record.map(r => r.data))
|
.reduce(((prev, next) => prev += next), '')
|
||||||
const row = viewer.data.find(data => viewer._makeRowKey(data) === viewer.rowKey)
|
},
|
||||||
if (row) viewer._setRowDetails(row)
|
|
||||||
else viewer.rowDetails = {}
|
|
||||||
},
|
|
||||||
|
|
||||||
inspectRow (eve) {
|
|
||||||
viewer.rowDetails = {}
|
|
||||||
const selectedRow = eve.currentTarget.rowIndex-1
|
|
||||||
viewer.rowKey = viewer._makeRowKey(viewer.data[selectedRow])
|
|
||||||
storageSet('rowKey', viewer.rowKey)
|
|
||||||
viewer._setRowDetails(viewer.data[selectedRow])
|
|
||||||
},
|
|
||||||
|
|
||||||
_setRowDetails(row) {
|
|
||||||
viewer.rowDetails = {}
|
|
||||||
row.forEach((line, colIndex) => {
|
|
||||||
viewer.rowDetails[viewer.columns[colIndex].name] = line
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
_makeRowKey(row) {
|
|
||||||
// to identify a row, build a key string out of all key columns' values
|
|
||||||
return row
|
|
||||||
.filter((_, colIndex) => viewer.columns[colIndex] && viewer.columns[colIndex].isKey)
|
|
||||||
.reduce(((prev, next) => prev += next), '')
|
|
||||||
},
|
|
||||||
|
|
||||||
isActiveRow(row) {
|
|
||||||
return viewer._makeRowKey(row) === viewer.rowKey
|
|
||||||
}
|
|
||||||
|
|
||||||
|
isActiveRow(row) {
|
||||||
|
return vue._makeRowKey(row) === vue.rowKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
viewer.fetchEntities()
|
vue.fetchEntities()
|
||||||
|
|||||||
@@ -2,80 +2,80 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Data Browser</title>
|
<title>Data Browser</title>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
|
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
||||||
<style>
|
<style>
|
||||||
.hovering tr:hover td { background: #ebeefc; cursor: pointer; }
|
.hovering tr:hover td { background: #ebeefc; cursor: pointer; }
|
||||||
.highlight { background: #ebeefc !important; }
|
.highlight { background: #ebeefc !important; }
|
||||||
.rating-stars { color:teal }
|
.rating-stars { color:teal }
|
||||||
.succeeded { color:teal }
|
.succeeded { color:teal }
|
||||||
.failed { color:red }
|
.failed { color:red }
|
||||||
.condensed { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.condensed { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.key { font-weight: bold }
|
.key { font-weight: bold }
|
||||||
.not-key { font-weight: lighter;}
|
.not-key { font-weight: lighter;}
|
||||||
.with-sidebar { display: flex; flex-wrap: wrap; gap: 1rem; }
|
.with-sidebar { display: flex; flex-wrap: wrap; gap: 1rem; }
|
||||||
.sidebar { flex-basis: 20rem; flex-grow: 1; }
|
.sidebar { flex-basis: 20rem; flex-grow: 1; }
|
||||||
.not-sidebar { flex-basis: 0; flex-grow: 999; min-inline-size: 50%;}
|
.not-sidebar { flex-basis: 0; flex-grow: 999; min-inline-size: 50%;}
|
||||||
.horizontal label { display: inline; }
|
.horizontal label { display: inline; }
|
||||||
.horizontal input { width: initial; display: inline; }
|
.horizontal input { width: initial; display: inline; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id='app' class="container">
|
<div id='app' class="container">
|
||||||
|
|
||||||
<h1> {{ document.title }}{{ entity ? ' – ' + entity.name : '' }}</h1>
|
<h1> {{ document.title }}{{ entity ? ' – ' + entity.name : '' }}</h1>
|
||||||
|
|
||||||
<div class="with-sidebar">
|
<div class="with-sidebar">
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<div class="horizontal" style="padding: 0.75rem 0;">
|
<div class="horizontal" style="padding: 0.75rem 0;">
|
||||||
<label>Datasource:</label>
|
<label>Datasource:</label>
|
||||||
<input type="radio" id="dataSource-db" value="db" v-model="dataSource">
|
<input type="radio" id="dataSource-db" value="db" v-model="dataSource">
|
||||||
<label for="dataSource-db">Database</label>
|
<label for="dataSource-db">Database</label>
|
||||||
<input type="radio" id="dataSource-srv" value="service" v-model="dataSource">
|
<input type="radio" id="dataSource-srv" value="service" v-model="dataSource">
|
||||||
<label for="dataSource-srv">Service</label>
|
<label for="dataSource-srv">Service</label>
|
||||||
</div>
|
</div>
|
||||||
<table id='entities' class="hovering">
|
<table id='entities' class="hovering">
|
||||||
<thead>
|
<thead>
|
||||||
<th>Entity Name</th>
|
<th>Entity Name</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tr v-for="e in entities" :key="e.name" @click="inspectEntity" :class="{'highlight': (entity && e.name === entity.name)}">
|
<tr v-for="e in entities" :key="e.name" @click="inspectEntity" :class="{'highlight': (entity && e.name === entity.name)}">
|
||||||
<td>{{ e.name }}</td>
|
<td>{{ e.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="not-sidebar">
|
|
||||||
<div class="horizontal">
|
|
||||||
<label for="skip">Skip:</label>
|
|
||||||
<input id="skip" v-model.lazy="skip" title="No. of entries to skip" type="number" min="0">
|
|
||||||
<label for="top">Top:</label>
|
|
||||||
<input id="top" v-model.lazy="top" title="No. of entries to read" type="number" min="0">
|
|
||||||
</div>
|
|
||||||
<div v-if="entity">
|
|
||||||
<table id='data' class="hovering striped-table condensed">
|
|
||||||
<thead>
|
|
||||||
<th v-for="col in columns" :title="col.type" :class="[col.isKey ? 'key' : 'not-key']">{{ col.name }} </th>
|
|
||||||
</thead>
|
|
||||||
<tr v-for="row in data" @click="inspectRow" :class="{'highlight': isActiveRow(row)}">
|
|
||||||
<td v-for="d in row" :title="d">{{ d }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="rowDetails">
|
|
||||||
<table id='rowDetails'>
|
|
||||||
<tr v-for="(key, value) in rowDetails" >
|
|
||||||
<td class="key">{{ value }}</td>
|
|
||||||
<td>{{ key }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="not-sidebar">
|
||||||
|
<div class="horizontal">
|
||||||
|
<label for="skip">Skip:</label>
|
||||||
|
<input id="skip" v-model.lazy="skip" title="No. of entries to skip" type="number" min="0">
|
||||||
|
<label for="top">Top:</label>
|
||||||
|
<input id="top" v-model.lazy="top" title="No. of entries to read" type="number" min="0">
|
||||||
|
</div>
|
||||||
|
<div v-if="entity">
|
||||||
|
<table id='data' class="hovering striped-table condensed">
|
||||||
|
<thead>
|
||||||
|
<th v-for="col in columns" :title="col.type" :class="[col.isKey ? 'key' : 'not-key']">{{ col.name }} </th>
|
||||||
|
</thead>
|
||||||
|
<tr v-for="row in data" @click="inspectRow" :class="{'highlight': isActiveRow(row)}">
|
||||||
|
<td v-for="d in row" :title="d">{{ d }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="rowDetails">
|
||||||
|
<table id='rowDetails'>
|
||||||
|
<tr v-for="(key, value) in rowDetails" >
|
||||||
|
<td class="key">{{ value }}</td>
|
||||||
|
<td>{{ key }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user