diff --git a/docs/widgets.md b/docs/widgets.md index 72ee3de0..82433158 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -40,6 +40,7 @@ Dashy has support for displaying dynamic content in the form of widgets. There a - [Mvg Connection](#mvg-connection) - [Custom search](#custom-search) - [Rescuetime overview](#rescuetime-overview) + - [Minecraft Server](#minecraft-server) - **[Self-Hosted Services Widgets](#self-hosted-services-widgets)** - [System Info](#system-info) - [Cron Monitoring](#cron-monitoring-health-checks) @@ -1378,6 +1379,45 @@ Show an overview of how you have spent your time for the current day. --- +### Minecraft Server + +Show minecraft server status + +<p align="center"><img width="380" src="https://i.ibb.co/hcmd4Wf/minecraft-widget.png" /></p> + +#### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`title`** | `string` | _Optional_ | Display title for server uses server address if not set. +**`server`** | `string` | Required | Server hostname or ip(:port) will use srv records. +**`bedrock`** | `boolean` | _Optional_ | If server is a bedrock edition server. (default false) +**`showMods`** | `boolean` | _Optional_ | Display mod list if available +**`showPlayers`** | `boolean` | _Optional_ | Display player list if available +**`showPlugins`** | `boolean` | _Optional_ | Display plugin list if available + +#### Example + +```yaml + - type: minecraft-status + options: + title: Venity Network + server: play.venitymc.com + bedrock: true + showMods: true + showPlayers: true + showPlugins: true +``` +#### Info + +- **CORS**: 🟢 Enabled +- **Auth**: 🟢 Not Required +- **Price**: 🟢 Free +- **Host**: [Minecraft Server Status](https://mcsrvstat.us/) +- **Privacy**: _See [Minecraft Server Status FAQ](https://mcsrvstat.us/faq)_ + +--- + ## Self-Hosted Services Widgets diff --git a/src/components/Widgets/MinecraftStatus.vue b/src/components/Widgets/MinecraftStatus.vue new file mode 100644 index 00000000..fc20f96d --- /dev/null +++ b/src/components/Widgets/MinecraftStatus.vue @@ -0,0 +1,258 @@ +<template> + <div class="minecraft-wrapper"> + <a class="minecraft-link" :href="serverLinkEndpoint"> + <h3 class="minecraft-title">{{ title }}</h3> + </a> + <div class="minecraft-icon-wrapper"> + <img :src="icon" alt="server-icon" class="minecraft-icon" /> + </div> + <div class="minecraft-content-wrapper"> + <StatusIndicator class="status-indicator" :statusSuccess="status ? online : undefined" + :statusText="status ? statusTooltip : undefined" /> + <span v-if="title != server" class="minecraft-server">{{ server }}<br /></span> + <span v-if="!online" class="minecraft-status">Server Offline</span> + <span v-if="online" class="minecraft-version"> + {{ software || (bedrock ? "Bedrock" : "Minecraft") }} {{ version }} + </span> + <ul v-if="online" class="minecraft-motd"> + <li v-for="(line, idx) in motd" :key="idx">{{ line }}</li> + </ul> + <div v-if="showPlayers" class="player-list"> + <span>{{ onlinePlayers }}/{{ maxPlayers }} Players</span> + <ul> + <li v-for="{ name, uuid } in players" :key="uuid"> + <a :href="playerLinkEndpoint(uuid)"> + <img :src="playerIconEndpoint(uuid)" :alt="`${name}'s Head'`"/>{{ name }} + </a> + </li> + </ul> + </div> + <div v-if="showMods" class="mod-list"> + <span>{{ mods.length }} Mods</span> + <ul> + <li v-for="{ name, version } in mods" :key="name"> + {{ name }}={{ version }} + </li> + </ul> + </div> + <div v-if="showPlugins" class="plugin-list"> + <span>{{ plugins.length }} Plugins</span> + <ul> + <li v-for="{ name, version } in plugins" :key="name"> + {{ name }}={{ version }} + </li> + </ul> + </div> + </div> + </div> +</template> + +<script> +import WidgetMixin from '@/mixins/WidgetMixin'; +import { widgetApiEndpoints } from '@/utils/defaults'; +import StatusIndicator from '@/components/LinkItems/StatusIndicator.vue'; + +export default { + mixins: [WidgetMixin], + components: { + StatusIndicator, + }, + data() { + return { + status: false, + online: false, + ip: '', + port: 0, + hostname: '', + iconData: null, + motd: null, + version: null, + software: null, + gamemode: null, + mods: [], + plugins: [], + players: [], + onlinePlayers: null, + maxPlayers: null, + }; + }, + computed: { + title() { + return this.options.title || this.options.server || this.error('options.server not set'); + }, + alt() { + return this.title; + }, + icon() { + return `https://api.mcsrvstat.us/icon/${this.server}`; + }, + server() { + return this.options.server || this.error('options.server not set'); + }, + bedrock() { + return this.options.bedrock === true; + }, + statusTooltip() { + if (!this.status) { + return 'Loading...'; + } + if (!this.online) { + return `${this.server} Offline`; + } + return `${this.onlinePlayers}/${this.maxPlayers} Online`; + }, + showPlayers() { + return this.options.showPlayers && this.players.length > 0; + }, + showMods() { + return this.options.showMods && this.mods.length > 0; + }, + showPlugins() { + return this.options.showPlugins && this.plugins.length > 0; + }, + endpoint() { + return `${widgetApiEndpoints.minecraftStatus}${this.bedrock ? 'bedrock/' : ''}3/${this.server}`; + }, + serverLinkEndpoint() { + return `${widgetApiEndpoints.minecraftServerLink}${this.server}`; + }, + }, + methods: { + playerIconEndpoint(uuid) { + return `${widgetApiEndpoints.minecraftPlayerIcon}${uuid}/32.png`; + }, + playerLinkEndpoint(uuid) { + return `${widgetApiEndpoints.minecraftPlayerLink}${uuid}`; + }, + /* Make GET request to McSrvStat.US API endpoint */ + fetchData() { + this.startLoading(); + fetch(this.endpoint) + .then(response => { + if (!response.ok) { + this.error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + this.processData(data); + }) + .catch(dataFetchError => { + this.error('Unable to fetch data', dataFetchError); + }) + .finally(() => { + this.finishLoading(); + }); + }, + /* Assign data variables to the returned data */ + processData(data) { + this.online = data.online; + this.ip = data.ip; + this.port = data.port; + this.hostname = data.hostname; + if (this.online) { + this.version = data.version; + this.iconData = data.icon; + this.software = data.software; + this.gamemode = data.gamemode; + this.motd = data.motd.clean || []; + this.players = data.players.list || []; + this.onlinePlayers = data.players.online; + this.maxPlayers = data.players.max; + this.mods = data.mods || []; + this.plugins = data.plugins || []; + } + this.status = true; + }, + update() { + this.fetchData(); + }, + }, +}; +</script> + +<style scoped lang="scss"> + +.minecraft-wrapper { + margin-top: -1em; + display: grid; + justify-content: center; + grid-template-columns: 64px 1fr; + color: var(--widget-text-color); + padding-top: 0.5rem; + + .minecraft-link { + grid-column: 1 / span 2; + text-decoration: none; + + .minecraft-title { + font-size: 1.2rem; + margin: 0.25rem auto; + border-bottom: 1px solid var(--widget-text-color); + color: var(--widget-text-color); + } + } + + .minecraft-icon { + display: flex; + width: 100%; + max-width: 64px; + margin: 0.25rem auto 0; + padding-top: 0.5rem; + border-radius: var(--curve-factor); + } + + .minecraft-content-wrapper { + position: relative; + padding: 0.5rem; + + ul.minecraft-motd { + border-top: 1px dashed var(--widget-text-color); + list-style-type: none; + padding: 0.5rem 0; + margin: 0.5rem 0 0 0; + } + + .player-list, + .mod-list, + .plugin-list { + span { + font-size: 1.2rem; + border-top: 1px dashed var(--widget-text-color); + padding: 0.25rem 0; + display: block; + } + ul { + list-style-type: '- '; + padding: 0; + margin: 0; + li { + padding: 1rem 0; + margin: 0; + } + } + } + + .player-list { + ul { + li { + padding: 0; + list-style-type: none; + img { + width: 1rem; + height: 1rem; + float: left; + position: relative; + margin-right: 1em; + } + } + } + } + } +} +</style> +<style lang="scss"> +.minecraft-alt-tt { + min-width: 20rem; +} +</style> diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index c4d4220f..b2e57caa 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -86,6 +86,7 @@ const COMPAT = { iframe: 'IframeWidget', image: 'ImageWidget', joke: 'Jokes', + 'minecraft-status': 'MinecraftStatus', 'mullvad-status': 'MullvadStatus', mvg: 'Mvg', linkding: 'Linkding', diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 88b5757d..787c440b 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -240,6 +240,10 @@ module.exports = { holidays: 'https://kayaposoft.com/enrico/json/v2.0/?action=getHolidaysForDateRange', jokes: 'https://v2.jokeapi.dev/joke/', news: 'https://api.currentsapi.services/v1/latest-news', + minecraftPlayerIcon: 'https://mc-heads.net/avatar/', + minecraftPlayerLink: 'https://minecraftuuid.com/?search=', + minecraftServerLink: 'https://mcsrvstat.us/server/', + minecraftStatus: 'https://api.mcsrvstat.us/', mullvad: 'https://am.i.mullvad.net/json', mvg: 'https://www.mvg.de/api/fib/v2/', publicIp: 'https://ipapi.co/json',