Merge pull request #4 from IcyNet/dev

Some changes
This commit is contained in:
Evert Prants 2018-01-27 15:48:05 +02:00 committed by GitHub
commit 92be101d3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 247 additions and 100 deletions

View File

@ -85,3 +85,9 @@
[logger] [logger]
write=true write=true
file="/var/log/icynet.log" file="/var/log/icynet.log"
# Matomo tracking
#[matomo]
# site_id=
# site_domain="icynet.eu"
# track_url="//analytics.icynet.eu/"

View File

@ -1,5 +1,6 @@
<div class="tos"> <div class="tos">
<h1>Privacy Policy</h1> <h1>Privacy Policy</h1>
<span class="last-modified">Last modified: Friday, 13 Oct 2017</span>
<p>By using Services of Icy Network, you acknowledge and agree to these policies.</p> <p>By using Services of Icy Network, you acknowledge and agree to these policies.</p>
<h2>Information We Collect and Use</h2> <h2>Information We Collect and Use</h2>
<p>Icy Network may collect and save some information about our users.</p> <p>Icy Network may collect and save some information about our users.</p>

View File

@ -1,5 +1,6 @@
<div class="tos"> <div class="tos">
<h1>Terms of Service</h1> <h1>Terms of Service</h1>
<span class="last-modified">Last modified: Wednesday, 03 Jan 2018</span>
<p>Please read the following Terms and Conditions carefully. By using Icy Network services, you signify that you have read, understood and agreed to be bound by these Terms and Conditions. Icy Network reserves the right to modify, replace or remove any of the Terms and Conditions written in this document at any given time without restrictions. We will try to notify you of any such amendments. If you do not agree to these Terms and Conditions, you may not use any of the services provided by Icy Network.</p> <p>Please read the following Terms and Conditions carefully. By using Icy Network services, you signify that you have read, understood and agreed to be bound by these Terms and Conditions. Icy Network reserves the right to modify, replace or remove any of the Terms and Conditions written in this document at any given time without restrictions. We will try to notify you of any such amendments. If you do not agree to these Terms and Conditions, you may not use any of the services provided by Icy Network.</p>
<p>Separate entities owned by Icy Network may have their own Terms and Conditions which you must read and comply with.</p> <p>Separate entities owned by Icy Network may have their own Terms and Conditions which you must read and comply with.</p>
<h2>Who May Use the Services</h2> <h2>Who May Use the Services</h2>
@ -16,4 +17,5 @@
<h4>Credits</h4> <h4>Credits</h4>
<p>Google Play and the Google Play logo are trademarks of Google Inc.</p> <p>Google Play and the Google Play logo are trademarks of Google Inc.</p>
<p>Apple and the Apple logo are trademarks of Apple Inc., registered in the U.S. and other countries. App Store is a service mark of Apple Inc., registered in the U.S. and other countries.</p> <p>Apple and the Apple logo are trademarks of Apple Inc., registered in the U.S. and other countries. App Store is a service mark of Apple Inc., registered in the U.S. and other countries.</p>
<p>Font Awesome by Dave Gandy - <a href="http://fontawesome.io" target="_blank" rel="_nofollow">http://fontawesome.io</a></p>
</div> </div>

