]>
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"> | |
3bf0edcf | 16 | <img v-if="config.logo" :src="config.logo" alt="dashboard logo" /> |
da6e676d | 17 | <i v-if="config.icon" :class="['fa-fw', config.icon]"></i> |
b9c5fcf0 BW |
18 | </div> |
19 | <div class="dashboard-title"> | |
20 | <span class="headline">{{ config.subtitle }}</span> | |
21 | <h1>{{ config.title }}</h1> | |
22 | </div> | |
23 | </div> | |
24 | </section> | |
25 | ||
26 | <Navbar | |
27 | :open="showMenu" | |
28 | :links="config.links" | |
29 | @navbar:toggle="showMenu = !showMenu" | |
30 | > | |
31 | <DarkMode @updated="isDark = $event" /> | |
32 | ||
33 | <SettingToggle | |
34 | @updated="vlayout = $event" | |
35 | name="vlayout" | |
36 | icon="fa-list" | |
37 | iconAlt="fa-columns" | |
38 | /> | |
39 | ||
40 | <SearchInput | |
41 | class="navbar-item is-inline-block-mobile" | |
42 | @input="filterServices" | |
43 | @search:focus="showMenu = true" | |
44 | @search:open="navigateToFirstService" | |
45 | @search:cancel="filterServices" | |
46 | /> | |
47 | </Navbar> | |
48 | </div> | |
49 | ||
50 | <section id="main-section" class="section"> | |
51 | <div v-cloak class="container"> | |
e9113b48 BW |
52 | <ConnectivityChecker |
53 | v-if="config.connectivityCheck" | |
54 | @network:status-update="offline = $event" | |
55 | /> | |
b9c5fcf0 BW |
56 | <div v-if="!offline"> |
57 | <!-- Optional messages --> | |
58 | <Message :item="config.message" /> | |
59 | ||
60 | <!-- Horizontal layout --> | |
61 | <div v-if="!vlayout || filter" class="columns is-multiline"> | |
62 | <template v-for="group in services"> | |
63 | <h2 v-if="group.name" class="column is-full group-title"> | |
da6e676d | 64 | <i v-if="group.icon" :class="['fa-fw', group.icon]"></i> |
b9c5fcf0 BW |
65 | {{ group.name }} |
66 | </h2> | |
67 | <Service | |
68 | v-for="item in group.items" | |
1a42e30a | 69 | :key="item.name" |
b9c5fcf0 | 70 | v-bind:item="item" |
9e4fe0d2 | 71 | :class="['column', `is-${12 / config.columns}`]" |
b9c5fcf0 BW |
72 | /> |
73 | </template> | |
74 | </div> | |
75 | ||
76 | <!-- Vertical layout --> | |
77 | <div | |
78 | v-if="!filter && vlayout" | |
79 | class="columns is-multiline layout-vertical" | |
80 | > | |
81 | <div | |
9e4fe0d2 | 82 | :class="['column', `is-${12 / config.columns}`]" |
b9c5fcf0 BW |
83 | v-for="group in services" |
84 | :key="group.name" | |
85 | > | |
86 | <h2 v-if="group.name" class="group-title"> | |
da6e676d | 87 | <i v-if="group.icon" :class="['fa-fw', group.icon]"></i> |
b9c5fcf0 BW |
88 | {{ group.name }} |
89 | </h2> | |
90 | <Service | |
91 | v-for="item in group.items" | |
92 | v-bind:item="item" | |
93 | :key="item.url" | |
94 | /> | |
95 | </div> | |
96 | </div> | |
97 | </div> | |
98 | </div> | |
99 | </section> | |
100 | ||
101 | <footer class="footer"> | |
102 | <div class="container"> | |
103 | <div | |
104 | class="content has-text-centered" | |
105 | v-if="config.footer" | |
106 | v-html="config.footer" | |
107 | ></div> | |
108 | </div> | |
109 | </footer> | |
110 | </div> | |
111 | </template> | |
112 | ||
113 | <script> | |
114 | const jsyaml = require("js-yaml"); | |
115 | const merge = require("lodash.merge"); | |
116 | ||
117 | import Navbar from "./components/Navbar.vue"; | |
118 | import ConnectivityChecker from "./components/ConnectivityChecker.vue"; | |
119 | import Service from "./components/Service.vue"; | |
120 | import Message from "./components/Message.vue"; | |
121 | import SearchInput from "./components/SearchInput.vue"; | |
122 | import SettingToggle from "./components/SettingToggle.vue"; | |
123 | import DarkMode from "./components/DarkMode.vue"; | |
124 | import DynamicTheme from "./components/DynamicTheme.vue"; | |
125 | ||
126 | import defaultConfig from "./assets/defaults.yml"; | |
127 | ||
128 | export default { | |
129 | name: "App", | |
130 | components: { | |
131 | Navbar, | |
132 | ConnectivityChecker, | |
133 | Service, | |
134 | Message, | |
135 | SearchInput, | |
136 | SettingToggle, | |
137 | DarkMode, | |
9814a037 | 138 | DynamicTheme, |
b9c5fcf0 BW |
139 | }, |
140 | data: function () { | |
141 | return { | |
142 | config: null, | |
143 | services: null, | |
144 | offline: false, | |
145 | filter: "", | |
146 | vlayout: true, | |
147 | isDark: null, | |
9814a037 | 148 | showMenu: false, |
b9c5fcf0 BW |
149 | }; |
150 | }, | |
151 | created: async function () { | |
bd910942 BW |
152 | const defaults = jsyaml.load(defaultConfig); |
153 | let config = await this.getConfig(); | |
154 | this.config = merge(defaults, config); | |
155 | this.services = this.config.services; | |
156 | document.title = `${this.config.title} | ${this.config.subtitle}`; | |
b9c5fcf0 BW |
157 | }, |
158 | methods: { | |
b102c9b2 | 159 | getConfig: function (path = "assets/config.yml") { |
1a42e30a BW |
160 | return fetch(path).then((response) => { |
161 | if (!response.ok) { | |
162 | throw Error(response.statusText); | |
163 | } | |
164 | ||
165 | const that = this; | |
166 | return response | |
167 | .text() | |
168 | .then((body) => { | |
bd910942 | 169 | return jsyaml.load(body); |
1a42e30a BW |
170 | }) |
171 | .then(function (config) { | |
172 | if (config.externalConfig) { | |
173 | return that.getConfig(config.externalConfig); | |
174 | } | |
175 | return config; | |
176 | }) | |
177 | .catch((error) => { | |
178 | return this.handleErrors("⚠️ Error loading configuration", error); | |
bd910942 | 179 | }); |
1a42e30a | 180 | }); |
b9c5fcf0 BW |
181 | }, |
182 | matchesFilter: function (item) { | |
183 | return ( | |
184 | item.name.toLowerCase().includes(this.filter) || | |
185 | (item.tag && item.tag.toLowerCase().includes(this.filter)) | |
186 | ); | |
187 | }, | |
188 | navigateToFirstService: function (target) { | |
189 | try { | |
190 | const service = this.services[0].items[0]; | |
191 | window.open(service.url, target || service.target || "_self"); | |
192 | } catch (error) { | |
193 | console.warning("fail to open service"); | |
194 | } | |
195 | }, | |
196 | filterServices: function (filter) { | |
197 | this.filter = filter; | |
198 | ||
199 | if (!filter) { | |
200 | this.services = this.config.services; | |
201 | return; | |
202 | } | |
203 | ||
204 | const searchResultItems = []; | |
205 | for (const group of this.config.services) { | |
206 | for (const item of group.items) { | |
207 | if (this.matchesFilter(item)) { | |
208 | searchResultItems.push(item); | |
209 | } | |
210 | } | |
211 | } | |
212 | ||
213 | this.services = [ | |
214 | { | |
215 | name: filter, | |
216 | icon: "fas fa-search", | |
9814a037 BW |
217 | items: searchResultItems, |
218 | }, | |
b9c5fcf0 | 219 | ]; |
9814a037 | 220 | }, |
bd910942 BW |
221 | handleErrors: function (title, content) { |
222 | return { | |
223 | message: { | |
224 | title: title, | |
225 | style: "is-danger", | |
226 | content: content, | |
227 | }, | |
228 | }; | |
229 | }, | |
9814a037 | 230 | }, |
b9c5fcf0 BW |
231 | }; |
232 | </script> |