Add maintenance banner on UI (#797)

* fix bug on email separator

Signed-off-by: Denis barbaron <denis.barbaron@orange.com>

* allow group name change

Signed-off-by: Denis barbaron <denis.barbaron@orange.com>

* add maintenance banner on UI

Signed-off-by: Denis barbaron <denis.barbaron@orange.com>

* removes unnecessary comments

Signed-off-by: Denis barbaron <denis.barbaron@orange.com>

---------

Signed-off-by: Denis barbaron <denis.barbaron@orange.com>
This commit is contained in:
Denis Barbaron
2024-07-19 10:33:52 +02:00
committed by GitHub
parent cf56911e9f
commit 489ba0427e
17 changed files with 446 additions and 16 deletions

View File

@@ -1036,6 +1036,19 @@ dbapi.loadUser = function(email) {
return db.run(r.table('users').get(email))
}
dbapi.updateUsersAlertMessage = function(alertMessage) {
return dbapi.getRootGroup().then(function(group) {
return db.run(r.table('users').get(group.owner.email).update({settings: {
alertMessage:
r.branch(
r.row.hasFields({settings: 'alertMessage'})
, r.row('settings')('alertMessage').merge(alertMessage)
, alertMessage
)
}}, {returnChanges: true}))
})
}
dbapi.updateUserSettings = function(email, changes) {
return db.run(r.table('users').get(email).update({
settings: changes

View File

@@ -1,5 +1,5 @@
/**
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
* Copyright © 2019-2024 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
const dbapi = require('../../../db/api')
@@ -11,6 +11,7 @@ const wire = require('../../../wire')
const wireutil = require('../../../wire/util')
const userapi = require('./user')
/* --------------------------------- PRIVATE FUNCTIONS --------------------------------------- */
function userApiWrapper(fn, req, res) {
@@ -142,6 +143,92 @@ function getUserInfo(req, email) {
})
}
function updateUsersAlertMessage(req, res) {
const lock = {}
return dbapi.lockUser(req.user.email).then(function(stats) {
if (!stats.replaced) {
return apiutil.lightComputeStats(res, stats)
}
lock.user = stats.changes[0].new_val
return dbapi.updateUsersAlertMessage(req.body).then(function(stats) {
if (stats.unchanged) {
dbapi.loadUser(req.user.email).then(function(user) {
apiutil.respond(res, 200, 'Unchanged (users alert message)',
{alertMessage: user.settings.alertMessage})
})
}
else {
apiutil.respond(res, 200, 'Updated (users alert message)',
{alertMessage: stats.changes[0].new_val.settings.alertMessage})
}
})
.catch(function(err) {
apiutil.internalError(res, 'Failed to update users alert message: ', err.stack)
})
})
.catch(function(err) {
if (err !== 'busy') {
throw err
}
})
.finally(function() {
lockutil.unlockUser(lock)
})
}
function getUsersAlertMessage(req, res) {
const fields = req.swagger.params.fields.value
dbapi.getRootGroup().then(function(group) {
return dbapi.loadUser(group.owner.email).then(function(user) {
if (typeof user.settings.alertMessage === 'undefined') {
const lock = {}
return dbapi.lockUser(req.user.email).then(function(stats) {
if (!stats.replaced) {
return apiutil.lightComputeStats(res, stats)
}
lock.user = stats.changes[0].new_val
const alertMessage = {
activation: 'False'
, data: '*** this site is currently under maintenance, please wait ***'
, level: 'Critical'
}
return dbapi.updateUsersAlertMessage(alertMessage).then(function(stats) {
if (!stats.errors) {
return stats.changes[0].new_val.settings.alertMessage
}
throw new Error('Failed to initialize users alert message')
})
})
.finally(function() {
lockutil.unlockUser(lock)
})
}
return user.settings.alertMessage
})
.then(function(alertMessage) {
if (fields) {
return _.pick(alertMessage, fields.split(','))
}
else {
return alertMessage
}
})
.then(function(alertMessage) {
apiutil.respond(res, 200, 'Users Alert Message', {alertMessage: alertMessage})
})
})
.catch(function(err) {
if (err !== 'busy') {
apiutil.internalError(res, 'Failed to get users alert message: ', err.stack)
}
})
}
function updateUserGroupsQuotas(req, res) {
const email = req.swagger.params.email.value
const duration =
@@ -379,6 +466,8 @@ module.exports = {
updateUserGroupsQuotas: updateUserGroupsQuotas
, updateDefaultUserGroupsQuotas: updateDefaultUserGroupsQuotas
, getUsers: getUsers
, getUsersAlertMessage: getUsersAlertMessage
, updateUsersAlertMessage: updateUsersAlertMessage
, getUserByEmail: getUserByEmail
, getUserInfo: getUserInfo
, createUser: createUser

View File

@@ -1,10 +1,10 @@
##
# Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
# Copyright © 2019-2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
##
swagger: "2.0"
info:
version: "2.4.0"
version: "2.4.1"
title: Smartphone Test Farm
description: Control and manages real Smartphone devices from browser and restful apis
license:
@@ -728,6 +728,62 @@ paths:
$ref: "#/definitions/UnexpectedErrorResponse"
security:
- accessTokenAuth: []
/users/alertMessage:
x-swagger-router-controller: users
get:
summary: Gets the users alert message
description: The Users Alert Message endpoint returns the current alert message launched by the administrator user
operationId: getUsersAlertMessage
tags:
- users
parameters:
- name: fields
in: query
description: Fields query parameter takes a comma seperated list of fields. Only listed field will be return in response
required: false
type: string
responses:
"200":
description: Current Users Alert Message
schema:
$ref: "#/definitions/AlertMessageResponse"
default:
description: >
Unexpected Error:
* 401: Unauthorized => bad credentials
* 500: Internal Server Error
schema:
$ref: "#/definitions/UnexpectedErrorResponse"
security:
- accessTokenAuth: []
put:
summary: Updates the users alert message
description: Updates the users alert message
operationId: updateUsersAlertMessage
tags:
- admin
parameters:
- name: alertMessage
in: body
description: Alert message properties
required: true
schema:
$ref: "#/definitions/AlertMessagePayload"
responses:
"200":
description: Updated Users Alert Message
schema:
$ref: "#/definitions/AlertMessageResponse"
default:
description: >
Unexpected Error:
* 400: Bad Request
* 401: Unauthorized => bad credentials
* 500: Internal Server Error
schema:
$ref: "#/definitions/UnexpectedErrorResponse"
security:
- accessTokenAuth: []
/users/groupsQuotas:
x-swagger-router-controller: users
put:
@@ -2270,6 +2326,49 @@ definitions:
type: string
user:
type: object
AlertMessage:
type: object
properties:
activation:
description: Enable or disablee the alert message
type: string
data:
description: Alert message text to display
type: string
level:
description: Alert message level
type: string
AlertMessageResponse:
required:
- success
- description
- alertMessage
properties:
success:
type: boolean
description:
type: string
alertMessage:
$ref: '#/definitions/AlertMessage'
AlertMessagePayload:
description: Payload object for updating the alert message
properties:
activation:
description: Enable or disablee the alert message
type: string
enum:
- 'True'
- 'False'
data:
description: Alert message text to display
type: string
level:
description: Alert message level
type: string
enum:
- Information
- Warning
- Critical
Token:
type: object
properties:

View File

@@ -1,5 +1,5 @@
/**
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
* Copyright © 2019-2024 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
const timeutil = require('../../../util/timeutil')
@@ -33,8 +33,9 @@ module.exports = function(pushdev) {
'email'
, 'name'
, 'privilege'
, {groups: ['quotas', 'subscribed']
})
, {groups: ['quotas', 'subscribed']}
, {settings: ['alertMessage']}
)
.changes(), function(err, cursor) {
if (err) {
throw err
@@ -77,6 +78,11 @@ module.exports = function(pushdev) {
!_.isEqual(data.new_val.groups.subscribed, data.old_val.groups.subscribed)) {
targets.push('settings')
}
else if (!_.isEqual(
data.new_val.settings.alertMessage
, data.old_val.settings.alertMessage)) {
targets.push('menu')
}
if (targets.length) {
sendUserChange(
data.new_val

View File

@@ -125,11 +125,21 @@ message UserGroupsField {
repeated string subscribed = 2;
}
message AlertMessageField {
required string activation = 1;
required string data = 2;
required string level = 3;
}
message UserSettingsField {
optional AlertMessageField alertMessage = 1;
}
message UserField {
required string email = 1;
required string name = 2;
required string privilege = 3;
required UserGroupsField groups = 4;
required UserSettingsField settings = 5;
}
message UserChangeMessage {

View File

@@ -1,5 +1,5 @@
/**
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
* Copyright © 2019-2024 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
const oboe = require('oboe')
@@ -28,6 +28,10 @@ module.exports = function UsersServiceFactory(
})
}
UsersService.getUsersAlertMessage = function() {
return $http.get('/api/v1/users/alertMessage')
}
UsersService.getUsers = function(fields) {
return $http.get('/api/v1/users?fields=' + fields)
}
@@ -92,5 +96,10 @@ module.exports = function UsersServiceFactory(
$rootScope.$apply()
})
socket.on('user.menu.users.updated', function(user) {
$rootScope.$broadcast('user.menu.users.updated', user)
$rootScope.$apply()
})
return UsersService
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
* Copyright © 2019-2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
require('./menu.css')
@@ -7,6 +7,8 @@ require('angular-cookies')
module.exports = angular.module('stf.menu', [
'ngCookies',
require('stf/users').name,
require('stf/app-state').name,
require('stf/socket').name,
require('stf/util/common').name,
require('stf/nav-menu').name,

View File

@@ -1,10 +1,12 @@
/**
* Copyright © 2019-2023 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
* Copyright © 2019-2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
module.exports = function MenuCtrl(
$scope
, $rootScope
, UsersService
, AppState
, SettingsService
, $location
, $http
@@ -48,4 +50,41 @@ module.exports = function MenuCtrl(
socket.disconnect()
}, 100)
}
$scope.alertMessage = {
activation: 'False'
, data: ''
, level: ''
}
if (AppState.user.privilege === 'admin') {
$scope.alertMessage = SettingsService.get('alertMessage')
}
else {
UsersService.getUsersAlertMessage().then(function(response) {
$scope.alertMessage = response.data.alertMessage
})
}
$scope.isAlertMessageActive = function() {
return $scope.alertMessage.activation === 'True'
}
$scope.isInformationAlert = function() {
return $scope.alertMessage.level === 'Information'
}
$scope.isWarningAlert = function() {
return $scope.alertMessage.level === 'Warning'
}
$scope.isCriticalAlert = function() {
return $scope.alertMessage.level === 'Critical'
}
$scope.$on('user.menu.users.updated', function(event, message) {
if (message.user.privilege === 'admin') {
$scope.alertMessage = message.user.settings.alertMessage
}
})
}

View File

@@ -1,3 +1,7 @@
/*
Copyright © 2019-2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
*/
.stf-menu .stf-logo {
background: url(../../common/logo/exports/STF-512.png) no-repeat 0 0;
width: 32px;
@@ -65,3 +69,44 @@
height: 44px !important;
min-height: 44px !important;
}
.stf-menu .information-level {
background-color: #5bc0df;
}
.stf-menu .warning-level {
background-color: #f0ad4f;
}
.stf-menu .critical-level {
background-color: #ff4b2b;
}
.stf-menu .maintenance-banner {
color: #ffffff;
padding: 10px;
position: relative;
text-align: center;
overflow: hidden;
height: 44px;
}
.stf-menu .maintenance-banner p {
font-size: 18px;
animation: slideBanner 15s linear infinite;
white-space: nowrap;
position: relative;
top: 50%;
transform: translate(0%,-50%);
line-height: 44px;
}
@keyframes slideBanner {
0% {
left: 100%;
}
100% {
left: -100%;
}
}

View File

@@ -1,5 +1,5 @@
//
Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
Copyright © 2019-2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
//
.navbar.stf-menu(ng-controller='MenuCtrl')
@@ -19,6 +19,10 @@
a(ng-href='/#!/settings')
span.fa.fa-gears
span(ng-if='!$root.basicMode', translate) Settings
ul.nav.stf-nav(ng-if='isAlertMessageActive()', nav-menu='current').unselectable.col-xs-5
li.col-xs-12
.maintenance-banner(ng-class="{'information-level': isInformationAlert(), 'warning-level': isWarningAlert(), 'critical-level': isCriticalAlert()}")
p(translate) {{ alertMessage.data }}
ul.nav.stf-nav.stf-feedback.pull-right(ng-cloak, nav-menu='current').unselectable
li.stf-nav-web-native-button(ng-if='!$root.basicMode && isControlRoute')
.btn-group

View File

@@ -0,0 +1,41 @@
/**
* Copyright © 2024 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
module.exports = function AlertMessageCtrl(
$scope
, SettingsService
) {
$scope.defaultAlertMessage = {
data: '*** This site is currently under maintenance, please wait ***'
, activation: 'False'
, level: 'Critical'
}
SettingsService.bind($scope, {
target: 'alertMessage'
, source: 'alertMessage'
, defaultValue: $scope.defaultAlertMessage
})
$scope.alertMessageActivationOptions = ['True', 'False']
$scope.alertMessageLevelOptions = ['Information', 'Warning', 'Critical']
$scope.$watch(
function() {
return SettingsService.get('alertMessage')
}
, function(newvalue) {
if (typeof newvalue === 'undefined') {
SettingsService.set('alertMessage', $scope.defaultAlertMessage)
}
}
)
$scope.$on('user.menu.users.updated', function(event, message) {
if (message.user.privilege === 'admin') {
$scope.alertMessage = message.user.settings.alertMessage
}
})
}

View File

@@ -0,0 +1,34 @@
//
Copyright © 2024 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
//
.widget-container.fluid-height(ng-controller='AlertMessageCtrl')
.heading
i.fa.fa-exclamation-triangle
span(translate) Alert Message
.widget-content.padded
.form-horizontal
.form-group.general-item
.input-group
.input-group-addon.input-sm
i.fa.fa-exclamation-triangle(
uib-tooltip="{{'Define your own alert message' | translate}}" tooltip-placement='auto top-right' tooltip-popup-delay='500')
input.form-control.input-sm(size='20' type='text' placeholder='' ng-model='alertMessage.data')
.form-group.general-item
label.general-label(
translate
uib-tooltip="{{'Alert message activation' | translate}}"
tooltip-placement='auto top-right'
tooltip-popup-delay='500') Activation
select(
ng-model='alertMessage.activation'
ng-options='option for option in alertMessageActivationOptions')
.form-group.general-item
label.general-label(
translate
uib-tooltip="{{'Alert message level' | translate}}"
tooltip-placement='auto top-right'
tooltip-popup-delay='500') Level
select(
ng-model='alertMessage.level'
ng-options='option for option in alertMessageLevelOptions')

View File

@@ -0,0 +1,14 @@
/**
* Copyright © 2024 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
module.exports = angular.module('stf.settings.general.alert-message', [
require('stf/settings').name
])
.run(['$templateCache', function($templateCache) {
$templateCache.put(
'settings/general/alert-message/alert-message.pug'
, require('./alert-message.pug')
)
}])
.controller('AlertMessageCtrl', require('./alert-message-controller'))

View File

@@ -1,3 +1,9 @@
module.exports = function GeneralCtrl() {
/**
* Copyright © 2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
module.exports = function GeneralCtrl($scope, AppState) {
$scope.isAdmin = function() {
return AppState.user.privilege === 'admin'
}
}

View File

@@ -1,3 +1,16 @@
/*
Copyright © 2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
*/
.stf-general {
}
}
.stf-general .general-item {
margin: 0px 10px 15px 0px;
}
.stf-general .general-label {
font-weight: bold;
margin-right: 10px;
}

View File

@@ -1,8 +1,9 @@
//
Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
Copyright © 2019-2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
//
.row
.div(ng-controller='GeneralCtrl')
.row.stf-general
.col-md-3
div(ng-include='"settings/general/local/local-settings.pug"')
.col-md-3
@@ -11,3 +12,6 @@
div(ng-include='"settings/general/date-format/date-format.pug"')
.col-md-3
div(ng-include='"settings/general/email-address-separator/email-address-separator.pug"')
.row.stf-general(ng-if='isAdmin()')
.col-md-12
div(ng-include='"settings/general/alert-message/alert-message.pug"')

View File

@@ -1,5 +1,5 @@
/**
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
* Copyright © 2019-2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
require('./general.css')
@@ -8,7 +8,9 @@ module.exports = angular.module('stf.settings.general', [
require('./language').name,
require('./local').name,
require('./email-address-separator').name,
require('./date-format').name
require('./date-format').name,
require('stf/app-state').name,
require('./alert-message').name
])
.run(['$templateCache', function($templateCache) {
$templateCache.put(