15
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "icynet.eu", "name": "icynet.eu",
"version": "0.0.1-alpha1", "version": "0.9.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -2679,6 +2679,14 @@
"pend": "1.2.0" "pend": "1.2.0"
} }
}, },
"feed": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/feed/-/feed-1.1.1.tgz",
"integrity": "sha1-kUiXUX6U+jJ8xvc7tYWkfEqe0yE=",
"requires": {
"xml": "1.0.1"
}
},
"figures": { "figures": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
@ -7740,6 +7748,11 @@
"mkdirp": "0.5.1" "mkdirp": "0.5.1"
} }
}, },
"xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU="
},
"xtend": { "xtend": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",

View File

@ -41,6 +41,7 @@
"express": "^4.16.2", "express": "^4.16.2",
"express-rate-limit": "^2.9.0", "express-rate-limit": "^2.9.0",
"express-session": "^1.15.6", "express-session": "^1.15.6",
"feed": "^1.1.1",
"fs-extra": "^4.0.2", "fs-extra": "^4.0.2",
"gm": "^1.23.0", "gm": "^1.23.0",
"knex": "^0.13.0", "knex": "^0.13.0",

View File

@ -1,8 +1,13 @@
import API from './index' import API from './index'
import Models from './models' import Models from './models'
import config from '../../scripts/load-config'
import Feed from 'feed'
const perPage = 8 const perPage = 8
let feed
function slugify (title) { function slugify (title) {
return title.toLowerCase().replace(/\W/g, '-').substring(0, 32) return title.toLowerCase().replace(/\W/g, '-').substring(0, 32)
} }
@ -22,13 +27,12 @@ async function cleanArticle (entry, shortenContent = false) {
if (poster) { if (poster) {
article.author = { article.author = {
id: poster.id, id: poster.id,
display_name: poster.display_name display_name: poster.display_name,
email: poster.email
} }
} }
if (shortenContent) { article.description = article.content.replace(/(<([^>]+)>)/ig, '').replace(/\n/g, ' ').substring(0, 197) + '...'
article.content = article.content.replace(/(<([^>]+)>)/ig, '').substring(0, 128) + '...'
}
return article return article
} }
@ -43,7 +47,7 @@ const News = {
let articles = [] let articles = []
for (let i in news) { for (let i in news) {
let entry = news[i] let entry = news[i]
articles.push(await cleanArticle(entry, true)) articles.push(await cleanArticle(entry))
} }
return articles return articles
@ -102,6 +106,55 @@ const News = {
let result = await Models.News.query().patchAndFetchById(id, patch) let result = await Models.News.query().patchAndFetchById(id, patch)
if (!result) throw new Error('Something went wrong.') if (!result) throw new Error('Something went wrong.')
return {} return {}
},
generateFeed: async () => {
if (feed && new Date(feed.options.updated).getTime() > Date.now() - 3600000) return feed // Update feed hourly
let posts = await Models.News.query().orderBy('created_at', 'desc').limit(perPage)
let cleanNews = []
for (let i in posts) {
cleanNews.push(await cleanArticle(posts[i]))
}
feed = new Feed({
title: 'Icy Network News',
description: 'Icy Network News',
id: config.server.domain + '/news',
link: config.server.domain + '/news',
image: config.server.domain + '/static/image/icynet-icon.png',
favicon: config.server.domain + '/favicon.ico',
copyright: '2018 Icy Network - Some Rights Reserved',
updated: new Date(),
feedLinks: {
json: config.server.domain + '/news/feed.json',
atom: config.server.domain + '/news/atom.xml'
},
author: {
name: 'Icy Network',
email: 'icynet@lunasqu.ee',
link: config.server.domain
}
})
for (let i in cleanNews) {
let post = cleanNews[i]
feed.addItem({
title: post.title,
id: post.id,
link: `${config.server.domain}/news/${post.id}-${slugify(post.title)}`,
description: post.description,
content: post.content,
author: [{
name: post.author.display_name,
email: post.author.email
}],
date: new Date(post.updated_at)
})
}
return feed
} }
} }

View File

