Compare commits

..

4 Commits

6 changed files with 309 additions and 139 deletions
+6 -4
View File
@@ -155,10 +155,12 @@ select:focus {
@media (max-width: 900px) {
.container {
width: calc(100% - 18px);
padding: 14px;
margin: 10px auto;
border-radius: 10px;
width: 100%;
max-width: 100%;
padding: 12px 12px 18px;
margin: 0;
border-radius: 0;
box-sizing: border-box;
}
}
+194 -5
View File
@@ -136,6 +136,10 @@
top: 0;
z-index: 1900;
}
.navbar .container-fluid {
gap: 10px;
}
.navbar-brand {
font-weight: bold;
@@ -538,22 +542,200 @@
}
@media (max-width: 768px) {
.module-selector-bar {
padding: 8px 12px;
.navbar {
border-bottom-left-radius: 14px;
border-bottom-right-radius: 14px;
}
.navbar .container-fluid {
align-items: flex-start;
flex-wrap: wrap;
padding-top: 0.2rem;
padding-bottom: 0.35rem;
}
.navbar-brand {
order: 1;
flex: 1 1 auto;
min-width: 0;
font-size: 1.08rem;
line-height: 1.1;
display: flex;
align-items: center;
gap: 6px;
padding-top: 0.2rem;
padding-bottom: 0.2rem;
}
.navbar-toggler {
order: 2;
margin-left: auto;
flex: 0 0 auto;
align-self: flex-start;
}
.navbar-collapse {
order: 4;
width: 100%;
margin-top: 8px;
padding: 10px 10px 6px;
border-radius: 14px;
background: rgba(15, 23, 42, 0.18);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.navbar-collapse.show,
.navbar-collapse.collapsing {
display: block;
}
.navbar-nav {
width: 100%;
align-items: stretch;
gap: 8px;
padding-top: 2px;
}
.navbar-nav .nav-item {
width: 100%;
}
.navbar-nav .nav-link {
width: 100%;
justify-content: flex-start;
min-height: 46px;
padding: 0.8rem 0.95rem;
border-radius: 12px;
background: rgba(255, 255, 255, 0.08);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.navbar-nav .nav-link.nav-active,
.navbar-nav .nav-link.quick-link-pill,
.navbar-nav .nav-link.nav-priority-link {
background: rgba(255, 255, 255, 0.16);
}
.navbar-nav .nav-item.dropdown {
width: 100%;
}
.navbar-nav .nav-item.dropdown .nav-link.dropdown-toggle {
width: 100%;
justify-content: space-between;
}
.navbar-nav .dropdown-menu {
width: 100%;
margin-left: 0;
margin-top: 6px;
border-radius: 12px;
}
.module-selector-bar {
padding: 8px 10px;
gap: 8px;
flex-wrap: wrap;
}
.module-selector-bar .module-label {
display: none;
}
.module-selector-bar .module-tabs {
width: 100%;
gap: 8px;
}
.module-selector-bar .module-tab {
padding: 5px 12px;
font-size: 0.9rem;
width: 100%;
text-align: center;
padding: 8px 12px;
font-size: 0.92rem;
}
.module-separator {
height: 18px;
display: none;
}
.function-search-wrap {
order: 3;
width: 100%;
margin: 0;
}
.function-search-form {
width: 100%;
flex-wrap: nowrap;
gap: 8px;
}
.function-search-input {
flex: 1 1 auto;
min-width: 0;
min-height: 44px;
}
.function-search-btn {
flex: 0 0 auto;
min-height: 44px;
min-width: 72px;
white-space: nowrap;
}
.navbar-text {
order: 5;
width: 100%;
margin: 0 !important;
padding: 8px 10px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.08);
text-align: center;
}
.user-menu-wrap {
order: 6;
width: 100%;
margin-right: 0 !important;
}
.user-menu-btn {
width: 100%;
min-height: 46px;
border-radius: 12px;
justify-content: center;
}
.dropdown-menu-end {
width: 100%;
}
.navbar-mobile-arranged .navbar-collapse {
display: block;
}
.navbar-mobile-arranged .navbar-nav {
flex-direction: column;
}
body.nav-mobile-tight .function-search-form {
flex-direction: column;
}
body.nav-mobile-tight .function-search-input,
body.nav-mobile-tight .function-search-btn,
body.nav-mobile-tight .user-menu-btn,
body.nav-mobile-tight .module-selector-bar .module-tab {
width: 100%;
}
body.nav-mobile-tight .module-selector-bar .module-tabs {
flex-direction: column;
}
body.nav-mobile-tight .navbar-text {
font-size: 0.88rem;
}
}
</style>
@@ -1286,6 +1468,13 @@
if (!navRoot || !navContainer) return;
const width = navContainer.clientWidth || window.innerWidth;
navRoot.classList.remove('nav-compact', 'nav-ultra-compact');
navRoot.classList.toggle('nav-mobile-arranged', window.innerWidth < 992);
navRoot.classList.toggle('nav-mobile-tight', window.innerWidth < 520);
if (document.body) {
document.body.classList.toggle('nav-mobile-arranged', window.innerWidth < 992);
document.body.classList.toggle('nav-mobile-tight', window.innerWidth < 520);
}
if (window.innerWidth < 992) {
return;
+46 -11
View File
@@ -774,8 +774,18 @@
return;
}
const distanceToEnd = itemsContainer.scrollWidth - (itemsContainer.scrollLeft + itemsContainer.clientWidth);
const prefetchThreshold = Math.max(260, itemsContainer.clientWidth * 1.4);
const styles = window.getComputedStyle(itemsContainer);
const isMobileLayout =
window.matchMedia('(max-width: 768px)').matches ||
styles.display === 'grid' ||
styles.flexDirection === 'column';
const distanceToEnd = isMobileLayout
? itemsContainer.scrollHeight - (itemsContainer.scrollTop + itemsContainer.clientHeight)
: itemsContainer.scrollWidth - (itemsContainer.scrollLeft + itemsContainer.clientWidth);
const prefetchThreshold = isMobileLayout
? Math.max(220, itemsContainer.clientHeight * 0.9)
: Math.max(260, itemsContainer.clientWidth * 1.4);
if (distanceToEnd > prefetchThreshold) {
return;
}
@@ -1142,6 +1152,12 @@
const itemsContainer = ensureMainItemsSentinel();
if (!itemsContainer) return;
const styles = window.getComputedStyle(itemsContainer);
const isMobileLayout =
window.matchMedia('(max-width: 768px)').matches ||
styles.display === 'grid' ||
styles.flexDirection === 'column';
if (mainItemsObserver) {
mainItemsObserver.disconnect();
mainItemsObserver = null;
@@ -1172,7 +1188,7 @@
}, {
root: itemsContainer,
threshold: 0.15,
rootMargin: '0px 900px 0px 0px'
rootMargin: isMobileLayout ? '0px 0px 900px 0px' : '0px 900px 0px 0px'
});
mainItemsObserver.observe(mainItemsSentinel);
@@ -2942,6 +2958,22 @@
animation: items-loader-slide 1.05s ease-in-out infinite;
}
@media (max-width: 768px) {
.items-loading-indicator {
width: 100%;
min-width: 0;
max-width: none;
min-height: 120px;
height: auto;
padding: 14px 10px;
scroll-snap-align: none;
}
.items-loading-indicator .loading-track {
width: min(240px, 82%);
}
}
@keyframes items-loader-slide {
0% { transform: translateX(-100%); }
100% { transform: translateX(260%); }
@@ -3342,9 +3374,12 @@
/* Mobile-responsive styles for user interface */
.container {
width: 95% !important;
margin: 10px auto !important;
padding: 15px !important;
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 12px 12px 18px !important;
border-radius: 0 !important;
box-sizing: border-box !important;
}
h1, h2 {
@@ -3414,7 +3449,7 @@
display: grid !important;
grid-template-columns: 1fr !important;
gap: 15px !important;
padding: 10px 0 !important;
padding: 10px 0 0 !important;
overflow-x: visible !important;
}
@@ -3431,12 +3466,12 @@
/* Modal improvements for mobile */
.modal-content {
width: 95% !important;
max-width: 500px !important;
margin: 20px auto !important;
width: calc(100vw - 16px) !important;
max-width: none !important;
margin: 8px auto !important;
max-height: 85vh !important;
overflow-y: auto !important;
padding: 20px !important;
padding: 16px !important;
}
/* Button improvements */
+61 -21
View File
@@ -369,6 +369,22 @@
animation: items-loader-slide 1.05s ease-in-out infinite;
}
@media (max-width: 768px) {
.items-loading-indicator {
width: 100%;
min-width: 0;
max-width: none;
min-height: 120px;
height: auto;
padding: 14px 10px;
scroll-snap-align: none;
}
.items-loading-indicator .loading-track {
width: min(240px, 82%);
}
}
@keyframes items-loader-slide {
0% { transform: translateX(-100%); }
100% { transform: translateX(260%); }
@@ -1221,9 +1237,11 @@
/* Mobile-responsive styles for admin interface */
.admin-content-container {
width: 95% !important;
margin: 10px auto !important;
padding: 15px !important;
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 12px 12px 18px !important;
box-sizing: border-box !important;
overflow-x: hidden !important;
}
@@ -1347,11 +1365,12 @@
/* Modal improvements for admin */
.modal-content {
width: 95% !important;
max-width: 500px !important;
margin: 20px auto !important;
width: calc(100vw - 16px) !important;
max-width: none !important;
margin: 8px auto !important;
max-height: 85vh !important;
overflow-y: auto !important;
padding: 16px !important;
}
/* Admin card improvements */
@@ -1510,9 +1529,11 @@
/* Mobile-responsive styles for admin interface */
@media screen and (max-width: 768px) {
.admin-content-container {
width: 95% !important;
margin: 10px auto !important;
padding: 15px !important;
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 12px 12px 18px !important;
box-sizing: border-box !important;
}
h1, h2 {
@@ -1599,12 +1620,12 @@
/* Modal improvements for admin */
.modal-content {
width: 95% !important;
max-width: 500px !important;
margin: 20px auto !important;
width: calc(100vw - 16px) !important;
max-width: none !important;
margin: 8px auto !important;
max-height: 85vh !important;
overflow-y: auto !important;
padding: 20px !important;
padding: 16px !important;
}
/* Button improvements */
@@ -1792,9 +1813,11 @@
}
.admin-content-container {
width: 95%;
margin: 10px auto;
padding: 15px;
width: 100%;
max-width: 100%;
margin: 0;
padding: 12px 12px 18px;
box-sizing: border-box;
overflow-x: hidden;
}
@@ -1808,12 +1831,13 @@
/* Better modal sizing for mobile */
.modal-content {
width: 95% !important;
width: calc(100vw - 16px) !important;
max-width: none !important;
margin: 10px auto !important;
margin: 8px auto !important;
max-height: 90vh !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important;
padding: 16px !important;
}
}
@@ -3366,8 +3390,18 @@ document.addEventListener('DOMContentLoaded', ()=>{
return;
}
const distanceToEnd = itemsContainer.scrollWidth - (itemsContainer.scrollLeft + itemsContainer.clientWidth);
const prefetchThreshold = Math.max(260, itemsContainer.clientWidth * 1.4);
const styles = window.getComputedStyle(itemsContainer);
const isMobileLayout =
window.matchMedia('(max-width: 768px)').matches ||
styles.display === 'grid' ||
styles.flexDirection === 'column';
const distanceToEnd = isMobileLayout
? itemsContainer.scrollHeight - (itemsContainer.scrollTop + itemsContainer.clientHeight)
: itemsContainer.scrollWidth - (itemsContainer.scrollLeft + itemsContainer.clientWidth);
const prefetchThreshold = isMobileLayout
? Math.max(220, itemsContainer.clientHeight * 0.9)
: Math.max(260, itemsContainer.clientWidth * 1.4);
if (distanceToEnd > prefetchThreshold) {
return;
}
@@ -3729,6 +3763,12 @@ document.addEventListener('DOMContentLoaded', ()=>{
const itemsContainer = ensureMainAdminItemsSentinel();
if (!itemsContainer) return;
const styles = window.getComputedStyle(itemsContainer);
const isMobileLayout =
window.matchMedia('(max-width: 768px)').matches ||
styles.display === 'grid' ||
styles.flexDirection === 'column';
if (mainAdminItemsObserver) {
mainAdminItemsObserver.disconnect();
mainAdminItemsObserver = null;
@@ -3759,7 +3799,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
}, {
root: itemsContainer,
threshold: 0.15,
rootMargin: '0px 900px 0px 0px'
rootMargin: isMobileLayout ? '0px 0px 900px 0px' : '0px 900px 0px 0px'
});
mainAdminItemsObserver.observe(mainAdminItemsSentinel);
+2 -24
View File
@@ -191,29 +191,7 @@ setup_scheduled_jobs() {
fi
local update_line backup_line
local slot_seed slot_hash slot_bucket slot_hour slot_minute
local update_window_minutes update_window_hours
update_window_minutes="${INVENTAR_UPDATE_WINDOW_MINUTES:-120}"
if ! [[ "$update_window_minutes" =~ ^[0-9]+$ ]]; then
update_window_minutes="120"
fi
if [ "$update_window_minutes" -lt 15 ]; then
update_window_minutes="15"
fi
update_window_hours=$(( (update_window_minutes + 59) / 60 ))
if [ "$update_window_hours" -lt 1 ]; then
update_window_hours=1
fi
slot_seed="$(cat /etc/machine-id 2>/dev/null || hostname)|${INVENTAR_INSTANCE_ID:-${HOSTNAME:-unknown}}|$SCRIPT_DIR|nightly-update"
slot_hash="$(printf '%s' "$slot_seed" | sha256sum | awk '{print $1}')"
slot_bucket=$((16#${slot_hash:0:8} % update_window_minutes))
slot_hour=$((3 + (slot_bucket / 60)))
slot_minute=$((slot_bucket % 60))
update_line="$slot_minute $slot_hour * * * cd $SCRIPT_DIR && INVENTAR_UPDATE_MODE=auto ./update.sh >> $SCRIPT_DIR/logs/update.log 2>&1"
update_line="0 3 * * * cd $SCRIPT_DIR && ./update.sh >> $SCRIPT_DIR/logs/update.log 2>&1"
backup_line="30 2 * * * cd $SCRIPT_DIR && ./backup.sh --mode auto >> $SCRIPT_DIR/logs/backup.log 2>&1"
local existing_cron
@@ -234,7 +212,7 @@ setup_scheduled_jobs() {
fi
echo "Nightly backup scheduled at 02:30"
printf 'Nightly auto-update scheduled at %02d:%02d (deterministic instance slot, %s-minute window)\n' "$slot_hour" "$slot_minute" "$update_window_minutes"
echo "Nightly auto-update scheduled at 03:00"
}
ensure_tls_certificates() {
-74
View File
@@ -15,9 +15,6 @@ APP_IMAGE_ASSET_PREFIX="inventarsystem-image-"
ENV_FILE="$PROJECT_DIR/.docker-build.env"
APP_IMAGE_REPO="ghcr.io/aiirondev/legendary-octo-garbanzo"
DIST_DIR="$PROJECT_DIR/dist"
LOCK_FILE="$PROJECT_DIR/.update.lock"
DEFAULT_UPDATE_BUFFER_MINUTES="${INVENTAR_UPDATE_BUFFER_MINUTES:-90}"
mkdir -p "$LOG_DIR"
chmod 777 "$LOG_DIR" 2>/dev/null || true
@@ -26,74 +23,6 @@ log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
safe_int() {
local raw="$1"
if [[ "$raw" =~ ^[0-9]+$ ]]; then
echo "$raw"
else
echo "0"
fi
}
acquire_update_lock() {
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
log_message "Another update process is already running. Exiting."
exit 0
fi
}
compute_update_buffer_seconds() {
if [ "${INVENTAR_SKIP_UPDATE_BUFFER:-0}" = "1" ]; then
echo "0"
return 0
fi
# Manual interactive runs should not wait by default.
if [ -t 1 ] && [ "${INVENTAR_FORCE_UPDATE_BUFFER:-0}" != "1" ]; then
echo "0"
return 0
fi
local explicit_seconds
explicit_seconds="$(safe_int "${INVENTAR_UPDATE_BUFFER_SECONDS:-0}")"
if [ "$explicit_seconds" -gt 0 ]; then
echo "$explicit_seconds"
return 0
fi
local window_minutes window_seconds
window_minutes="$(safe_int "$DEFAULT_UPDATE_BUFFER_MINUTES")"
if [ "$window_minutes" -le 0 ]; then
echo "0"
return 0
fi
window_seconds=$((window_minutes * 60))
local machine_id instance_id seed hash_hex hash_dec
machine_id="$(cat /etc/machine-id 2>/dev/null || hostname)"
instance_id="${INVENTAR_INSTANCE_ID:-${HOSTNAME:-unknown}}"
seed="$machine_id|$instance_id|$PROJECT_DIR|update-buffer"
hash_hex="$(printf '%s' "$seed" | sha256sum | awk '{print $1}')"
hash_dec=$((16#${hash_hex:0:8}))
echo $((hash_dec % window_seconds))
}
apply_update_buffer_if_needed() {
local delay_seconds
delay_seconds="$(compute_update_buffer_seconds)"
if [ "$delay_seconds" -le 0 ]; then
log_message "Update buffer: no delay applied"
return 0
fi
log_message "Update buffer active: delaying start by ${delay_seconds}s to reduce cross-instance peak load"
sleep "$delay_seconds"
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
log_message "ERROR: Required command not found: $1"
@@ -466,9 +395,6 @@ verify_stack_health() {
}
main() {
acquire_update_lock
apply_update_buffer_if_needed
ensure_runtime_dependencies
ensure_tls_certificates
ensure_nginx_config_mount_source