mirror of
https://github.com/unraid/api.git
synced 2025-12-30 13:09:52 -06:00
feat / OEM whitelabel support (#973)
* feat: begin fixing dark mode in the webcomponents * feat: lots of progress on colors * feat: begin nuking alpha beta gamma * feat: set background color on webcomponents * fix: more color work * feat: eliminate all alpha beta gamma variable usage * feat: move variable declarations to theme.ts * feat: begin fixing dark mode in the webcomponents * feat: lots of progress on colors * feat: begin nuking alpha beta gamma * feat: set background color on webcomponents * fix: more color work * feat: eliminate all alpha beta gamma variable usage * feat: download nodejs and install on legacy OS versions * feat: do not move upgradepkg * feat: array iteration for restoring files * feat: separate install process * feat: extract node to usr/local/ * feat: move variable declarations to theme.ts * feat: remove nghttp3 and only bundle nodejs * feat: validate entries correctly * fix: upgradepkg * feat: copy only needed files for nodejs * fix: install syntax error * fix: strip components from tar line * feat: error when nodejs download fails * feat(php): add OEM data extraction functionality * feat(web): WIP add OEM activation modal and integrate OEM data handling * refactor: replace oem etamology with activation * feat(web): enhance activation modal with header and main title options * refactor(web): update activation modal title to handle partner name conditionally * feat: make partnerName optional in activation code data * refactor: remove activationCodeStore from UserProfile component * feat: integrate activation code data into server store * feat: enhance activation code store to include callback actions and improve modal visibility logic * refactor: remove unused theme reference from ColorSwitcher component * fix: update partnerName in activationCodeData for clarity * refactor: adjust gap spacing in Modal component for improved layout * feat: enhance Activation Modal component with dynamic title and description, and add documentation buttons * feat: implement localization for Activation Modal component titles and button texts * feat: add new translations for activation prompts and welcome messages in the WebComponentTranslations class * feat: implement key sequence to close Activation Modal and persist visibility state * feat: update activation logic to conditionally activate or redeem based on activation code presence * feat(plg): plg install / remove partner banner * feat(plg): on install update config to enable display settings banner and on remove banner setting if no custom banner used * fix(plg): plg remove, display banner no if no custom image * feat(plg): implement partner banner handling and system model identification from activation JSON * feat(web): add loading and error states to notification sidebar * refactor(web): add container for loading & error states * feat(web): add count labels to notification tabs * refactor(web): lift notifications overview query to Sidebar from Indicator * fix(web): refetch notifications for sidebar when new notifications arrive * feat(web): move notification indicator icons to top-right of bell icon previously, icons were placed next to bell icon because the status indicators were not accessible to color-blind users. this commit replaces circular status indicators with the icons. * feat(web): remove notification indicator pulse the pulse was initially added to provide visual feedback when: 1. a new notification arrived 2. an alert notification was unread because we began using the legacy notify script, we now get a toast on new notifications. re:2, feedback on the pulse was mixed, so i'm removing it. * refactor(plg): improve conditional for activation & partner setup * feat(plg): add case model icon handling based on activation JSON * feat(plg): enhance partner setup process with detailed logging and system identification updates and setup flag * feat(plg): add setup flag file creation and logging for activation changes * feat(plg): update activation setup flag checks for banner, case model icon, and system identity * fix(plg): add error handling for invalid activation JSON and improve conditional checks for setup flags * fix(plg): improve partner banner and case model icon setup logic with enhanced checks and comments * fix(plg): update case model icon configuration to prevent newline issues in parsing * fix(plg): remove unnecessary echo debug statements and improve setup flag handling for partner details * fix(plg): remove unnecessary registration page check from activation code modal logic * fix(plg): add overlay opacity prop to modal components for customizable background transparency * fix(plg): enhance modal component with vertical centering option and adjust max-width * fix(plg): enhance configuration handling by dynamically updating multiple header parameters in the config file * fix(plg): improve comments and enhance setup flag handling for custom icons in activation logic * fix(translations): correct capitalization and punctuation in user prompts for consistency * fix(store): enhance activation code data structure with additional properties and correct partner URL reference * fix(modal): enhance activation modal with dynamic partner logo and improved title handling * fix(store): update activation code data structure with additional properties and correct partner details * fix(index): refactor badge and button color handling with typed constants for improved maintainability * fix(dependencies): add vue-tsc for improved TypeScript support in Vue projects * refactor(plugin): remove unnecessary comments and clean up code structure * refactor(plugin): remove todo comment for custom header logo * fix(Modal): add partnerUrl handling for logo link and improve code structure * fix(serverState): update partnerLogoPath to use a placeholder image for development * feat(plugin): add rsync activation script for testing activation modal & installs * refactor(activationCode): remove debug log for modal visibility check * feat(plugin): enhance activation process by adding logo and case model handling * refactor(activationCode): rename partnerLogoPath to partnerLogo and update handling for logo file type * refactor(activationCode): remove debug log for partnerLogo watcher * chore(nuxt.config): add ignore rule for webGui images directory * refactor(plugin): rename params array to DISPLAY_PARAMS and update handling for configuration updates * fix(web): partner logo handling for themes * fix(plg): activation oem, custom case icon cfg handling * fix(plugin): replace sed with awk for config file updates in DISPLAY_PARAMS handling * fix(web): add target and rel attributes to partner logo link for security * feat(plugin): add theme parameter to DISPLAY_PARAMS and activation code interface * fix(plugin): replace sed with parameter expansion and awk for config file updates * fix(plugin): reboot logic for plg install to prevent settings overwrite * feat(plugin): streamline activation and partner setup by abstracting scripts for better maintainability * fix(plugin): update script source paths for activation and partner setup * fix(plugin): update script source paths for activation and partner setup to use relative paths * fix(plugin): update script source paths for activation and partner setup to use absolute paths * fix(plugin): add debug mode support to activation scripts and update setup flag handling * fix(plugin): enhance activation scripts with debug mode and conditional execution for safety * refactor(plugin): reposition execution of activation_code setup / remove scripts * fix(plugin): add checks for activation_code_setup script existence and improve deletion logging * fix(plugin): add option to remove setup flag after script execution * fix(plugin): streamline activation script execution by removing unnecessary checks * fix(plugin): remove duplicate activation_code_setup script sourcing * feat(modal): add overlay color prop and enhance class binding for modal background * feat(activation): update modal overlay color and opacity * feat(plg): add WebComponentsExtractor class for managing JS file retrieval * feat(plugin): myservers1.phpintegrate WebComponentsExtractor for dynamic script tag generation * feat(activation): enhance getData method to support JSON output and add getDataForHtmlAttr for HTML-safe JSON * refactor(activation): simplify getData method by removing parameters * feat(plugin): add welcome-modal.php for displaying server state and web components * refactor(web): activation code store rename modal visibility variables for clarity * feat(web): add WelcomeModal component for user onboarding and server setup * feat(plugin): activation_code setup and remove script support welcome modal * refactor(plugin): streamline activation code setup script by removing some comments * fix(plugin): update activation code setup script to prepend items to auth request allow list array * feat: enhance ActivationCodeExtractor with partner logo handling and metadata extraction - Added constants for document root and web GUI images directory. - Introduced properties for partner name, URL, logo path, and logo file type. - Implemented methods to retrieve partner logo path, render logo string, partner name, and URL. - Enhanced data extraction logic to include partner-related information from JSON data. - Added debug method for outputting internal state for troubleshooting. * refactor: update activation-data.php to include debug output for ActivationCodeExtractor - Removed JSON response and replaced it with a debug output wrapped in <pre> tags. - This change allows for easier troubleshooting by displaying internal state information directly on the page. * feat: inject partner logo into DefaultPageLayout and update JS file paths - Modified the activation code setup script to change the path of JavaScript files to be relative to the web GUI's webroot. - Added functionality to inject a partner logo into the DefaultPageLayout by replacing the existing logo string. - Implemented a conditional backup mechanism for the DefaultPageLayout file before making changes. - Included debug output to confirm successful injection or report errors during the process. * feat: add partner logo display functionality and style adjustments - Introduced a new file for displaying the partner logo, which retrieves the logo and URL based on the activation code. - Added CSS styles to adjust the display properties of the partner logo in the header, ensuring proper sizing and alignment. - Enhanced the integration of the partner logo into the existing plugin structure. * refactor: simplify partner logo injection debug output in activation code setup script - Removed syntax check for the DefaultPageLayout file after injecting the partner logo. - Streamlined debug output to confirm successful injection without additional checks, enhancing clarity and reducing complexity. * style: adjust partner logo height in myservers1.php * style: update partner logo width in PartnerLogoImg.vue - Changed the CSS class for the partner logo image from a max height constraint to a fixed width of 72 units, ensuring better responsiveness and alignment in the layout. * fix: update modal title attribute for conditional close behavior - Modified the title attribute of the modal overlay to conditionally display the close instruction based on the showCloseX property. This change improves accessibility by ensuring the title is only set when the close button is visible. * refactor: update partner logo handling in activation code scripts and extractor - Changed the partner logo file name to 'partner-logo.svg' in ActivationCodeExtractor. - Simplified logo path handling by removing unnecessary file type checks and directly using the SVG file. - Updated CSS in myservers1.php to target only the SVG logo. - Modified activation code setup and removal scripts to consistently reference the SVG logo, improving clarity and maintainability. * feat: enhance activation code setup script to include server name handling - Added logic to retrieve and set the server name from the activation JSON if the current system model or comment is not set, or if the current name is "Tower". - Improved debug output to include the partner server name, enhancing visibility during the activation process. - Sanitized the partner server name to remove quotes and backslashes for better handling. * feat: validate activation JSON before proceeding with setup - Added validation for the activation JSON file to ensure it is correctly formatted before executing the setup process. - Enhanced debug output to indicate whether the JSON is valid or not, improving troubleshooting capabilities. - The setup will now only proceed if the activation JSON is confirmed to be valid, preventing potential errors during execution. * fix(plg): improve warning message for invalid activation JSON in setup script * feat: enhance activation code handling with partner logo integration - Added a new optional property 'partnerLogo' to the ActivationCodeExtractor class and updated related interfaces to support boolean values for logo display. - Removed the PNG logo reference from the activation code setup script, streamlining the logo handling process. - Updated the server state to utilize the new 'partnerLogo' property, ensuring consistent logo display logic across the application. - Adjusted computed properties in the activation code store to reflect the changes in logo handling, improving clarity and maintainability. * fix: correct activation JSON validation in setup script - Updated the activation code setup script to use 'jq empty' for validating the activation JSON file, ensuring proper error handling and validation. - This change improves the robustness of the setup process by accurately checking the JSON format before proceeding. * fix: correct comment formatting in dynamix.unraid.net.plg - Fixed a formatting issue in the comment regarding the use of myservers.cfg values to prevent conflicts during installation. This change improves code readability and clarity. * chore: update rsync activation script comments for clarity - Revised comments in the rsync-activation-dir.sh script to better describe its purpose and usage. - Enhanced documentation with an example usage to improve user understanding of the script's functionality. * refactor: clean up activation data and extractor files - Removed outdated copyright comments from activation-data.php and activation-code-extractor.php for improved readability. - Simplified the debug output in activation-data.php by using short PHP tags. - Updated the comment in the ActivationCodeExtractor class to be more concise, enhancing clarity for future developers. * fix: remove unnecessary CSS properties in myservers1.php - Eliminated redundant display and margin properties from the CSS in myservers1.php to streamline the styling and improve code clarity. * refactor: streamline WebComponentsExtractor class and update method names - Removed outdated copyright comments from web-components-extractor.php for improved readability. - Renamed method getJsFileUrl() to getJSFileRelativePath() to enhance clarity and consistency in naming conventions. - Adjusted indentation for better code formatting and readability. * refactor: simplify partner logo handling in activation code script - Removed unnecessary variable initialization and included only essential requires for improved clarity. - Streamlined the code in partner-logo.php to focus on the core functionality of displaying the partner logo. - Enhanced maintainability by reducing complexity in the script. * refactor: clean up activation and server state files - Removed outdated copyright comments and unnecessary variable initializations from activation-data.php and server-state.php for improved readability. - Streamlined the code by eliminating redundant require statements, focusing on essential functionality. - Enhanced maintainability and clarity by simplifying the structure of both files. * feat: add password creation prompts to translations - Introduced new translation strings for password creation in both PHP and JSON files. - Enhanced user guidance by providing detailed messages about the importance of the password for system access and management. - Improved overall user experience by ensuring clarity in the password setup process. * refactor: update activation code setup script comments * refactor: enhance activation code removal script functionality - Updated the activation code removal script to delete additional related files, including PHP and setup scripts, for a more thorough cleanup. - Improved comments for clarity on script usage and options, particularly regarding the self-delete functionality. - Streamlined the deletion process by using an array to manage files to be removed, enhancing maintainability and readability. * fix: update WelcomeModal to disable close button * refactor: reorganize activation code setup and improve script clarity - Moved the activation and partner setup section to follow the web component timestamp check to ensure correct targeting during setup. - Updated comments for better clarity regarding the activation code setup process. - Retained the warning message about not closing the window yet for user guidance. * refactor: enhance activation code setup script with improved comments and logic - Updated comments to clarify the purpose of server name, model, and description checks. - Modified conditional logic to include additional checks for server identification. - Added a TODO note regarding the necessity of updating the ident.cfg file, ensuring future review for potential optimizations. * refactor: update activation code scripts to use .done flag * refactor: introduce constant for activation modal storage key - Added a new constant for the activation code modal hidden storage key to improve code maintainability and clarity. - Updated the activation code store to utilize the new constant, replacing hardcoded string references. * feat: enhance user onboarding and modal components - Added new translation strings for activating Unraid licenses and creating Unraid.net accounts to improve user onboarding experience. - Updated the WelcomeModal and Activation Modal components to reflect new messaging and improved styling options, including the ability to disable shadows. - Implemented a workaround in the WelcomeModal to address font-size inconsistencies between login and authenticated pages. - Refactored the index page to correctly pass server data to the WelcomeModal component. * chore: comment out WelcomeModalCe component for testing * feat: add disableOverlayClose prop to Modal and WelcomeModal components - Introduced a new prop `disableOverlayClose` to the Modal component, allowing users to prevent closing the modal by clicking on the overlay. - Updated the WelcomeModal component to utilize the new `disableOverlayClose` prop, enhancing modal behavior customization. * refactor: update activation code removal script to delete by default - Changed the activation code removal script to use a dry-run flag instead of a self-delete flag, enhancing safety during execution. - Updated the plugin file to reflect the removal of the --delete option, ensuring consistency with the new script behavior. --------- Co-authored-by: Eli Bosley <ekbosley@gmail.com> Co-authored-by: Pujit Mehrotra <pujit@lime-technology.com>
This commit is contained in:
@@ -165,6 +165,9 @@ version=
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/unraid-version
|
||||
|
||||
# Undo some activation / partner setup
|
||||
source /usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove
|
||||
|
||||
echo
|
||||
echo "⚠️ Do not close this window yet"
|
||||
echo
|
||||
@@ -741,10 +744,6 @@ if [ "${PLGTYPE}" = "production" ] || [ ! -f /boot/config/plugins/dynamix.my.ser
|
||||
echo "env=\"${PLGTYPE}\"">/boot/config/plugins/dynamix.my.servers/env
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "⚠️ Do not close this window yet"
|
||||
echo
|
||||
|
||||
# Use myservers.cfg values to help prevent conflicts when installing
|
||||
CFG=/boot/config/plugins/dynamix.my.servers/myservers.cfg
|
||||
# shellcheck disable=SC1090
|
||||
@@ -838,9 +837,9 @@ preventDowngradeAction() {
|
||||
}
|
||||
|
||||
# Extract "ts" values from both files
|
||||
plgWebComponentPath="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components"
|
||||
backupWebComponentPath="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components-"
|
||||
plgManifestTs=$(extract_ts "$plgWebComponentPath/manifest.json")
|
||||
plgWebComponentPath="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components"
|
||||
backupWebComponentPath="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components-"
|
||||
plgManifestTs=$(extract_ts "$plgWebComponentPath/manifest.json")
|
||||
webguiManifestTs=$(extract_ts "$backupWebComponentPath/manifest.json")
|
||||
|
||||
# Compare the "ts" values and return the file path of the higher value
|
||||
@@ -854,6 +853,14 @@ if [[ "$webguiManifestTs" -gt "$plgManifestTs" ]]; then
|
||||
echo "♻️ Reverted to stock web component files"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "⚠️ Do not close this window yet"
|
||||
echo
|
||||
|
||||
# Activation and partner setup
|
||||
# - Must come after the web component timestamp check to avoid potentially targeting the wrong JS during this setup
|
||||
source /usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_setup
|
||||
|
||||
# Install the API (previously in rc.d script)
|
||||
echo "Extracting and installing the Unraid API"
|
||||
|
||||
@@ -885,21 +892,10 @@ ln -sf /usr/local/bin/unraid-api /usr/bin/unraid-api
|
||||
# bail if expected file does not exist
|
||||
[[ ! -f "${api_base_directory}/package.json" ]] && echo "unraid-api install failed" && exit 1
|
||||
|
||||
# Start a background process to wait for /var/local/emhttp/var.ini
|
||||
( timeout=30 elapsed=0
|
||||
while (( elapsed < timeout )); do
|
||||
if [ -f /var/local/emhttp/var.ini ]; then
|
||||
logger "Starting flash backup (if enabled)"
|
||||
echo "/etc/rc.d/rc.flash_backup start" | at -M now &>/dev/null
|
||||
logger "Starting Unraid API"
|
||||
${unraid_binary_path} start
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
(( elapsed++ ))
|
||||
done
|
||||
echo "Timeout waiting for /var/local/emhttp/var.ini"
|
||||
) &
|
||||
logger "Starting flash backup (if enabled)"
|
||||
echo "/etc/rc.d/rc.flash_backup start" | at -M now &>/dev/null
|
||||
logger "Starting Unraid API"
|
||||
${unraid_binary_path} start
|
||||
|
||||
echo
|
||||
echo "✅ Installation is complete, it is safe to close this window"
|
||||
|
||||
91
plugin/scripts/rsync-activation-dir.sh
Executable file
91
plugin/scripts/rsync-activation-dir.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Bash script to sync local activation code directory to the correct location on the Unraid server's boot device
|
||||
|
||||
# Usage: ./sync_files.sh --local-directory <local_directory> [--remote-user <remote_user>] [--remote-host <remote_host>] [--remote-path <remote_path>]
|
||||
|
||||
# Example usage 0
|
||||
# ./plugin/scripts/rsync-activation-dir.sh --local-directory /Users/zack/Downloads/activation_code_pdfs_12_19_2024_1436
|
||||
|
||||
# Path to store the last used remote host
|
||||
state_file="$HOME/.deploy_state"
|
||||
|
||||
# Read the last used remote host from the state file
|
||||
if [[ -f "$state_file" ]]; then
|
||||
LAST_REMOTE_HOST=$(cat "$state_file")
|
||||
else
|
||||
LAST_REMOTE_HOST=""
|
||||
fi
|
||||
|
||||
# Default values
|
||||
REMOTE_USER="root"
|
||||
REMOTE_PATH="/boot/config/activation"
|
||||
|
||||
# Parse named flag parameters
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--local-directory)
|
||||
LOCAL_DIRECTORY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--remote-user)
|
||||
REMOTE_USER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--remote-host)
|
||||
REMOTE_HOST="$2"
|
||||
shift 2
|
||||
;;
|
||||
--remote-path)
|
||||
REMOTE_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Usage: $0 --local-directory <local_directory> [--remote-user <remote_user>] [--remote-host <remote_host>] [--remote-path <remote_path>]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate required parameter
|
||||
if [[ -z "$LOCAL_DIRECTORY" ]]; then
|
||||
echo "Error: --local-directory is required."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use last remote host if none is provided
|
||||
REMOTE_HOST=${REMOTE_HOST:-$LAST_REMOTE_HOST}
|
||||
|
||||
# Check if remote host is provided
|
||||
if [[ -z "$REMOTE_HOST" ]]; then
|
||||
echo "Please provide the remote host using --remote-host."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Save the current remote host to the state file
|
||||
echo "$REMOTE_HOST" > "$state_file"
|
||||
|
||||
# Check if local directory ends with a slash
|
||||
if [[ "$LOCAL_DIRECTORY" != */ ]]; then
|
||||
echo "The local directory does not end with a slash."
|
||||
read -p "Do you want to append a slash to upload its contents? (y/n): " RESPONSE
|
||||
if [[ "$RESPONSE" =~ ^[Yy]$ ]]; then
|
||||
LOCAL_DIRECTORY+="/"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Execute the rsync command and capture its output
|
||||
RSYNC_OUTPUT=$(rsync -av -e ssh "$LOCAL_DIRECTORY" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH" 2>&1)
|
||||
RSYNC_EXIT_CODE=$?
|
||||
|
||||
# Output the rsync command's output
|
||||
echo "$RSYNC_OUTPUT"
|
||||
|
||||
# Check if the command was successful
|
||||
if [ $RSYNC_EXIT_CODE -eq 0 ]; then
|
||||
echo "Files synchronized successfully!"
|
||||
else
|
||||
echo "An error occurred during synchronization."
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/activation-code-extractor.php";
|
||||
|
||||
$activationCodeExtractor = new ActivationCodeExtractor();
|
||||
?>
|
||||
<pre>
|
||||
<? $activationCodeExtractor->debug(); ?>
|
||||
</pre>
|
||||
@@ -1,19 +1,5 @@
|
||||
<?php
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
$var = (array)parse_ini_file('state/var.ini');
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/state.php";
|
||||
|
||||
$serverState = new ServerState();
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
class ActivationCodeExtractor {
|
||||
public const DIR = '/boot/config/activation';
|
||||
public const FILE_PATTERN = '/activation_code_([A-Za-z0-9]+)\.activationcode/';
|
||||
|
||||
public const DOCROOT = '/usr/local/emhttp';
|
||||
public const WEBGUI_IMAGES_BASE_DIR = '/webGui/images';
|
||||
public const PARTNER_LOGO_FILE_NAME = 'partner-logo.svg';
|
||||
public const DEFAULT_LOGO = self::DOCROOT . self::WEBGUI_IMAGES_BASE_DIR . '/UN-logotype-gradient.svg';
|
||||
|
||||
/** @var array{
|
||||
* code: string,
|
||||
* partnerName: string,
|
||||
* partnerUrl?: string,
|
||||
* sysModel?: string,
|
||||
* comment?: string,
|
||||
* caseIcon: string,
|
||||
* partnerLogo?: boolean,
|
||||
* header?: string,
|
||||
* headermetacolor?: string,
|
||||
* background?: string,
|
||||
* showBannerGradient?: string,
|
||||
* theme?: "azure" | "black" | "gray" | "white
|
||||
* }
|
||||
*/
|
||||
private array $data = [];
|
||||
private string $partnerName = '';
|
||||
private string $partnerUrl = 'https://unraid.net';
|
||||
private string $partnerLogoPath = '';
|
||||
|
||||
/**
|
||||
* Constructor to automatically fetch JSON data from all matching files.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->data = $this->fetchJsonData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON data from all files matching the pattern.
|
||||
*
|
||||
* @return array Array of extracted JSON data.
|
||||
*/
|
||||
private function fetchJsonData(): array {
|
||||
$data = [];
|
||||
|
||||
if (!is_dir(self::DIR)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$files = scandir(self::DIR);
|
||||
|
||||
if ($files === false || count($files) === 0) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filePath = self::DIR . DIRECTORY_SEPARATOR . $file;
|
||||
|
||||
if (preg_match(self::FILE_PATTERN, $file, $matches)) {
|
||||
// $activationCode = $matches[1];
|
||||
$fileContent = file_get_contents($filePath);
|
||||
$jsonData = json_decode($fileContent, true);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$data = $jsonData;
|
||||
} else {
|
||||
$data = ['error' => 'Invalid JSON format'];
|
||||
}
|
||||
|
||||
break; // Stop after the first match
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['partnerName'])) {
|
||||
$this->partnerName = $data['partnerName'];
|
||||
}
|
||||
|
||||
if (isset($data['partnerUrl'])) {
|
||||
$this->partnerUrl = $data['partnerUrl'];
|
||||
}
|
||||
|
||||
/**
|
||||
* During the plg install, the partner logo asset is copied to the webgui images dir.
|
||||
*/
|
||||
$logo = self::DOCROOT . self::WEBGUI_IMAGES_BASE_DIR . '/' . self::PARTNER_LOGO_FILE_NAME;
|
||||
if (file_exists($logo)) {
|
||||
$this->partnerLogoPath = $logo;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the partner logo path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartnerLogoPath(): string {
|
||||
return $this->partnerLogoPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extracted data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the activation code data as JSON string with converted special characters to HTML entities
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDataForHtmlAttr(): string {
|
||||
$json = json_encode($this->getData());
|
||||
return htmlspecialchars($json, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the partner logo render string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartnerLogoRenderString(): string {
|
||||
if (empty($this->partnerLogoPath)) { // default logo
|
||||
return file_get_contents(self::DEFAULT_LOGO);
|
||||
}
|
||||
|
||||
return file_get_contents($this->partnerLogoPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the partner name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartnerName(): string {
|
||||
return $this->partnerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the partner URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartnerUrl(): string {
|
||||
return $this->partnerUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output for debugging
|
||||
*
|
||||
* @see https://tower.local/plugins/dynamix.my.servers/data/activation-data.php
|
||||
* @return void
|
||||
*/
|
||||
public function debug(): void {
|
||||
echo "data: "; var_dump($this->data);
|
||||
echo "partnerName: "; var_dump($this->partnerName);
|
||||
echo "partnerUrl: "; var_dump($this->partnerUrl);
|
||||
echo "partnerLogoPath: "; var_dump($this->partnerLogoPath);
|
||||
|
||||
echo $this->getPartnerLogoRenderString();
|
||||
}
|
||||
}
|
||||
@@ -36,27 +36,15 @@ a[href="/Tools/Downgrade"] .icon-update:before {
|
||||
display: inline-block; /* required otherwise the rotation won't work */
|
||||
rotate: 180deg;
|
||||
}
|
||||
/* overriding #header .logo svg */
|
||||
#header .logo .partner-logo svg {
|
||||
fill: var(--header-text-primary);
|
||||
width: auto;
|
||||
height: 28px;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
// Set the path for the local manifest file
|
||||
$localManifestFile = '/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/manifest.json';
|
||||
require_once("$docroot/plugins/dynamix.my.servers/include/web-components-extractor.php");
|
||||
|
||||
// Load the local manifest
|
||||
$localManifest = json_decode(file_get_contents($localManifestFile), true);
|
||||
|
||||
$searchText = 'unraid-components.client.mjs';
|
||||
$fileValue = null;
|
||||
|
||||
foreach ($localManifest as $key => $value) {
|
||||
if (strpos($key, $searchText) !== false && isset($value["file"])) {
|
||||
$fileValue = $value["file"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fileValue !== null) {
|
||||
$prefixedPath = '/plugins/dynamix.my.servers/unraid-components/';
|
||||
echo '<script src="' . $prefixedPath . $fileValue . '"></script>';
|
||||
} else {
|
||||
echo '<script>console.error("%cNo matching key containing \'' . $searchText . '\' found.", "font-weight: bold; color: white; background-color: red");</script>';
|
||||
}
|
||||
$wcExtractor = new WebComponentsExtractor();
|
||||
echo $wcExtractor->getScriptTagHtml();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Display the partner logo that was installed with the activation code.
|
||||
*/
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/activation-code-extractor.php";
|
||||
|
||||
$activationCodeExtractor = new ActivationCodeExtractor();
|
||||
?>
|
||||
|
||||
<a href="<?= $activationCodeExtractor->getPartnerUrl() ?>" target="_blank" class="partner-logo">
|
||||
<?= $activationCodeExtractor->getPartnerLogoRenderString() ?>
|
||||
</a>
|
||||
@@ -14,6 +14,7 @@
|
||||
$webguiGlobals = $GLOBALS;
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/activation-code-extractor.php";
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/reboot-details.php";
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/UnraidCheck.php";
|
||||
/**
|
||||
@@ -72,6 +73,8 @@ class ServerState
|
||||
public $registered = false;
|
||||
public $myServersMiniGraphConnected = false;
|
||||
public $keyfileBase64 = '';
|
||||
public $activationCodeData = [];
|
||||
public $state = 'UNKNOWN';
|
||||
|
||||
/**
|
||||
* Constructor to initialize class properties and gather server information.
|
||||
@@ -89,6 +92,7 @@ class ServerState
|
||||
$this->var = (array)parse_ini_file('state/var.ini');
|
||||
$this->nginxCfg = @parse_ini_file('/var/local/emhttp/nginx.ini') ?? [];
|
||||
|
||||
$this->state = strtoupper(empty($this->var['regCheck']) ? $this->var['regTy'] : $this->var['regCheck']);
|
||||
$this->osVersion = $this->var['version'];
|
||||
$this->osVersionBranch = trim(@exec('plugin category /var/log/plugins/unRAIDServer.plg') ?? 'stable');
|
||||
|
||||
@@ -109,6 +113,7 @@ class ServerState
|
||||
$this->updateOsResponse = $this->updateOsCheck->getUnraidOSCheckResult();
|
||||
|
||||
$this->setConnectValues();
|
||||
$this->detectActivationCode();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,6 +233,22 @@ class ServerState
|
||||
}
|
||||
}
|
||||
|
||||
private function detectActivationCode() {
|
||||
// Fresh server and we're not loading with a callback param to install
|
||||
if ($this->state !== 'ENOKEYFILE' || !empty($_GET['c'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$activationCodeData = new ActivationCodeExtractor();
|
||||
$data = $activationCodeData->getData();
|
||||
|
||||
if (empty($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activationCodeData = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the server information as an associative array
|
||||
*
|
||||
@@ -286,7 +307,7 @@ class ServerState
|
||||
"registered" => $this->registered,
|
||||
"registeredTime" => $this->registeredTime,
|
||||
"site" => _var($_SERVER, 'REQUEST_SCHEME') . "://" . _var($_SERVER, 'HTTP_HOST'),
|
||||
"state" => strtoupper(empty($this->var['regCheck']) ? $this->var['regTy'] : $this->var['regCheck']),
|
||||
"state" => $this->state,
|
||||
"theme" => [
|
||||
"banner" => !empty($this->getWebguiGlobal('display', 'banner')),
|
||||
"bannerGradient" => $this->getWebguiGlobal('display', 'showBannerGradient') === 'yes' ?? false,
|
||||
@@ -318,6 +339,10 @@ class ServerState
|
||||
$serverState['updateOsResponse'] = $this->updateOsResponse;
|
||||
}
|
||||
|
||||
if ($this->activationCodeData) {
|
||||
$serverState['activationCodeData'] = $this->activationCodeData;
|
||||
}
|
||||
|
||||
return $serverState;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ class WebComponentTranslations
|
||||
'<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>' => '<p>' . _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.') . '</p>',
|
||||
'A Trial key provides all the functionality of an Unleashed Registration key' => _('A Trial key provides all the functionality of an Unleashed Registration key'),
|
||||
'Acklowledge that you have made a Flash Backup to enable this action' => _('Acklowledge that you have made a Flash Backup to enable this action'),
|
||||
'Activate Now' => _('Activate Now'),
|
||||
'ago' => _('ago'),
|
||||
'All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.' => _('All you need is an active internet connection, an Unraid.net account, and the Connect plugin.') . ' ' . _('Get started by installing the plugin.'),
|
||||
'Attached Storage Devices' => _('Attached Storage Devices'),
|
||||
@@ -117,6 +118,7 @@ class WebComponentTranslations
|
||||
'Copy Key URL' => _('Copy Key URL'),
|
||||
'Copy your Key URL: {0}' => sprintf(_('Copy your Key URL: %s'), '{0}'),
|
||||
'Create Flash Backup' => _('Create Flash Backup'),
|
||||
'Create a password' => _('Create a password'),
|
||||
'Current Version {0}' => sprintf(_('Current Version %s'), '{0}'),
|
||||
'Current Version: Unraid {0}' => sprintf(_('Current Version: Unraid %s'), '{0}'),
|
||||
'Customizable Dashboard Tiles' => _('Customizable Dashboard Tiles'),
|
||||
@@ -204,6 +206,7 @@ class WebComponentTranslations
|
||||
'Learn more and link your key to your account' => _('Learn more and link your key to your account'),
|
||||
'Learn More' => _('Learn More'),
|
||||
'Learn more' => _('Learn more'),
|
||||
'Let\'s activate your Unraid license!' => _('Let\'s activate your Unraid license!'),
|
||||
'Let\'s Unleash your Hardware!' => _('Let\'s Unleash your Hardware!'),
|
||||
'License key actions' => _('License key actions'),
|
||||
'License key type' => _('License key type'),
|
||||
@@ -218,6 +221,8 @@ class WebComponentTranslations
|
||||
'minute' => sprintf(_('%s minute'), '{n}') . ' | ' . sprintf(_('%s minutes'), '{n}'),
|
||||
'Missing key file' => _('Missing key file'),
|
||||
'month' => sprintf(_('%s month'), '{n}') . ' | ' . sprintf(_('%s months'), '{n}'),
|
||||
'More about Unraid.net Accounts' => _('More about Unraid.net Accounts'),
|
||||
'More about Unraid.net' => _('More about Unraid.net'),
|
||||
'More options' => _('More options'),
|
||||
'Multiple License Keys Present' => _('Multiple License Keys Present'),
|
||||
'Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.' => _('Never ever be left without a backup of your config.') . ' ' . _('If you need to change flash drives, generate a backup from Connect and be up and running in minutes.'),
|
||||
@@ -302,6 +307,7 @@ class WebComponentTranslations
|
||||
'SSL certificates for unraid.net deprecated' => _('SSL certificates for unraid.net deprecated'),
|
||||
'Stale Server' => _('Stale Server'),
|
||||
'Stale' => _('Stale'),
|
||||
'Start by creating an Unraid.net account — this will let you manage your license and access support. Once that\'s done, we\'ll guide you through a quick checkout process to register your license and install your key.' => _('Start by creating an Unraid.net account — this will let you manage your license and access support.') . ' ' . _('Once that\'s done, we\'ll guide you through a quick checkout process to register your license and install your key.'),
|
||||
'Start Free 30 Day Trial' => _('Start Free 30 Day Trial'),
|
||||
'Starting your free 30 day trial' => _('Starting your free 30 day trial'),
|
||||
'Success!' => _('Success!'),
|
||||
@@ -372,6 +378,8 @@ class WebComponentTranslations
|
||||
'View on Docs' => _('View on Docs'),
|
||||
'View release notes' => _('View release notes'),
|
||||
'We recommend backing up your USB Flash Boot Device before starting the update.' => _('We recommend backing up your USB Flash Boot Device before starting the update.'),
|
||||
'Welcome to your new ${0} system, powered by Unraid!' => _('Welcome to your new ${0} system, powered by Unraid!'),
|
||||
'Welcome to Unraid!' => _('Welcome to Unraid!'),
|
||||
'year' => sprintf(_('%s year'), '{n}') . ' | ' . sprintf(_('%s years'), '{n}'),
|
||||
'You are still eligible to access OS updates that were published on or before {1}.' => sprintf(_('You are still eligible to access OS updates that were published on or before %s.'), '{1}'),
|
||||
'You can also manually create a new backup by clicking the Create Flash Backup button.' => _('You can also manually create a new backup by clicking the Create Flash Backup button.'),
|
||||
@@ -380,6 +388,7 @@ class WebComponentTranslations
|
||||
'You have exceeded the number of devices allowed for your license. Please remove a device before adding another.' => _('You have exceeded the number of devices allowed for your license. Please remove a device before adding another.'),
|
||||
'You have not activated the Flash Backup feature via the Unraid Connect plugin.' => _('You have not activated the Flash Backup feature via the Unraid Connect plugin.'),
|
||||
'You may still update to releases dated prior to your update expiration date.' => _('You may still update to releases dated prior to your update expiration date.'),
|
||||
'You\'re about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.' => _('You\'re about to create a password to secure access to your system.') . ' ' . _('This password is essential for managing and configuring your server.') . ' ' . _('You’ll use this password every time you access the Unraid web interface.'),
|
||||
'You\'re one step closer to enhancing your Unraid experience' => _('You\'re one step closer to enhancing your Unraid experience'),
|
||||
'Your {0} Key has been replaced!' => sprintf(_('Your %s Key has been replaced!'), '{0}'),
|
||||
'Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.' => sprintf(_('Your %s license included one year of free updates at the time of purchase.'), '{0}') . ' ' . _('You are now eligible to extend your license and access the latest OS updates.') . ' ' . sprintf(_('You are still eligible to access OS updates that were published on or before %s.'), '{1}'),
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
class WebComponentsExtractor {
|
||||
private const MANIFEST_FILE = '/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/manifest.json';
|
||||
private const SEARCH_TEXT = 'unraid-components.client.mjs';
|
||||
private const PREFIXED_PATH = '/plugins/dynamix.my.servers/unraid-components/';
|
||||
|
||||
private string $jsFileName = '';
|
||||
|
||||
public function __construct() {
|
||||
$localManifest = json_decode(file_get_contents(self::MANIFEST_FILE), true);
|
||||
|
||||
foreach ($localManifest as $key => $value) {
|
||||
if (strpos($key, self::SEARCH_TEXT) !== false && isset($value["file"])) {
|
||||
$this->jsFileName = $value["file"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getJsFileName(): string {
|
||||
return $this->jsFileName;
|
||||
}
|
||||
|
||||
public function getJSFileRelativePath(): string {
|
||||
return self::PREFIXED_PATH . $this->jsFileName;
|
||||
}
|
||||
|
||||
public function getScriptTagHtml(): string {
|
||||
if (empty($this->jsFileName)) {
|
||||
return '<script>console.error("%cNo matching key containing \'' . self::SEARCH_TEXT . '\' found.", "font-weight: bold; color: white; background-color: red");</script>';
|
||||
}
|
||||
return '<script src="' . $this->getJSFileRelativePath() . '"></script>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?
|
||||
/**
|
||||
* Caveats to get the modal to display
|
||||
*
|
||||
* /usr/local/emhttp/auth-request.php must be updated to include the exact URLs of anything that is being loaded.
|
||||
* Otherwise, the request for the asset will be blocked and redirected to /login.
|
||||
*
|
||||
* The modification of these files should be done via the plugin's install script.
|
||||
*/
|
||||
require_once("$docroot/plugins/dynamix.my.servers/include/state.php");
|
||||
require_once("$docroot/plugins/dynamix.my.servers/include/web-components-extractor.php");
|
||||
|
||||
$serverState = new ServerState();
|
||||
|
||||
$wcExtractor = new WebComponentsExtractor();
|
||||
echo $wcExtractor->getScriptTagHtml();
|
||||
?>
|
||||
|
||||
<unraid-i18n-host>
|
||||
<unraid-welcome-modal server="<?= $serverState->getServerStateJsonForHtmlAttr() ?>"></unraid-welcome-modal>
|
||||
</unraid-i18n-host>
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/bin/bash
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Description:
|
||||
# This script resets the web GUI banners and logos on the system. It performs
|
||||
# the following actions:
|
||||
# - Verifies if the setup flag exists before proceeding.
|
||||
# - Removes the partner logo symbolic link if it exists.
|
||||
# - Restores the original web GUI banner if it was replaced by a partner banner.
|
||||
# - Adjusts the display banner configuration if no custom banner is set.
|
||||
# - Optionally deletes itself and an adjacent file named "activate_code_setup"
|
||||
# when the "--delete" flag is passed.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Run the script for testing:
|
||||
# ./usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove
|
||||
# ./usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove --debug
|
||||
#
|
||||
# 2. Run the script with the delete flag to self-delete, should be done in the plugin's removal script:
|
||||
# ./usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove --delete
|
||||
#
|
||||
# 3. Run the script and remove the setup flag after completion for a fresh state:
|
||||
# ./usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove --debug --remove-setup-flag
|
||||
#
|
||||
# Prerequisites:
|
||||
# - The setup flag file must exist at "/boot/config/activation/.done".
|
||||
#
|
||||
# Options:
|
||||
# --delete Deletes the script itself and related script & php files.
|
||||
# --debug Enables debug mode to display additional information.
|
||||
# --remove-setup-flag Removes the setup flag after the script completes.
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Default flags
|
||||
DEBUG_MODE=false
|
||||
DRY_RUN=false
|
||||
REMOVE_SETUP_FLAG=false
|
||||
# Check for flags
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--debug)
|
||||
DEBUG_MODE=true
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
;;
|
||||
--remove-setup-flag)
|
||||
REMOVE_SETUP_FLAG=true
|
||||
;;
|
||||
*)
|
||||
# You can optionally handle other arguments if needed
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Debug function
|
||||
debug_echo() {
|
||||
if [[ $DEBUG_MODE == true ]]; then
|
||||
echo "[DEBUG] [activation_code_remove]: $1"
|
||||
fi
|
||||
}
|
||||
|
||||
ACTIVATION_DIR="/boot/config/activation"
|
||||
ACTIVATION_SETUP_FLAG="$ACTIVATION_DIR/.done"
|
||||
|
||||
debug_echo "Checking for setup flag at $ACTIVATION_SETUP_FLAG"
|
||||
|
||||
# only proceed if ACTIVATION_SETUP_FLAG exists
|
||||
if [[ -f "$ACTIVATION_SETUP_FLAG" ]]; then
|
||||
debug_echo "Setup flag found, proceeding with removal"
|
||||
|
||||
# Restore the original auth-request and .set-password files modified for the welcome modal
|
||||
AUTH_REQUEST_FILE="/usr/local/emhttp/auth-request.php"
|
||||
AUTH_REQUEST_BK_FILE="$AUTH_REQUEST_FILE.bak"
|
||||
rm -f "$AUTH_REQUEST_FILE"
|
||||
mv -f "$AUTH_REQUEST_BK_FILE" "$AUTH_REQUEST_FILE"
|
||||
|
||||
WELCOME_MODAL_INJECT_FILE="/usr/local/emhttp/plugins/dynamix/include/.set-password.php"
|
||||
WELCOME_MODAL_INJECT_BK_FILE="$WELCOME_MODAL_INJECT_FILE.bak"
|
||||
rm -f "$WELCOME_MODAL_INJECT_FILE"
|
||||
mv -f "$WELCOME_MODAL_INJECT_BK_FILE" "$WELCOME_MODAL_INJECT_FILE"
|
||||
|
||||
debug_echo "Restored auth-request and .set-password files"
|
||||
|
||||
# Remove the partner logo symbolic link if it exists
|
||||
WEBGUI_IMAGES_DIR="/usr/local/emhttp/webGui/images"
|
||||
|
||||
PARTNER_LOGO="$WEBGUI_IMAGES_DIR/partner-logo.svg"
|
||||
debug_echo "Checking for partner logo at $PARTNER_LOGO"
|
||||
if [[ -L "$PARTNER_LOGO" ]]; then
|
||||
rm -f "$PARTNER_LOGO"
|
||||
debug_echo "Partner logo symbolic link removed"
|
||||
fi
|
||||
|
||||
# restores the original webgui banner if it was replaced by the partner banner
|
||||
WEBGUI_BANNER_OG="$WEBGUI_IMAGES_DIR/banner.png"
|
||||
WEBGUI_BANNER_BK="$WEBGUI_BANNER_OG-"
|
||||
|
||||
debug_echo "Checking for original webgui banner backup at $WEBGUI_BANNER_BK"
|
||||
if [[ -f $WEBGUI_BANNER_BK ]]; then
|
||||
cp -f "$WEBGUI_BANNER_BK" "$WEBGUI_BANNER_OG"
|
||||
rm -f "$WEBGUI_BANNER_BK"
|
||||
debug_echo "Original webgui banner restored"
|
||||
|
||||
# if there's not a custom banner, set display banner to no aka empty string
|
||||
# otherwise leave this display setting alone to keep the user's custom banner
|
||||
CUSTOM_BANNER="/boot/config/plugins/dynamix/banner.png"
|
||||
|
||||
if [[ ! -f "$CUSTOM_BANNER" ]]; then
|
||||
debug_echo "No custom banner found, setting display banner to no"
|
||||
CONFIG_FILE="/boot/config/plugins/dynamix/dynamix.cfg"
|
||||
CONFIG_SECTION="display"
|
||||
CONFIG_KEY="banner"
|
||||
CONFIG_NEW_VALUE=""
|
||||
|
||||
sed -i -E "/\[$CONFIG_SECTION\]/,/^\[/ s/^($CONFIG_KEY=).*/\1$CONFIG_NEW_VALUE/" "$CONFIG_FILE"
|
||||
debug_echo "Display banner configuration set to no"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $REMOVE_SETUP_FLAG == true ]]; then
|
||||
debug_echo "Removing setup flag"
|
||||
rm -f "$ACTIVATION_SETUP_FLAG"
|
||||
debug_echo "Setup flag removed"
|
||||
fi
|
||||
|
||||
if [[ $DRY_RUN == false ]]; then
|
||||
debug_echo "Deleting activation code related setup and php files"
|
||||
FILES_TO_DELETE=(
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/data/activation-data.php"
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/include/activation-code-extractor.php"
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/include/partner-logo.php"
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/include/welcome-modal.php"
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove"
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_setup"
|
||||
)
|
||||
|
||||
for file in "${FILES_TO_DELETE[@]}"; do
|
||||
rm -f "$file"
|
||||
debug_echo "Deleted $file"
|
||||
done
|
||||
fi
|
||||
|
||||
debug_echo "Removal complete"
|
||||
else
|
||||
debug_echo "Setup flag not found, doing nothing"
|
||||
fi
|
||||
@@ -0,0 +1,403 @@
|
||||
#!/bin/bash
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Script Name: activation_code_setup
|
||||
#
|
||||
# Description:
|
||||
# This script sets up the activation environment by performing various tasks,
|
||||
# such as setting system banners, icons, and metadata based on activation assets
|
||||
# and configurations. The script ensures that activation settings are applied only
|
||||
# once by using a setup flag.
|
||||
#
|
||||
# Key Features:
|
||||
# - Verifies if the activation directory and setup flag exist before proceeding.
|
||||
# - Updates partner logos, banners, and other visual assets in the system.
|
||||
# - Configures system display settings (e.g., theme, colors) based on activation JSON.
|
||||
# - Sets server identification (name, model, and description) if not already configured.
|
||||
# - Creates a setup flag to prevent re-execution of tasks.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Ensure that the activation directory exists at "/boot/config/activation".
|
||||
# - The script requires `jq` for JSON parsing. Install it if not already available.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Run the script normally:
|
||||
# ./activation_code_setup
|
||||
#
|
||||
# 2. Run the script with debug statements:
|
||||
# ./activation_code_setup --debug
|
||||
#
|
||||
# Options:
|
||||
# --debug Enables debug mode to display additional information.
|
||||
#
|
||||
# Files Used:
|
||||
# - Activation JSON: A configuration file with `.activationcode` extension, located in
|
||||
# the activation directory.
|
||||
# - Partner assets: Logo, banner, and case model located in the "assets" subdirectory
|
||||
# of the activation directory.
|
||||
# - System configuration files such as:
|
||||
# - `/boot/config/plugins/dynamix/dynamix.cfg`
|
||||
# - `/boot/config/plugins/dynamix/case-model.cfg`
|
||||
# - `/boot/config/ident.cfg`
|
||||
# - System state file: `/usr/local/emhttp/state/var.ini`
|
||||
#
|
||||
# Notes:
|
||||
# - Existing configurations will not be overwritten unless required (e.g., no backup exists).
|
||||
# - Any errors during setup will print warnings but allow the script to continue.
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Default flags
|
||||
DEBUG_MODE=false
|
||||
# Check for flags
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--debug)
|
||||
DEBUG_MODE=true
|
||||
;;
|
||||
*)
|
||||
# You can optionally handle other arguments if needed
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Debug function
|
||||
debug_echo() {
|
||||
if [[ $DEBUG_MODE == true ]]; then
|
||||
echo "[DEBUG] [activation_code_setup]: $1"
|
||||
fi
|
||||
}
|
||||
|
||||
ACTIVATION_DIR="/boot/config/activation"
|
||||
|
||||
debug_echo "Checking for activation directory at $ACTIVATION_DIR"
|
||||
|
||||
if [[ -d "$ACTIVATION_DIR" ]]; then
|
||||
debug_echo "Activation directory found"
|
||||
ACTIVATION_SETUP_FLAG="$ACTIVATION_DIR/.done"
|
||||
ACTIVATION_JSON_EXTENSION=".activationcode"
|
||||
# get the first file in the dir that matches the extension, later parsed as JSON for specific values
|
||||
ACTIVATION_JSON=$(find "$ACTIVATION_DIR" -maxdepth 1 -type f -name "*$ACTIVATION_JSON_EXTENSION" | head -n 1)
|
||||
PARTNER_ASSETS_DIR="$ACTIVATION_DIR/assets"
|
||||
WEBGUI_IMAGES_DIR="/usr/local/emhttp/webGui/images"
|
||||
|
||||
ACTIVATION_JSON_VALID=false
|
||||
if [[ -f "$ACTIVATION_JSON" ]]; then
|
||||
debug_echo "Activation JSON found at $ACTIVATION_JSON, validating..."
|
||||
# Validate the JSON file
|
||||
if jq empty "$ACTIVATION_JSON" 2>/dev/null; then
|
||||
ACTIVATION_JSON_VALID=true
|
||||
debug_echo "Activation JSON is valid"
|
||||
else
|
||||
echo "⚠️ Warning: Activation JSON is not valid, skipping setup"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$ACTIVATION_JSON_VALID" == true ]]; then
|
||||
debug_echo "Activation JSON is valid, proceeding with setup"
|
||||
# create the setup flag file to prevent re-running this setup
|
||||
touch "$ACTIVATION_SETUP_FLAG"
|
||||
|
||||
#
|
||||
# Inject welcome modal into .set-password.php
|
||||
#
|
||||
# We first need to add files to allow them to be requested without authentication
|
||||
AUTH_REQUEST_FILE="/usr/local/emhttp/auth-request.php"
|
||||
WEB_COMPS_DIR="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/_nuxt/"
|
||||
mapfile -t JS_FILES < <(find "$WEB_COMPS_DIR" -type f -name "*.js" | sed 's|/usr/local/emhttp||') # modifying the path to be relative to the webgui's webroot
|
||||
debug_echo "Found ${#JS_FILES[@]} .js files in $WEB_COMPS_DIR"
|
||||
|
||||
FILES_TO_ADD=(
|
||||
"/webGui/images/partner-logo.svg"
|
||||
)
|
||||
FILES_TO_ADD+=("${JS_FILES[@]}")
|
||||
|
||||
if grep -q "\$arrWhitelist" "$AUTH_REQUEST_FILE"; then
|
||||
cp "$AUTH_REQUEST_FILE" "${AUTH_REQUEST_FILE}.bak"
|
||||
debug_echo "Backup of $AUTH_REQUEST_FILE created at ${AUTH_REQUEST_FILE}.bak"
|
||||
|
||||
# prepending items to the array as appending them to the end of the array was causing issues with trying to detect the last item with or without a comma
|
||||
awk -v files_to_add="$(printf "%s\n" "${FILES_TO_ADD[@]}" | awk '{printf " \x27%s\x27,\n", $0}')" '
|
||||
BEGIN { added = 0 }
|
||||
/\$arrWhitelist\s*=\s*\[/ {
|
||||
print $0
|
||||
print files_to_add
|
||||
added = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$AUTH_REQUEST_FILE" > "${AUTH_REQUEST_FILE}.tmp"
|
||||
|
||||
mv "${AUTH_REQUEST_FILE}.tmp" "$AUTH_REQUEST_FILE"
|
||||
debug_echo "Default values and .js files from $WEB_COMPS_DIR added to \$arrWhitelist."
|
||||
else
|
||||
debug_echo "\$arrWhitelist array not found in the file."
|
||||
fi
|
||||
# Inject the welcome modal into the .set-password.php file
|
||||
WELCOME_MODAL_INJECT_FILE="/usr/local/emhttp/plugins/dynamix/include/.set-password.php"
|
||||
# shellcheck disable=SC2016
|
||||
WELCOME_MODAL_INJECT_STRING='<?include "$docroot/plugins/dynamix.my.servers/include/welcome-modal.php"?>'
|
||||
|
||||
if grep -q "</body>" "$WELCOME_MODAL_INJECT_FILE"; then
|
||||
cp "$WELCOME_MODAL_INJECT_FILE" "${WELCOME_MODAL_INJECT_FILE}.bak"
|
||||
debug_echo "Backup of $WELCOME_MODAL_INJECT_FILE created at ${WELCOME_MODAL_INJECT_FILE}.bak"
|
||||
|
||||
awk -v inject="$WELCOME_MODAL_INJECT_STRING" '
|
||||
/<\/body>/ {
|
||||
print inject
|
||||
}
|
||||
{ print }
|
||||
' "$WELCOME_MODAL_INJECT_FILE" > "${WELCOME_MODAL_INJECT_FILE}.tmp"
|
||||
|
||||
mv "${WELCOME_MODAL_INJECT_FILE}.tmp" "$WELCOME_MODAL_INJECT_FILE"
|
||||
debug_echo "Welcome modal injected into $WELCOME_MODAL_INJECT_FILE"
|
||||
else
|
||||
debug_echo "Failed to inject welcome modal into $WELCOME_MODAL_INJECT_FILE"
|
||||
fi
|
||||
|
||||
# copy the logo into a location that the webgui can access
|
||||
PARTNER_LOGO="$PARTNER_ASSETS_DIR/logo.svg"
|
||||
if [[ -f "$PARTNER_LOGO" ]]; then
|
||||
debug_echo "Partner logo found"
|
||||
# symlink the file to the correct destination with the correct extension
|
||||
LINK_DEST="$WEBGUI_IMAGES_DIR/partner-logo.svg"
|
||||
ln -sf "$PARTNER_LOGO" "$LINK_DEST"
|
||||
debug_echo "Partner logo symlinked to $LINK_DEST"
|
||||
else
|
||||
debug_echo "No partner logo found"
|
||||
fi
|
||||
|
||||
# if partner banner exists and there's not a webgui banner backup file that exists on the system
|
||||
# then backup the original banner and replace the original banner with the included partner banner
|
||||
PARTNER_BANNER="$PARTNER_ASSETS_DIR/banner.png"
|
||||
WEBGUI_BANNER_OG="$WEBGUI_IMAGES_DIR/banner.png"
|
||||
WEBGUI_BANNER_BK="$WEBGUI_BANNER_OG-"
|
||||
|
||||
# On uninstall of the PLG the default banner is reverted. So we won't rely on the setup flag. Instead whether the backup file doesn't exist.
|
||||
debug_echo "Checking for partner banner at $PARTNER_BANNER and backup banner at $WEBGUI_BANNER_BK"
|
||||
if [[ -f "$PARTNER_BANNER" && ! -f "$WEBGUI_BANNER_BK" ]]; then
|
||||
debug_echo "Partner banner found at $PARTNER_BANNER"
|
||||
cp -f "$WEBGUI_BANNER_OG" "$WEBGUI_BANNER_BK" 2>/dev/null
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "⚠️ Warning: Failed to back up the original banner."
|
||||
else
|
||||
debug_echo "Original banner backed up to $WEBGUI_BANNER_BK"
|
||||
fi
|
||||
cp -f "$PARTNER_BANNER" "$WEBGUI_BANNER_OG" 2>/dev/null
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "⚠️ Warning: Failed to replace the original banner with the partner banner."
|
||||
else
|
||||
debug_echo "Partner banner replaced the original banner"
|
||||
fi
|
||||
else
|
||||
debug_echo "Skipping partner banner setup"
|
||||
fi
|
||||
|
||||
#
|
||||
# Inject the partner logo in DefaultPageLayout
|
||||
#
|
||||
DEFAULT_PAGE_LAYOUT_FILE="/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php"
|
||||
# shellcheck disable=SC2016
|
||||
WEBGUI_LOGO_STRING_FIND='<a href="https://unraid.net" target="_blank"><?readfile("$docroot/webGui/images/UN-logotype-gradient.svg")?></a>'
|
||||
# shellcheck disable=SC2016
|
||||
PARTNER_LOGO_STRING_REPLACE='<?include "$docroot/plugins/dynamix.my.servers/include/partner-logo.php"?>'
|
||||
# if a backup of the original layout doesn't already exist, then we'll create it.
|
||||
if [[ ! -f "${DEFAULT_PAGE_LAYOUT_FILE}-" ]]; then
|
||||
cp -f "$DEFAULT_PAGE_LAYOUT_FILE" "${DEFAULT_PAGE_LAYOUT_FILE}-"
|
||||
debug_echo "Backup of $DEFAULT_PAGE_LAYOUT_FILE created at ${DEFAULT_PAGE_LAYOUT_FILE}-"
|
||||
fi
|
||||
|
||||
debug_echo "Injecting partner logo into $DEFAULT_PAGE_LAYOUT_FILE"
|
||||
sed -i "s|$WEBGUI_LOGO_STRING_FIND|$PARTNER_LOGO_STRING_REPLACE|" "$DEFAULT_PAGE_LAYOUT_FILE"
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
debug_echo "Injected partner logo into $DEFAULT_PAGE_LAYOUT_FILE"
|
||||
else
|
||||
debug_echo "Failed to inject partner logo into $DEFAULT_PAGE_LAYOUT_FILE, awk error"
|
||||
fi
|
||||
|
||||
# Set the display settings, if we haven't done it once already
|
||||
if ! grep -q "display" "$ACTIVATION_SETUP_FLAG"; then
|
||||
debug_echo "Checking for display settings in the activation JSON"
|
||||
HEADER_TEXT_COLOR=$(jq -r '.header // empty' "$ACTIVATION_JSON" 2>/dev/null || true)
|
||||
HEADER_META_COLOR=$(jq -r '.headermetacolor // empty' "$ACTIVATION_JSON" 2>/dev/null || true)
|
||||
HEADER_BG_COLOR=$(jq -r '.background // empty' "$ACTIVATION_JSON" 2>/dev/null || true)
|
||||
HEADER_SHOW_BANNER_GRADIENT=$(jq -r '.showBannerGradient // empty' "$ACTIVATION_JSON" 2>/dev/null || true)
|
||||
WEBGUI_THEME=$(jq -r '.theme // empty' "$ACTIVATION_JSON" 2>/dev/null || true)
|
||||
|
||||
# Initialize array for parameters
|
||||
declare -A DISPLAY_PARAMS
|
||||
|
||||
# Check if variables have values and add them to the array, while stripping any leading # from the color values b/c the settings config doesn't use them
|
||||
[[ -n $HEADER_TEXT_COLOR ]] && DISPLAY_PARAMS["header"]=${HEADER_TEXT_COLOR//\#/}
|
||||
[[ -n $HEADER_META_COLOR ]] && DISPLAY_PARAMS["headermetacolor"]=${HEADER_META_COLOR//\#/}
|
||||
[[ -n $HEADER_BG_COLOR ]] && DISPLAY_PARAMS["background"]=${HEADER_BG_COLOR//\#/}
|
||||
[[ -n $HEADER_SHOW_BANNER_GRADIENT ]] && DISPLAY_PARAMS["showBannerGradient"]=$HEADER_SHOW_BANNER_GRADIENT
|
||||
[[ -n $WEBGUI_THEME ]] && DISPLAY_PARAMS["theme"]=$WEBGUI_THEME
|
||||
DISPLAY_PARAMS["banner"]="image"
|
||||
|
||||
debug_echo "Display settings found:"
|
||||
for key in "${!DISPLAY_PARAMS[@]}"; do
|
||||
debug_echo "$key: ${DISPLAY_PARAMS[$key]}"
|
||||
done
|
||||
|
||||
CONFIG_FILE="/boot/config/plugins/dynamix/dynamix.cfg"
|
||||
CONFIG_SECTION="display"
|
||||
debug_echo "Updating display settings in $CONFIG_FILE"
|
||||
# Iterate over the DISPLAY_PARAMS array and update the config file
|
||||
for key in "${!DISPLAY_PARAMS[@]}"; do
|
||||
value=${DISPLAY_PARAMS[$key]}
|
||||
debug_echo "Setting $key to $value"
|
||||
|
||||
awk -v section="$CONFIG_SECTION" -v key="$key" -v value="$value" '
|
||||
BEGIN { in_section = 0; key_found = 0 }
|
||||
/^\[.*\]$/ {
|
||||
if (in_section && !key_found) {
|
||||
# Append the key-value pair before leaving the section
|
||||
print key "=" "\"" value "\""
|
||||
}
|
||||
in_section = ($0 == "[" section "]")
|
||||
key_found = 0
|
||||
}
|
||||
{
|
||||
if (in_section && $0 ~ "^" key "=") {
|
||||
print key "=" "\"" value "\""
|
||||
key_found = 1
|
||||
next
|
||||
}
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (in_section && !key_found) {
|
||||
# Append key-value pair at the end of the section if not found
|
||||
print key "=" "\"" value "\""
|
||||
}
|
||||
}' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
||||
done
|
||||
|
||||
# Add what changed to the .done flag file
|
||||
echo "display" >> "$ACTIVATION_SETUP_FLAG"
|
||||
debug_echo "Display settings updated"
|
||||
else
|
||||
debug_echo "Skipping display settings setup, flag already set"
|
||||
fi
|
||||
|
||||
# Set the case model icon, if we haven't done it once already
|
||||
if ! grep -q "case" "$ACTIVATION_SETUP_FLAG"; then
|
||||
debug_echo "Check for case model icon setup"
|
||||
CASE_MODEL_CFG="/boot/config/plugins/dynamix/case-model.cfg"
|
||||
CUSTOM_CASE_FILE_NAME="case-model.png"
|
||||
# if the system has a custom icon already set, we need to skip this step
|
||||
# First preference is to use a custom icon, if it exists in the activation/assets dir
|
||||
# Second preference is to use an included icon with the system that's been specified in the activation json
|
||||
CURRENT_CASE_MODEL=$(cat $CASE_MODEL_CFG 2>/dev/null || true)
|
||||
debug_echo "Current case model icon: $CURRENT_CASE_MODEL"
|
||||
# If CURRENT_CASE_MODEL === "case-model.png" then a custom icon has already been set and we should skip this step
|
||||
if [[ -n "$CURRENT_CASE_MODEL" && "$CURRENT_CASE_MODEL" != "$CUSTOM_CASE_FILE_NAME" ]]; then
|
||||
PARTNER_CASE_MODEL="$PARTNER_ASSETS_DIR/$CUSTOM_CASE_FILE_NAME" # for custom image
|
||||
PARTNER_CASE_ICON=$(jq -r '.caseIcon // empty' "$ACTIVATION_JSON" 2>/dev/null || true); # for included with system icon
|
||||
debug_echo "Partner case model icon: $PARTNER_CASE_MODEL"
|
||||
if [[ -f "$PARTNER_CASE_MODEL" ]]; then
|
||||
cp -f "$PARTNER_CASE_MODEL" "$WEBGUI_IMAGES_DIR/$CUSTOM_CASE_FILE_NAME"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "⚠️ Warning: Failed to replace the original case model icon with the custom icon."
|
||||
else
|
||||
echo -n "$CUSTOM_CASE_FILE_NAME" > $CASE_MODEL_CFG # set the custom icon in the config file
|
||||
echo "case: $CUSTOM_CASE_FILE_NAME" >> "$ACTIVATION_SETUP_FLAG" # add what changed to the .done flag file
|
||||
debug_echo "Custom case model set"
|
||||
fi
|
||||
elif [[ -n "$PARTNER_CASE_ICON" ]]; then
|
||||
echo -n "$PARTNER_CASE_ICON" > $CASE_MODEL_CFG # set the parsed icon name in the config file
|
||||
echo "case: $PARTNER_CASE_ICON" >> "$ACTIVATION_SETUP_FLAG" # add what changed to the .done flag file
|
||||
debug_echo "Case model set to $PARTNER_CASE_ICON"
|
||||
fi
|
||||
else
|
||||
echo "case: skipped, already custom" >> "$ACTIVATION_SETUP_FLAG" # add what changed to the .done flag file
|
||||
debug_echo "Skipping case model setup, already set as custom icon"
|
||||
fi
|
||||
else
|
||||
debug_echo "Skipping case model setup, flag already set"
|
||||
fi
|
||||
|
||||
# Set the server name, model, and description if we haven't already done it once
|
||||
if ! grep -q "identity" "$ACTIVATION_SETUP_FLAG"; then
|
||||
debug_echo "Checking server identification"
|
||||
VAR_INI="/usr/local/emhttp/state/var.ini"
|
||||
# If there's not a set system model or comment (aka description), we'll attempt to set it from activation json
|
||||
CURRENT_NAME=$(awk -F "=" '/NAME/ {print $2}' $VAR_INI 2>/dev/null | tr -d '"')
|
||||
CURRENT_SYS_MODEL=$(awk -F "=" '/SYS_MODEL/ {print $2}' $VAR_INI 2>/dev/null | tr -d '"')
|
||||
CURRENT_COMMENT=$(awk -F "=" '/COMMENT/ {print $2}' $VAR_INI 2>/dev/null | tr -d '"')
|
||||
|
||||
if [[ -z "$CURRENT_SYS_MODEL" || -z "$CURRENT_COMMENT" || "$CURRENT_COMMENT" == "Media server" || "$CURRENT_NAME" == "Tower" ]]; then
|
||||
PARTNER_SERVER_NAME=$(jq -r '.serverName // empty' "$ACTIVATION_JSON" 2>/dev/null || true)
|
||||
PARTNER_SYS_MODEL=$(jq -r '.sysModel // empty' "$ACTIVATION_JSON" 2>/dev/null || true)
|
||||
PARTNER_COMMENT=$(jq -r '.comment // empty' "$ACTIVATION_JSON" 2>/dev/null || true)
|
||||
# Sanitize strings to remove quotes and backslashes
|
||||
PARTNER_SERVER_NAME=${PARTNER_SERVER_NAME//["\\"]/}
|
||||
PARTNER_SYS_MODEL=${PARTNER_SYS_MODEL//["\\"]/}
|
||||
PARTNER_COMMENT=${PARTNER_COMMENT//["\\"]/}
|
||||
|
||||
debug_echo "Partner server name: $PARTNER_SERVER_NAME"
|
||||
debug_echo "Partner system model: $PARTNER_SYS_MODEL"
|
||||
debug_echo "Partner comment: $PARTNER_COMMENT"
|
||||
|
||||
declare -A IDENT_PARAMS
|
||||
if [[ -n "$PARTNER_SERVER_NAME" ]]; then
|
||||
IDENT_PARAMS["NAME"]=$PARTNER_SERVER_NAME
|
||||
fi
|
||||
if [[ -n "$PARTNER_SYS_MODEL" ]]; then
|
||||
IDENT_PARAMS["SYS_MODEL"]=$PARTNER_SYS_MODEL
|
||||
fi
|
||||
if [[ -n "$PARTNER_COMMENT" ]]; then
|
||||
IDENT_PARAMS["COMMENT"]=$PARTNER_COMMENT
|
||||
fi
|
||||
|
||||
if [[ ${#IDENT_PARAMS[@]} -gt 0 ]]; then
|
||||
echo "⏳ Please wait...updating system identification"
|
||||
|
||||
# @todo - This may not be needed, double check if emhttp is updating the ident.cfg file
|
||||
for key in "${!IDENT_PARAMS[@]}"; do
|
||||
value=${IDENT_PARAMS[$key]}
|
||||
CONFIG_FILE="/boot/config/ident.cfg"
|
||||
debug_echo "Setting $key to $value in $CONFIG_FILE"
|
||||
|
||||
# Use awk to update or append the key-value pair
|
||||
awk -v key="$key" -v value="$value" '
|
||||
BEGIN { key_found = 0 }
|
||||
{
|
||||
if ($0 ~ "^" key "=") {
|
||||
print key "=" "\"" value "\""
|
||||
key_found = 1
|
||||
} else {
|
||||
print $0
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (!key_found) {
|
||||
print key "=" "\"" value "\""
|
||||
}
|
||||
}' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
||||
done
|
||||
|
||||
EM_CMD=$(IFS="&"; for key in "${!IDENT_PARAMS[@]}"; do echo "$key=${IDENT_PARAMS[$key]}"; done | paste -sd '&')
|
||||
|
||||
debug_echo "updating system identification for emhttp"
|
||||
|
||||
emcmd "$EM_CMD&changeNames=Apply"
|
||||
|
||||
debug_echo "System identification updated"
|
||||
echo "✅ Identification updated: $EM_CMD"
|
||||
|
||||
# Add what changed to the .done flag file
|
||||
echo "identity: $EM_CMD" >> "$ACTIVATION_SETUP_FLAG"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
debug_echo "Skipping server identification setup, flag already set"
|
||||
fi
|
||||
|
||||
debug_echo "Activation setup complete"
|
||||
fi
|
||||
else
|
||||
debug_echo "Activation directory not found"
|
||||
fi
|
||||
@@ -44,9 +44,9 @@ import type {
|
||||
// EBLACKLISTED2
|
||||
// ENOCONN
|
||||
|
||||
const state: ServerState = "ENOKEYFILE2" as ServerState;
|
||||
const currentFlashGuid = "4444-1111-FOUR-999900008888"; // this is the flash drive that's been booted from
|
||||
const regGuid = "4444-1111-FOUR-999900008888"; // this guid is registered in key server
|
||||
const state: ServerState = "ENOKEYFILE" as ServerState;
|
||||
const currentFlashGuid = "1111-1111-YIJD-ZACK1234TEST"; // this is the flash drive that's been booted from
|
||||
const regGuid = "1111-1111-YIJD-ZACK1234TEST"; // this guid is registered in key server
|
||||
const keyfileBase64 = "";
|
||||
|
||||
// const randomGuid = `1111-1111-${makeid(4)}-123412341234`; // this guid is registered in key server
|
||||
@@ -133,6 +133,19 @@ const osVersionBranch = "stable";
|
||||
// };
|
||||
|
||||
export const serverState: Server = {
|
||||
activationCodeData: {
|
||||
"code": "CC2KP3TDRF",
|
||||
"partnerName": "OEM Partner",
|
||||
"partnerUrl": "https://unraid.net/OEM+Partner",
|
||||
"sysModel": "OEM Partner v1",
|
||||
"comment": "OEM Partner NAS",
|
||||
"caseIcon": "case-model.png",
|
||||
"header": "#ffffff",
|
||||
"headermetacolor": "#eeeeee",
|
||||
"background": "#000000",
|
||||
"showBannerGradient": "yes",
|
||||
"partnerLogo": true,
|
||||
},
|
||||
apiKey: "unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810",
|
||||
avatar: "https://source.unsplash.com/300x300/?portrait",
|
||||
config: {
|
||||
|
||||
121
web/components/Activation/Modal.vue
Normal file
121
web/components/Activation/Modal.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts" setup>
|
||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/solid";
|
||||
import { storeToRefs } from "pinia";
|
||||
import type { ComposerTranslation } from "vue-i18n";
|
||||
|
||||
import { useActivationCodeStore } from "~/store/activationCode";
|
||||
import { usePurchaseStore } from "~/store/purchase";
|
||||
import type { ButtonProps } from "~/types/ui/button";
|
||||
|
||||
import ActivationPartnerLogo from "~/components/Activation/PartnerLogo.vue";
|
||||
|
||||
export interface Props {
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const activationCodeStore = useActivationCodeStore();
|
||||
const { partnerLogo, showActivationModal } = storeToRefs(activationCodeStore);
|
||||
const purchaseStore = usePurchaseStore();
|
||||
|
||||
const title = computed<string>(() => props.t("Let's activate your Unraid license!"));
|
||||
const description = computed<string>(() =>
|
||||
props.t(
|
||||
`Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.`
|
||||
)
|
||||
);
|
||||
const docsButtons = computed<ButtonProps[]>(() => {
|
||||
return [
|
||||
{
|
||||
btnStyle: "underline",
|
||||
external: true,
|
||||
href: "https://docs.unraid.net/unraid-os/faq/licensing-faq/",
|
||||
iconRight: ArrowTopRightOnSquareIcon,
|
||||
size: "14px",
|
||||
text: props.t("More about Licensing"),
|
||||
},
|
||||
{
|
||||
btnStyle: "underline",
|
||||
external: true,
|
||||
href: "https://docs.unraid.net/account/",
|
||||
iconRight: ArrowTopRightOnSquareIcon,
|
||||
size: "14px",
|
||||
text: props.t("More about Unraid.net Accounts"),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
/**
|
||||
* Listen for a key sequence to close the modal
|
||||
* @todo - temporary solution until we have a better way to handle this
|
||||
*/
|
||||
onMounted(() => {
|
||||
const keySequence = [
|
||||
"ArrowUp",
|
||||
"ArrowUp",
|
||||
"ArrowDown",
|
||||
"ArrowDown",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"b",
|
||||
"a",
|
||||
];
|
||||
let sequenceIndex = 0;
|
||||
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (event.key === keySequence[sequenceIndex]) {
|
||||
sequenceIndex++;
|
||||
} else {
|
||||
sequenceIndex = 0;
|
||||
}
|
||||
|
||||
if (sequenceIndex === keySequence.length) {
|
||||
activationCodeStore.setActivationModalHidden(true);
|
||||
window.location.href = "/Tools/Registration";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("keydown", () => {});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-if="showActivationModal"
|
||||
:t="t"
|
||||
:open="showActivationModal"
|
||||
:show-close-x="false"
|
||||
:title="title"
|
||||
:title-in-main="!!partnerLogo"
|
||||
:description="description"
|
||||
overlay-color="bg-background"
|
||||
overlay-opacity="bg-opacity-100"
|
||||
max-width="max-w-800px"
|
||||
:modal-vertical-center="false"
|
||||
:disable-shadow="true"
|
||||
>
|
||||
<template v-if="partnerLogo" #header>
|
||||
<ActivationPartnerLogo />
|
||||
</template>
|
||||
|
||||
<template #main>
|
||||
<div class="flex justify-center gap-4 mx-auto w-full">
|
||||
<BrandButton v-for="button in docsButtons" :key="button.text" v-bind="button" />
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="w-full flex gap-8px justify-center mx-auto">
|
||||
<BrandButton
|
||||
:text="t('Activate Now')"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
@click="purchaseStore.activate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
17
web/components/Activation/PartnerLogo.vue
Normal file
17
web/components/Activation/PartnerLogo.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
import { useActivationCodeStore } from "~/store/activationCode";
|
||||
import ActivationPartnerLogoImg from "~/components/Activation/PartnerLogoImg.vue";
|
||||
|
||||
const { partnerLogo, partnerUrl } = storeToRefs(useActivationCodeStore());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="partnerLogo">
|
||||
<a v-if="partnerUrl" :href="partnerUrl" class="opacity-100 hover:opacity-75 focus:opacity-75" target="_blank" rel="noopener noreferrer">
|
||||
<ActivationPartnerLogoImg />
|
||||
</a>
|
||||
<ActivationPartnerLogoImg v-else />
|
||||
</template>
|
||||
</template>
|
||||
17
web/components/Activation/PartnerLogoImg.vue
Normal file
17
web/components/Activation/PartnerLogoImg.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* The actual image should be full black filled #000000.
|
||||
* This allows us to use the `invert` class to change the color to white when displayed in dark mode.
|
||||
*/
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
import { useActivationCodeStore } from "~/store/activationCode";
|
||||
import { useThemeStore } from "~/store/theme";
|
||||
|
||||
const { partnerLogo } = storeToRefs(useActivationCodeStore());
|
||||
const { darkMode } = storeToRefs(useThemeStore());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img v-if="partnerLogo" :src="partnerLogo" class="w-72" :class="{ 'invert': darkMode }" />
|
||||
</template>
|
||||
@@ -2,17 +2,27 @@
|
||||
import Input from '~/components/shadcn/input/Input.vue';
|
||||
import Label from '~/components/shadcn/label/Label.vue';
|
||||
import { defaultColors, useThemeStore, type Theme } from '~/store/theme';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { darkMode } = toRefs(themeStore);
|
||||
|
||||
const setDarkMode = ref<boolean>(false);
|
||||
const setGradient = ref<boolean>(false);
|
||||
const setDescription = ref<boolean>(true);
|
||||
const setBanner = ref<boolean>(true);
|
||||
|
||||
const [setDarkMode, toggleDarkMode] = useToggle(false);
|
||||
const [setGradient, toggleGradient] = useToggle(false);
|
||||
const [setDescription, toggleDescription] = useToggle(true);
|
||||
const [setBanner, toggleBanner] = useToggle(true);
|
||||
|
||||
const toggleSwitch = (value: boolean) => {
|
||||
setDarkMode.value = value;
|
||||
};
|
||||
const toggleGradient = (value: boolean) => {
|
||||
setGradient.value = value;
|
||||
};
|
||||
const toggleDescription = (value: boolean) => {
|
||||
setDescription.value = value;
|
||||
};
|
||||
const toggleBanner = (value: boolean) => {
|
||||
setBanner.value = value;
|
||||
};
|
||||
|
||||
const textPrimary = ref<string>('');
|
||||
const textSecondary = ref<string>('');
|
||||
@@ -22,7 +32,7 @@ const textPrimaryToSet = computed(() => {
|
||||
if (textPrimary.value) {
|
||||
return textPrimary.value;
|
||||
}
|
||||
return darkMode.value ? defaultColors.dark['--headerTextPrimary'] : defaultColors.light['--headerTextPrimary'];
|
||||
return darkMode.value ? defaultColors.dark.headerTextPrimary : defaultColors.light.headerTextPrimary;
|
||||
});
|
||||
|
||||
const textSecondaryToSet = computed(() => {
|
||||
@@ -30,8 +40,8 @@ const textSecondaryToSet = computed(() => {
|
||||
return textSecondary.value;
|
||||
}
|
||||
return darkMode.value
|
||||
? defaultColors.dark['--header-text-secondary']
|
||||
: defaultColors.light['--header-text-secondary'];
|
||||
? defaultColors.dark.headerTextSecondary
|
||||
: defaultColors.light.headerTextSecondary;
|
||||
});
|
||||
|
||||
const bgColorToSet = computed(() => {
|
||||
@@ -39,52 +49,42 @@ const bgColorToSet = computed(() => {
|
||||
return bgColor.value;
|
||||
}
|
||||
return darkMode.value
|
||||
? defaultColors.dark['--header-background']
|
||||
: defaultColors.light['--header-background'];
|
||||
? defaultColors.dark.headerBackgroundColor
|
||||
: defaultColors.light.headerBackgroundColor;
|
||||
});
|
||||
|
||||
watch(
|
||||
[
|
||||
setDarkMode,
|
||||
bgColorToSet,
|
||||
textSecondaryToSet,
|
||||
textPrimaryToSet,
|
||||
setDescription,
|
||||
setBanner,
|
||||
setGradient,
|
||||
],
|
||||
() => {
|
||||
const themeToSet: Theme = {
|
||||
banner: setBanner.value,
|
||||
bannerGradient: setGradient.value,
|
||||
descriptionShow: setDescription.value,
|
||||
textColor: textPrimaryToSet.value,
|
||||
metaColor: textSecondaryToSet.value,
|
||||
bgColor: bgColorToSet.value,
|
||||
name: setDarkMode.value ? 'black' : 'light',
|
||||
};
|
||||
themeStore.setTheme(themeToSet);
|
||||
}
|
||||
);
|
||||
watch([setDarkMode, bgColorToSet, textSecondaryToSet, textPrimaryToSet], (newVal) => {
|
||||
console.log(newVal);
|
||||
const themeToSet: Theme = {
|
||||
banner: setBanner.value,
|
||||
bannerGradient: setGradient.value,
|
||||
descriptionShow: setDescription.value,
|
||||
textColor: textPrimaryToSet.value,
|
||||
metaColor: textSecondaryToSet.value,
|
||||
bgColor: bgColorToSet.value,
|
||||
name: setDarkMode.value ? 'black' : 'light',
|
||||
};
|
||||
themeStore.setTheme(themeToSet);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 border-solid border-2 p-2 border-r-2">
|
||||
<h1 class="text-lg">Color Theme Customization</h1>
|
||||
<Label for="header-primary-text">Header Primary Text Color</Label>
|
||||
<Input id="header-primary-text" v-model="textPrimary" />
|
||||
<Label for="header-secondary-text">Header Secondary Text Color</Label>
|
||||
<Input id="header-secondary-text" v-model="textSecondary" />
|
||||
<Label for="header-background">Header Background Color</Label>
|
||||
<Input id="header-background" v-model="bgColor" />
|
||||
<Label for="primary-text-color">Header Primary Text Color</Label>
|
||||
<Input id="primary-text-color" v-model="textPrimary" />
|
||||
<Label for="primary-text-color">Header Secondary Text Color</Label>
|
||||
<Input id="primary-text-color" v-model="textSecondary" />
|
||||
<Label for="primary-text-color">Header Background Color</Label>
|
||||
<Input id="primary-text-color" v-model="bgColor" />
|
||||
<Label for="dark-mode">Dark Mode</Label>
|
||||
<Switch id="dark-mode" :checked="setDarkMode" @update:checked="toggleDarkMode" />
|
||||
<Switch id="dark-mode" @update:checked="toggleSwitch" />
|
||||
<Label for="gradient">Gradient</Label>
|
||||
<Switch id="gradient" :checked="setGradient" @update:checked="toggleGradient" />
|
||||
<Switch id="gradient" @update:checked="toggleGradient" />
|
||||
<Label for="description">Description</Label>
|
||||
<Switch id="description" :checked="setDescription" @update:checked="toggleDescription" />
|
||||
<Switch id="description" @update:checked="toggleDescription" />
|
||||
<Label for="banner">Banner</Label>
|
||||
<Switch id="banner" :checked="setBanner" @update:checked="toggleBanner" />
|
||||
<Switch id="banner" @update:checked="toggleBanner" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { TransitionChild, TransitionRoot } from '@headlessui/vue';
|
||||
import { XMarkIcon } from '@heroicons/vue/24/outline';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
import { cn } from '~/components/shadcn/utils';
|
||||
|
||||
export interface Props {
|
||||
centerContent?: boolean;
|
||||
@@ -14,6 +15,13 @@ export interface Props {
|
||||
t: ComposerTranslation;
|
||||
tallContent?: boolean;
|
||||
title?: string;
|
||||
titleInMain?: boolean;
|
||||
headerJustifyCenter?: boolean;
|
||||
overlayColor?: string;
|
||||
overlayOpacity?: string;
|
||||
modalVerticalCenter?: boolean | string;
|
||||
disableShadow?: boolean;
|
||||
disableOverlayClose?: boolean;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
centerContent: true,
|
||||
@@ -25,6 +33,13 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
success: false,
|
||||
tallContent: false,
|
||||
title: '',
|
||||
titleInMain: false,
|
||||
headerJustifyCenter: true,
|
||||
overlayColor: 'bg-black',
|
||||
overlayOpacity: 'bg-opacity-80',
|
||||
modalVerticalCenter: true,
|
||||
disableShadow: false,
|
||||
disableOverlayClose: false,
|
||||
});
|
||||
watchEffect(() => {
|
||||
// toggle body scrollability
|
||||
@@ -40,8 +55,16 @@ const closeModal = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const ariaLablledById = computed((): string|undefined => props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined);
|
||||
|
||||
const ariaLablledById = computed<string|undefined>(() => props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined);
|
||||
const computedVerticalCenter = computed<string>(() => {
|
||||
if (props.tallContent) {
|
||||
return 'items-start sm:items-center';
|
||||
}
|
||||
if (typeof props.modalVerticalCenter === 'string') {
|
||||
return props.modalVerticalCenter;
|
||||
}
|
||||
return props.modalVerticalCenter ? 'items-center' : 'items-start';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -56,10 +79,7 @@ const ariaLablledById = computed((): string|undefined => props.title ? `ModalTit
|
||||
>
|
||||
<div
|
||||
class="fixed inset-0 flex min-h-screen w-screen justify-center p-8px sm:p-16px overflow-y-auto"
|
||||
:class="{
|
||||
'items-start sm:items-center': tallContent,
|
||||
'items-center': !tallContent,
|
||||
}"
|
||||
:class="computedVerticalCenter"
|
||||
>
|
||||
<TransitionChild
|
||||
appear
|
||||
@@ -72,9 +92,9 @@ const ariaLablledById = computed((): string|undefined => props.title ? `ModalTit
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<div
|
||||
class="fixed inset-0 z-0 bg-black bg-opacity-80 transition-opacity"
|
||||
:title="t('Click to close modal')"
|
||||
@click="closeModal"
|
||||
:class="cn('fixed inset-0 z-0 transition-opacity', overlayColor, overlayOpacity)"
|
||||
:title="showCloseX ? t('Click to close modal') : undefined"
|
||||
@click="!disableOverlayClose ? closeModal : undefined"
|
||||
/>
|
||||
</TransitionChild>
|
||||
<TransitionChild
|
||||
@@ -90,11 +110,12 @@ const ariaLablledById = computed((): string|undefined => props.title ? `ModalTit
|
||||
<div
|
||||
:class="[
|
||||
maxWidth,
|
||||
disableShadow ? 'shadow-none border-none' : 'shadow-xl',
|
||||
error ? 'shadow-unraid-red/30 border-unraid-red/10' : '',
|
||||
success ? 'shadow-green-600/30 border-green-600/10' : '',
|
||||
!error && !success ? 'shadow-orange/10 border-white/10' : '',
|
||||
!error && !success && !disableShadow ? 'shadow-orange/10 border-white/10' : '',
|
||||
]"
|
||||
class="text-16px text-foreground bg-muted dark:bg-background text-left relative z-10 flex flex-col justify-around border-2 border-solid shadow-xl transform overflow-hidden rounded-lg transition-all sm:w-full"
|
||||
class="text-16px text-foreground bg-background text-left relative z-10 flex flex-col justify-around border-2 border-solid transform overflow-hidden rounded-lg transition-all sm:w-full"
|
||||
>
|
||||
<div v-if="showCloseX" class="absolute z-20 right-0 top-0 pt-4px pr-4px hidden sm:block">
|
||||
<button
|
||||
@@ -111,13 +132,13 @@ const ariaLablledById = computed((): string|undefined => props.title ? `ModalTit
|
||||
class="relative z-0 grid items-start gap-2 p-16px md:p-24px rounded-t"
|
||||
:class="{
|
||||
'sm:pr-40px': showCloseX,
|
||||
'justify-between': $slots['header'],
|
||||
'justify-center': !$slots['header'],
|
||||
'justify-between': !headerJustifyCenter,
|
||||
'justify-center': headerJustifyCenter,
|
||||
}"
|
||||
>
|
||||
<div class="absolute -z-10 inset-0 opacity-10 bg-card" />
|
||||
<template v-if="!$slots['header']">
|
||||
<h1 v-if="title" :id="ariaLablledById" class="text-center text-20px sm:text-24px font-semibold flex flex-wrap justify-center gap-x-4px">
|
||||
<h1 v-if="title && !titleInMain" :id="ariaLablledById" class="text-center text-20px sm:text-24px font-semibold flex flex-wrap justify-center gap-x-4px">
|
||||
{{ title }}
|
||||
<slot name="headerTitle" />
|
||||
</h1>
|
||||
@@ -127,10 +148,19 @@ const ariaLablledById = computed((): string|undefined => props.title ? `ModalTit
|
||||
|
||||
<div
|
||||
v-if="$slots['main'] || description"
|
||||
class="relative max-h-[65vh] tall:max-h-[75vh] flex flex-col gap-y-16px sm:gap-y-24px p-16px md:p-24px overflow-y-auto shadow-inner"
|
||||
:class="centerContent && 'text-center'"
|
||||
class="relative max-h-[65vh] tall:max-h-[75vh] flex flex-col gap-y-16px sm:gap-y-24px p-16px md:p-24px overflow-y-auto"
|
||||
:class="[
|
||||
centerContent && 'text-center',
|
||||
!disableShadow && 'shadow-inner',
|
||||
]"
|
||||
>
|
||||
<h2 v-if="description" class="text-18px sm:text-20px opacity-75" v-html="description" />
|
||||
<div class="flex flex-col gap-y-12px">
|
||||
<h1 v-if="title && titleInMain" :id="ariaLablledById" class="text-center text-20px sm:text-24px font-semibold flex flex-wrap justify-center gap-x-4px">
|
||||
{{ title }}
|
||||
<slot name="headerTitle" />
|
||||
</h1>
|
||||
<h2 v-if="description" class="text-18px sm:text-20px opacity-75" v-html="description" />
|
||||
</div>
|
||||
<div v-if="$slots['main']">
|
||||
<slot name="main" />
|
||||
</div>
|
||||
|
||||
@@ -22,11 +22,12 @@ const { releaseForUpdate: updateOsChangelogModalVisible } = storeToRefs(useUpdat
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="modals" class="relative z-[99999]">
|
||||
<div id="modals" ref="modals" class="relative z-[99999]">
|
||||
<UpcCallbackFeedback :t="t" :open="callbackStatus !== 'ready'" />
|
||||
<UpcTrial :t="t" :open="trialModalVisible" />
|
||||
<UpdateOsCheckUpdateResponseModal :t="t" :open="updateOsModalVisible" />
|
||||
<UpdateOsChangelogModal :t="t" :open="!!updateOsChangelogModalVisible" />
|
||||
<ActivationModal :t="t" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ const title = computed((): string => {
|
||||
<ExclamationTriangleIcon v-if="errors[0].level === 'warning'" class="text-unraid-red fill-current relative w-24px h-24px" />
|
||||
<ShieldExclamationIcon v-if="errors[0].level === 'error'" class="text-unraid-red fill-current relative w-24px h-24px" />
|
||||
</template>
|
||||
<span v-if="text" class="relative leading-none">
|
||||
<span v-if="text" class="relative leading-none ">
|
||||
<span>{{ text }}</span>
|
||||
<span class="absolute bottom-[-3px] inset-x-0 h-2px w-full bg-gradient-to-r from-unraid-red to-orange rounded opacity-0 group-hover:opacity-100 group-focus:opacity-100 transition-opacity" />
|
||||
</span>
|
||||
|
||||
@@ -21,7 +21,7 @@ const upgradeAction = computed((): ServerStateDataAction | undefined => {
|
||||
<span class="flex flex-row items-center gap-x-8px">
|
||||
<template v-if="upgradeAction">
|
||||
<UpcServerStateBuy
|
||||
class="text-header-text-secondary"
|
||||
class="text-white"
|
||||
:title="t('Upgrade Key')"
|
||||
@click="upgradeAction.click?.()"
|
||||
>
|
||||
|
||||
158
web/components/WelcomeModal.ce.vue
Normal file
158
web/components/WelcomeModal.ce.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { useActivationCodeStore } from '~/store/activationCode';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { Server } from '~/types/server';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
export interface Props {
|
||||
server?: Server | string;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const activationCodeStore = useActivationCodeStore();
|
||||
const serverStore = useServerStore();
|
||||
|
||||
const { partnerLogo, partnerName } = storeToRefs(activationCodeStore);
|
||||
|
||||
const title = computed<string>(() =>
|
||||
partnerName.value
|
||||
? t(`Welcome to your new {0} system, powered by Unraid!`, [partnerName.value])
|
||||
: t('Welcome to Unraid!')
|
||||
);
|
||||
|
||||
const description = computed<string>(() =>
|
||||
t(`You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.`)
|
||||
);
|
||||
|
||||
const showModal = ref<boolean>(true);
|
||||
const dropdownHide = () => { showModal.value = false; };
|
||||
|
||||
watchEffect(() => {
|
||||
/**
|
||||
* A necessary workaround for how the webgui handles font-size.
|
||||
* There's not a shared CSS file between /login and any of the authenticated webgui pages.
|
||||
* Which has lead to font-size differences.
|
||||
* The authed webgui pages have CSS of `html { font-size: 62.5%; }` which makes REMs act as if the base font-size is 10px.
|
||||
* The /login page doesn't do this.
|
||||
* So we'll target the HTML element and toggle the font-size to be 62.5% when the modal is open and 100% when it's closed.
|
||||
* */
|
||||
const $confirmPasswordField = window.document.querySelector('#confirmPassword');
|
||||
|
||||
if ($confirmPasswordField) {
|
||||
if (showModal.value) {
|
||||
window.document.documentElement.style.setProperty('font-size', '62.5%');
|
||||
} else {
|
||||
window.document.documentElement.style.setProperty('font-size', '100%');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (!props.server) {
|
||||
throw new Error('Server data not present');
|
||||
}
|
||||
|
||||
if (typeof props.server === 'object') { // Handles the testing dev Vue component
|
||||
serverStore.setServer(props.server);
|
||||
} else if (typeof props.server === 'string') { // Handle web component
|
||||
const parsedServerProp = JSON.parse(props.server);
|
||||
serverStore.setServer(parsedServerProp);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="modals" ref="modals" class="relative z-[99999]">
|
||||
<Modal
|
||||
v-if="showModal"
|
||||
:t="t"
|
||||
:open="showModal"
|
||||
:show-close-x="false"
|
||||
:title="title"
|
||||
:title-in-main="!!partnerLogo"
|
||||
:description="description"
|
||||
overlay-color="bg-background"
|
||||
overlay-opacity="bg-opacity-100"
|
||||
max-width="max-w-800px"
|
||||
:disable-shadow="true"
|
||||
:modal-vertical-center="false"
|
||||
:disable-overlay-close="true"
|
||||
@close="dropdownHide"
|
||||
>
|
||||
<template v-if="partnerLogo" #header>
|
||||
<ActivationPartnerLogo />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="w-full flex gap-8px justify-center mx-auto">
|
||||
<BrandButton
|
||||
:text="t('Create a password')"
|
||||
@click="dropdownHide"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
.unraid_mark_2,
|
||||
.unraid_mark_4 {
|
||||
animation: mark_2 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_3 {
|
||||
animation: mark_3 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_6,
|
||||
.unraid_mark_8 {
|
||||
animation: mark_6 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_7 {
|
||||
animation: mark_7 1.5s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes mark_2 {
|
||||
50% {
|
||||
transform: translateY(-40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_3 {
|
||||
50% {
|
||||
transform: translateY(-62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_6 {
|
||||
50% {
|
||||
transform: translateY(40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_7 {
|
||||
50% {
|
||||
transform: translateY(62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
1
web/consts.ts
Normal file
1
web/consts.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY = 'activationCodeModalHidden';
|
||||
@@ -34,6 +34,7 @@
|
||||
"A valid keyfile is required to check for OS updates.": "A valid keyfile is required to check for OS updates.",
|
||||
"A valid OS version is required to check for OS updates.": "A valid OS version is required to check for OS updates.",
|
||||
"Acklowledge that you have made a Flash Backup to enable this action": "Acklowledge that you have made a Flash Backup to enable this action",
|
||||
"Activate Now": "Activate Now",
|
||||
"ago": "ago",
|
||||
"All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.": "All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.",
|
||||
"Attached Storage Devices": "Attached Storage Devices",
|
||||
@@ -73,6 +74,7 @@
|
||||
"Copy Key URL": "Copy Key URL",
|
||||
"Copy your Key URL: {0}": "Copy your Key URL: {0}",
|
||||
"Create Flash Backup": "Create Flash Backup",
|
||||
"Create a password": "Create a password",
|
||||
"Current Version {0}": "Current Version {0}",
|
||||
"Current Version: Unraid {0}": "Current Version: Unraid {0}",
|
||||
"Customizable Dashboard Tiles": "Customizable Dashboard Tiles",
|
||||
@@ -173,6 +175,7 @@
|
||||
"Learn more and link your key to your account": "Learn more and link your key to your account",
|
||||
"Learn more": "Learn more",
|
||||
"Learn More": "Learn More",
|
||||
"Let's activate your Unraid license!": "Let's activate your Unraid license!",
|
||||
"Let's Unleash your Hardware!": "Let's Unleash your Hardware!",
|
||||
"License key actions": "License key actions",
|
||||
"License key type": "License key type",
|
||||
@@ -189,6 +192,8 @@
|
||||
"minute": "{n} minute | {n} minutes",
|
||||
"Missing key file": "Missing key file",
|
||||
"month": "{n} month | {n} months",
|
||||
"More about Licensing": "More about Licensing",
|
||||
"More about Unraid.net Accounts": "More about Unraid.net Accounts",
|
||||
"More options": "More options",
|
||||
"Multiple License Keys Present": "Multiple License Keys Present",
|
||||
"Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.": "Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.",
|
||||
@@ -279,6 +284,7 @@
|
||||
"SSL certificates for unraid.net deprecated": "SSL certificates for unraid.net deprecated",
|
||||
"Stale Server": "Stale Server",
|
||||
"Stale": "Stale",
|
||||
"Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.": "Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.",
|
||||
"Start Free 30 Day Trial": "Start Free 30 Day Trial",
|
||||
"Starter": "Starter",
|
||||
"Starting your free 30 day trial": "Starting your free 30 day trial",
|
||||
@@ -352,6 +358,8 @@
|
||||
"View on Docs": "View on Docs",
|
||||
"View release notes": "View release notes",
|
||||
"We recommend backing up your USB Flash Boot Device before starting the update.": "We recommend backing up your USB Flash Boot Device before starting the update.",
|
||||
"Welcome to your new {0} system, powered by Unraid!": "Welcome to your new {0} system, powered by Unraid!",
|
||||
"Welcome to Unraid!": "Welcome to Unraid!",
|
||||
"year": "{n} year | {n} years",
|
||||
"You are still eligible to access OS updates that were published on or before {1}.": "You are still eligible to access OS updates that were published on or before {1}.",
|
||||
"You can also manually create a new backup by clicking the Create Flash Backup button.": "You can also manually create a new backup by clicking the Create Flash Backup button.",
|
||||
@@ -360,6 +368,7 @@
|
||||
"You have exceeded the number of devices allowed for your license. Please remove a device before adding another.": "You have exceeded the number of devices allowed for your license. Please remove a device before adding another.",
|
||||
"You have not activated the Flash Backup feature via the Unraid Connect plugin.": "You have not activated the Flash Backup feature via the Unraid Connect plugin.",
|
||||
"You may still update to releases dated prior to your update expiration date.": "You may still update to releases dated prior to your update expiration date.",
|
||||
"You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.": "You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.",
|
||||
"You're one step closer to enhancing your Unraid experience": "You're one step closer to enhancing your Unraid experience",
|
||||
"Your {0} Key has been replaced!": "Your {0} Key has been replaced!",
|
||||
"Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.": "Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.",
|
||||
|
||||
@@ -40,106 +40,119 @@ const charsToReserve = '_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
ssr: false,
|
||||
|
||||
devServer: {
|
||||
port: 4321,
|
||||
},
|
||||
devServer: {
|
||||
port: 4321,
|
||||
},
|
||||
|
||||
devtools: {
|
||||
enabled: true,
|
||||
},
|
||||
devtools: {
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
modules: [
|
||||
'@vueuse/nuxt',
|
||||
'@pinia/nuxt',
|
||||
'@nuxtjs/tailwindcss',
|
||||
'nuxt-custom-elements',
|
||||
'@nuxt/eslint',
|
||||
'shadcn-nuxt',
|
||||
],
|
||||
modules: [
|
||||
"@vueuse/nuxt",
|
||||
"@pinia/nuxt",
|
||||
"@nuxtjs/tailwindcss",
|
||||
"nuxt-custom-elements",
|
||||
"@nuxt/eslint",
|
||||
"shadcn-nuxt",
|
||||
],
|
||||
|
||||
components: [{ path: '~/components/UserProfile', prefix: 'Upc' }, '~/components'],
|
||||
ignore: ['/webGui/images'],
|
||||
|
||||
// typescript: {
|
||||
// typeCheck: true
|
||||
// },
|
||||
shadcn: {
|
||||
prefix: '',
|
||||
componentDir: './components/shadcn',
|
||||
},
|
||||
components: [
|
||||
{ path: "~/components/Brand", prefix: "Brand" },
|
||||
{ path: "~/components/ConnectSettings", prefix: "ConnectSettings" },
|
||||
{ path: "~/components/Ui", prefix: "Ui" },
|
||||
{ path: "~/components/UserProfile", prefix: "Upc" },
|
||||
{ path: "~/components/UpdateOs", prefix: "UpdateOs" },
|
||||
"~/components",
|
||||
],
|
||||
|
||||
vite: {
|
||||
plugins: [
|
||||
!process.env.VITE_ALLOW_CONSOLE_LOGS &&
|
||||
removeConsole({
|
||||
includes: ['log', 'warn', 'error', 'info', 'debug'],
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
mangle: {
|
||||
reserved: terserReservations(charsToReserve),
|
||||
toplevel: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// typescript: {
|
||||
// typeCheck: true
|
||||
// },
|
||||
shadcn: {
|
||||
prefix: "",
|
||||
componentDir: "./components/shadcn",
|
||||
},
|
||||
|
||||
customElements: {
|
||||
entries: [
|
||||
{
|
||||
name: 'UnraidComponents',
|
||||
tags: [
|
||||
{
|
||||
name: 'UnraidI18nHost',
|
||||
path: '@/components/I18nHost.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidAuth',
|
||||
path: '@/components/Auth.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidConnectSettings',
|
||||
path: '@/components/ConnectSettings/ConnectSettings.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidDownloadApiLogs',
|
||||
path: '@/components/DownloadApiLogs.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidHeaderOsVersion',
|
||||
path: '@/components/HeaderOsVersion.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidModals',
|
||||
path: '@/components/Modals.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidUserProfile',
|
||||
path: '@/components/UserProfile.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidUpdateOs',
|
||||
path: '@/components/UpdateOs.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidDowngradeOs',
|
||||
path: '@/components/DowngradeOs.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidRegistration',
|
||||
path: '@/components/Registration.ce',
|
||||
},
|
||||
{
|
||||
name: 'UnraidWanIpCheck',
|
||||
path: '@/components/WanIpCheck.ce',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
vite: {
|
||||
plugins: [
|
||||
!process.env.VITE_ALLOW_CONSOLE_LOGS &&
|
||||
removeConsole({
|
||||
includes: ["log", "warn", "error", "info", "debug"],
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
minify: "terser",
|
||||
terserOptions: {
|
||||
mangle: {
|
||||
reserved: terserReservations(charsToReserve),
|
||||
toplevel: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
compatibilityDate: '2024-12-05',
|
||||
customElements: {
|
||||
entries: [
|
||||
{
|
||||
name: "UnraidComponents",
|
||||
tags: [
|
||||
{
|
||||
name: "UnraidI18nHost",
|
||||
path: "@/components/I18nHost.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidAuth",
|
||||
path: "@/components/Auth.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidConnectSettings",
|
||||
path: "@/components/ConnectSettings/ConnectSettings.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidDownloadApiLogs",
|
||||
path: "@/components/DownloadApiLogs.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidHeaderOsVersion",
|
||||
path: "@/components/HeaderOsVersion.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidModals",
|
||||
path: "@/components/Modals.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidUserProfile",
|
||||
path: "@/components/UserProfile.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidUpdateOs",
|
||||
path: "@/components/UpdateOs.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidDowngradeOs",
|
||||
path: "@/components/DowngradeOs.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidRegistration",
|
||||
path: "@/components/Registration.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidWanIpCheck",
|
||||
path: "@/components/WanIpCheck.ce",
|
||||
},
|
||||
{
|
||||
name: "UnraidWelcomeModal",
|
||||
path: "@/components/WelcomeModal.ce",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
compatibilityDate: "2024-12-05"
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"lint:fix": "eslint . --fix",
|
||||
"type-check": "nuxi typecheck",
|
||||
"prebuild:dev": "./scripts/prebuild-webgui-set-env.sh .env.staging",
|
||||
"build:dev": "npm run type-check && nuxt build && npm run manifest-ts && npm run deploy-to-unraid:dev",
|
||||
"build:dev": "nuxt build && npm run manifest-ts && npm run deploy-to-unraid:dev",
|
||||
"postbuild:dev": "./scripts/postbuild-webgui-restore-env.sh",
|
||||
"prebuild:webgui": "./scripts/prebuild-webgui-set-env.sh",
|
||||
"build:webgui": "npm run type-check && nuxt build && npm run manifest-ts && npm run copy-to-webgui-repo",
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { serverState } from '~/_data/serverState';
|
||||
import type { SendPayloads } from '~/store/callback';
|
||||
import type { UiBadgePropsColor } from '~/types/ui/badge';
|
||||
import type { ButtonStyle } from '~/types/ui/button';
|
||||
import AES from 'crypto-js/aes';
|
||||
import BrandButton from '~/components/Brand/Button.vue';
|
||||
import type { ButtonStyle } from '~/types/ui/button';
|
||||
import type { UiBadgePropsColor } from '~/types/ui/badge';
|
||||
const { registerEntry } = useCustomElements();
|
||||
onBeforeMount(() => {
|
||||
registerEntry('UnraidComponents');
|
||||
@@ -60,7 +60,34 @@ onMounted(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const badgeColors = [
|
||||
'black',
|
||||
'white',
|
||||
'red',
|
||||
'yellow',
|
||||
'green',
|
||||
'blue',
|
||||
'indigo',
|
||||
'purple',
|
||||
'pink',
|
||||
'orange',
|
||||
'transparent',
|
||||
'current',
|
||||
'gray',
|
||||
'custom',
|
||||
] as UiBadgePropsColor[];
|
||||
|
||||
const buttonColors = [
|
||||
'black',
|
||||
'fill',
|
||||
'gray',
|
||||
'outline',
|
||||
'outline-black',
|
||||
'outline-white',
|
||||
'underline',
|
||||
'underline-hover-red',
|
||||
'white',
|
||||
] as ButtonStyle[];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -113,6 +140,10 @@ onMounted(() => {
|
||||
<h3 class="text-lg font-semibold font-mono">ModalsCe</h3>
|
||||
<ModalsCe />
|
||||
<hr class="border-black dark:border-white" />
|
||||
<h3 class="text-lg font-semibold font-mono">WelcomeModalCe</h3>
|
||||
<!-- <WelcomeModalCe :server="serverState ?? undefined" /> -->
|
||||
Uncomment the WelcomeModalCe component to test it.
|
||||
<hr class="border-black dark:border-white" />
|
||||
<h3 class="text-lg font-semibold font-mono">Test Callback Builder</h3>
|
||||
<div class="flex flex-col justify-end gap-8px">
|
||||
<p>
|
||||
@@ -133,14 +164,14 @@ onMounted(() => {
|
||||
<div class="bg-background">
|
||||
<hr class="border-black dark:border-white" />
|
||||
<h2 class="text-xl font-semibold font-mono">Legacy Badge Components</h2>
|
||||
<template v-for="color in ['black', 'white', 'red', 'yellow', 'green', 'blue', 'indigo', 'purple', 'pink', 'orange', 'transparent', 'current', 'gray', 'custom']" :key="color">
|
||||
<UiBadge size="14px" :icon="ExclamationTriangleIcon" :color="color as UiBadgePropsColor">{{ color }}</UiBadge>
|
||||
<template v-for="color in badgeColors" :key="color">
|
||||
<UiBadge size="14px" :icon="ExclamationTriangleIcon" :color="color">{{ color }}</UiBadge>
|
||||
</template>
|
||||
</div>
|
||||
<div class="bg-background">
|
||||
<hr class="border-black dark:border-white" />
|
||||
<h2 class="text-xl font-semibold font-mono">Legacy Button Components</h2>
|
||||
<template v-for="color in ['black', 'fill', 'gray', 'outline', 'outline-black', 'outline-white', 'underline', 'underline-hover-red', 'white',]" :key="color">
|
||||
<template v-for="color in buttonColors" :key="color">
|
||||
<BrandButton type="button" size="14px" :icon="ExclamationTriangleIcon" :btn-style="color as ButtonStyle">{{ color }}</BrandButton>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
71
web/store/activationCode.ts
Normal file
71
web/store/activationCode.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { defineStore, createPinia, setActivePinia } from 'pinia';
|
||||
import { ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY } from '~/consts';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useCallbackActionsStore } from '~/store/callbackActions';
|
||||
|
||||
setActivePinia(createPinia()); /** required in web component context */
|
||||
|
||||
export interface ActivationCodeData {
|
||||
background?: string;
|
||||
caseIcon?: string;
|
||||
code: string;
|
||||
comment?: string;
|
||||
header?: string;
|
||||
headermetacolor?: string;
|
||||
partnerLogo?: boolean;
|
||||
partnerName?: string;
|
||||
partnerUrl?: string;
|
||||
showBannerGradient?: 'yes';
|
||||
sysModel?: string;
|
||||
theme?: 'azure' | 'black' | 'gray' | 'white';
|
||||
}
|
||||
|
||||
export const useActivationCodeStore = defineStore('activationCode', () => {
|
||||
const data = ref<ActivationCodeData | null>(null);
|
||||
|
||||
const setData = (newData: ActivationCodeData) => {
|
||||
console.debug('[useActivationCodeStore] setData', newData);
|
||||
data.value = newData;
|
||||
};
|
||||
|
||||
const code = computed<string | null>(() => data.value?.code || null);
|
||||
const partnerName = computed<string | null>(() => data.value?.partnerName || null);
|
||||
const partnerUrl = computed<string | null>(() => data.value?.partnerUrl || null);
|
||||
const partnerLogo = computed<string | null>(() => data.value?.partnerLogo ? `/webGui/images/partner-logo.svg` : null);
|
||||
|
||||
const activationModalHidden = ref<boolean>(sessionStorage.getItem(ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY) === 'true');
|
||||
const setActivationModalHidden = (value: boolean) => activationModalHidden.value = value;
|
||||
watch(activationModalHidden, (newVal) => {
|
||||
return newVal ? sessionStorage.setItem(ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY, 'true') : sessionStorage.removeItem(ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY);
|
||||
});
|
||||
/**
|
||||
* Should only see this if
|
||||
* 1. fresh server install where no keyfile has been present before
|
||||
* 2. there's not callback data
|
||||
* 3. we're not on the registration page
|
||||
* 4. it's not been manually hidden
|
||||
*/
|
||||
const showActivationModal = computed<boolean>(() => {
|
||||
if (!data.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { callbackData } = storeToRefs(useCallbackActionsStore());
|
||||
const { state } = storeToRefs(useServerStore());
|
||||
|
||||
const isFreshInstall = state.value === 'ENOKEYFILE';
|
||||
const noCallbackData = !callbackData.value;
|
||||
|
||||
return isFreshInstall && noCallbackData && !activationModalHidden.value;
|
||||
});
|
||||
|
||||
return {
|
||||
code,
|
||||
partnerName,
|
||||
partnerUrl,
|
||||
partnerLogo,
|
||||
showActivationModal,
|
||||
setData,
|
||||
setActivationModalHidden,
|
||||
};
|
||||
});
|
||||
@@ -27,9 +27,10 @@ export type DowngradeOs = 'downgradeOs';
|
||||
export type Manage = 'manage';
|
||||
export type MyKeys = 'myKeys';
|
||||
export type LinkKey = 'linkKey';
|
||||
export type Activate = 'activate';
|
||||
export type AccountActionTypes = Troubleshoot | SignIn | SignOut | OemSignOut | Manage | MyKeys | LinkKey;
|
||||
export type AccountKeyActionTypes = Recover | Replace | TrialExtend | TrialStart | UpdateOs | DowngradeOs;
|
||||
export type PurchaseActionTypes = Purchase | Redeem | Renew | Upgrade;
|
||||
export type PurchaseActionTypes = Purchase | Redeem | Renew | Upgrade | Activate;
|
||||
|
||||
export type ServerActionTypes = AccountActionTypes | AccountKeyActionTypes | PurchaseActionTypes;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { defineStore, createPinia, setActivePinia } from 'pinia';
|
||||
import { useServerStore } from './server';
|
||||
import { useActivationCodeStore } from './activationCode';
|
||||
/**
|
||||
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
|
||||
* @see https://github.com/vuejs/pinia/discussions/1085
|
||||
@@ -8,20 +9,24 @@ import { useServerStore } from './server';
|
||||
setActivePinia(createPinia());
|
||||
|
||||
export const useDropdownStore = defineStore('dropdown', () => {
|
||||
const serverStore = useServerStore();
|
||||
|
||||
const dropdownVisible = ref<boolean>(false);
|
||||
|
||||
const dropdownHide = () => { dropdownVisible.value = false; };
|
||||
const dropdownShow = () => { dropdownVisible.value = true; };
|
||||
const dropdownToggle = useToggle(dropdownVisible);
|
||||
|
||||
|
||||
/**
|
||||
* Automatically open the user dropdown on first page load when ENOKEYFILE aka a new server
|
||||
*/
|
||||
const serverStateEnokeyfile = computed(() => serverStore.state === 'ENOKEYFILE');
|
||||
watch(serverStateEnokeyfile, (newVal) => {
|
||||
const autoOpenSessionStorage = `unraid_${serverStore.guid.slice(-12) ?? 'NO_GUID'}_ENOKEYFILE`;
|
||||
const serverStore = useServerStore();
|
||||
const activationCodeStore = useActivationCodeStore();
|
||||
const { guid, state } = storeToRefs(serverStore);
|
||||
const { showActivationModal } = storeToRefs(activationCodeStore);
|
||||
const autoShowDropdown = computed(() => state.value === 'ENOKEYFILE' && !showActivationModal.value);
|
||||
|
||||
watch(autoShowDropdown, (newVal) => {
|
||||
const autoOpenSessionStorage = `unraid_${guid.value.slice(-12) ?? 'NO_GUID'}_ENOKEYFILE`;
|
||||
if (newVal && !sessionStorage.getItem(autoOpenSessionStorage)) {
|
||||
sessionStorage.setItem(autoOpenSessionStorage, 'true');
|
||||
dropdownShow();
|
||||
|
||||
@@ -14,6 +14,18 @@ export const usePurchaseStore = defineStore('purchase', () => {
|
||||
const callbackStore = useCallbackStore();
|
||||
const serverStore = useServerStore();
|
||||
|
||||
const activate = () => {
|
||||
callbackStore.send(
|
||||
PURCHASE_CALLBACK.toString(),
|
||||
[{
|
||||
server: {
|
||||
...serverStore.serverPurchasePayload,
|
||||
},
|
||||
type: 'activate',
|
||||
}],
|
||||
serverStore.inIframe ? 'newTab' : undefined,
|
||||
);
|
||||
};
|
||||
const redeem = () => {
|
||||
callbackStore.send(
|
||||
PURCHASE_CALLBACK.toString(),
|
||||
@@ -64,6 +76,7 @@ export const usePurchaseStore = defineStore('purchase', () => {
|
||||
};
|
||||
|
||||
return {
|
||||
activate,
|
||||
redeem,
|
||||
purchase,
|
||||
upgrade,
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
WEBGUI_TOOLS_UPDATE,
|
||||
} from "~/helpers/urls";
|
||||
import { useAccountStore } from "~/store/account";
|
||||
import { useActivationCodeStore } from '~/store/activationCode';
|
||||
import { useErrorsStore, type Error } from "~/store/errors";
|
||||
import { usePurchaseStore } from "~/store/purchase";
|
||||
import { useThemeStore, type Theme } from "~/store/theme";
|
||||
@@ -258,7 +259,7 @@ export const useServerStore = defineStore("server", () => {
|
||||
keyTypeForPurchase = "Unleashed";
|
||||
break;
|
||||
}
|
||||
const server = {
|
||||
const server: ServerPurchaseCallbackSendPayload = {
|
||||
apiVersion: apiVersion.value,
|
||||
connectPluginVersion: connectPluginVersion.value,
|
||||
deviceCount: deviceCount.value,
|
||||
@@ -276,8 +277,19 @@ export const useServerStore = defineStore("server", () => {
|
||||
state: state.value,
|
||||
site: site.value,
|
||||
};
|
||||
|
||||
const { code, partnerName } = storeToRefs(useActivationCodeStore());
|
||||
if (code.value) {
|
||||
server['activationCodeData'] = {
|
||||
code: code.value,
|
||||
};
|
||||
if (partnerName.value) {
|
||||
server['activationCodeData']['partnerName'] = partnerName.value;
|
||||
}
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
const serverAccountPayload = computed(
|
||||
@@ -410,15 +422,16 @@ export const useServerStore = defineStore("server", () => {
|
||||
};
|
||||
});
|
||||
const redeemAction = computed((): ServerStateDataAction => {
|
||||
const { code } = storeToRefs(useActivationCodeStore());
|
||||
return {
|
||||
click: () => {
|
||||
purchaseStore.redeem();
|
||||
code.value ? purchaseStore.activate() : purchaseStore.redeem();
|
||||
},
|
||||
disabled: serverActionsDisable.value.disable,
|
||||
external: true,
|
||||
icon: KeyIcon,
|
||||
name: "redeem",
|
||||
text: "Redeem Activation Code",
|
||||
name: code.value ? "activate" : "redeem",
|
||||
text: code.value ? "Activate Now" : "Redeem Activation Code",
|
||||
title: serverActionsDisable.value.title,
|
||||
};
|
||||
});
|
||||
@@ -1195,6 +1208,11 @@ export const useServerStore = defineStore("server", () => {
|
||||
if (typeof data?.regTo !== "undefined") {
|
||||
regTo.value = data.regTo;
|
||||
}
|
||||
|
||||
if (typeof data.activationCodeData !== "undefined") {
|
||||
const activationCodeStore = useActivationCodeStore();
|
||||
activationCodeStore.setData(data.activationCodeData);
|
||||
}
|
||||
};
|
||||
|
||||
const setUpdateOsResponse = (response: ServerUpdateOsResponse) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Config, PartialCloudFragment } from '~/composables/gql/graphql';
|
||||
import type { Theme } from '~/store/theme';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
import type { ActivationCodeData } from '~/store/activationCode';
|
||||
|
||||
export type ServerState = 'BASIC'
|
||||
| 'PLUS'
|
||||
@@ -66,6 +67,7 @@ export interface ServerStateArray {
|
||||
}
|
||||
|
||||
export interface Server {
|
||||
activationCodeData?: ActivationCodeData;
|
||||
apiKey?: string;
|
||||
apiVersion?: string;
|
||||
array?: ServerStateArray;
|
||||
@@ -151,6 +153,7 @@ export interface ServerAccountCallbackSendPayload {
|
||||
export type ServerKeyTypeForPurchase = 'Basic' | 'Plus' | 'Pro' | 'Starter' | 'Trial' | 'Unleashed';
|
||||
|
||||
export interface ServerPurchaseCallbackSendPayload {
|
||||
activationCodeData?: ActivationCodeData;
|
||||
apiVersion?: string;
|
||||
connectPluginVersion?: string;
|
||||
deviceCount: number;
|
||||
@@ -163,12 +166,13 @@ export interface ServerPurchaseCallbackSendPayload {
|
||||
osVersionBranch?: ServerOsVersionBranch;
|
||||
registered: boolean;
|
||||
regExp?: number;
|
||||
regTy?: string;
|
||||
regUpdatesExpired?: boolean;
|
||||
state: ServerState;
|
||||
site: string;
|
||||
}
|
||||
|
||||
export type ServerStateDataKeyActions = 'purchase' | 'redeem' | 'upgrade' | 'recover' | 'renew' | 'replace' | 'trialExtend' | 'trialStart' | 'updateOs';
|
||||
export type ServerStateDataKeyActions = 'activate' | 'purchase' | 'redeem' | 'upgrade' | 'recover' | 'renew' | 'replace' | 'trialExtend' | 'trialStart' | 'updateOs';
|
||||
|
||||
export type ServerStateDataAccountActions = 'signIn' | 'signOut' | 'troubleshoot';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user