@ -797,6 +797,20 @@ router.get('/news/', wrap(async (req, res) => {
res.render('news/news', {news: news}) res.render('news/news', {news: news})
})) }))
router.get('/news/atom.xml', wrap(async (req, res) => {
let feed = await News.generateFeed()
res.set('Content-Type', 'application/atom+xml')
res.send(feed.atom1())
}))
router.get('/news/feed.json', wrap(async (req, res) => {
let feed = await News.generateFeed()
res.set('Content-Type', 'application/json')
res.send(feed.json1())
}))
// Render partials // Render partials
router.get('/partials/:view', (req, res, next) => { router.get('/partials/:view', (req, res, next) => {
if (!req.params.view) return next() if (!req.params.view) return next()

View File

@ -53,8 +53,8 @@ app.use((req, res, next) => {
}) })
// Add Piwik tracker if configured // Add Piwik tracker if configured
if (config.piwik && config.piwik.site_id) { if (config.matomo && config.matomo.site_id) {
res.locals.piwik = config.piwik res.locals.matomo = config.matomo
} }
next() next()

View File

@ -6,7 +6,7 @@
pagination(:page="pagination.page" :pages="pagination.pages" v-on:page="getClients") pagination(:page="pagination.page" :pages="pagination.pages" v-on:page="getClients")
.list.client .list.client
o-auth-client(v-for="client in clients" v-bind="client" :key="client.id") o-auth-client(v-for="client in clients" v-bind="client" :key="client.id")
client-modal(:show="editing != 0" @close='editing = 0', :id='editing') client-modal(:show="editing != 0" @close='editing = 0', :id='editing')
</template> </template>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -48,6 +48,7 @@ $(document).ready(function () {
} }
function loadAuthorizations () { function loadAuthorizations () {
$('#clientlist').html('<span class="load"><i class="fa fa-spin fa-spinner fa-2x"></i><span>Loading list</span></span>')
$.get({ $.get({
url: '/api/oauth2/authorized-clients', url: '/api/oauth2/authorized-clients',
dataType: 'json', dataType: 'json',
@ -119,6 +120,14 @@ $(document).ready(function () {
}) })
} }
$('body').click(function (e) {
$('#show-menu').prop('checked', false)
})
$('.menu').click(function (e) {
e.stopPropagation()
})
$('#dialog #close').click(function (e) { $('#dialog #close').click(function (e) {
window.Dialog.close() window.Dialog.close()
}) })
@ -164,11 +173,6 @@ $(document).ready(function () {
}, 1000, 'swing') }, 1000, 'swing')
}) })
$('#mobile').click(function (e) {
e.preventDefault()
$('.flexview').toggleClass('extended')
})
$('body').click(function (e) { $('body').click(function (e) {
if (!$(e.target).is('#mobile') && !$(e.target).is('#mobile i') && $('.flexview').hasClass('extended')) { if (!$(e.target).is('#mobile') && !$(e.target).is('#mobile i') && $('.flexview').hasClass('extended')) {
$('.flexview').removeClass('extended') $('.flexview').removeClass('extended')
@ -185,6 +189,7 @@ $(document).ready(function () {
} }
if ($('.newsfeed').length) { if ($('.newsfeed').length) {
$('.newsfeed').html('<span class="load"><i class="fa fa-spin fa-spinner fa-2x"></i><span>Loading feed</span></span>')
$.ajax({ $.ajax({
type: 'get', type: 'get',
url: '/api/news', url: '/api/news',
@ -199,7 +204,7 @@ $(document).ready(function () {
html += '<div class="prvarticle">' html += '<div class="prvarticle">'
html += '<a class="title" href="/news/' + article.id + '-' + article.slug + '">' + article.title + '</a>' html += '<a class="title" href="/news/' + article.id + '-' + article.slug + '">' + article.title + '</a>'
html += '<span class="timestamp">Published at ' + new Date(article.created_at) + '</span>' html += '<span class="timestamp">Published at ' + new Date(article.created_at) + '</span>'
html += '<div class="prvcontent">' + article.content + '</div>' html += '<div class="prvcontent">' + article.description + '</div>'
html += '<a href="/news/' + article.id + '-' + article.slug + '">Read More</a>' html += '<a href="/news/' + article.id + '-' + article.slug + '">Read More</a>'
html += '</div>' html += '</div>'
} }

View File

@ -45,10 +45,13 @@ a
display: none !important display: none !important
.navigator .navigator
position: relative
padding: 0 50px padding: 0 50px
transition: background-color 0.1s linear transition: background-color 0.1s linear
background-color: rgb(0, 219, 247) background-color: rgb(0, 219, 247)
z-index: 5 z-index: 5
height: 68px
&.fix &.fix
top: 0 top: 0
left: 0 left: 0
@ -57,6 +60,7 @@ a
background-color: rgba(0, 219, 247, 0.67) background-color: rgba(0, 219, 247, 0.67)
#navlogo #navlogo
float: left
&.hidden &.hidden
display: none display: none
@ -65,8 +69,6 @@ a
margin: 0 margin: 0
display: inline-block display: inline-block
list-style-type: none list-style-type: none
&.floating
float: right
li li
font-size: 30px font-size: 30px
@ -83,6 +85,29 @@ a
transition: background-color 0.1s linear transition: background-color 0.1s linear
&:hover &:hover
background-color: rgba(255, 255, 255, 0.25) background-color: rgba(255, 255, 255, 0.25)
.showButton
margin: 0
padding: 20px
display: none
float: right
font-size: 150%
position: relative
cursor: pointer
color: green
.list
display: flex
flex-direction: row
.floating
margin-left: auto
input[type=checkbox]
display: none
&:checked ~ .list
max-height: 500px
section section
font-family: "Open Sans" font-family: "Open Sans"
@ -134,53 +159,22 @@ section
.wrapper .wrapper
overflow: hidden overflow: hidden
min-height: 100vh min-height: 100vh
width: 100%
.document .document
overflow: hidden overflow: hidden
min-height: 100vh; min-height: 100vh
padding: 50px; padding: 50px
background-color: #fff; background-color: #fff
.tos .tos
display: inline-block display: inline-block
width: 60% width: 60%
font-size: 120% font-size: 120%
//.cssextend:hover > .flexview
// display: block
// left: 40%
.g-recaptcha .g-recaptcha
margin-top: 10px margin-top: 10px
ul.flexview
position: fixed
right: 0
left: 100%
width: auto
top: 68px
bottom: 0
background-color: rgba(53, 53, 53, 0.8)
z-index: 1
overflow: hidden
transition: all 0.1s linear
.division
border-top: 1px solid #a7a7a7
li
display: block
min-width: 100px
a
display: block
padding: 5px 20px
color: #fff
font-weight: normal
text-transform: none
&.extended
display: block
left: 40%
code code
white-space: pre white-space: pre
@ -194,6 +188,7 @@ input:not([type="submit"])
border-radius: 5px border-radius: 5px
border: 1px solid #c1c1c1 border: 1px solid #c1c1c1
background-color: #f5f5f5 background-color: #f5f5f5
color: #000
box-shadow: inset 2px 2px 5px #ddd box-shadow: inset 2px 2px 5px #ddd
transition: border 0.1s linear transition: border 0.1s linear
@ -201,6 +196,7 @@ button, .button, input[type="submit"]
display: block display: block
padding: 5px 10px padding: 5px 10px
background-color: #fbfbfb background-color: #fbfbfb
color: #000
border: 1px solid #c1c1c1 border: 1px solid #c1c1c1
border-radius: 5px border-radius: 5px
font-size: 120% font-size: 120%
@ -209,6 +205,9 @@ button, .button, input[type="submit"]
&.active &.active
background-color: #ddd background-color: #ddd
input[disabled]
color: #959595
.button .button
display: inline-block display: inline-block
margin-right: 5px margin-right: 5px
@ -222,7 +221,7 @@ button, .button, input[type="submit"]
box-shadow: 5px 5px 15px #868686 box-shadow: 5px 5px 15px #868686
border: 1px solid #ddd border: 1px solid #ddd
margin-bottom: 10% margin-bottom: 10%
h1:first-child, h2:first-child, h3:first-child h1:first-child, h3:first-child
margin-top: 0 margin-top: 0
.left, .right .left, .right
display: inline-block display: inline-block
@ -316,7 +315,7 @@ button, .button, input[type="submit"]
border: 1px solid #ddd border: 1px solid #ddd
border-radius: 5px border-radius: 5px
text-decoration: none text-decoration: none
display: inline-block display: block
cursor: pointer cursor: pointer
i i
color: #fff color: #fff
@ -397,11 +396,14 @@ span.divider
.info .info
margin-left: 130px margin-left: 130px
.name .name
font-size: 150% font-size: 120%
font-weight: bold font-weight: bold
text-overflow: ellipsis
overflow: hidden
.description .description
font-size: 80% font-size: 80%
font-style: italic font-style: italic
word-wrap: break-word
.url .url
display: block display: block
text-overflow: ellipsis text-overflow: ellipsis
@ -584,17 +586,54 @@ select
display: inline-block display: inline-block
vertical-align: top vertical-align: top
noscript
position: fixed
display: block
width: 100%
background-color: rgba(255, 95, 95, 0.6)
color: #fff
text-shadow: 1px 1px 3px #000
text-align: center
z-index: 100
.last-modified
font-style: italic
color: #7d7d7d
display: block
@media all and (max-width: 800px) @media all and (max-width: 800px)
.navigator .navigator
padding: 0 10px padding: 0 10px
.showButton
display: inline-block !important
.menu
.list
display: block
position: absolute
top: 68px
left: 0
right: 0
background-color: rgba(0, 188, 212, 0.72)
transition: max-height 0.1s linear
max-height: 0
overflow: hidden
ul, li, li a
display: block
li a
padding: 6px 20px
color: #ffffff
text-transform: none
.floating
border-top: 2px solid #00a1b5
#navlogo #navlogo
display: inline-block !important display: inline-block !important
.banner .banner
display: none display: none
.fullview
display: none !important
.mobview
display: inline-block !important
.logo .logo
font-size: 10vw font-size: 10vw
.document .document
@ -620,7 +659,7 @@ select
float: initial !important float: initial !important
display: block !important display: block !important
border: 0 !important border: 0 !important
width: fit-content !important width: auto !important
margin: auto margin: auto
.dialog .dialog
width: 100vw !important width: 100vw !important

View File

@ -7,7 +7,7 @@ block body
.boxcont .boxcont
.box#donate .box#donate
h1 Donate h1 Donate
p Donating any amount would be highly appreciated! p Donations help us pay for the services required to keep Icy Network running. Donating any amount would be highly appreciated!
- var formurl = "https://www.paypal.com/cgi-bin/webscr" - var formurl = "https://www.paypal.com/cgi-bin/webscr"
if sandbox if sandbox
@ -42,4 +42,4 @@ block body
i.fa.fa-fw.fa-paypal i.fa.fa-fw.fa-paypal
|Donate |Donate
br br
b Currently you can only donate using a PayPal account. b Currently you can only donate using PayPal.

View File

@ -23,7 +23,5 @@ block body
.content .content
h1 Icy Network News h1 Icy Network News
.newsfeed .newsfeed
span.load p Please enable JavaScript to view this content.
i.fa.fa-spin.fa-spinner.fa-2x a.older(href="/news") View all articles
span Loading feed
a.older(href="/news") View older articles

View File

@ -3,16 +3,26 @@ html
head head
meta(charset="utf8") meta(charset="utf8")
meta(name="viewport", content="width=device-width, initial-scale=1") meta(name="viewport", content="width=device-width, initial-scale=1")
if piwik block meta
meta(name="og:title", content="Icy Network")
meta(name="og:description", content="Icy Network is a Global Network of Communities and Websites, United by a Single Login")
meta(name="og:image", content="https://icynet.eu/static/image/icynet-icon.png")
meta(name="og:type", content="website")
meta(name="twitter:card", content="summary")
meta(name="twitter:title", content="Icy Network")
meta(name="twitter:description", content="Icy Network is a Global Network of Communities and Websites, United by a Single Login")
if matomo
script(type="text/javascript"). script(type="text/javascript").
var _paq = _paq || []; var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */ /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.#{matomo.site_domain}"]);
_paq.push(["setDomains", ["*.#{matomo.site_domain}"]]);
_paq.push(['trackPageView']); _paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']); _paq.push(['enableLinkTracking']);
(function() { (function() {
var u="#{piwik.track_url}"; var u="#{matomo.track_url}";
_paq.push(['setTrackerUrl', u+'piwik.php']); _paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '#{piwik.site_id}']); _paq.push(['setSiteId', '#{matomo.site_id}']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s); g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})(); })();
@ -44,6 +54,12 @@ html
title title
block title block title
body body
noscript
h2 This site requires JavaScript to work properly!
p Please consider enabling JavaScript for the full experience.
if matomo
p
img(src="#{matomo.track_url}piwik.php?idsite=1&rec=1" style="border:0;" alt="")
block banner block banner
.banner .banner
.logo .logo
@ -60,39 +76,24 @@ html
block nav block nav
.anchor .anchor
nav.navigator nav.navigator
ul li.hidden#navlogo
li.hidden#navlogo a(href="/")
a(href="/") .logo.small
.logo.small .part1 Icy
.part1 Icy .part2 Network
.part2 Network .menu
li.fullview label.showButton(for="show-menu")
a.scroll(href="/#home") Home i.fa.fa-fw.fa-bars
li.fullview input(type="checkbox" id="show-menu")
a.scroll(href="/#news") News .list
li.fullview ul
a(href="https://forum.icynet.eu/") Forum
ul.fullview.floating
if user
li#user
a(href="/user/manage") #{user.display_name}
li
a(href="/logout") Log out
else
li
a(href="/login") Log in
ul.mobview.floating
li.cssextend
a#mobile(href="#")
i.fa.fa-fw.fa-bars
ul.flexview
li li
a.scroll(href="/#home") Home a.scroll(href="/#home") Home
li li
a.scroll(href="/#news") News a.scroll(href="/#news") News
li li
a(href="https://forum.icynet.eu/") Forum a(href="https://forum.icynet.eu/") Forum
.division ul.floating
if user if user
li#user li#user
a(href="/user/manage") #{user.display_name} a(href="/user/manage") #{user.display_name}
@ -116,7 +117,7 @@ html
i.fa.fa-fw.fa-twitter i.fa.fa-fw.fa-twitter
a.socialbtn#discord(href="https://discord.gg/Xe7MKSx" target="_blank") a.socialbtn#discord(href="https://discord.gg/Xe7MKSx" target="_blank")
span.discordlogo span.discordlogo
span &copy; 2017 - Icy Network - Some Rights Reserved span &copy; 2018 - Icy Network - Some Rights Reserved
br br
span span
a(href="/docs/terms-of-service") Terms of Service a(href="/docs/terms-of-service") Terms of Service

View File

@ -1,5 +1,18 @@
extends ../layout.pug extends ../layout.pug
block meta
if article
meta(name="description", content=article.description)
meta(name="og:title", content=article.title)
meta(name="og:description", content=article.description)
meta(name="og:image", content="https://icynet.eu/static/image/icynet-icon.png")
meta(name="og:type", content="article")
meta(name="og:updated_time", content=article.updated_at)
meta(name="twitter:card", content="summary")
meta(name="twitter:title", content=article.title)
meta(name="twitter:description", content=article.description)
link(rel="alternate", href="/news/atom.xml", title="Icy Network News", type="application/atom+xml")
block append links block append links
if editing if editing
script(src="//cdnjs.cloudflare.com/ajax/libs/ckeditor/4.7.2/ckeditor.js") script(src="//cdnjs.cloudflare.com/ajax/libs/ckeditor/4.7.2/ckeditor.js")

View File

@ -1,5 +1,8 @@
extends ../layout.pug extends ../layout.pug
block append meta
link(rel="alternate", href="/news/atom.xml", title="Icy Network News", type="application/atom+xml")
block title block title
|Icy Network - News |Icy Network - News

View File

@ -69,6 +69,4 @@ block body
h2 Authorized Applications h2 Authorized Applications
.specify(title="Applications which have access to basic user information. You may restrict access at any time by pressing the red icon on the top right of the application card.") ? .specify(title="Applications which have access to basic user information. You may restrict access at any time by pressing the red icon on the top right of the application card.") ?
.cl#clientlist .cl#clientlist
span.load p Please enable JavaScript to view this content.
i.fa.fa-spin.fa-spinner.fa-2x
span Loading list