@ -0,0 +1,15 @@ |
||||
FROM alpine:3.11 |
||||
|
||||
COPY ./ /www/ |
||||
|
||||
ENV USER darkhttpd |
||||
ENV GROUP darkhttpd |
||||
ENV GID 911 |
||||
ENV UID 911 |
||||
|
||||
RUN addgroup -S ${GROUP} -g ${GID} && adduser -D -S -u ${UID} ${USER} ${GROUP} && \ |
||||
apk add -U darkhttpd |
||||
|
||||
USER darkhttpd |
||||
|
||||
ENTRYPOINT ["darkhttpd","/www/"] |
@ -0,0 +1,201 @@ |
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
@ -0,0 +1,277 @@ |
||||
/* raleway-regular - latin */ |
||||
@font-face { |
||||
font-family: 'Raleway'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
font-display: swap; |
||||
src: local("Raleway"), local("Raleway-Regular"), url("./webfonts/raleway/raleway-v14-latin-regular.woff2") format("woff2"), url("./webfonts/raleway/raleway-v14-latin-regular.woff") format("woff"); |
||||
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } |
||||
|
||||
/* lato-regular - latin */ |
||||
@font-face { |
||||
font-family: 'Lato'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
font-display: swap; |
||||
src: local("Lato Regular"), local("Lato-Regular"), url("./webfonts/lato/lato-v16-latin-regular.woff2") format("woff2"), url("./webfonts/lato/lato-v16-latin-regular.woff") format("woff"); |
||||
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } |
||||
|
||||
html { |
||||
height: 100%; } |
||||
|
||||
body { |
||||
font-family: 'Raleway', sans-serif; |
||||
height: 100%; } |
||||
body #app { |
||||
min-height: 100%; |
||||
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms; |
||||
background-color: #f5f5f5; |
||||
color: #363636; } |
||||
body #app a:hover { |
||||
color: #363636; } |
||||
body #app .title { |
||||
color: #303030; } |
||||
body #app .subtitle { |
||||
color: #424242; } |
||||
body #app .card { |
||||
background-color: #ffffff; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); } |
||||
body #app .card:hover { |
||||
background-color: #ffffff; } |
||||
body #app .footer { |
||||
background-color: #ffffff; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); } |
||||
@media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) { |
||||
body #app { |
||||
background-color: #f5f5f5; |
||||
color: #363636; } |
||||
body #app a:hover { |
||||
color: #363636; } |
||||
body #app .title { |
||||
color: #303030; } |
||||
body #app .subtitle { |
||||
color: #424242; } |
||||
body #app .card { |
||||
background-color: #ffffff; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); } |
||||
body #app .card:hover { |
||||
background-color: #ffffff; } |
||||
body #app .footer { |
||||
background-color: #ffffff; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); } } |
||||
@media (prefers-color-scheme: dark) { |
||||
body #app { |
||||
background-color: #131313; |
||||
color: #eaeaea; } |
||||
body #app a:hover { |
||||
color: #ffdd57; } |
||||
body #app .title { |
||||
color: #fafafa; } |
||||
body #app .subtitle { |
||||
color: #f5f5f5; } |
||||
body #app .card { |
||||
background-color: #2b2b2b; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.4); } |
||||
body #app .card:hover { |
||||
background-color: #2b2b2b; } |
||||
body #app .footer { |
||||
background-color: #2b2b2b; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.4); } } |
||||
body #app.is-light { |
||||
background-color: #f5f5f5; |
||||
color: #363636; } |
||||
body #app.is-light a:hover { |
||||
color: #363636; } |
||||
body #app.is-light .title { |
||||
color: #303030; } |
||||
body #app.is-light .subtitle { |
||||
color: #424242; } |
||||
body #app.is-light .card { |
||||
background-color: #ffffff; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); } |
||||
body #app.is-light .card:hover { |
||||
background-color: #ffffff; } |
||||
body #app.is-light .footer { |
||||
background-color: #ffffff; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); } |
||||
body #app.is-dark { |
||||
background-color: #131313; |
||||
color: #eaeaea; } |
||||
body #app.is-dark a:hover { |
||||
color: #ffdd57; } |
||||
body #app.is-dark .title { |
||||
color: #fafafa; } |
||||
body #app.is-dark .subtitle { |
||||
color: #f5f5f5; } |
||||
body #app.is-dark .card { |
||||
background-color: #2b2b2b; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.4); } |
||||
body #app.is-dark .card:hover { |
||||
background-color: #2b2b2b; } |
||||
body #app.is-dark .footer { |
||||
background-color: #2b2b2b; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.4); } |
||||
body h1, body h2, body h3, body h4, body h5, body h6 { |
||||
font-family: 'Lato', sans-serif; } |
||||
body h1 { |
||||
font-size: 2rem; } |
||||
body h2 { |
||||
font-size: 1.7rem; |
||||
margin-top: 2rem; |
||||
margin-bottom: 1rem; } |
||||
body h2 .fas, body h2 .fab, body h2 .far { |
||||
margin-right: 10px; } |
||||
body h2 span { |
||||
font-weight: bold; |
||||
color: #4285f4; } |
||||
body [v-cloak] { |
||||
display: none; } |
||||
body #bighead { |
||||
color: #ffffff; } |
||||
body #bighead .dashboard-title { |
||||
padding: 6px 0 0 80px; } |
||||
body #bighead .first-line { |
||||
height: 100px; |
||||
vertical-align: center; |
||||
background-color: #3367d6; } |
||||
body #bighead .first-line h1 { |
||||
margin-top: -12px; |
||||
font-size: 2rem; } |
||||
body #bighead .first-line .headline { |
||||
margin-top: 5px; |
||||
font-size: 0.9rem; } |
||||
body #bighead .first-line .container { |
||||
height: 80px; |
||||
padding: 10px 0; } |
||||
body #bighead .first-line .logo { |
||||
float: left; } |
||||
body #bighead .first-line .logo i { |
||||
vertical-align: top; |
||||
padding: 8px 15px; |
||||
font-size: 50px; } |
||||
body #bighead .first-line .logo img { |
||||
padding: 10px; |
||||
max-height: 70px; |
||||
max-width: 70px; } |
||||
body #bighead .navbar { |
||||
background-color: #4285f4; } |
||||
body #bighead .navbar a { |
||||
color: #ffffff; } |
||||
body #bighead .navbar a:hover, body #bighead .navbar a:focus { |
||||
color: #ffffff; |
||||
background-color: #5a95f5; } |
||||
body #main-section { |
||||
margin-bottom: 2rem; |
||||
padding: 0; } |
||||
body #main-section h2 { |
||||
border-bottom: 1px dashed #ccc; |
||||
padding-bottom: 10px; } |
||||
body #main-section .title { |
||||
font-size: 1.1em; } |
||||
body #main-section .subtitle { |
||||
font-size: .9em; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; } |
||||
body #main-section .container { |
||||
padding: 1.2rem .75rem; } |
||||
body #main-section .message { |
||||
margin-top: 45px; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); } |
||||
body #main-section .message .message-header { |
||||
font-weight: bold; } |
||||
body #main-section .message .message-body { |
||||
border: none; } |
||||
body .media-content { |
||||
overflow: inherit; } |
||||
body .tag { |
||||
color: #4285f4; |
||||
background-color: #4285f4; |
||||
position: absolute; |
||||
top: 1rem; |
||||
right: -0.2rem; |
||||
width: 3px; |
||||
overflow: hidden; |
||||
transition: all 0.2s ease-out; |
||||
padding: 0; } |
||||
body .tag .tag-text { |
||||
display: none; } |
||||
body .card { |
||||
border-radius: 5px; |
||||
border: none; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); |
||||
transition: cubic-bezier(0.165, 0.84, 0.44, 1) 300ms; } |
||||
body .card a { |
||||
outline: none; } |
||||
body .card:hover { |
||||
transform: translate(0, -3px); } |
||||
body .card:hover .tag { |
||||
width: auto; |
||||
color: #ffffff; |
||||
padding: 0 0.75em; } |
||||
body .card:hover .tag .tag-text { |
||||
display: block; } |
||||
body .card-content { |
||||
height: 85px; |
||||
padding: 1.3rem; } |
||||
body .layout-vertical .card { |
||||
border-radius: 0; } |
||||
body .layout-vertical .column div:first-of-type .card { |
||||
border-radius: 5px 5px 0 0; } |
||||
body .layout-vertical .column div:last-child .card { |
||||
border-radius: 0 0 5px 5px; } |
||||
body .footer { |
||||
position: fixed; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
padding: 0.5rem; |
||||
text-align: left; |
||||
color: #676767; |
||||
font-size: 0.85rem; |
||||
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms; } |
||||
body .no-footer #main-section { |
||||
margin-bottom: 0; } |
||||
body .no-footer .footer { |
||||
display: none; } |
||||
body .search-bar { |
||||
position: relative; |
||||
display: inline-block; } |
||||
body .search-bar #search { |
||||
border: none; |
||||
background-color: #5f98f6; |
||||
border-radius: 5px; |
||||
padding: 2px 12px 2px 30px; |
||||
margin: 10px 0; |
||||
transition: all 100ms linear; |
||||
color: #ffffff; |
||||
height: 30px; |
||||
width: 100px; } |
||||
body .search-bar #search:focus { |
||||
color: #000000; |
||||
width: 250px; |
||||
background-color: #ffffff; } |
||||
body .search-bar .search-label::before { |
||||
font-family: 'Font Awesome 5 Free'; |
||||
position: absolute; |
||||
top: 12px; |
||||
left: 8px; |
||||
content: "\f002"; |
||||
font-weight: 900; |
||||
width: 20px; |
||||
height: 20px; } |
||||
body .search-bar:focus-within .search-label::before { |
||||
color: #4a4a4a; } |
||||
body .icon-button { |
||||
display: inline-block; } |
||||
body .offline-message { |
||||
text-align: center; |
||||
margin: 35px 0; } |
||||
body .offline-message i { |
||||
font-size: 2rem; } |
||||
body .offline-message i.fa-redo-alt { |
||||
font-size: 1.3rem; |
||||
line-height: 1rem; |
||||
vertical-align: middle; |
||||
cursor: pointer; |
||||
color: #3273dc; } |
@ -0,0 +1,120 @@ |
||||
const app = new Vue({ |
||||
el: '#app', |
||||
data: { |
||||
config: null, |
||||
offline: false, |
||||
filter: '', |
||||
vlayout: true, |
||||
isDark: null |
||||
}, |
||||
created: async function () { |
||||
let that = this; |
||||
|
||||
this.isDark = 'overrideDark' in localStorage ? |
||||
JSON.parse(localStorage.overrideDark) : matchMedia("(prefers-color-scheme: dark)").matches; |
||||
|
||||
if ('vlayout' in localStorage) { |
||||
this.vlayout = JSON.parse(localStorage.vlayout) |
||||
} |
||||
|
||||
this.checkOffline(); |
||||
try { |
||||
this.config = await this.getConfig(); |
||||
} catch (error) { |
||||
this.offline = true; |
||||
} |
||||
|
||||
// Look for a new message if an endpoint is provided.
|
||||
if (this.config.message && this.config.message.url) { |
||||
this.getMessage(this.config.message.url).then(function(message){ |
||||
// keep the original config value if no value is provided by the endpoint
|
||||
for (const prop of ['title','style','content']) { |
||||
if (prop in message && message[prop] !== null) { |
||||
that.config.message[prop] = message[prop]; |
||||
}
|
||||
} |
||||
}); |
||||
} |
||||
|
||||
document.addEventListener('visibilitychange', function () { |
||||
if (document.visibilityState == "visible") { |
||||
that.checkOffline(); |
||||
} |
||||
}, false); |
||||
}, |
||||
methods: { |
||||
checkOffline: function () { |
||||
let that = this; |
||||
return fetch(window.location.href + "?alive", { |
||||
method: 'HEAD', |
||||
cache: 'no-store' |
||||
}).then(function () { |
||||
that.offline = false; |
||||
}).catch(function () { |
||||
that.offline = true; |
||||
}); |
||||
}, |
||||
getConfig: function (event) { |
||||
return fetch('config.yml').then(function (response) { |
||||
if (response.status != 200) { |
||||
return |
||||
} |
||||
return response.text().then(function (body) { |
||||
return jsyaml.load(body); |
||||
}); |
||||
}); |
||||
}, |
||||
getMessage: function (url) { |
||||
return fetch(url).then(function (response) { |
||||
if (response.status != 200) { |
||||
return; |
||||
} |
||||
return response.json(); |
||||
}); |
||||
}, |
||||
toggleTheme: function() { |
||||
this.isDark = !this.isDark; |
||||
localStorage.overrideDark = this.isDark;
|
||||
},
|
||||
toggleLayout: function() { |
||||
this.vlayout = !this.vlayout; |
||||
localStorage.vlayout = this.vlayout; |
||||
}, |
||||
} |
||||
}); |
||||
|
||||
Vue.component('service', { |
||||
props: ['item'], |
||||
template: `<div>
|
||||
<div class="card"> |
||||
<a :href="item.url" :target="item.target"> |
||||
<div class="card-content"> |
||||
<div class="media"> |
||||
<div v-if="item.logo" class="media-left"> |
||||
<figure class="image is-48x48"> |
||||
<img :src="item.logo" /> |
||||
</figure> |
||||
</div> |
||||
<div v-if="item.icon" class="media-left"> |
||||
<figure class="image is-48x48"> |
||||
<i style="font-size: 35px" :class="item.icon"></i> |
||||
</figure> |
||||
</div> |
||||
<div class="media-content"> |
||||
<p class="title is-4">{{ item.name }}</p> |
||||
<p class="subtitle is-6">{{ item.subtitle }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="tag" :class="item.tagstyle" v-if="item.tag"> |
||||
<strong class="tag-text">#{{ item.tag }}</strong> |
||||
</div> |
||||
</div> |
||||
</a> |
||||
</div></div>` |
||||
}); |
||||
|
||||
if ('serviceWorker' in navigator) { |
||||
window.addEventListener('load', function () { |
||||
navigator.serviceWorker.register('worker.js'); |
||||
}); |
||||
} |
@ -0,0 +1,374 @@ |
||||
$primary-color: #3367d6; |
||||
$secondary-color: #4285f4; |
||||
|
||||
|
||||
// /!\ Keep background colors sync with `theme-color` meta info |
||||
$theme-light: ( |
||||
background: #f5f5f5, |
||||
card-background: #ffffff, |
||||
text: #363636, |
||||
text-title: #303030, |
||||
text-subtitle: #424242, |
||||
card-shadow: rgba(0, 0, 0, 0.1), |
||||
a-hover: #363636 |
||||
); |
||||
$theme-dark: ( |
||||
background: #131313, |
||||
card-background: #2b2b2b, |
||||
text: #eaeaea, |
||||
text-title: #fafafa, |
||||
text-subtitle: #f5f5f5, |
||||
card-shadow: rgba(0, 0, 0, 0.4), |
||||
a-hover: #ffdd57 |
||||
); |
||||
|
||||
/* raleway-regular - latin */ |
||||
@font-face { |
||||
font-family: 'Raleway'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
font-display: swap; |
||||
src: local('Raleway'), local('Raleway-Regular'), |
||||
url('./webfonts/raleway/raleway-v14-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ |
||||
url('./webfonts/raleway/raleway-v14-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ |
||||
} |
||||
|
||||
/* lato-regular - latin */ |
||||
@font-face { |
||||
font-family: 'Lato'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
font-display: swap; |
||||
src: local('Lato Regular'), local('Lato-Regular'), |
||||
url('./webfonts/lato/lato-v16-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ |
||||
url('./webfonts/lato/lato-v16-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ |
||||
} |
||||
|
||||
@mixin theme($theme) { |
||||
background-color: map-get($theme, "background"); |
||||
color: map-get($theme, "text"); |
||||
a { |
||||
&:hover { |
||||
color: map-get($theme, "a-hover"); |
||||
} |
||||
} |
||||
|
||||
.title { |
||||
color: map-get($theme, "text-title"); |
||||
} |
||||
.subtitle { |
||||
color: map-get($theme, "text-subtitle"); |
||||
} |
||||
|
||||
.card { |
||||
background-color: map-get($theme, "card-background"); |
||||
box-shadow: 0 2px 15px 0 map-get($theme, "card-shadow"); |
||||
&:hover { |
||||
background-color: map-get($theme, "card-background"); |
||||
} |
||||
} |
||||
|
||||
.footer { |
||||
background-color: map-get($theme, "card-background"); |
||||
box-shadow: 0 2px 15px 0 map-get($theme, "card-shadow"); |
||||
} |
||||
} |
||||
|
||||
html { |
||||
height: 100%; |
||||
} |
||||
|
||||
body { |
||||
font-family: 'Raleway', sans-serif; |
||||
height: 100%; |
||||
|
||||
#app { |
||||
min-height: 100%; |
||||
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms; |
||||
|
||||
// Default theme |
||||
@include theme($theme-light); |
||||
|
||||
// System pref theme |
||||
@media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) { |
||||
@include theme($theme-light); |
||||
} |
||||
@media (prefers-color-scheme: dark) { |
||||
@include theme($theme-dark); |
||||
} |
||||
|
||||
// User override theme |
||||
&.is-light { |
||||
@include theme($theme-light); |
||||
} |
||||
&.is-dark { |
||||
@include theme($theme-dark); |
||||
} |
||||
} |
||||
|
||||
h1, h2, h3, h4, h5, h6 { |
||||
font-family: 'Lato', sans-serif; |
||||
} |
||||
|
||||
h1 { |
||||
font-size: 2rem; |
||||
} |
||||
|
||||
h2 { |
||||
font-size: 1.7rem; |
||||
margin-top: 2rem; |
||||
margin-bottom: 1rem; |
||||
|
||||
.fas, .fab, .far { |
||||
margin-right: 10px; |
||||
} |
||||
|
||||
span { |
||||
font-weight: bold; |
||||
color: $secondary-color; |
||||
} |
||||
} |
||||
|
||||
[v-cloak] { |
||||
display: none |
||||
} |
||||
|
||||
#bighead { |
||||
color: #ffffff; |
||||
|
||||
.dashboard-title { |
||||
padding: 6px 0 0 80px; |
||||
} |
||||
|
||||
.first-line { |
||||
height: 100px; |
||||
vertical-align: center; |
||||
background-color: $primary-color; |
||||
|
||||
h1 { |
||||
margin-top: -12px; |
||||
font-size: 2rem; |
||||
} |
||||
|
||||
.headline { |
||||
margin-top: 5px; |
||||
font-size: 0.9rem; |
||||
} |
||||
|
||||
.container { |
||||
height: 80px; |
||||
padding: 10px 0; |
||||
} |
||||
|
||||
.logo { |
||||
float: left; |
||||
i { |
||||
vertical-align: top; |
||||
padding: 8px 15px; |
||||
font-size: 50px |
||||
} |
||||
|
||||
img { |
||||
padding: 10px; |
||||
max-height: 70px; |
||||
max-width: 70px; |
||||
} |
||||
} |
||||
} |
||||
.navbar { |
||||
background-color: $secondary-color; |
||||
|
||||
a { |
||||
color: #ffffff; |
||||
&:hover, &:focus { |
||||
color: #ffffff; |
||||
background-color: lighten( $secondary-color, 5% ); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#main-section { |
||||
margin-bottom: 2rem; |
||||
padding: 0; |
||||
|
||||
h2 { |
||||
border-bottom: 1px dashed #ccc; |
||||
padding-bottom: 10px; |
||||
} |
||||
|
||||
.title { |
||||
font-size: 1.1em; |
||||
} |
||||
|
||||
.subtitle { |
||||
font-size: .9em; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
} |
||||
|
||||
.container { |
||||
padding: 1.2rem .75rem; |
||||
} |
||||
|
||||
.message { |
||||
margin-top: 45px; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); |
||||
|
||||
.message-header { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.message-body { |
||||
border: none; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.media-content { |
||||
overflow: inherit; |
||||
} |
||||
|
||||
.tag { |
||||
color: $secondary-color; |
||||
background-color: $secondary-color; |
||||
position: absolute; |
||||
top: 1rem; |
||||
right: -0.2rem; |
||||
width: 3px; |
||||
overflow: hidden; |
||||
transition: all 0.2s ease-out; |
||||
padding: 0; |
||||
|
||||
.tag-text { |
||||
display: none; |
||||
} |
||||
} |
||||
|
||||
.card { |
||||
border-radius: 5px; |
||||
border: none; |
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); |
||||
transition: cubic-bezier(0.165, 0.84, 0.44, 1) 300ms; |
||||
|
||||
a { |
||||
outline: none; |
||||
} |
||||
} |
||||
|
||||
.card:hover { |
||||
transform: translate(0, -3px); |
||||
|
||||
.tag { |
||||
width: auto; |
||||
color: #ffffff; |
||||
padding: 0 0.75em; |
||||
|
||||
.tag-text { |
||||
display: block; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.card-content { |
||||
height: 85px; |
||||
padding: 1.3rem; |
||||
} |
||||
|
||||
.layout-vertical { |
||||
.card { |
||||
border-radius: 0; |
||||
} |
||||
|
||||
.column div:first-of-type .card { |
||||
border-radius: 5px 5px 0 0; |
||||
} |
||||
|
||||
.column div:last-child .card { |
||||
border-radius: 0 0 5px 5px; |
||||
} |
||||
} |
||||
|
||||
.footer { |
||||
position: fixed; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
padding: 0.5rem; |
||||
text-align: left; |
||||
color: #676767; |
||||
font-size: 0.85rem; |
||||
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms; |
||||
} |
||||
|
||||
.no-footer { |
||||
#main-section { |
||||
margin-bottom: 0; |
||||
} |
||||
|
||||
.footer { |
||||
display: none; |
||||
} |
||||
} |
||||
|
||||
.search-bar { |
||||
position: relative; |
||||
display: inline-block; |
||||
#search { |
||||
border: none; |
||||
background-color: lighten( $secondary-color, 6% ); |
||||
border-radius: 5px; |
||||
padding: 2px 12px 2px 30px; |
||||
margin: 10px 0; |
||||
transition: all 100ms linear; |
||||
color: #ffffff; |
||||
height: 30px; |
||||
width: 100px; |
||||
|
||||
|
||||
&:focus { |
||||
color: #000000; |
||||
width: 250px; |
||||
background-color: #ffffff; |
||||
} |
||||
} |
||||
|
||||
.search-label::before { |
||||
font-family: 'Font Awesome 5 Free'; |
||||
position: absolute; |
||||
top: 12px; |
||||
left: 8px; |
||||
content: "\f002"; |
||||
font-weight: 900; |
||||
width: 20px; |
||||
height: 20px; |
||||
} |
||||
|
||||
&:focus-within .search-label::before { |
||||
color: #4a4a4a; |
||||
} |
||||
} |
||||
|
||||
.icon-button { |
||||
display: inline-block; |
||||
} |
||||
|
||||
.offline-message { |
||||
text-align: center; |
||||
margin: 35px 0; |
||||
|
||||
i { |
||||
font-size: 2rem; |
||||
} |
||||
|
||||
i.fa-redo-alt { |
||||
font-size: 1.3rem; |
||||
line-height: 1rem; |
||||
vertical-align: middle; |
||||
cursor: pointer; |
||||
color: #3273dc; |
||||
} |
||||
} |
||||
|
||||
} |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 963 KiB |
@ -0,0 +1,119 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
|
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
<meta name="robots" content="noindex"> |
||||
<link rel="icon" type="image/png" href="assets/favicon.png"> |
||||
<title>Homer</title> |
||||
<link defer rel="stylesheet" href="vendors/font-awesone.min.css"> |
||||
<link defer rel="stylesheet" href="vendors/bulma.min.css"> |
||||
<link rel="stylesheet" href="app.css"> |
||||
</head> |
||||
|
||||
<body> |
||||
<div id="app" v-if="config" :class="[isDark ? 'is-dark' : 'is-light', !config.footer ? 'no-footer': '']"> |
||||
<div id="bighead"> |
||||
<section class="first-line"> |
||||
<div v-cloak class="container"> |
||||
<div class="logo"> |
||||
<img v-if="config.logo" :src="config.logo" /> |
||||
<i v-if="config.icon" :class="config.icon"></i> |
||||
</div> |
||||
<div class="dashboard-title"> |
||||
<span class="headline">{{ config.subtitle }}</span> |
||||
<h1>{{ config.title }}</h1> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
<div v-cloak v-if="config.links" class="container-fluid"> |
||||
<nav class="navbar" role="navigation" aria-label="main navigation"> |
||||
<div class="container"> |
||||
<div class="navbar-menu"> |
||||
<div class="navbar-start"> |
||||
<a v-for="link in config.links" class="navbar-item" :href="link.url" :target="link.target"> |
||||
<i v-if="link.icon" style="margin-right: 6px;" :class="link.icon"></i> |
||||
{{ link.name }} |
||||
</a> |
||||
</div> |
||||
<div class="end"> |
||||
<a |
||||
v-on:click="toggleTheme()" |
||||
aria-label="Toggle dark mode" |
||||
><i class="fas fa-adjust"></i> |
||||
</a> |
||||
<a v-on:click="toggleLayout()" class="icon-button navbar-item"><i |
||||
:class="['fas', vlayout ? 'fa-list' : 'fa-columns']"></i></a> |
||||
<div class="search-bar"> |
||||
<label for="search" class="search-label"></label> |
||||
<input type="text" id="search" v-model="filter" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</nav> |
||||
</div> |
||||
</div> |
||||
|
||||
<section id="main-section" class="section"> |
||||
<div v-cloak class="container"> |
||||
<div v-if="offline" class="offline-message"> |
||||
<i class="far fa-dizzy"></i> |
||||
<h1>You're offline bro. <i class="fas fa-redo-alt" v-on:click="checkOffline()"></i></h1> |
||||
</div> |
||||
<div v-else> |
||||
<!-- Optional messages --> |
||||
<article v-if="config && config.message" class="message" :class="config.message.style"> |
||||
<div v-if="config.message.title" class="message-header"> |
||||
<p>{{ config.message.title }}</p> |
||||
</div> |
||||
<div v-if="config.message.content" class="message-body"> |
||||
{{ config.message.content }} |
||||
</div> |
||||
</article> |
||||
|
||||
<h2 v-if="filter"><i class="fas fa-search"></i> Search</h2> |
||||
|
||||
<!-- Horizontal layout --> |
||||
<div v-if="!vlayout || filter" class="columns is-multiline"> |
||||
<template v-for="(group, index) in config.services"> |
||||
<h2 v-if="!filter && group.name" class="column is-full"><i v-if="group.icon" :class="group.icon"></i><span |
||||
v-else>#</span> |
||||
{{ group.name }}</h2> |
||||
<service v-for="item in group.items" v-bind:item="item" class="column is-one-third-widescreen" |
||||
v-if="!filter || (item && (item.name.toLowerCase().includes(filter.toLowerCase()) || (item.tag && item.tag.toLowerCase().includes(filter.toLowerCase()))))"> |
||||
</service> |
||||
</template> |
||||
</div> |
||||
|
||||
<!-- Vertical layout --> |
||||
<div v-if="!filter && vlayout" class="columns is-multiline layout-vertical"> |
||||
<div class="column is-one-third-widescreen" v-for="(group, index) in config.services"> |
||||
<h2 v-if="!filter && group.name"><i v-if="group.icon" :class="group.icon"></i><span v-else>#</span> |
||||
{{ group.name }}</h2> |
||||
<service v-for="item in group.items" v-bind:item="item" |
||||
v-if="!filter || (item && (item.name.toLowerCase().includes(filter.toLowerCase()) || (item.tag && item.tag.toLowerCase().includes(filter.toLowerCase()))))"> |
||||
</service> |
||||
</div> |
||||
</div> |
||||
|
||||
|
||||
</div> |
||||
</div> |
||||
</section> |
||||
<footer class="footer"> |
||||
<div class="container"> |
||||
<div class="content has-text-centered" v-if="config.footer" v-html="config.footer"> |
||||
|
||||
</div> |
||||
</div> |
||||
</footer> |
||||
</div> |
||||
|
||||
<script src="vendors/vue.min.js"></script> |
||||
<script src="vendors/js-yaml.min.js"></script> |
||||
<script src="app.js"></script> |
||||
</body> |
||||
|
||||
</html> |
After Width: | Height: | Size: 148 KiB |