Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • xeruf/dashboard
  • stackspin/dashboard
2 results
Show changes
Showing
with 6525 additions and 6858 deletions
export const api = {
hostname: process.env.REACT_APP_API_URL,
hostname: process.env.REACT_APP_API_URL || '/api/v1',
};
......@@ -9,11 +9,11 @@ const transformAppStatus = (ext: boolean, status: AppStatus) => {
export const transformApp = (response: any): App => {
const assetSlug = !response.external
? `/assets/${response.slug}.svg`
: `/custom/assets/${response.slug.replace('ext-', '')}.svg`;
? `/icons/${response.slug}.svg`
: `/icons/${response.slug.replace('ext-', '')}.svg`;
const markdownSlug = !response.external
? `/markdown/${response.slug}.md`
: `/custom/markdown/${response.slug.replace('ext-', '')}.md`;
: `/markdown/${response.slug.replace('ext-', '')}.md`;
return {
id: response.id ?? '',
name: response.name ?? '',
......
export { useResources } from './use-resources';
import { useDispatch, useSelector } from 'react-redux';
import { fetchResources } from '../redux';
import { getResources } from '../redux/selectors';
export function useResources() {
const dispatch = useDispatch();
const resources = useSelector(getResources);
function loadResources() {
return dispatch(fetchResources());
}
return {
resources,
loadResources,
};
}
export * from './types';
export { reducer } from './redux';
export { useResources } from './hooks';
import { Dispatch } from 'redux';
import { performApiCall } from 'src/services/api';
import { transformResources } from '../transformations';
export enum ResourcesActionTypes {
FETCH_RESOURCES = 'resources/fetch_resources',
}
export const fetchResources = () => async (dispatch: Dispatch<any>) => {
try {
const { data } = await performApiCall({
path: '/resources',
method: 'GET',
});
dispatch({
type: ResourcesActionTypes.FETCH_RESOURCES,
payload: transformResources(data),
});
} catch (err) {
console.error(err);
}
};
export * from './actions';
export { default as reducer } from './reducers';
export { getResources } from './selectors';
export * from './types';
import { ResourcesActionTypes } from './actions';
const initialResourcesState: any = {
resources: {},
};
const resourcesReducer = (state: any = initialResourcesState, action: any) => {
switch (action.type) {
case ResourcesActionTypes.FETCH_RESOURCES:
return {
...state,
resources: action.payload,
};
default:
return state;
}
};
export default resourcesReducer;
import { State } from 'src/redux';
export const getResources = (state: State) => state.resources.resources;
import { Resources } from '../types';
export interface ResourcesState {
resources: Resources;
}
import { Resources } from './types';
export const transformResources = (response: any): Resources | null => {
if (!response.success) {
return null;
}
return {
cpu: (response.cpu / response.cpu_total) * 100,
memory_used: (response.memory_total - response.memory_available) / 1e9,
memory_total: response.memory_total / 1e9,
disk_used: (response.disk_total - response.disk_free) / 1e9,
disk_total: response.disk_total / 1e9,
};
};
export interface Resources {
cpu: number;
memory_used: number;
memory_total: number;
disk_used: number;
disk_total: number;
}
......@@ -5,12 +5,15 @@ import {
fetchUserById,
fetchPersonalInfo,
updateUserById,
updateMultipleUsers,
updatePersonalInfo,
createUser,
deleteUser,
clearCurrentUser,
createBatchUsers,
fetchRecoveryLink,
resetTotpById,
resetWebAuthnById,
} from '../redux';
import { getUserById, getRecoveryLink, getUserModalLoading, getUserslLoading } from '../redux/selectors';
......@@ -42,6 +45,10 @@ export function useUsers() {
return dispatch(updateUserById(data));
}
function editMultipleUsers(data: any) {
return dispatch(updateMultipleUsers(data));
}
function editPersonalInfo(data: any) {
return dispatch(updatePersonalInfo(data));
}
......@@ -57,10 +64,19 @@ export function useUsers() {
function deleteUserById(id: string) {
return dispatch(deleteUser(id));
}
function getRecoveryLinkUserById(id: string) {
return dispatch(fetchRecoveryLink(id));
}
function resetTotp(id: string) {
return dispatch(resetTotpById(id));
}
function resetWebAuthn(id: string) {
return dispatch(resetWebAuthnById(id));
}
return {
users,
user,
......@@ -69,6 +85,7 @@ export function useUsers() {
loadUsers,
loadPersonalInfo,
editUserById,
editMultipleUsers,
editPersonalInfo,
userModalLoading,
userTableLoading,
......@@ -77,5 +94,7 @@ export function useUsers() {
getRecoveryLinkUserById,
clearSelectedUser,
createUsers,
resetTotp,
resetWebAuthn,
};
}
......@@ -7,9 +7,13 @@ import { AuthActionTypes } from 'src/services/auth';
import {
transformBatchResponse,
transformRequestMultipleUsers,
transformRequestUpdateMultipleUsers,
transformRequestUser,
transformUser,
transformUpdateMultipleUsers,
transformRecoveryLink,
transformTotp,
transformWebAuthn,
} from '../transformations';
export enum UserActionTypes {
......@@ -22,6 +26,9 @@ export enum UserActionTypes {
SET_USER_MODAL_LOADING = 'users/user_modal_loading',
SET_USERS_LOADING = 'users/users_loading',
CREATE_BATCH_USERS = 'users/create_batch_users',
UPDATE_MULTIPLE_USERS = '/users/multi-edit',
RESET_TOTP = 'users/reset-totp-user',
RESET_WEBAUTHN = 'users/reset-webauthn-user',
}
export const setUsersLoading = (isLoading: boolean) => (dispatch: Dispatch<any>) => {
......@@ -132,6 +139,35 @@ export const updateUserById = (user: any) => async (dispatch: Dispatch<any>, get
dispatch(setUserModalLoading(false));
};
// ////////////////////
export const updateMultipleUsers = (users: any) => async (dispatch: Dispatch<any>) => {
dispatch(setUserModalLoading(true));
try {
const { data } = await performApiCall({
path: '/users/multi-edit',
method: 'PUT',
body: transformRequestUpdateMultipleUsers(users),
});
dispatch({
type: UserActionTypes.UPDATE_MULTIPLE_USERS,
payload: transformUpdateMultipleUsers(data),
});
showToast('Users updated successfully.', ToastType.Success);
dispatch(fetchUsers());
} catch (err) {
console.error(err);
}
dispatch(setUserModalLoading(false));
};
// /////////////////////
export const updatePersonalInfo = (user: any) => async (dispatch: Dispatch<any>) => {
dispatch(setUserModalLoading(true));
......@@ -203,6 +239,38 @@ export const fetchRecoveryLink = (id: string) => async (dispatch: Dispatch<any>)
}
};
export const resetTotpById = (id: string) => async (dispatch: Dispatch<any>) => {
try {
const { data } = await performApiCall({
path: `/users/${id}/reset_totp`,
method: 'POST',
});
dispatch({
type: UserActionTypes.RESET_TOTP,
payload: transformTotp(data),
});
} catch (err) {
console.error(err);
}
};
export const resetWebAuthnById = (id: string) => async (dispatch: Dispatch<any>) => {
try {
const { data } = await performApiCall({
path: `/users/${id}/reset_webauthn`,
method: 'POST',
});
dispatch({
type: UserActionTypes.RESET_WEBAUTHN,
payload: transformWebAuthn(data),
});
} catch (err) {
console.error(err);
}
};
export const deleteUser = (id: string) => async (dispatch: Dispatch<any>) => {
dispatch(setUserModalLoading(true));
......
......@@ -45,6 +45,20 @@ export const transformRequestAppRoles = (data: AppRoles): any => {
};
};
export const transformTotp = (data: any) => {
if (data.credentials !== undefined) {
return data.credentials.totp !== undefined;
}
return undefined;
};
export const transformWebAuthn = (data: any) => {
if (data.credentials !== undefined) {
return data.credentials.webauthn !== undefined;
}
return undefined;
};
export const transformUser = (response: any): User => {
return {
id: response.id ?? '',
......@@ -53,6 +67,11 @@ export const transformUser = (response: any): User => {
name: response.traits.name ?? '',
preferredUsername: response.preferredUsername ?? '',
status: response.state ?? '',
totp: transformTotp(response),
webauthn: transformWebAuthn(response),
tags: response.stackspin_data.tags ?? '',
admin: response.stackspin_data.stackspin_admin ?? '',
meta: response.metadata_admin ?? '',
};
};
......@@ -64,6 +83,22 @@ export const transformRequestUser = (data: Pick<User, 'app_roles' | 'name' | 'em
};
};
export const transformUpdateMultipleUsers = (response: any): any => {
return {
success: response.success,
existing: response.existing,
failed: response.failed,
};
};
export const transformRequestUpdateMultipleUsers = (data: any) => {
return {
users: _.map(data, (user: Pick<User, 'email' | 'id' | 'app_roles'>) => {
return { email: user.email ?? '', id: user.id ?? '', app_roles: user.app_roles.map(transformRequestAppRoles) };
}),
};
};
const extractUsersFromCsv = (csvData: string) => {
const csvRows = csvData.split('\n');
......@@ -95,3 +130,11 @@ export const transformBatchResponse = (response: any): any => {
export const transformRecoveryLink = (response: any): string => {
return response.recovery_link;
};
export const transformResetTotpById = (response: any): any => {
return {
success: response.success,
existing: response.existing,
failed: response.failed,
};
};
......@@ -5,6 +5,11 @@ export interface User {
name: string;
preferredUsername: string;
status: string;
totp?: boolean;
webauthn?: boolean;
tags?: [];
admin?: boolean;
meta?: [];
}
export interface FormUser extends User {
......@@ -18,11 +23,20 @@ export enum UserRole {
User = 'user',
}
export enum NoChange {
NoChange = 'no_change',
}
export interface AppRoles {
name: string | null;
role: UserRole | null;
}
export interface MultiEditAppRoles {
name: string | null;
role: UserRole | NoChange | null;
}
export interface UserApiRequest {
id: number | null;
email: string;
......@@ -34,3 +48,11 @@ export interface MultipleUsersData {
csvUserData: string;
appRoles: AppRoles[];
}
export interface MultiEditUser {
userIds: string[];
userEmails: string[];
userNames: string[];
app_roles: MultiEditAppRoles[];
status: string;
}
module.exports = {
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./public/index.html',
],
theme: {
extend: {
colors: {
primary: {
50: '#F2FFFF',
100: '#D6FDFF',
200: '#B6F7FB',
300: '#7AE5EA',
400: '#55C6CC',
500: '#39A9B1',
600: '#24929C',
700: '#157983',
800: '#135D66',
900: '#0F4F57',
light: '#54C6CC',
DEFAULT: '#54C6CC',
dark: '#1E8290',
50: 'var(--colour-primary-50)',
100: 'var(--colour-primary-100)',
200: 'var(--colour-primary-200)',
300: 'var(--colour-primary-300)',
400: 'var(--colour-primary-400)',
500: 'var(--colour-primary-500)',
600: 'var(--colour-primary-600)',
700: 'var(--colour-primary-700)',
800: 'var(--colour-primary-800)',
900: 'var(--colour-primary-900)',
950: 'var(--colour-primary-950)',
light: 'var(--colour-primary-light)',
DEFAULT: 'var(--colour-primary-default)',
dark: 'var(--colour-primary-dark)',
},
},
},
},
variants: {
extend: {
tableLayout: ['hover', 'focus'],
},
},
plugins: [
require('@tailwindcss/forms'), // eslint-disable-line
require('@tailwindcss/typography'), // eslint-disable-line
......
source diff could not be displayed: it is too large. Options to address this: view the blob.
......@@ -9,8 +9,13 @@
"packageRules": [
{
"matchDepNames": ["node"],
"matchFiles": ["frontend/Dockerfile", ".gitlab-ci.yml"],
"allowedVersions": "!/^\d*[13579](-.*)?$/"
"matchFileNames": ["frontend/Dockerfile", ".gitlab-ci.yml"],
"allowedVersions": "!/^\\d*[13579](-.*)?$/"
},
{
"matchPackageNames": ["ory-hydra-client"],
"matchFileNames": ["backend/requirements.in"],
"allowedVersions": "<2.0.0"
}
]
}
#!/usr/bin/env bash
if [ -f "./backend/kubeconfig/kube_config_cluster.yml" ]; then
echo "Local KUBECONFIG configuration file found, applying custom configuration."
export KUBECONFIG=./backend/kubeconfig/kube_config_cluster.yml
else
echo "no Local KUBECONFIG configuration file found, skipping custom configuration."
fi
set -euo pipefail
dockerComposeArgs=$@
export DATABASE_PASSWORD=$(kubectl get secret -n flux-system stackspin-single-sign-on-variables -o jsonpath --template '{.data.dashboard_database_password}' | base64 -d)
export DOMAIN=$(kubectl get secret -n flux-system stackspin-cluster-variables -o jsonpath --template '{.data.domain}' | base64 -d)
export HYDRA_CLIENT_SECRET=$(kubectl get secret -n flux-system stackspin-dashboard-local-oauth-variables -o jsonpath --template '{.data.client_secret}' | base64 -d)
export FLASK_SECRET_KEY=$(kubectl get secret -n flux-system stackspin-dashboard-variables -o jsonpath --template '{.data.backend_secret_key}' | base64 -d)
if [[ -z "$DATABASE_PASSWORD" ]]; then
echo "Could not find database password in stackspin-single-sign-on-variables secret"
exit 1
fi
if [[ -z "$DOMAIN" ]]; then
echo "Could not find domain name in stackspin-cluster-variables secret"
exit 1
fi
if [[ -z "$FLASK_SECRET_KEY" ]]; then
echo "Could not find backend_secret_key in stackspin-dashboard-variables secret"
exit 1
fi
if [[ -z "$HYDRA_CLIENT_SECRET" ]]; then
echo "Could not find client_secret in stackspin-dashboard-local-oauth-variables secret"
echo "make sure you add this secret following instructions in the dashboard-dev-overrides repository"
exit 1
fi
KUBECTL_UID=${UID:-1001} KUBECTL_GID=${GID:-0} docker compose up $dockerComposeArgs