Things you don't have access to yet. Request what you need.
@@ -99,6 +124,17 @@
});
}
+ // Render icon as either Font Awesome class or for URL
+ function renderIcon(icon, kind) {
+ if (!icon) icon = KIND_ICONS[kind] || 'fa-solid fa-cube';
+ // If it starts with http, treat it as an image URL
+ if (/^https?:\/\//i.test(icon)) {
+ return '';
+ }
+ // Otherwise it's a Font Awesome class
+ return '';
+ }
+
// "How do I actually use this?" — the question the directory exists to
// answer and the one the old portal never did. Everything here is derived
// from directory metadata; nothing is hardcoded per-service.
@@ -142,7 +178,6 @@
function cardHtml(r, accessible) {
var md = r.metadata || {};
- var icon = md.icon || KIND_ICONS[r.kind] || 'fa-solid fa-cube';
var blurb = md.tagline || r.description || 'No description provided';
var href = accessible ? linkFor(r) : null;
var lines = accessible ? howTo(r) : [];
@@ -166,10 +201,12 @@
+ ' Request access';
}
+ var iconHtml = renderIcon(md.icon, r.kind);
+
return '
'
+ '
'
+ '
'
- + ''
+ + iconHtml
+ '' + esc(r.name) + ''
+ '
'
+ '
' + esc(r.kind)
@@ -235,7 +272,12 @@
}
function renderAll() {
- renderGrid('my-services', state.mine, true);
+ // Split mine into services and hosts
+ var myServices = state.mine.filter(function(r) { return r.kind === 'service' || r.kind === 'oauth'; });
+ var myHosts = state.mine.filter(function(r) { return r.kind === 'host'; });
+
+ renderGrid('my-services', myServices, true);
+ renderGrid('my-hosts', myHosts, true);
renderGrid('other-services', state.others, false);
renderRequests();
renderApprovals();
diff --git a/nodejs/views/profile.ejs b/nodejs/views/profile.ejs
index 0493205..e846086 100644
--- a/nodejs/views/profile.ejs
+++ b/nodejs/views/profile.ejs
@@ -6,13 +6,10 @@
var isOwnProfile = !location.pathname.includes('/users/');
function renderProfile(user){
- // data.photo = unescape(encodeURIComponent(data.jpegPhoto));
user.createTimestamp = moment(user.createTimestamp, "YYYYMMDDHHmmssZ").fromNow();
user.modifyTimestamp = moment(user.modifyTimestamp, "YYYYMMDDHHmmssZ").fromNow();
user.managerUids = (user.manager || []).map(app.user.dnToUid);
-
$.scope.user.update(user);
- $.scope.passwordReset.update(user);
};
async function renderUserGroups(user){
@@ -92,9 +89,6 @@
async function renderMyServices(){
try{
let res = await app.api.get('discovery/me');
- // The server already resolves this by walking the graph, so a service
- // with no address of its own inherits its host's. Only fall back
- // locally when it genuinely resolved to nothing.
res.results.forEach(r => {
r.resolvedAddress = r.resolvedAddress
|| (r.metadata && r.metadata.address)
@@ -112,34 +106,32 @@
try{
let uid = currentUser.uid;
let res = await app.api.get(`metrics/user/${uid}`);
- if (res && res.results) {
- const renderList = (items, id) => {
- const el = document.getElementById(id);
- if(!el) return;
- el.innerHTML = '';
- if (!items || items.length === 0) {
- el.innerHTML = '
';
}
}
-
async function determinUser(){
if(location.pathname.includes('/users/')){
let uid = location.pathname.replace('/users/', '');
-
return (await app.api.get('user/'+uid)).results;
}else{
return await app.auth.asyncUser;
@@ -148,16 +140,9 @@
function editUser(user){
user = user || currentUser;
-
var $profileCard = $('#userProfile');
var $editCard = $('#editProfile');
-
$.scope.editProfile.update(user);
- // jq-repeat's update() is trailing-edge throttled (~50ms) as of 2.1.0 --
- // wait for the throttle tick to land before sliding the updated card
- // into view, or it can briefly show stale/empty data. The manager
- // picker is a JS widget, not a mustache-bound input, so it also has to
- // wait for update() to (re-)render its empty mount div before attaching.
setTimeout(function(){
app.ui.userSelect('#edit-manager', {
name: 'manager',
@@ -172,8 +157,6 @@
function editUserSeccess(data){
currentUser = data.results;
renderProfile(currentUser);
- // Same throttle-tick wait as editUser() above -- renderProfile() calls
- // $.scope.user.update()/passwordReset.update() internally.
setTimeout(function(){
$('#editProfile').slideUp();
$('#userProfile').slideDown()
@@ -201,6 +184,29 @@
});
}
+ // Password reset modal
+ function openPasswordResetModal(){
+ app.modal.open({
+ title: 'Reset Password for ' + currentUser.uid,
+ bodyHtml:
+ ''
+ + '',
+ footer: {
+ buttonsHtml: ''
+ + '',
+ },
+ });
+ }
+
$(document).ready(async function(){
currentUser = await determinUser();
@@ -210,7 +216,7 @@
renderMyServices();
if(!isOwnProfile) {
renderMyMetrics();
- $('#user-metrics-card').show();
+ $('#tab-metrics').parent().show();
}
$('#personal-group-uid-label').text(currentUser.uid);
@@ -221,8 +227,6 @@
name: 'members', values: [], placeholder: 'Type a username…',
});
- // API Tokens are self-service only — never shown when an admin is
- // viewing someone else's profile via /users/:uid.
if(isOwnProfile){
$('#own-api-tokens-section').show();
tableAJAX();
@@ -230,318 +234,275 @@
});
-