From b64e4c9560e2b27ba7fa12e2b4247a9ea24a0209 Mon Sep 17 00:00:00 2001 From: SoyRageAgency Date: Tue, 17 Jun 2025 23:36:04 +0200 Subject: [PATCH 1/2] Enhance AuthManager with localforage integration for token management --- packages/app/src/classes/AuthManager/index.js | 30 ++++++++++++++++++- packages/app/src/components/Login/index.jsx | 15 +++++----- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/packages/app/src/classes/AuthManager/index.js b/packages/app/src/classes/AuthManager/index.js index 6203eeed..f966b5f7 100644 --- a/packages/app/src/classes/AuthManager/index.js +++ b/packages/app/src/classes/AuthManager/index.js @@ -1,6 +1,7 @@ import AuthModel from "@models/auth" import SessionModel from "@models/session" import UserModel from "@models/user" +import localforage from "localforage" import { Login } from "@components" @@ -21,7 +22,32 @@ export default class AuthManager { user: null, } + store = localforage.createInstance({ + driver: localforage.INDEXEDDB, + name: "tokens", + }) + + listAvailableTokens = () => { + return this.store.keys() + } + + loadTokenFromUserId = async (user_id) => { + const session = await this.store.getItem(user_id) + + if (!session) { + console.error("Session not found") + return false + } + + SessionModel.token = session.token + SessionModel.refreshToken = session.refreshToken + + this.initialize() + } + public = { + loadTokenFromUserId: this.loadTokenFromUserId, + listAvailableTokens: this.listAvailableTokens, login: () => { app.layout.draggable.open("login", Login, { componentProps: { @@ -105,5 +131,7 @@ export default class AuthManager { } } - //onLoginCallback = async (state, result) => {} + onLoginCallback = async (state, result) => { + this.store.setItem(result.user_id, result) + } } diff --git a/packages/app/src/components/Login/index.jsx b/packages/app/src/components/Login/index.jsx index 48a6fcc0..d24a39c3 100755 --- a/packages/app/src/components/Login/index.jsx +++ b/packages/app/src/components/Login/index.jsx @@ -20,7 +20,7 @@ const stepsValidations = { return { exists: false, } - }, + } ) return check.exists @@ -65,7 +65,9 @@ class Login extends React.Component { this.toggleLoading(true) await AuthModel.login(payload, this.onDone).catch((error) => { - if (error.response.data) { + if (error.response && error.response.data) { + this.onError(error.response.data.error) + if (error.response.data.violation) { return this.setState({ forbidden: error.response.data.violation, @@ -85,7 +87,6 @@ class Login extends React.Component { console.error(error, error.response) this.toggleLoading(false) - this.onError(error.response.data.error) return false }) @@ -124,7 +125,7 @@ class Login extends React.Component { try { await AuthModel.activateAccount( this.state.activation.user_id, - this.state.activation.code, + this.state.activation.code ) this.handleFinish() @@ -148,7 +149,7 @@ class Login extends React.Component { } const rensendObj = await AuthModel.resendActivationCode( - activationObj.user_id, + activationObj.user_id ).catch((error) => { app.message.info(`Please try again later...`) return null @@ -383,7 +384,7 @@ class Login extends React.Component { onChange={(e) => this.onUpdateInput( "username", - e.target.value, + e.target.value ) } onPressEnter={this.nextStep} @@ -406,7 +407,7 @@ class Login extends React.Component { onChange={(e) => this.onUpdateInput( "password", - e.target.value, + e.target.value ) } onPressEnter={this.nextStep} From 6bd11087dcdf83e7d010ec1240a5ba8dbde0ecac Mon Sep 17 00:00:00 2001 From: SoyRageAgency Date: Wed, 25 Jun 2025 23:51:37 +0200 Subject: [PATCH 2/2] New function related SwitchAccount --- get-docker.sh | 694 ++++++++++++++++++ packages/app/src/classes/AuthManager/index.js | 58 +- .../src/layouts/components/sidebar/index.jsx | 459 +++++++----- .../src/layouts/components/sidebar/index.less | 74 ++ .../server/{compose-dev.yml => compose.yml} | 23 +- .../server/gateway/managers/nginx/index.js | 1 + 6 files changed, 1100 insertions(+), 209 deletions(-) create mode 100644 get-docker.sh rename packages/server/{compose-dev.yml => compose.yml} (57%) diff --git a/get-docker.sh b/get-docker.sh new file mode 100644 index 00000000..fb078a8f --- /dev/null +++ b/get-docker.sh @@ -0,0 +1,694 @@ +#!/bin/sh +set -e +# Docker Engine for Linux installation script. +# +# This script is intended as a convenient way to configure docker's package +# repositories and to install Docker Engine, This script is not recommended +# for production environments. Before running this script, make yourself familiar +# with potential risks and limitations, and refer to the installation manual +# at https://docs.docker.com/engine/install/ for alternative installation methods. +# +# The script: +# +# - Requires `root` or `sudo` privileges to run. +# - Attempts to detect your Linux distribution and version and configure your +# package management system for you. +# - Doesn't allow you to customize most installation parameters. +# - Installs dependencies and recommendations without asking for confirmation. +# - Installs the latest stable release (by default) of Docker CLI, Docker Engine, +# Docker Buildx, Docker Compose, containerd, and runc. When using this script +# to provision a machine, this may result in unexpected major version upgrades +# of these packages. Always test upgrades in a test environment before +# deploying to your production systems. +# - Isn't designed to upgrade an existing Docker installation. When using the +# script to update an existing installation, dependencies may not be updated +# to the expected version, resulting in outdated versions. +# +# Source code is available at https://github.com/docker/docker-install/ +# +# Usage +# ============================================================================== +# +# To install the latest stable versions of Docker CLI, Docker Engine, and their +# dependencies: +# +# 1. download the script +# +# $ curl -fsSL https://get.docker.com -o install-docker.sh +# +# 2. verify the script's content +# +# $ cat install-docker.sh +# +# 3. run the script with --dry-run to verify the steps it executes +# +# $ sh install-docker.sh --dry-run +# +# 4. run the script either as root, or using sudo to perform the installation. +# +# $ sudo sh install-docker.sh +# +# Command-line options +# ============================================================================== +# +# --version +# Use the --version option to install a specific version, for example: +# +# $ sudo sh install-docker.sh --version 23.0 +# +# --channel +# +# Use the --channel option to install from an alternative installation channel. +# The following example installs the latest versions from the "test" channel, +# which includes pre-releases (alpha, beta, rc): +# +# $ sudo sh install-docker.sh --channel test +# +# Alternatively, use the script at https://test.docker.com, which uses the test +# channel as default. +# +# --mirror +# +# Use the --mirror option to install from a mirror supported by this script. +# Available mirrors are "Aliyun" (https://mirrors.aliyun.com/docker-ce), and +# "AzureChinaCloud" (https://mirror.azure.cn/docker-ce), for example: +# +# $ sudo sh install-docker.sh --mirror AzureChinaCloud +# +# ============================================================================== + + +# Git commit from https://github.com/docker/docker-install when +# the script was uploaded (Should only be modified by upload job): +SCRIPT_COMMIT_SHA="53a22f61c0628e58e1d6680b49e82993d304b449" + +# strip "v" prefix if present +VERSION="${VERSION#v}" + +# The channel to install from: +# * stable +# * test +DEFAULT_CHANNEL_VALUE="stable" +if [ -z "$CHANNEL" ]; then + CHANNEL=$DEFAULT_CHANNEL_VALUE +fi + +DEFAULT_DOWNLOAD_URL="https://download.docker.com" +if [ -z "$DOWNLOAD_URL" ]; then + DOWNLOAD_URL=$DEFAULT_DOWNLOAD_URL +fi + +DEFAULT_REPO_FILE="docker-ce.repo" +if [ -z "$REPO_FILE" ]; then + REPO_FILE="$DEFAULT_REPO_FILE" + # Automatically default to a staging repo fora + # a staging download url (download-stage.docker.com) + case "$DOWNLOAD_URL" in + *-stage*) REPO_FILE="docker-ce-staging.repo";; + esac +fi + +mirror='' +DRY_RUN=${DRY_RUN:-} +while [ $# -gt 0 ]; do + case "$1" in + --channel) + CHANNEL="$2" + shift + ;; + --dry-run) + DRY_RUN=1 + ;; + --mirror) + mirror="$2" + shift + ;; + --version) + VERSION="${2#v}" + shift + ;; + --*) + echo "Illegal option $1" + ;; + esac + shift $(( $# > 0 ? 1 : 0 )) +done + +case "$mirror" in + Aliyun) + DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce" + ;; + AzureChinaCloud) + DOWNLOAD_URL="https://mirror.azure.cn/docker-ce" + ;; + "") + ;; + *) + >&2 echo "unknown mirror '$mirror': use either 'Aliyun', or 'AzureChinaCloud'." + exit 1 + ;; +esac + +case "$CHANNEL" in + stable|test) + ;; + *) + >&2 echo "unknown CHANNEL '$CHANNEL': use either stable or test." + exit 1 + ;; +esac + +command_exists() { + command -v "$@" > /dev/null 2>&1 +} + +# version_gte checks if the version specified in $VERSION is at least the given +# SemVer (Maj.Minor[.Patch]), or CalVer (YY.MM) version.It returns 0 (success) +# if $VERSION is either unset (=latest) or newer or equal than the specified +# version, or returns 1 (fail) otherwise. +# +# examples: +# +# VERSION=23.0 +# version_gte 23.0 // 0 (success) +# version_gte 20.10 // 0 (success) +# version_gte 19.03 // 0 (success) +# version_gte 26.1 // 1 (fail) +version_gte() { + if [ -z "$VERSION" ]; then + return 0 + fi + version_compare "$VERSION" "$1" +} + +# version_compare compares two version strings (either SemVer (Major.Minor.Path), +# or CalVer (YY.MM) version strings. It returns 0 (success) if version A is newer +# or equal than version B, or 1 (fail) otherwise. Patch releases and pre-release +# (-alpha/-beta) are not taken into account +# +# examples: +# +# version_compare 23.0.0 20.10 // 0 (success) +# version_compare 23.0 20.10 // 0 (success) +# version_compare 20.10 19.03 // 0 (success) +# version_compare 20.10 20.10 // 0 (success) +# version_compare 19.03 20.10 // 1 (fail) +version_compare() ( + set +x + + yy_a="$(echo "$1" | cut -d'.' -f1)" + yy_b="$(echo "$2" | cut -d'.' -f1)" + if [ "$yy_a" -lt "$yy_b" ]; then + return 1 + fi + if [ "$yy_a" -gt "$yy_b" ]; then + return 0 + fi + mm_a="$(echo "$1" | cut -d'.' -f2)" + mm_b="$(echo "$2" | cut -d'.' -f2)" + + # trim leading zeros to accommodate CalVer + mm_a="${mm_a#0}" + mm_b="${mm_b#0}" + + if [ "${mm_a:-0}" -lt "${mm_b:-0}" ]; then + return 1 + fi + + return 0 +) + +is_dry_run() { + if [ -z "$DRY_RUN" ]; then + return 1 + else + return 0 + fi +} + +is_wsl() { + case "$(uname -r)" in + *microsoft* ) true ;; # WSL 2 + *Microsoft* ) true ;; # WSL 1 + * ) false;; + esac +} + +is_darwin() { + case "$(uname -s)" in + *darwin* ) true ;; + *Darwin* ) true ;; + * ) false;; + esac +} + +deprecation_notice() { + distro=$1 + distro_version=$2 + echo + printf "\033[91;1mDEPRECATION WARNING\033[0m\n" + printf " This Linux distribution (\033[1m%s %s\033[0m) reached end-of-life and is no longer supported by this script.\n" "$distro" "$distro_version" + echo " No updates or security fixes will be released for this distribution, and users are recommended" + echo " to upgrade to a currently maintained version of $distro." + echo + printf "Press \033[1mCtrl+C\033[0m now to abort this script, or wait for the installation to continue." + echo + sleep 10 +} + +get_distribution() { + lsb_dist="" + # Every system that we officially support has /etc/os-release + if [ -r /etc/os-release ]; then + lsb_dist="$(. /etc/os-release && echo "$ID")" + fi + # Returning an empty string here should be alright since the + # case statements don't act unless you provide an actual value + echo "$lsb_dist" +} + +echo_docker_as_nonroot() { + if is_dry_run; then + return + fi + if command_exists docker && [ -e /var/run/docker.sock ]; then + ( + set -x + $sh_c 'docker version' + ) || true + fi + + # intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output + echo + echo "================================================================================" + echo + if version_gte "20.10"; then + echo "To run Docker as a non-privileged user, consider setting up the" + echo "Docker daemon in rootless mode for your user:" + echo + echo " dockerd-rootless-setuptool.sh install" + echo + echo "Visit https://docs.docker.com/go/rootless/ to learn about rootless mode." + echo + fi + echo + echo "To run the Docker daemon as a fully privileged service, but granting non-root" + echo "users access, refer to https://docs.docker.com/go/daemon-access/" + echo + echo "WARNING: Access to the remote API on a privileged Docker daemon is equivalent" + echo " to root access on the host. Refer to the 'Docker daemon attack surface'" + echo " documentation for details: https://docs.docker.com/go/attack-surface/" + echo + echo "================================================================================" + echo +} + +# Check if this is a forked Linux distro +check_forked() { + + # Check for lsb_release command existence, it usually exists in forked distros + if command_exists lsb_release; then + # Check if the `-u` option is supported + set +e + lsb_release -a -u > /dev/null 2>&1 + lsb_release_exit_code=$? + set -e + + # Check if the command has exited successfully, it means we're in a forked distro + if [ "$lsb_release_exit_code" = "0" ]; then + # Print info about current distro + cat <<-EOF + You're using '$lsb_dist' version '$dist_version'. + EOF + + # Get the upstream release info + lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[:space:]') + dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[:space:]') + + # Print info about upstream distro + cat <<-EOF + Upstream release is '$lsb_dist' version '$dist_version'. + EOF + else + if [ -r /etc/debian_version ] && [ "$lsb_dist" != "ubuntu" ] && [ "$lsb_dist" != "raspbian" ]; then + if [ "$lsb_dist" = "osmc" ]; then + # OSMC runs Raspbian + lsb_dist=raspbian + else + # We're Debian and don't even know it! + lsb_dist=debian + fi + dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')" + case "$dist_version" in + 13) + dist_version="trixie" + ;; + 12) + dist_version="bookworm" + ;; + 11) + dist_version="bullseye" + ;; + 10) + dist_version="buster" + ;; + 9) + dist_version="stretch" + ;; + 8) + dist_version="jessie" + ;; + esac + fi + fi + fi +} + +do_install() { + echo "# Executing docker install script, commit: $SCRIPT_COMMIT_SHA" + + if command_exists docker; then + cat >&2 <<-'EOF' + Warning: the "docker" command appears to already exist on this system. + + If you already have Docker installed, this script can cause trouble, which is + why we're displaying this warning and provide the opportunity to cancel the + installation. + + If you installed the current Docker package using this script and are using it + again to update Docker, you can ignore this message, but be aware that the + script resets any custom changes in the deb and rpm repo configuration + files to match the parameters passed to the script. + + You may press Ctrl+C now to abort this script. + EOF + ( set -x; sleep 20 ) + fi + + user="$(id -un 2>/dev/null || true)" + + sh_c='sh -c' + if [ "$user" != 'root' ]; then + if command_exists sudo; then + sh_c='sudo -E sh -c' + elif command_exists su; then + sh_c='su -c' + else + cat >&2 <<-'EOF' + Error: this installer needs the ability to run commands as root. + We are unable to find either "sudo" or "su" available to make this happen. + EOF + exit 1 + fi + fi + + if is_dry_run; then + sh_c="echo" + fi + + # perform some very rudimentary platform detection + lsb_dist=$( get_distribution ) + lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" + + if is_wsl; then + echo + echo "WSL DETECTED: We recommend using Docker Desktop for Windows." + echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop/" + echo + cat >&2 <<-'EOF' + + You may press Ctrl+C now to abort this script. + EOF + ( set -x; sleep 20 ) + fi + + case "$lsb_dist" in + + ubuntu) + if command_exists lsb_release; then + dist_version="$(lsb_release --codename | cut -f2)" + fi + if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then + dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")" + fi + ;; + + debian|raspbian) + dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')" + case "$dist_version" in + 13) + dist_version="trixie" + ;; + 12) + dist_version="bookworm" + ;; + 11) + dist_version="bullseye" + ;; + 10) + dist_version="buster" + ;; + 9) + dist_version="stretch" + ;; + 8) + dist_version="jessie" + ;; + esac + ;; + + centos|rhel) + if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then + dist_version="$(. /etc/os-release && echo "$VERSION_ID")" + fi + ;; + + *) + if command_exists lsb_release; then + dist_version="$(lsb_release --release | cut -f2)" + fi + if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then + dist_version="$(. /etc/os-release && echo "$VERSION_ID")" + fi + ;; + + esac + + # Check if this is a forked Linux distro + check_forked + + # Print deprecation warnings for distro versions that recently reached EOL, + # but may still be commonly used (especially LTS versions). + case "$lsb_dist.$dist_version" in + centos.8|centos.7|rhel.7) + deprecation_notice "$lsb_dist" "$dist_version" + ;; + debian.buster|debian.stretch|debian.jessie) + deprecation_notice "$lsb_dist" "$dist_version" + ;; + raspbian.buster|raspbian.stretch|raspbian.jessie) + deprecation_notice "$lsb_dist" "$dist_version" + ;; + ubuntu.bionic|ubuntu.xenial|ubuntu.trusty) + deprecation_notice "$lsb_dist" "$dist_version" + ;; + ubuntu.mantic|ubuntu.lunar|ubuntu.kinetic|ubuntu.impish|ubuntu.hirsute|ubuntu.groovy|ubuntu.eoan|ubuntu.disco|ubuntu.cosmic) + deprecation_notice "$lsb_dist" "$dist_version" + ;; + fedora.*) + if [ "$dist_version" -lt 40 ]; then + deprecation_notice "$lsb_dist" "$dist_version" + fi + ;; + esac + + # Run setup for each distro accordingly + case "$lsb_dist" in + ubuntu|debian|raspbian) + pre_reqs="ca-certificates curl" + apt_repo="deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] $DOWNLOAD_URL/linux/$lsb_dist $dist_version $CHANNEL" + ( + if ! is_dry_run; then + set -x + fi + $sh_c 'apt-get -qq update >/dev/null' + $sh_c "DEBIAN_FRONTEND=noninteractive apt-get -y -qq install $pre_reqs >/dev/null" + $sh_c 'install -m 0755 -d /etc/apt/keyrings' + $sh_c "curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" -o /etc/apt/keyrings/docker.asc" + $sh_c "chmod a+r /etc/apt/keyrings/docker.asc" + $sh_c "echo \"$apt_repo\" > /etc/apt/sources.list.d/docker.list" + $sh_c 'apt-get -qq update >/dev/null' + ) + pkg_version="" + if [ -n "$VERSION" ]; then + if is_dry_run; then + echo "# WARNING: VERSION pinning is not supported in DRY_RUN" + else + # Will work for incomplete versions IE (17.12), but may not actually grab the "latest" if in the test channel + pkg_pattern="$(echo "$VERSION" | sed 's/-ce-/~ce~.*/g' | sed 's/-/.*/g')" + search_command="apt-cache madison docker-ce | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3" + pkg_version="$($sh_c "$search_command")" + echo "INFO: Searching repository for VERSION '$VERSION'" + echo "INFO: $search_command" + if [ -z "$pkg_version" ]; then + echo + echo "ERROR: '$VERSION' not found amongst apt-cache madison results" + echo + exit 1 + fi + if version_gte "18.09"; then + search_command="apt-cache madison docker-ce-cli | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3" + echo "INFO: $search_command" + cli_pkg_version="=$($sh_c "$search_command")" + fi + pkg_version="=$pkg_version" + fi + fi + ( + pkgs="docker-ce${pkg_version%=}" + if version_gte "18.09"; then + # older versions didn't ship the cli and containerd as separate packages + pkgs="$pkgs docker-ce-cli${cli_pkg_version%=} containerd.io" + fi + if version_gte "20.10"; then + pkgs="$pkgs docker-compose-plugin docker-ce-rootless-extras$pkg_version" + fi + if version_gte "23.0"; then + pkgs="$pkgs docker-buildx-plugin" + fi + if ! is_dry_run; then + set -x + fi + $sh_c "DEBIAN_FRONTEND=noninteractive apt-get -y -qq install $pkgs >/dev/null" + ) + echo_docker_as_nonroot + exit 0 + ;; + centos|fedora|rhel) + if [ "$(uname -m)" = "s390x" ]; then + echo "Effective v27.5, please consult RHEL distro statement for s390x support." + exit 1 + fi + repo_file_url="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE" + ( + if ! is_dry_run; then + set -x + fi + if command_exists dnf5; then + $sh_c "dnf -y -q --setopt=install_weak_deps=False install dnf-plugins-core" + $sh_c "dnf5 config-manager addrepo --overwrite --save-filename=docker-ce.repo --from-repofile='$repo_file_url'" + + if [ "$CHANNEL" != "stable" ]; then + $sh_c "dnf5 config-manager setopt \"docker-ce-*.enabled=0\"" + $sh_c "dnf5 config-manager setopt \"docker-ce-$CHANNEL.enabled=1\"" + fi + $sh_c "dnf makecache" + elif command_exists dnf; then + $sh_c "dnf -y -q --setopt=install_weak_deps=False install dnf-plugins-core" + $sh_c "rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo" + $sh_c "dnf config-manager --add-repo $repo_file_url" + + if [ "$CHANNEL" != "stable" ]; then + $sh_c "dnf config-manager --set-disabled \"docker-ce-*\"" + $sh_c "dnf config-manager --set-enabled \"docker-ce-$CHANNEL\"" + fi + $sh_c "dnf makecache" + else + $sh_c "yum -y -q install yum-utils" + $sh_c "rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo" + $sh_c "yum-config-manager --add-repo $repo_file_url" + + if [ "$CHANNEL" != "stable" ]; then + $sh_c "yum-config-manager --disable \"docker-ce-*\"" + $sh_c "yum-config-manager --enable \"docker-ce-$CHANNEL\"" + fi + $sh_c "yum makecache" + fi + ) + pkg_version="" + if command_exists dnf; then + pkg_manager="dnf" + pkg_manager_flags="-y -q --best" + else + pkg_manager="yum" + pkg_manager_flags="-y -q" + fi + if [ -n "$VERSION" ]; then + if is_dry_run; then + echo "# WARNING: VERSION pinning is not supported in DRY_RUN" + else + if [ "$lsb_dist" = "fedora" ]; then + pkg_suffix="fc$dist_version" + else + pkg_suffix="el" + fi + pkg_pattern="$(echo "$VERSION" | sed 's/-ce-/\\\\.ce.*/g' | sed 's/-/.*/g').*$pkg_suffix" + search_command="$pkg_manager list --showduplicates docker-ce | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'" + pkg_version="$($sh_c "$search_command")" + echo "INFO: Searching repository for VERSION '$VERSION'" + echo "INFO: $search_command" + if [ -z "$pkg_version" ]; then + echo + echo "ERROR: '$VERSION' not found amongst $pkg_manager list results" + echo + exit 1 + fi + if version_gte "18.09"; then + # older versions don't support a cli package + search_command="$pkg_manager list --showduplicates docker-ce-cli | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'" + cli_pkg_version="$($sh_c "$search_command" | cut -d':' -f 2)" + fi + # Cut out the epoch and prefix with a '-' + pkg_version="-$(echo "$pkg_version" | cut -d':' -f 2)" + fi + fi + ( + pkgs="docker-ce$pkg_version" + if version_gte "18.09"; then + # older versions didn't ship the cli and containerd as separate packages + if [ -n "$cli_pkg_version" ]; then + pkgs="$pkgs docker-ce-cli-$cli_pkg_version containerd.io" + else + pkgs="$pkgs docker-ce-cli containerd.io" + fi + fi + if version_gte "20.10"; then + pkgs="$pkgs docker-compose-plugin docker-ce-rootless-extras$pkg_version" + fi + if version_gte "23.0"; then + pkgs="$pkgs docker-buildx-plugin" + fi + if ! is_dry_run; then + set -x + fi + $sh_c "$pkg_manager $pkg_manager_flags install $pkgs" + ) + echo_docker_as_nonroot + exit 0 + ;; + sles) + echo "Effective v27.5, please consult SLES distro statement for s390x support." + exit 1 + ;; + *) + if [ -z "$lsb_dist" ]; then + if is_darwin; then + echo + echo "ERROR: Unsupported operating system 'macOS'" + echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop" + echo + exit 1 + fi + fi + echo + echo "ERROR: Unsupported distribution '$lsb_dist'" + echo + exit 1 + ;; + esac + exit 1 +} + +# wrapped up in a function so that we have some protection against only getting +# half the file during "curl | sh" +do_install diff --git a/packages/app/src/classes/AuthManager/index.js b/packages/app/src/classes/AuthManager/index.js index f966b5f7..8866a1a5 100644 --- a/packages/app/src/classes/AuthManager/index.js +++ b/packages/app/src/classes/AuthManager/index.js @@ -11,6 +11,7 @@ export default class AuthManager { this.params = params this.behaviors = params.behaviors ?? {} + this.public.initialize = this.initialize this.runtime.registerPublicField("auth", this.public) for (const [event, handler] of Object.entries(this.events)) { @@ -27,22 +28,56 @@ export default class AuthManager { name: "tokens", }) - listAvailableTokens = () => { - return this.store.keys() + listAvailableTokens = async () => { + const keys = await this.store.keys() + console.log("Tokens guardados en localforage:", keys) + + const sessions = await Promise.all( + keys.map(async (key) => { + const session = await this.store.getItem(key) + console.log("Sesión leída:", key, session) + return { + userId: key, + name: session?.name || `Usuario ${key}`, + avatar: session?.avatarUrl || null, + } + }) + ) + return sessions + } + listAvailableTokens = async () => { + const keys = await this.store.keys() + console.log("Tokens guardados en localforage:", keys) + + const sessions = await Promise.all( + keys.map(async (key) => { + const session = await this.store.getItem(key) + console.log("Sesión leída:", key, session) + return { + userId: key, + name: session?.name || `Usuario ${key}`, + avatar: session?.avatarUrl || null, + } + }) + ) + return sessions } loadTokenFromUserId = async (user_id) => { const session = await this.store.getItem(user_id) - + if (!session) { - console.error("Session not found") - return false + throw new Error("No session user found for the given user ID.") } SessionModel.token = session.token SessionModel.refreshToken = session.refreshToken - this.initialize() + await this.initialize() + + if (!this.state.user) { + throw new Error("No user data found after loading token.") + } } public = { @@ -132,6 +167,15 @@ export default class AuthManager { } onLoginCallback = async (state, result) => { - this.store.setItem(result.user_id, result) + console.log("Guardando sesión:", result) + await this.store.setItem(result.user_id, { + ...result, + name: result.name || result.username || `Usuario ${result.user_id}`, + avatarUrl: result.avatarUrl || result.avatar || null, + }) + + if (this.runtime && this.runtime.eventBus) { + this.runtime.eventBus.emit("auth:tokens_updated") + } } } diff --git a/packages/app/src/layouts/components/sidebar/index.jsx b/packages/app/src/layouts/components/sidebar/index.jsx index 25ccd9ce..b2e41b19 100755 --- a/packages/app/src/layouts/components/sidebar/index.jsx +++ b/packages/app/src/layouts/components/sidebar/index.jsx @@ -74,7 +74,9 @@ export default class Sidebar extends React.Component { state = { visible: false, expanded: false, - + showAccountSwitcher: false, + switcherUsers: [], + hoveredUserId: null, topItems: GenerateMenuItems(TopMenuItems), bottomItems: GenerateMenuItems(BottomMenuItems), @@ -83,67 +85,48 @@ export default class Sidebar extends React.Component { } sidebarRef = React.createRef() - + switcherRef = React.createRef() collapseDebounce = null interface = (window.app.layout.sidebar = { toggleVisibility: (to) => { if (to === false) { - this.interface.toggleExpanded(false, { - instant: true, - }) + this.interface.toggleExpanded(false, { instant: true }) } - this.setState({ visible: to ?? !this.state.visible }) }, toggleExpanded: async ( to, - { instant = false, isDropdown = false } = {}, + { instant = false, isDropdown = false } = {} ) => { to = to ?? !this.state.expanded - if (this.collapseDebounce) { clearTimeout(this.collapseDebounce) this.collapseDebounce = null } - if ( (to === false) & (this.state.dropdownOpen === true) && isDropdown === true ) { - // FIXME: This is a walkaround for a bug in antd, causing when dropdown set to close, item click event is not fired - // The desing defines when sidebar should be collapsed, dropdown should be closed, but in this case, gonna to keep it open untils dropdown is closed - //this.setState({ dropdownOpen: false }) - return false } - - if (to === false) { - if (instant === false) { - await new Promise((resolve) => - setTimeout( - resolve, - window.app.cores.settings.get( - "sidebar.collapse_delay_time", - ) ?? 500, - ), + if (to === false && instant === false) { + await new Promise((resolve) => + setTimeout( + resolve, + window.app.cores.settings.get( + "sidebar.collapse_delay_time" + ) ?? 500 ) - } + ) } - this.setState({ expanded: to }) - app.eventBus.emit("sidebar.expanded", to) }, isVisible: () => this.state.visible, isExpanded: () => this.state.expanded, renderNavigationBar: (component, options) => { - this.setState({ - navigationRender: { - component, - options, - }, - }) + this.setState({ navigationRender: { component, options } }) }, updateMenuItemProps: this.updateBottomItemProps, addMenuItem: this.addMenuItem, @@ -151,110 +134,108 @@ export default class Sidebar extends React.Component { }) events = { - "router.navigate": (path) => { - this.calculateSelectedMenuItem(path) - }, + "router.navigate": (path) => this.calculateSelectedMenuItem(path), } componentDidMount = async () => { this.calculateSelectedMenuItem(window.location.pathname) - for (const [event, handler] of Object.entries(this.events)) { + Object.entries(this.events).forEach(([event, handler]) => app.eventBus.on(event, handler) - } + ) + + // Escuchar evento para recargar lista de usuarios cuando se actualicen los tokens + app.eventBus.on("auth:tokens_updated", this.loadSwitcherUsers) + + // Carga inicial de usuarios + await this.loadSwitcherUsers() + + setTimeout(() => this.interface.toggleVisibility(true), 10) - setTimeout(() => { - this.interface.toggleVisibility(true) - }, 10) + document.addEventListener("mousedown", this.handleClickOutside) } componentWillUnmount = () => { - for (const [event, handler] of Object.entries(this.events)) { + Object.entries(this.events).forEach(([event, handler]) => app.eventBus.off(event, handler) - } + ) + app.eventBus.off("auth:tokens_updated", this.loadSwitcherUsers) // Quitar listener delete app.layout.sidebar + document.removeEventListener("mousedown", this.handleClickOutside) + } + + // Método separado para recargar la lista de usuarios + loadSwitcherUsers = async () => { + try { + const users = await app.auth.listAvailableTokens() + this.setState({ switcherUsers: users }) + } catch (error) { + console.error("Error cargando usuarios para switcher:", error) + } + } + handleClickOutside = (event) => { + if (!this.state.showAccountSwitcher) return + + if ( + this.switcherRef.current && + this.switcherRef.current.contains(event.target) + ) + return + + if ( + this.sidebarRef.current && + this.sidebarRef.current.contains(event.target) + ) + return + + this.setState({ showAccountSwitcher: false, switcherExpanded: false }) } calculateSelectedMenuItem = (path) => { const items = [...this.state.topItems, ...this.state.bottomItems] - this.setState({ selectedMenuItem: items.find((item) => - String(item.path).includes(path), + String(item.path).includes(path) ), }) } addMenuItem = (group, item) => { - group = this.getMenuItemGroupStateKey(group) - - if (!group) { - throw new Error("Invalid group") - } - - const newItems = [...this.state[group], item] - - this.setState({ - [group]: newItems, - }) - + const key = this.getMenuItemGroupStateKey(group) + if (!key) throw new Error("Invalid group") + const newItems = [...this.state[key], item] + this.setState({ [key]: newItems }) return newItems } removeMenuItem = (group, id) => { - group = this.getMenuItemGroupStateKey(group) - - if (!group) { - throw new Error("Invalid group") - } - - const newItems = this.state[group].filter((item) => item.id !== id) - - this.setState({ - [group]: newItems, - }) - + const key = this.getMenuItemGroupStateKey(group) + if (!key) throw new Error("Invalid group") + const newItems = this.state[key].filter((item) => item.id !== id) + this.setState({ [key]: newItems }) return newItems } updateBottomItemProps = (group, id, newProps) => { - group = this.getMenuItemGroupStateKey(group) - - if (!group) { - throw new Error("Invalid group") - } - - let updatedValue = this.state[group] - - updatedValue = updatedValue.map((item) => { + const key = this.getMenuItemGroupStateKey(group) + if (!key) throw new Error("Invalid group") + const updatedValue = this.state[key].map((item) => { if (item.id === id) { - item.props = { - ...item.props, - ...newProps, - } + item.props = { ...item.props, ...newProps } } + return item }) - - this.setState({ - [group]: updatedValue, - }) - + this.setState({ [key]: updatedValue }) return updatedValue } getMenuItemGroupStateKey = (group) => { - switch (group) { - case "top": { - return "topItems" - } - case "bottom": { - return "bottomItems" - } - default: { - return null - } - } + return group === "top" + ? "topItems" + : group === "bottom" + ? "bottomItems" + : null } injectUserItems(items = []) { @@ -284,71 +265,53 @@ export default class Sidebar extends React.Component { icon: , }) } - return items } handleClick = (e) => { - if (e.item.props.ignore_click === "true") { - return - } - - if (e.item.props.override_event) { + if (e.item.props.ignore_click === "true") return + if (e.item.props.override_event) return app.eventBus.emit( e.item.props.override_event, - e.item.props.override_event_props, + e.item.props.override_event_props ) - } - - if (typeof e.key === "undefined") { - app.eventBus.emit("invalidSidebarKey", e) - return false - } - - if (typeof ItemsClickHandlers[e.key] === "function") { + if (typeof e.key === "undefined") + return app.eventBus.emit("invalidSidebarKey", e) + if (typeof ItemsClickHandlers[e.key] === "function") return ItemsClickHandlers[e.key](e) - } app.cores.sfx.play("sidebar.switch_tab") - - let item = [...this.state.topItems, ...this.state.bottomItems].find( - (item) => item.id === e.key, + const item = [...this.state.topItems, ...this.state.bottomItems].find( + (item) => item.id === e.key ) - - return app.location.push(`/${item.path ?? e.key}`, 150) + return app.location.push(`/${item?.path ?? e.key}`, 150) } onMouseEnter = () => { - if (!this.state.visible || app.layout.drawer.isMaskVisible()) { + if (!this.state.visible || app.layout.drawer.isMaskVisible()) return false - } - return this.interface.toggleExpanded(true) } handleMouseLeave = () => { - if (!this.state.visible) { - return false - } - + if (!this.state.visible) return false return this.interface.toggleExpanded(false) } onDropdownOpenChange = (to) => { - // this is another walkaround for a bug in antd, causing when dropdown set to close, item click event is not fired if (!to && this.state.expanded) { this.interface.toggleExpanded(false, true) } - this.setState({ dropdownOpen: to }) } onClickDropdownItem = (item) => { - const handler = ItemsClickHandlers[item.key] - - if (typeof handler === "function") { - handler() + if (item.key === "switch_account") { + this.setState({ showAccountSwitcher: true }) + return } + const handler = ItemsClickHandlers[item.key] + if (typeof handler === "function") handler() } render() { @@ -368,67 +331,185 @@ export default class Sidebar extends React.Component { {this.state.visible && ( - -
-
- app.navigation.goMain()} - /> + <> + {/* Sidebar Principal */} + +
+
+ + app.navigation.goMain() + } + /> + αlpha +
+
- αlpha +
+
-
-
- -
- -
- -
- +
+ +
+ + + {/* Sidebar de cambio de cuenta */} + {this.state.showAccountSwitcher && ( + + this.setState({ + switcherExpanded: true, + }) + } + onMouseLeave={() => + this.setState({ + switcherExpanded: false, + }) + } + > +
+ {this.state.switcherExpanded ? ( + <> + Switch Account + + ) : ( + + )} +
+ + ({ + key: user.userId, + label: + user.name || + `Usuario ${app.userData?.name}`, + icon: ( + + {!user.avatar && + user.name + ? user.name + .charAt( + 0 + ) + .toUpperCase() + : null} + + ), + }) + ), + { + type: "divider", + }, + { + key: "add_account", + label: "Add Account", + icon: , + }, + ]} + onClick={async ({ key }) => { + if (key === "add_account") { + try { + await app.auth.login() + } catch (error) { + console.error( + "Error to add account:", + error + ) + alert( + "No es posible añadir cuenta. Por favor, inténtalo más tarde." + ) + } + return + } + + try { + await app.auth.loadTokenFromUserId( + key + ) + await app.auth.initialize() + + this.setState({ + showAccountSwitcher: false, + switcherExpanded: false, + }) + + app.eventBus.emit( + "auth:login_success" + ) + } catch (err) { + console.error( + "Error al cambiar de cuenta:", + err + ) + alert( + "No es posible cambiar de cuenta. Por favor, inténtalo más tarde." + ) + } + }} + /> + + )} + )} diff --git a/packages/app/src/layouts/components/sidebar/index.less b/packages/app/src/layouts/components/sidebar/index.less index 07925180..68b05323 100755 --- a/packages/app/src/layouts/components/sidebar/index.less +++ b/packages/app/src/layouts/components/sidebar/index.less @@ -233,4 +233,78 @@ } } } +} + +.app_sidebar_switcher { + position: absolute; + bottom: 20px; + left: calc(@sidebar_width + 20px); // posición por defecto (sidebar contraído) + + width: 240px; + background-color: #2f2f2f; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + overflow: hidden; + z-index: 100; + transition: left 0.25s ease; + display: flex; + flex-direction: column; + + .switcher_header { + text-align: center; + padding: 12px; + background: transparent; + color: #d1d1d1; + font-weight: 600; + font-size: 14px; + position: relative; + + button { + position: absolute; + right: 12px; + top: 12px; + background: none; + border: none; + color: #aaa; + cursor: pointer; + + &:hover { + color: #fff; + } + + svg { + width: 16px; + height: 16px; + } + } + } + + .ant-menu { + background: transparent; + border: none; + + .ant-menu-item { + padding: 10px 14px; + color: #ccc; // gris claro para items + font-size: 13px; + display: flex; + align-items: center; + gap: 10px; + transition: background 0.2s ease; + + .ant-avatar { + width: 24px; + height: 24px; + border-radius: 4px; + } + + &:hover { + background-color: #444; // gris más claro al pasar mouse + } + } + } +} + +.app_sidebar.expanded + .app_sidebar_switcher { + left: calc(@sidebar_width_expanded + 20px); } \ No newline at end of file diff --git a/packages/server/compose-dev.yml b/packages/server/compose.yml similarity index 57% rename from packages/server/compose-dev.yml rename to packages/server/compose.yml index 9be47a31..0849d006 100644 --- a/packages/server/compose-dev.yml +++ b/packages/server/compose.yml @@ -1,22 +1,19 @@ services: - api: - build: . - restart: unless-stopped - networks: - - internal_network + + mongo: + image: mongo + restart: always ports: - - "9000:9000" - env_file: - - ./.env + - 27017:27017 + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example redis: image: docker.dragonflydb.io/dragonflydb/dragonfly:latest restart: unless-stopped + ports: + - 6379:6379 ulimits: memlock: -1 - networks: - - internal_network command: "dragonfly --logtostderr --cache_mode=true --maxmemory=8gb --cluster_mode=emulated --lock_on_hashtags --default_lua_flags=allow-undeclared-keys" - -networks: - internal_network: diff --git a/packages/server/gateway/managers/nginx/index.js b/packages/server/gateway/managers/nginx/index.js index a4a8813f..72b0ffad 100755 --- a/packages/server/gateway/managers/nginx/index.js +++ b/packages/server/gateway/managers/nginx/index.js @@ -427,6 +427,7 @@ ${locationDirective} { ${rewriteConfig} # Proxy pass to service + proxy_ssl_server_name on; proxy_pass ${route.target}; } `