]>
Commit | Line | Data |
---|---|---|
b9c5fcf0 BW |
1 | <template> |
2 | <div | |
3 | id="app" | |
4 | v-if="config" | |
5 | :class="[ | |
6 | `theme-${config.theme}`, | |
34259e1e | 7 | `page-${currentPage}`, |
b9c5fcf0 | 8 | isDark ? 'is-dark' : 'is-light', |
9814a037 | 9 | !config.footer ? 'no-footer' : '', |
b9c5fcf0 BW |
10 | ]" |
11 | > | |
12 | <DynamicTheme :themes="config.colors" /> | |
13 | <div id="bighead"> | |
14 | <section v-if="config.header" class="first-line"> | |
15 | <div v-cloak class="container"> | |
16 | <div class="logo"> | |
ba07da6b BW |
17 | <a href="#"> |
18 | <img v-if="config.logo" :src="config.logo" alt="dashboard logo" /> | |
19 | </a> | |
239ef168 | 20 | <i v-if="config.icon" :class="config.icon"></i> |
b9c5fcf0 | 21 | </div> |
4f56c2c1 BW |
22 | <div |
23 | class="dashboard-title" | |
24 | :class="{ 'no-logo': !config.icon || !config.logo }" | |
25 | > | |
b9c5fcf0 BW |
26 | <span class="headline">{{ config.subtitle }}</span> |
27 | <h1>{{ config.title }}</h1> | |
28 | </div> | |
29 | </div> | |
30 | </section> | |
31 | ||
32 | <Navbar | |
33 | :open="showMenu" | |
34 | :links="config.links" | |
ed8b17e0 | 35 | @navbar-toggle="showMenu = !showMenu" |
b9c5fcf0 | 36 | > |
5db2414d A |
37 | <DarkMode |
38 | @updated="isDark = $event" | |
39 | :defaultValue="this.config.defaults.colorTheme" | |
40 | /> | |
b9c5fcf0 BW |
41 | |
42 | <SettingToggle | |
43 | @updated="vlayout = $event" | |
44 | name="vlayout" | |
45 | icon="fa-list" | |
46 | iconAlt="fa-columns" | |
5db2414d | 47 | :defaultValue="this.config.defaults.layout == 'columns'" |
b9c5fcf0 BW |
48 | /> |
49 | ||
50 | <SearchInput | |
51 | class="navbar-item is-inline-block-mobile" | |
b4a2db6e | 52 | :hotkey="searchHotkey()" |
9582b187 | 53 | @input="filterServices($event.target?.value)" |
ed8b17e0 | 54 | @search-focus="showMenu = true" |
9582b187 BW |
55 | @search-open="navigateToFirstService($event?.target?.value)" |
56 | @search-cancel="filterServices()" | |
b9c5fcf0 BW |
57 | /> |
58 | </Navbar> | |
59 | </div> | |
60 | ||
61 | <section id="main-section" class="section"> | |
62 | <div v-cloak class="container"> | |
e9113b48 BW |
63 | <ConnectivityChecker |
64 | v-if="config.connectivityCheck" | |
ed8b17e0 | 65 | @network-status-update="offline = $event" |
e9113b48 | 66 | /> |
f3980069 | 67 | |
4f56c2c1 | 68 | <GetStarted v-if="configurationNeeded" /> |
f3980069 | 69 | |
b9c5fcf0 BW |
70 | <div v-if="!offline"> |
71 | <!-- Optional messages --> | |
72 | <Message :item="config.message" /> | |
73 | ||
74 | <!-- Horizontal layout --> | |
75 | <div v-if="!vlayout || filter" class="columns is-multiline"> | |
4d35b1fd | 76 | <template v-for="(group, groupIndex) in services"> |
9bb51bb2 | 77 | <h2 |
4d35b1fd ES |
78 | v-if="group.name" |
79 | class="column is-full group-title" | |
cc0bc909 ES |
80 | :key="`header-${groupIndex}`" |
81 | > | |
da6e676d | 82 | <i v-if="group.icon" :class="['fa-fw', group.icon]"></i> |
42477020 GC |
83 | <div v-else-if="group.logo" class="group-logo media-left"> |
84 | <figure class="image is-48x48"> | |
85 | <img :src="group.logo" :alt="`${group.name} logo`" /> | |
86 | </figure> | |
87 | </div> | |
b9c5fcf0 BW |
88 | {{ group.name }} |
89 | </h2> | |
90 | <Service | |
e1ecf86f | 91 | v-for="(item, index) in group.items" |
4d35b1fd | 92 | :key="`service-${groupIndex}-${index}`" |
0a3be103 BW |
93 | :item="item" |
94 | :proxy="config.proxy" | |
9e4fe0d2 | 95 | :class="['column', `is-${12 / config.columns}`]" |
b9c5fcf0 BW |
96 | /> |
97 | </template> | |
98 | </div> | |
99 | ||
100 | <!-- Vertical layout --> | |
101 | <div | |
102 | v-if="!filter && vlayout" | |
103 | class="columns is-multiline layout-vertical" | |
104 | > | |
105 | <div | |
9e4fe0d2 | 106 | :class="['column', `is-${12 / config.columns}`]" |
4d35b1fd ES |
107 | v-for="(group, groupIndex) in services" |
108 | :key="groupIndex" | |
b9c5fcf0 BW |
109 | > |
110 | <h2 v-if="group.name" class="group-title"> | |
da6e676d | 111 | <i v-if="group.icon" :class="['fa-fw', group.icon]"></i> |
42477020 GC |
112 | <div v-else-if="group.logo" class="group-logo media-left"> |
113 | <figure class="image is-48x48"> | |
114 | <img :src="group.logo" :alt="`${group.name} logo`" /> | |
115 | </figure> | |
116 | </div> | |
b9c5fcf0 BW |
117 | {{ group.name }} |
118 | </h2> | |
119 | <Service | |
e1ecf86f | 120 | v-for="(item, index) in group.items" |
121 | :key="index" | |
0a3be103 BW |
122 | :item="item" |
123 | :proxy="config.proxy" | |
b9c5fcf0 BW |
124 | /> |
125 | </div> | |
126 | </div> | |
127 | </div> | |
128 | </div> | |
129 | </section> | |
130 | ||
131 | <footer class="footer"> | |
132 | <div class="container"> | |
133 | <div | |
134 | class="content has-text-centered" | |
135 | v-if="config.footer" | |
136 | v-html="config.footer" | |
137 | ></div> | |
138 | </div> | |
139 | </footer> | |
140 | </div> | |
141 | </template> | |
142 | ||
143 | <script> | |
7172b6f5 | 144 | import { parse } from "yaml"; |
cbbed634 | 145 | import merge from "lodash.merge"; |
b9c5fcf0 BW |
146 | |
147 | import Navbar from "./components/Navbar.vue"; | |
f3980069 | 148 | import GetStarted from "./components/GetStarted.vue"; |
b9c5fcf0 BW |
149 | import ConnectivityChecker from "./components/ConnectivityChecker.vue"; |
150 | import Service from "./components/Service.vue"; | |
151 | import Message from "./components/Message.vue"; | |
152 | import SearchInput from "./components/SearchInput.vue"; | |
153 | import SettingToggle from "./components/SettingToggle.vue"; | |
154 | import DarkMode from "./components/DarkMode.vue"; | |
155 | import DynamicTheme from "./components/DynamicTheme.vue"; | |
156 | ||
cbbed634 | 157 | import defaultConfig from "./assets/defaults.yml?raw"; |
b9c5fcf0 BW |
158 | |
159 | export default { | |
160 | name: "App", | |
161 | components: { | |
162 | Navbar, | |
f3980069 | 163 | GetStarted, |
b9c5fcf0 BW |
164 | ConnectivityChecker, |
165 | Service, | |
166 | Message, | |
167 | SearchInput, | |
168 | SettingToggle, | |
169 | DarkMode, | |
9814a037 | 170 | DynamicTheme, |
b9c5fcf0 BW |
171 | }, |
172 | data: function () { | |
173 | return { | |
f3980069 | 174 | loaded: false, |
34259e1e | 175 | currentPage: null, |
4f56c2c1 | 176 | configNotFound: false, |
b9c5fcf0 BW |
177 | config: null, |
178 | services: null, | |
179 | offline: false, | |
180 | filter: "", | |
181 | vlayout: true, | |
182 | isDark: null, | |
9814a037 | 183 | showMenu: false, |
b9c5fcf0 BW |
184 | }; |
185 | }, | |
4f56c2c1 BW |
186 | computed: { |
187 | configurationNeeded: function () { | |
188 | return (this.loaded && !this.services) || this.configNotFound; | |
189 | }, | |
190 | }, | |
b9c5fcf0 | 191 | created: async function () { |
ba07da6b BW |
192 | this.buildDashboard(); |
193 | window.onhashchange = this.buildDashboard; | |
f3980069 | 194 | this.loaded = true; |
b9c5fcf0 BW |
195 | }, |
196 | methods: { | |
446e78d2 RS |
197 | searchHotkey() { |
198 | if (this.config.hotkey && this.config.hotkey.search) { | |
199 | return this.config.hotkey.search; | |
200 | } | |
201 | }, | |
ba07da6b | 202 | buildDashboard: async function () { |
7172b6f5 | 203 | const defaults = parse(defaultConfig); |
ba07da6b BW |
204 | let config; |
205 | try { | |
206 | config = await this.getConfig(); | |
34259e1e BW |
207 | this.currentPage = window.location.hash.substring(1) || "default"; |
208 | ||
209 | if (this.currentPage !== "default") { | |
5418c629 BW |
210 | let pageConfig = await this.getConfig( |
211 | `assets/${this.currentPage}.yml` | |
212 | ); | |
34259e1e | 213 | config = Object.assign(config, pageConfig); |
ba07da6b BW |
214 | } |
215 | } catch (error) { | |
216 | console.log(error); | |
217 | config = this.handleErrors("⚠️ Error loading configuration", error); | |
218 | } | |
219 | this.config = merge(defaults, config); | |
220 | this.services = this.config.services; | |
f3980069 | 221 | |
ba07da6b BW |
222 | document.title = |
223 | this.config.documentTitle || | |
224 | `${this.config.title} | ${this.config.subtitle}`; | |
225 | if (this.config.stylesheet) { | |
226 | let stylesheet = ""; | |
227 | for (const file of this.config.stylesheet) { | |
228 | stylesheet += `@import "${file}";`; | |
229 | } | |
230 | this.createStylesheet(stylesheet); | |
231 | } | |
232 | }, | |
b102c9b2 | 233 | getConfig: function (path = "assets/config.yml") { |
1a42e30a | 234 | return fetch(path).then((response) => { |
132e9a0d | 235 | if (response.status == 404 || response.redirected) { |
4f56c2c1 BW |
236 | this.configNotFound = true; |
237 | return {}; | |
238 | } | |
239 | ||
1a42e30a | 240 | if (!response.ok) { |
0ae40f78 | 241 | throw Error(`${response.statusText}: ${response.body}`); |
1a42e30a BW |
242 | } |
243 | ||
244 | const that = this; | |
245 | return response | |
246 | .text() | |
247 | .then((body) => { | |
7172b6f5 | 248 | return parse(body); |
1a42e30a BW |
249 | }) |
250 | .then(function (config) { | |
251 | if (config.externalConfig) { | |
252 | return that.getConfig(config.externalConfig); | |
253 | } | |
254 | return config; | |
bd910942 | 255 | }); |
1a42e30a | 256 | }); |
b9c5fcf0 BW |
257 | }, |
258 | matchesFilter: function (item) { | |
9582b187 | 259 | const needle = this.filter?.toLowerCase(); |
b9c5fcf0 | 260 | return ( |
9582b187 BW |
261 | item.name.toLowerCase().includes(needle) || |
262 | (item.subtitle && item.subtitle.toLowerCase().includes(needle)) || | |
263 | (item.tag && item.tag.toLowerCase().includes(needle)) || | |
264 | (item.keywords && item.keywords.toLowerCase().includes(needle)) | |
b9c5fcf0 BW |
265 | ); |
266 | }, | |
267 | navigateToFirstService: function (target) { | |
268 | try { | |
269 | const service = this.services[0].items[0]; | |
270 | window.open(service.url, target || service.target || "_self"); | |
271 | } catch (error) { | |
272 | console.warning("fail to open service"); | |
273 | } | |
274 | }, | |
275 | filterServices: function (filter) { | |
9582b187 | 276 | console.log(filter); |
b9c5fcf0 BW |
277 | this.filter = filter; |
278 | ||
279 | if (!filter) { | |
280 | this.services = this.config.services; | |
281 | return; | |
282 | } | |
283 | ||
284 | const searchResultItems = []; | |
285 | for (const group of this.config.services) { | |
286 | for (const item of group.items) { | |
287 | if (this.matchesFilter(item)) { | |
288 | searchResultItems.push(item); | |
289 | } | |
290 | } | |
291 | } | |
292 | ||
293 | this.services = [ | |
294 | { | |
295 | name: filter, | |
296 | icon: "fas fa-search", | |
9814a037 BW |
297 | items: searchResultItems, |
298 | }, | |
b9c5fcf0 | 299 | ]; |
9814a037 | 300 | }, |
bd910942 BW |
301 | handleErrors: function (title, content) { |
302 | return { | |
303 | message: { | |
304 | title: title, | |
305 | style: "is-danger", | |
306 | content: content, | |
307 | }, | |
308 | }; | |
309 | }, | |
ffe3404a BW |
310 | createStylesheet: function (css) { |
311 | let style = document.createElement("style"); | |
6777bc34 GC |
312 | style.appendChild(document.createTextNode(css)); |
313 | document.head.appendChild(style); | |
314 | }, | |
9814a037 | 315 | }, |
b9c5fcf0 BW |
316 | }; |
317 | </script> |