mirror of
https://github.com/Lissy93/dashy.git
synced 2024-12-25 09:56:15 +03:00
commit
18c2af774d
@ -35,6 +35,8 @@ Dashy has support for displaying dynamic content in the form of widgets. There a
|
|||||||
- [GitHub Trending](#github-trending)
|
- [GitHub Trending](#github-trending)
|
||||||
- [GitHub Profile Stats](#github-profile-stats)
|
- [GitHub Profile Stats](#github-profile-stats)
|
||||||
- [Healthchecks Status](#healthchecks status)
|
- [Healthchecks Status](#healthchecks status)
|
||||||
|
- [Mvg Departure](#mvg-departure)
|
||||||
|
- [Mvg Connection](#mvg-connection)
|
||||||
- **[Self-Hosted Services Widgets](#self-hosted-services-widgets)**
|
- **[Self-Hosted Services Widgets](#self-hosted-services-widgets)**
|
||||||
- [System Info](#system-info)
|
- [System Info](#system-info)
|
||||||
- [Cron Monitoring](#cron-monitoring-health-checks)
|
- [Cron Monitoring](#cron-monitoring-health-checks)
|
||||||
@ -1174,6 +1176,92 @@ Display status of one or more HealthChecks project(s). Works with healthcheck.io
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### MVG Departure
|
||||||
|
|
||||||
|
Display departure time of a MVG (Münchner Verkehrs Gesellschaft) station.
|
||||||
|
|
||||||
|
From https://www.mvg.de/impressum.html:
|
||||||
|
|
||||||
|
> [...] Die Verarbeitung unserer Inhalte oder Daten durch Dritte erfordert unsere ausdrückliche Zustimmung. Für private, nicht-kommerzielle Zwecke, wird eine gemäßigte Nutzung ohne unsere ausdrückliche Zustimmung geduldet. Jegliche Form von Data-Mining stellt keine gemäßigte Nutzung dar.[...]
|
||||||
|
|
||||||
|
In other words: Private, noncomercial, moderate use of the API is tolerated. They don’t consider data mining as moderate use. (This is not a legal advice)
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
**Field** | **Type** | **Required** | **Description**
|
||||||
|
--- | --- | --- | ---
|
||||||
|
**`location`** | `string` | Required | The name of the location (exact) or the location id, startin with `de:09162:`
|
||||||
|
**`limit`** | `integer` | _Optional_ | Limit number of entries, defaults to 10.
|
||||||
|
**`title`** | `string` | _Optional_ | A custom title to be displayed.
|
||||||
|
**`header`** | `bool` | _Optional_ | Shall the title be shown?
|
||||||
|
**`filters`** | `object` | _Optional_ | Filter results
|
||||||
|
**`filters.line`** | `string/array` | _Optional_ | Filter results for given line(s).
|
||||||
|
**`filters.product`** | `string/array` | _Optional_ | Filter results for specific product (TRAM, UBAHN, SBAHN, BUS).
|
||||||
|
**`filters.destination`** | `string/object` | _Optional_ | Filter results for specific destination(s)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: mvg
|
||||||
|
options:
|
||||||
|
location: Marienplatz
|
||||||
|
limit: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Info
|
||||||
|
|
||||||
|
- **CORS**: 🟢 Enabled
|
||||||
|
- **Auth**: 🟢 Not Required
|
||||||
|
- **Price**: 🟢 Free / Private use only
|
||||||
|
- **Host**: [MVG](https://mvg.de)
|
||||||
|
- **Privacy**: _See [MVG Datenschutz](https://www.mvg.de/datenschutz-mvg.html)_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### MVG Connection
|
||||||
|
|
||||||
|
Display the next connection for two addresses/coordinates, stations or POI within Munich using MVG MVG (Münchner Verkehrs Gesellschaft).
|
||||||
|
|
||||||
|
From https://www.mvg.de/impressum.html:
|
||||||
|
|
||||||
|
> [...] Die Verarbeitung unserer Inhalte oder Daten durch Dritte erfordert unsere ausdrückliche Zustimmung. Für private, nicht-kommerzielle Zwecke, wird eine gemäßigte Nutzung ohne unsere ausdrückliche Zustimmung geduldet. Jegliche Form von Data-Mining stellt keine gemäßigte Nutzung dar.[...]
|
||||||
|
|
||||||
|
In other words: Private, noncomercial, moderate use of the API is tolerated. They don’t consider data mining as moderate use. (This is not a legal advice)
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
**Field** | **Type** | **Required** | **Description**
|
||||||
|
--- | --- | --- | ---
|
||||||
|
**`origin`** | `string` | Required | Origin of the connection.
|
||||||
|
**`destination`** | `string` | Required | Destination of the connection.
|
||||||
|
**`title`** | `string` | _Optional_ | A custom title to be displayed.
|
||||||
|
**`header`** | `bool` | _Optional_ | Shall the title be shown?
|
||||||
|
**`filters`** | `object` | _Optional_ | Filter results
|
||||||
|
**`filters.line`** | `string/array` | _Optional_ | Filter results for given line(s).
|
||||||
|
**`filters.product`** | `string/array` | _Optional_ | Filter results for specific product (TRAM, UBAHN, SBAHN, BUS).
|
||||||
|
**`filters.destination`** | `string/object` | _Optional_ | Filter results for specific destination(s)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: mvg-connection
|
||||||
|
options:
|
||||||
|
from: Marienplatz
|
||||||
|
from: Dachauer Straße 123
|
||||||
|
header: true
|
||||||
|
filters:
|
||||||
|
product: [UBAHN]
|
||||||
|
line: [U1,U2,U4,U5]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Info
|
||||||
|
|
||||||
|
- **CORS**: 🟢 Enabled
|
||||||
|
- **Auth**: 🟢 Not Required
|
||||||
|
- **Price**: 🟢 Free / Private use only
|
||||||
|
- **Host**: [MVG](https://mvg.de)
|
||||||
|
- **Privacy**: _See [MVG Datenschutz](https://www.mvg.de/datenschutz-mvg.html)_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
## Self-Hosted Services Widgets
|
## Self-Hosted Services Widgets
|
||||||
|
|
||||||
### System Info
|
### System Info
|
||||||
|
353
src/components/Widgets/Mvg.vue
Normal file
353
src/components/Widgets/Mvg.vue
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mvg-wrapper" v-if="departures">
|
||||||
|
<template
|
||||||
|
v-for="departure in departures"
|
||||||
|
>
|
||||||
|
<div class="departure" v-bind:key="departure.key" v-tooltip="mvgTooltipDeparture(departure)">
|
||||||
|
<span :class="{live: departure.live}">
|
||||||
|
{{ departure.realtimeDepartureTime | formatDepartureTime }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class='line'
|
||||||
|
v-bind:key="departure.key + 'line'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="transport"
|
||||||
|
:class="['type-' + departure.transportType,
|
||||||
|
'line-' + departure.label,
|
||||||
|
]"
|
||||||
|
>{{ departure.label }}</div>
|
||||||
|
<div
|
||||||
|
class='destination'
|
||||||
|
v-tooltip="mvgTooltipDestination(departure)"
|
||||||
|
:class="{cancelled: departure.cancelled}">{{ departure.destination }}</div>
|
||||||
|
<span class="delay"
|
||||||
|
:class="{'has-delay': departure.realtimeDepartureTime > departure.plannedDepartureTime}"
|
||||||
|
>{{ Math.max(0,
|
||||||
|
(departure.realtimeDepartureTime - departure.plannedDepartureTime)/60000) }}</span>
|
||||||
|
<span class="occupancy"
|
||||||
|
:class="'occupancy-' + departure.occupancy"
|
||||||
|
v-if="departure.occupancy != 'UNKNOWN'"
|
||||||
|
v-tooltip="departure.occupancy"
|
||||||
|
>■</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
import { timestampToTime } from '@/utils/MiscHelpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [WidgetMixin],
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
departures: null,
|
||||||
|
locationSearch: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.isLocationId) {
|
||||||
|
this.makeRequest(this.endpointLocation).then(
|
||||||
|
(response) => {
|
||||||
|
const stations = response.filter((r) => r.type === 'STATION');
|
||||||
|
if (stations.length > 0) {
|
||||||
|
this.location = stations[0].globalId;
|
||||||
|
this.fetchData();
|
||||||
|
} else {
|
||||||
|
this.error('Cannot find station for specified string');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.location = this.options.location;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
formatDepartureTime(timestamp) {
|
||||||
|
const msDifference = new Date(timestamp).getTime() - new Date().getTime();
|
||||||
|
const diff = Math.max(0, Math.round(msDifference / 60000));
|
||||||
|
return diff;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLocationId() {
|
||||||
|
if (!this.options.location) {
|
||||||
|
this.error('Location is required');
|
||||||
|
}
|
||||||
|
if (typeof this.options.location !== 'string') this.error('Location can only be a string');
|
||||||
|
if (this.options.location.startsWith('de:09162:')) return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
offset() {
|
||||||
|
if (this.options.offset) return this.options.offset;
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
limit() {
|
||||||
|
return this.options.limit || 10;
|
||||||
|
},
|
||||||
|
endpointDeparture() {
|
||||||
|
return `${widgetApiEndpoints.mvg}/departure?globalId=${this.location}&limit=30&offsetInMinutes=${this.offset}&transportTypes=UBAHN,TRAM,BUS,SBAHN`;
|
||||||
|
},
|
||||||
|
endpointLocation() {
|
||||||
|
return `${widgetApiEndpoints.mvg}/location?query=${encodeURIComponent(this.options.location)}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
update() {
|
||||||
|
this.startLoading();
|
||||||
|
this.fetchData();
|
||||||
|
this.finishLoading();
|
||||||
|
},
|
||||||
|
fetchData() {
|
||||||
|
if (this.location !== undefined) {
|
||||||
|
this.makeRequest(this.endpointDeparture).then(
|
||||||
|
(response) => { this.processData(response); },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* Assign data variables to the returned data */
|
||||||
|
processData(data) {
|
||||||
|
let i = 0;
|
||||||
|
const results = [];
|
||||||
|
data
|
||||||
|
.filter(this.filter_results)
|
||||||
|
.sort(this.sort_results)
|
||||||
|
.slice(0, this.limit).forEach((dep) => {
|
||||||
|
results.push({ ...dep, key: `mvg-dep-${this.location}-${i}` });
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
this.departures = results;
|
||||||
|
},
|
||||||
|
ensure_array(value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return [value];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
filter_results(value) {
|
||||||
|
if (!this.options.filters) return true;
|
||||||
|
let useEntry = (
|
||||||
|
(!this.options.filters.line)
|
||||||
|
|| this.ensure_array(this.options.filters.line).includes(value.label)
|
||||||
|
);
|
||||||
|
useEntry = useEntry
|
||||||
|
&& (
|
||||||
|
(!this.options.filters.product)
|
||||||
|
|| this.ensure_array(this.options.filters.product)
|
||||||
|
.some(x => x.toLowerCase() === value.transportType.toLowerCase())
|
||||||
|
);
|
||||||
|
useEntry = useEntry
|
||||||
|
&& (
|
||||||
|
(!this.options.filters.destination)
|
||||||
|
|| this.ensure_array(this.options.filters.destination)
|
||||||
|
.some(x => x.toLowerCase() === value.destination.toLowerCase())
|
||||||
|
);
|
||||||
|
return useEntry;
|
||||||
|
},
|
||||||
|
sort_results(a, b) {
|
||||||
|
const depa = a.realtimeDepartureTime ? a.realtimeDepartureTime : a.plannedDepartureTime;
|
||||||
|
const depb = b.realtimeDepartureTime ? b.realtimeDepartureTime : b.plannedDepartureTime;
|
||||||
|
if (depa > depb) return 1;
|
||||||
|
if (depa < depb) return -1;
|
||||||
|
if (a.label < b.label) return 1;
|
||||||
|
if (a.label > b.label) return -1;
|
||||||
|
if (a.destination < b.destination) return 1;
|
||||||
|
if (a.destination > b.destination) return -1;
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
makeUrl(cronId) {
|
||||||
|
const base = this.options.host || 'https://healthchecks.io';
|
||||||
|
return `${base}/checks/${cronId}/details`;
|
||||||
|
},
|
||||||
|
mvgTooltipDeparture(data) {
|
||||||
|
let departureDetails = '';
|
||||||
|
if (data.realtime) {
|
||||||
|
departureDetails += `Live: ${timestampToTime(data.realtimeDepartureTime)}<br />`;
|
||||||
|
}
|
||||||
|
departureDetails += `Planned: ${timestampToTime(data.plannedDepartureTime)}<br />`;
|
||||||
|
if (data.realtime) {
|
||||||
|
departureDetails += 'Live!<br />';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
content: departureDetails, html: true, trigger: 'hover', delay: 250, classes: 'mvg-info-tt',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mvgTooltipDestination(data) {
|
||||||
|
let departureDetails = `<b>Infos:</b><br />${data.messages.join('<br />')}`;
|
||||||
|
if (data.platform) {
|
||||||
|
departureDetails += `Platform: ${data.platform}<br />`;
|
||||||
|
}
|
||||||
|
if (data.cancelled) {
|
||||||
|
departureDetails += '<b>Cancelled!</b><br />';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
content: departureDetails, html: true, trigger: 'hover', delay: 250, classes: 'mvg-info-tt',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.mvg-wrapper {
|
||||||
|
display: grid;
|
||||||
|
justify-content: left;
|
||||||
|
grid-template-columns: 1fr 9fr;
|
||||||
|
color: var(--widget-text-color);
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
grid-row-gap: 0.4em;
|
||||||
|
.departure {
|
||||||
|
min-width: 1rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
span.live {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
margin: 0;
|
||||||
|
padding-right: 0.2em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2.2em 1fr minmax(1.5em,max-content) 0.75em;
|
||||||
|
.type-UBAHN {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
.type-SBAHN {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
.type-BUS {
|
||||||
|
}
|
||||||
|
.type-TRAM {
|
||||||
|
}
|
||||||
|
.transport{
|
||||||
|
border-top-left-radius: 0.2em 0.2em;
|
||||||
|
border-bottom-left-radius: 0.2em 0.2em;
|
||||||
|
margin: 0em;
|
||||||
|
padding: 0.15em 0;
|
||||||
|
color: #FFFFFF;
|
||||||
|
margin-right: 0.40em;
|
||||||
|
text-align: center;
|
||||||
|
span {
|
||||||
|
min-width: 2em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
&.line-U1 {
|
||||||
|
background-color: #468447;
|
||||||
|
}
|
||||||
|
&.line-U2 {
|
||||||
|
background-color: #dd3d4d;
|
||||||
|
}
|
||||||
|
&.line-U3 {
|
||||||
|
background-color: #ef8824;
|
||||||
|
}
|
||||||
|
&.line-U4 {
|
||||||
|
background-color: #04af90;
|
||||||
|
}
|
||||||
|
&.line-U5 {
|
||||||
|
background-color: #b78730;
|
||||||
|
}
|
||||||
|
&.line-U6 {
|
||||||
|
background-color: #0472b3;
|
||||||
|
}
|
||||||
|
&.line-S1 {
|
||||||
|
background-color: #79c6e7;
|
||||||
|
}
|
||||||
|
&.line-S2 {
|
||||||
|
background-color: #9bc04c;
|
||||||
|
}
|
||||||
|
&.line-S3 {
|
||||||
|
background-color: #942d8d;
|
||||||
|
}
|
||||||
|
&.line-S4 {
|
||||||
|
background-color: #d4214d;
|
||||||
|
}
|
||||||
|
&.line-S5 {
|
||||||
|
background-color: #03a074;
|
||||||
|
}
|
||||||
|
&.line-S6 {
|
||||||
|
background-color: #03a074;
|
||||||
|
}
|
||||||
|
&.line-S7 {
|
||||||
|
background-color: #964438;
|
||||||
|
}
|
||||||
|
&.line-S8 {
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
&.type-BUS {
|
||||||
|
background-color: #0d5c70;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.destination{
|
||||||
|
border-radius: 0.2em;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
color: #000;
|
||||||
|
padding-top: 0.15em;
|
||||||
|
padding-bottom: 0.15em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
span.cancelled {
|
||||||
|
color: var(--danger);
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
span.destination {
|
||||||
|
overflow: clip;
|
||||||
|
margin-right: 0.25em;
|
||||||
|
width: 75%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay{
|
||||||
|
padding: 0.15em;
|
||||||
|
font-weight: bold;
|
||||||
|
&.has-delay{
|
||||||
|
padding: 0.15em;
|
||||||
|
background-color: var(--danger);
|
||||||
|
color: #FFF;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.delay::before{
|
||||||
|
content: "+";
|
||||||
|
}
|
||||||
|
.occupancy{
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 0.15em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
&.occupancy-LOW {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
&.occupancy-MEDIUM {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
&.occupancy-HIGH {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px dashed var(--widget-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ping-times-tt {
|
||||||
|
min-width: 20rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.mvg-info-tt {
|
||||||
|
min-width: 20rem;
|
||||||
|
}
|
||||||
|
</style>
|
372
src/components/Widgets/MvgConnection.vue
Normal file
372
src/components/Widgets/MvgConnection.vue
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mvg-connections-outer-wrapper">
|
||||||
|
<div class="mvg-connections-header" v-if="showTitle">{{ connectionName }}</div>
|
||||||
|
<div class="mvg-wrapper" v-if="connections">
|
||||||
|
<div
|
||||||
|
v-for="connection in connections"
|
||||||
|
v-bind:key="connection.uniqueId"
|
||||||
|
class="line"
|
||||||
|
v-tooltip="mvgTooltipConnection(connection)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="departure"
|
||||||
|
>
|
||||||
|
<span class="time"
|
||||||
|
>
|
||||||
|
{{connection.parts[0].from.plannedDeparture | formatTime}}
|
||||||
|
</span>
|
||||||
|
<span class="delay"
|
||||||
|
:class="{'has-delay': connection.parts[0].from.departureDelayInMinutes > 0}"
|
||||||
|
>{{ Math.max(parseInt(connection.parts[0].from.departureDelayInMinutes) || 0, 0) }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="changes"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(part,index) in connection.parts"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="index > 0"
|
||||||
|
v-bind:key="'change-' + index"
|
||||||
|
class="change"
|
||||||
|
v-tooltip="part.from.name"
|
||||||
|
>⬌</span>
|
||||||
|
<span
|
||||||
|
v-bind:key="'transport-' + index"
|
||||||
|
:class="['type-' + part.line.transportType,
|
||||||
|
'line-' + part.line.label,
|
||||||
|
]"
|
||||||
|
v-if="part.line.transportType != 'PEDESTRIAN'"
|
||||||
|
class="transport"
|
||||||
|
>{{part.line.label}}</span>
|
||||||
|
<span v-else
|
||||||
|
v-bind:key="'transport-' + index"
|
||||||
|
>🚶</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<span class="time">
|
||||||
|
{{Date.parse(connection.parts[connection.parts.length-1]
|
||||||
|
.to.plannedDeparture) - Date.parse(connection.parts[0]
|
||||||
|
.from.plannedDeparture) | formatDuration}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [WidgetMixin],
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
connections: null,
|
||||||
|
locationSearch: null,
|
||||||
|
connectionName: null,
|
||||||
|
defaultTitle: "Connection",
|
||||||
|
locations: {
|
||||||
|
origin: undefined,
|
||||||
|
destination: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const promStart = this.getLocationId(this.start);
|
||||||
|
const promEnd = this.getLocationId(this.end);
|
||||||
|
Promise.all([promStart, promEnd]).then(
|
||||||
|
(results) => {
|
||||||
|
[this.locations.origin, this.locations.destination] = results.map((r) => r[0]);
|
||||||
|
this.defaultTitle = `${this.locations.origin.name} - ${this.locations.destination.name}`;
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
formatDepartureTime(timestamp) {
|
||||||
|
const msDifference = new Date(timestamp).getTime() - new Date().getTime();
|
||||||
|
const diff = Math.max(0, Math.round(msDifference / 60000));
|
||||||
|
return diff;
|
||||||
|
},
|
||||||
|
formatTime(str) {
|
||||||
|
const d = new Date(Date.parse(str));
|
||||||
|
function ii(i) {
|
||||||
|
let s = `${i}`;
|
||||||
|
if (s.length < 2) s = `0${s}`;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return `${ii(d.getHours())}:${ii(d.getMinutes())}`;
|
||||||
|
},
|
||||||
|
formatDuration(val) {
|
||||||
|
function ii(i) {
|
||||||
|
let s = `${i}`;
|
||||||
|
if (s.length < 2) s = `0${s}`;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return `${Math.floor(val / 3600000)}:${ii(Math.floor(val / 60000))}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
start() {
|
||||||
|
return this.options.from || this.options.start || this.options.origin || 'Marienplatz';
|
||||||
|
},
|
||||||
|
end() {
|
||||||
|
return this.options.to || this.options.end || this.options.destination || 'Giesing';
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
if (this.options.title) {
|
||||||
|
return this.options.title;
|
||||||
|
}
|
||||||
|
return this.defaultTitle;
|
||||||
|
},
|
||||||
|
showTitle() {
|
||||||
|
return (this.options.header) ? this.options.header : true;
|
||||||
|
},
|
||||||
|
transportTypes() {
|
||||||
|
if (this.options.transportations) {
|
||||||
|
return this.options.transportations.join(',');
|
||||||
|
}
|
||||||
|
return 'UBAHN,TRAM,BUS,SBAHN';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatPoint(point, typ) {
|
||||||
|
if (point.type === 'ADDRESS' || point.type === 'POI') {
|
||||||
|
return `${typ}Latitude=${point.latitude}&${typ}Longitude=${point.longitude}`;
|
||||||
|
}
|
||||||
|
return `${typ}StationGlobalId=${point.globalId}`;
|
||||||
|
},
|
||||||
|
isLocationId(loc) {
|
||||||
|
if (!loc) {
|
||||||
|
this.error('Location is required');
|
||||||
|
}
|
||||||
|
if (typeof loc !== 'string') this.error('Location can only be a string');
|
||||||
|
return (loc.startsWith('de:09162:'));
|
||||||
|
},
|
||||||
|
getLocationId(loc) {
|
||||||
|
return this.makeRequest(this.getEndpointLocation(loc));
|
||||||
|
},
|
||||||
|
getEndpointLocation(loc) {
|
||||||
|
return `${widgetApiEndpoints.mvg}/location?query=${encodeURIComponent(loc)}`;
|
||||||
|
},
|
||||||
|
endpointConnection() {
|
||||||
|
return `${widgetApiEndpoints.mvg}/connection?${this.formatPoint(this.locations.origin, 'origin')}&${this.formatPoint(this.locations.destination, 'destination')}&routingDateTime=${(new Date()).toISOString()}&offsetInMinutes=${this.offset}&transportTypes=${this.transportTypes}`;
|
||||||
|
},
|
||||||
|
update() {
|
||||||
|
this.startLoading();
|
||||||
|
this.fetchData();
|
||||||
|
this.finishLoading();
|
||||||
|
},
|
||||||
|
fetchData() {
|
||||||
|
if (this.locations.origin !== undefined
|
||||||
|
&& this.locations.destination !== undefined) {
|
||||||
|
this.makeRequest(this.endpointConnection()).then(
|
||||||
|
(response) => { this.processData(response); },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* Assign data variables to the returned data */
|
||||||
|
processData(data) {
|
||||||
|
this.connections = data;
|
||||||
|
},
|
||||||
|
ensure_array(value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return [value];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
mvgTooltipConnection(data) {
|
||||||
|
let connectionDetails = '';
|
||||||
|
const self = this;
|
||||||
|
function addStep(step) {
|
||||||
|
connectionDetails += `<b>${self.$options.filters.formatTime(step.plannedDeparture)}</b>
|
||||||
|
<span class="delay">+${Math.max(parseInt(step.departureDelayInMinutes, 10) || 0, 0)}</span>
|
||||||
|
<span>${step.name}</span>`;
|
||||||
|
}
|
||||||
|
addStep(data.parts[0].from);
|
||||||
|
data.parts.forEach((part) => {
|
||||||
|
addStep(part.to);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
content: connectionDetails, html: true, trigger: 'hover', delay: 250, classes: 'mvg-connection-detail',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.mvg-header {
|
||||||
|
color: var(--widget-text-color);
|
||||||
|
font-size:1.2em;
|
||||||
|
}
|
||||||
|
.mvg-wrapper {
|
||||||
|
display: grid;
|
||||||
|
justify-content: left;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
color: var(--widget-text-color);
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
grid-row-gap: 0.4em;
|
||||||
|
.departure {
|
||||||
|
min-width: 1rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
span.live {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
margin: 0em;
|
||||||
|
padding: 0em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 5fr 0.75fr;
|
||||||
|
.changes {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.type-UBAHN {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
.type-SBAHN {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
.type-BUS {
|
||||||
|
}
|
||||||
|
.type-TRAM {
|
||||||
|
background-color: #dd3d4d;
|
||||||
|
}
|
||||||
|
.transport{
|
||||||
|
border-radius: 0.2em;
|
||||||
|
margin: 0em;
|
||||||
|
padding: 0.15em 0.15em;
|
||||||
|
color: #FFFFFF;
|
||||||
|
margin-right: 0.40em;
|
||||||
|
margin-left: 0.40em;
|
||||||
|
text-align: center;
|
||||||
|
span {
|
||||||
|
min-width: 2em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
&.line-Fussweg {
|
||||||
|
text-indent: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
&.line-U1 {
|
||||||
|
background-color: #468447;
|
||||||
|
}
|
||||||
|
&.line-U2 {
|
||||||
|
background-color: #dd3d4d;
|
||||||
|
}
|
||||||
|
&.line-U3 {
|
||||||
|
background-color: #ef8824;
|
||||||
|
}
|
||||||
|
&.line-U4 {
|
||||||
|
background-color: #04af90;
|
||||||
|
}
|
||||||
|
&.line-U5 {
|
||||||
|
background-color: #b78730;
|
||||||
|
}
|
||||||
|
&.line-U6 {
|
||||||
|
background-color: #0472b3;
|
||||||
|
}
|
||||||
|
&.line-S1 {
|
||||||
|
background-color: #79c6e7;
|
||||||
|
}
|
||||||
|
&.line-S2 {
|
||||||
|
background-color: #9bc04c;
|
||||||
|
}
|
||||||
|
&.line-S3 {
|
||||||
|
background-color: #942d8d;
|
||||||
|
}
|
||||||
|
&.line-S4 {
|
||||||
|
background-color: #d4214d;
|
||||||
|
}
|
||||||
|
&.line-S5 {
|
||||||
|
background-color: #03a074;
|
||||||
|
}
|
||||||
|
&.line-S6 {
|
||||||
|
background-color: #03a074;
|
||||||
|
}
|
||||||
|
&.line-S7 {
|
||||||
|
background-color: #964438;
|
||||||
|
}
|
||||||
|
&.line-S8 {
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
&.type-BUS {
|
||||||
|
background-color: #0d5c70;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.destination{
|
||||||
|
border-radius: 0.2em;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
color: #000;
|
||||||
|
padding-top: 0.15em;
|
||||||
|
padding-bottom: 0.15em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
span.cancelled {
|
||||||
|
color: var(--danger);
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
span.destination {
|
||||||
|
overflow: clip;
|
||||||
|
margin-right: 0.25em;
|
||||||
|
width: 75%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay{
|
||||||
|
padding: 0.15em;
|
||||||
|
font-weight: bold;
|
||||||
|
&.has-delay{
|
||||||
|
padding: 0.15em;
|
||||||
|
background-color: var(--danger);
|
||||||
|
color: #FFF;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.delay::before{
|
||||||
|
content: "+";
|
||||||
|
}
|
||||||
|
.occupancy{
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 0.15em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
&.occupancy-LOW {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
&.occupancy-MEDIUM {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
&.occupancy-HIGH {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px dashed var(--widget-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ping-times-tt {
|
||||||
|
min-width: 20rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.mvg-connection-detail .tooltip-inner {
|
||||||
|
min-width: 20rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr 6fr;
|
||||||
|
}
|
||||||
|
</style>
|
@ -83,6 +83,8 @@ const COMPAT = {
|
|||||||
image: 'ImageWidget',
|
image: 'ImageWidget',
|
||||||
joke: 'Jokes',
|
joke: 'Jokes',
|
||||||
'mullvad-status': 'MullvadStatus',
|
'mullvad-status': 'MullvadStatus',
|
||||||
|
mvg: 'Mvg',
|
||||||
|
'mvg-connection': 'MvgConnection',
|
||||||
'nd-cpu-history': 'NdCpuHistory',
|
'nd-cpu-history': 'NdCpuHistory',
|
||||||
'nd-load-history': 'NdLoadHistory',
|
'nd-load-history': 'NdLoadHistory',
|
||||||
'nd-ram-history': 'NdRamHistory',
|
'nd-ram-history': 'NdRamHistory',
|
||||||
|
@ -236,6 +236,7 @@ module.exports = {
|
|||||||
jokes: 'https://v2.jokeapi.dev/joke/',
|
jokes: 'https://v2.jokeapi.dev/joke/',
|
||||||
news: 'https://api.currentsapi.services/v1/latest-news',
|
news: 'https://api.currentsapi.services/v1/latest-news',
|
||||||
mullvad: 'https://am.i.mullvad.net/json',
|
mullvad: 'https://am.i.mullvad.net/json',
|
||||||
|
mvg: 'https://www.mvg.de/api/fib/v2/',
|
||||||
publicIp: 'https://ipapi.co/json',
|
publicIp: 'https://ipapi.co/json',
|
||||||
publicIp2: 'https://api.ipgeolocation.io/ipgeo',
|
publicIp2: 'https://api.ipgeolocation.io/ipgeo',
|
||||||
publicIp3: 'http://ip-api.com/json',
|
publicIp3: 'http://ip-api.com/json',
|
||||||
|
Loading…
Reference in New Issue
Block a user