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