mirror of
https://github.com/unraid/api.git
synced 2026-01-02 14:40:01 -06:00
Compare commits
2 Commits
fix-array-
...
4.9.4-buil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce16b90c3e | ||
|
|
8f07eab623 |
4
.github/workflows/deploy-storybook.yml
vendored
4
.github/workflows/deploy-storybook.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.17.0'
|
||||
node-version: '20.19.3'
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Cache APT Packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.4.3
|
||||
with:
|
||||
packages: bash procps python3 libvirt-dev jq zstd git build-essential libvirt-daemon-system
|
||||
version: 1.0
|
||||
|
||||
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Cache APT Packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.4.3
|
||||
with:
|
||||
packages: bash procps python3 libvirt-dev jq zstd git build-essential libvirt-daemon-system
|
||||
version: 1.0
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Cache APT Packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.4.3
|
||||
with:
|
||||
packages: bash procps python3 libvirt-dev jq zstd git build-essential
|
||||
version: 1.0
|
||||
@@ -267,7 +267,7 @@ jobs:
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Cache APT Packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.4.3
|
||||
with:
|
||||
packages: bash procps python3 libvirt-dev jq zstd git build-essential
|
||||
version: 1.0
|
||||
|
||||
2
.github/workflows/test-libvirt.yml
vendored
2
.github/workflows/test-libvirt.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
python-version: "3.13.5"
|
||||
|
||||
- name: Cache APT Packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.4.3
|
||||
with:
|
||||
packages: libvirt-dev
|
||||
version: 1.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"4.10.0"}
|
||||
{".":"4.9.4"}
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [4.10.0](https://github.com/unraid/api/compare/v4.9.5...v4.10.0) (2025-07-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* trial extension allowed within 5 days of expiration ([#1490](https://github.com/unraid/api/issues/1490)) ([f34a33b](https://github.com/unraid/api/commit/f34a33bc9f1a7e135d453d9d31888789bfc3f878))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* delay `nginx:reload` file mod effect by 10 seconds ([#1512](https://github.com/unraid/api/issues/1512)) ([af33e99](https://github.com/unraid/api/commit/af33e999a0480a77e3e6b2aa833b17b38b835656))
|
||||
* **deps:** update all non-major dependencies ([#1489](https://github.com/unraid/api/issues/1489)) ([53b05eb](https://github.com/unraid/api/commit/53b05ebe5e2050cb0916fcd65e8d41370aee0624))
|
||||
* ensure no crash if emhttp state configs are missing ([#1514](https://github.com/unraid/api/issues/1514)) ([1a7d35d](https://github.com/unraid/api/commit/1a7d35d3f6972fd8aff58c17b2b0fb79725e660e))
|
||||
* **my.servers:** improve DNS resolution robustness for backup server ([#1518](https://github.com/unraid/api/issues/1518)) ([eecd9b1](https://github.com/unraid/api/commit/eecd9b1017a63651d1dc782feaa224111cdee8b6))
|
||||
* over-eager cloud query from web components ([#1506](https://github.com/unraid/api/issues/1506)) ([074370c](https://github.com/unraid/api/commit/074370c42cdecc4dbc58193ff518aa25735c56b3))
|
||||
* replace myservers.cfg reads in UpdateFlashBackup.php ([#1517](https://github.com/unraid/api/issues/1517)) ([441e180](https://github.com/unraid/api/commit/441e1805c108a6c1cd35ee093246b975a03f8474))
|
||||
* rm short-circuit in `rc.unraid-api` if plugin config dir is absent ([#1515](https://github.com/unraid/api/issues/1515)) ([29dcb7d](https://github.com/unraid/api/commit/29dcb7d0f088937cefc5158055f48680e86e5c36))
|
||||
|
||||
## [4.9.5](https://github.com/unraid/api/compare/v4.9.4...v4.9.5) (2025-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **connect:** rm eager restart on `ERROR_RETYING` connection status ([#1502](https://github.com/unraid/api/issues/1502)) ([dd759d9](https://github.com/unraid/api/commit/dd759d9f0f841b296f8083bc67c6cd3f7a69aa5b))
|
||||
|
||||
## [4.9.4](https://github.com/unraid/api/compare/v4.9.3...v4.9.4) (2025-07-09)
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
{
|
||||
"version": "4.9.5",
|
||||
"version": "4.8.0",
|
||||
"extraOrigins": [
|
||||
"https://google.com",
|
||||
"https://test.com"
|
||||
],
|
||||
"sandbox": true,
|
||||
"ssoSubIds": [],
|
||||
"plugins": [
|
||||
"unraid-api-plugin-connect"
|
||||
]
|
||||
"plugins": ["unraid-api-plugin-connect"]
|
||||
}
|
||||
@@ -247,6 +247,347 @@ A field whose value conforms to the standard URL format as specified in RFC3986:
|
||||
"""
|
||||
scalar URL
|
||||
|
||||
type DiskPartition {
|
||||
"""The name of the partition"""
|
||||
name: String!
|
||||
|
||||
"""The filesystem type of the partition"""
|
||||
fsType: DiskFsType!
|
||||
|
||||
"""The size of the partition in bytes"""
|
||||
size: Float!
|
||||
}
|
||||
|
||||
"""The type of filesystem on the disk partition"""
|
||||
enum DiskFsType {
|
||||
XFS
|
||||
BTRFS
|
||||
VFAT
|
||||
ZFS
|
||||
EXT4
|
||||
NTFS
|
||||
}
|
||||
|
||||
type Disk implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""The device path of the disk (e.g. /dev/sdb)"""
|
||||
device: String!
|
||||
|
||||
"""The type of disk (e.g. SSD, HDD)"""
|
||||
type: String!
|
||||
|
||||
"""The model name of the disk"""
|
||||
name: String!
|
||||
|
||||
"""The manufacturer of the disk"""
|
||||
vendor: String!
|
||||
|
||||
"""The total size of the disk in bytes"""
|
||||
size: Float!
|
||||
|
||||
"""The number of bytes per sector"""
|
||||
bytesPerSector: Float!
|
||||
|
||||
"""The total number of cylinders on the disk"""
|
||||
totalCylinders: Float!
|
||||
|
||||
"""The total number of heads on the disk"""
|
||||
totalHeads: Float!
|
||||
|
||||
"""The total number of sectors on the disk"""
|
||||
totalSectors: Float!
|
||||
|
||||
"""The total number of tracks on the disk"""
|
||||
totalTracks: Float!
|
||||
|
||||
"""The number of tracks per cylinder"""
|
||||
tracksPerCylinder: Float!
|
||||
|
||||
"""The number of sectors per track"""
|
||||
sectorsPerTrack: Float!
|
||||
|
||||
"""The firmware revision of the disk"""
|
||||
firmwareRevision: String!
|
||||
|
||||
"""The serial number of the disk"""
|
||||
serialNum: String!
|
||||
|
||||
"""The interface type of the disk"""
|
||||
interfaceType: DiskInterfaceType!
|
||||
|
||||
"""The SMART status of the disk"""
|
||||
smartStatus: DiskSmartStatus!
|
||||
|
||||
"""The current temperature of the disk in Celsius"""
|
||||
temperature: Float
|
||||
|
||||
"""The partitions on the disk"""
|
||||
partitions: [DiskPartition!]!
|
||||
}
|
||||
|
||||
"""The type of interface the disk uses to connect to the system"""
|
||||
enum DiskInterfaceType {
|
||||
SAS
|
||||
SATA
|
||||
USB
|
||||
PCIE
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
"""
|
||||
The SMART (Self-Monitoring, Analysis and Reporting Technology) status of the disk
|
||||
"""
|
||||
enum DiskSmartStatus {
|
||||
OK
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
type KeyFile {
|
||||
location: String
|
||||
contents: String
|
||||
}
|
||||
|
||||
type Registration implements Node {
|
||||
id: PrefixedID!
|
||||
type: registrationType
|
||||
keyFile: KeyFile
|
||||
state: RegistrationState
|
||||
expiration: String
|
||||
updateExpiration: String
|
||||
}
|
||||
|
||||
enum registrationType {
|
||||
BASIC
|
||||
PLUS
|
||||
PRO
|
||||
STARTER
|
||||
UNLEASHED
|
||||
LIFETIME
|
||||
INVALID
|
||||
TRIAL
|
||||
}
|
||||
|
||||
enum RegistrationState {
|
||||
TRIAL
|
||||
BASIC
|
||||
PLUS
|
||||
PRO
|
||||
STARTER
|
||||
UNLEASHED
|
||||
LIFETIME
|
||||
EEXPIRED
|
||||
EGUID
|
||||
EGUID1
|
||||
ETRIAL
|
||||
ENOKEYFILE
|
||||
ENOKEYFILE1
|
||||
ENOKEYFILE2
|
||||
ENOFLASH
|
||||
ENOFLASH1
|
||||
ENOFLASH2
|
||||
ENOFLASH3
|
||||
ENOFLASH4
|
||||
ENOFLASH5
|
||||
ENOFLASH6
|
||||
ENOFLASH7
|
||||
EBLACKLISTED
|
||||
EBLACKLISTED1
|
||||
EBLACKLISTED2
|
||||
ENOCONN
|
||||
}
|
||||
|
||||
type Vars implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Unraid version"""
|
||||
version: String
|
||||
maxArraysz: Int
|
||||
maxCachesz: Int
|
||||
|
||||
"""Machine hostname"""
|
||||
name: String
|
||||
timeZone: String
|
||||
comment: String
|
||||
security: String
|
||||
workgroup: String
|
||||
domain: String
|
||||
domainShort: String
|
||||
hideDotFiles: Boolean
|
||||
localMaster: Boolean
|
||||
enableFruit: String
|
||||
|
||||
"""Should a NTP server be used for time sync?"""
|
||||
useNtp: Boolean
|
||||
|
||||
"""NTP Server 1"""
|
||||
ntpServer1: String
|
||||
|
||||
"""NTP Server 2"""
|
||||
ntpServer2: String
|
||||
|
||||
"""NTP Server 3"""
|
||||
ntpServer3: String
|
||||
|
||||
"""NTP Server 4"""
|
||||
ntpServer4: String
|
||||
domainLogin: String
|
||||
sysModel: String
|
||||
sysArraySlots: Int
|
||||
sysCacheSlots: Int
|
||||
sysFlashSlots: Int
|
||||
useSsl: Boolean
|
||||
|
||||
"""Port for the webui via HTTP"""
|
||||
port: Int
|
||||
|
||||
"""Port for the webui via HTTPS"""
|
||||
portssl: Int
|
||||
localTld: String
|
||||
bindMgt: Boolean
|
||||
|
||||
"""Should telnet be enabled?"""
|
||||
useTelnet: Boolean
|
||||
porttelnet: Int
|
||||
useSsh: Boolean
|
||||
portssh: Int
|
||||
startPage: String
|
||||
startArray: Boolean
|
||||
spindownDelay: String
|
||||
queueDepth: String
|
||||
spinupGroups: Boolean
|
||||
defaultFormat: String
|
||||
defaultFsType: String
|
||||
shutdownTimeout: Int
|
||||
luksKeyfile: String
|
||||
pollAttributes: String
|
||||
pollAttributesDefault: String
|
||||
pollAttributesStatus: String
|
||||
nrRequests: Int
|
||||
nrRequestsDefault: Int
|
||||
nrRequestsStatus: String
|
||||
mdNumStripes: Int
|
||||
mdNumStripesDefault: Int
|
||||
mdNumStripesStatus: String
|
||||
mdSyncWindow: Int
|
||||
mdSyncWindowDefault: Int
|
||||
mdSyncWindowStatus: String
|
||||
mdSyncThresh: Int
|
||||
mdSyncThreshDefault: Int
|
||||
mdSyncThreshStatus: String
|
||||
mdWriteMethod: Int
|
||||
mdWriteMethodDefault: String
|
||||
mdWriteMethodStatus: String
|
||||
shareDisk: String
|
||||
shareUser: String
|
||||
shareUserInclude: String
|
||||
shareUserExclude: String
|
||||
shareSmbEnabled: Boolean
|
||||
shareNfsEnabled: Boolean
|
||||
shareAfpEnabled: Boolean
|
||||
shareInitialOwner: String
|
||||
shareInitialGroup: String
|
||||
shareCacheEnabled: Boolean
|
||||
shareCacheFloor: String
|
||||
shareMoverSchedule: String
|
||||
shareMoverLogging: Boolean
|
||||
fuseRemember: String
|
||||
fuseRememberDefault: String
|
||||
fuseRememberStatus: String
|
||||
fuseDirectio: String
|
||||
fuseDirectioDefault: String
|
||||
fuseDirectioStatus: String
|
||||
shareAvahiEnabled: Boolean
|
||||
shareAvahiSmbName: String
|
||||
shareAvahiSmbModel: String
|
||||
shareAvahiAfpName: String
|
||||
shareAvahiAfpModel: String
|
||||
safeMode: Boolean
|
||||
startMode: String
|
||||
configValid: Boolean
|
||||
configError: ConfigErrorState
|
||||
joinStatus: String
|
||||
deviceCount: Int
|
||||
flashGuid: String
|
||||
flashProduct: String
|
||||
flashVendor: String
|
||||
regCheck: String
|
||||
regFile: String
|
||||
regGuid: String
|
||||
regTy: registrationType
|
||||
regState: RegistrationState
|
||||
|
||||
"""Registration owner"""
|
||||
regTo: String
|
||||
regTm: String
|
||||
regTm2: String
|
||||
regGen: String
|
||||
sbName: String
|
||||
sbVersion: String
|
||||
sbUpdated: String
|
||||
sbEvents: Int
|
||||
sbState: String
|
||||
sbClean: Boolean
|
||||
sbSynced: Int
|
||||
sbSyncErrs: Int
|
||||
sbSynced2: Int
|
||||
sbSyncExit: String
|
||||
sbNumDisks: Int
|
||||
mdColor: String
|
||||
mdNumDisks: Int
|
||||
mdNumDisabled: Int
|
||||
mdNumInvalid: Int
|
||||
mdNumMissing: Int
|
||||
mdNumNew: Int
|
||||
mdNumErased: Int
|
||||
mdResync: Int
|
||||
mdResyncCorr: String
|
||||
mdResyncPos: String
|
||||
mdResyncDb: String
|
||||
mdResyncDt: String
|
||||
mdResyncAction: String
|
||||
mdResyncSize: Int
|
||||
mdState: String
|
||||
mdVersion: String
|
||||
cacheNumDevices: Int
|
||||
cacheSbNumDisks: Int
|
||||
fsState: String
|
||||
|
||||
"""Human friendly string of array events happening"""
|
||||
fsProgress: String
|
||||
|
||||
"""
|
||||
Percentage from 0 - 100 while upgrading a disk or swapping parity drives
|
||||
"""
|
||||
fsCopyPrcnt: Int
|
||||
fsNumMounted: Int
|
||||
fsNumUnmountable: Int
|
||||
fsUnmountableMask: String
|
||||
|
||||
"""Total amount of user shares"""
|
||||
shareCount: Int
|
||||
|
||||
"""Total amount shares with SMB enabled"""
|
||||
shareSmbCount: Int
|
||||
|
||||
"""Total amount shares with NFS enabled"""
|
||||
shareNfsCount: Int
|
||||
|
||||
"""Total amount shares with AFP enabled"""
|
||||
shareAfpCount: Int
|
||||
shareMoverActive: Boolean
|
||||
csrfToken: String
|
||||
}
|
||||
|
||||
"""Possible error states for configuration"""
|
||||
enum ConfigErrorState {
|
||||
UNKNOWN_ERROR
|
||||
INELIGIBLE
|
||||
INVALID
|
||||
NO_KEY_SERVER
|
||||
WITHDRAWN
|
||||
}
|
||||
|
||||
type Permission {
|
||||
resource: Resource!
|
||||
actions: [String!]!
|
||||
@@ -620,102 +961,6 @@ enum ThemeName {
|
||||
white
|
||||
}
|
||||
|
||||
type DiskPartition {
|
||||
"""The name of the partition"""
|
||||
name: String!
|
||||
|
||||
"""The filesystem type of the partition"""
|
||||
fsType: DiskFsType!
|
||||
|
||||
"""The size of the partition in bytes"""
|
||||
size: Float!
|
||||
}
|
||||
|
||||
"""The type of filesystem on the disk partition"""
|
||||
enum DiskFsType {
|
||||
XFS
|
||||
BTRFS
|
||||
VFAT
|
||||
ZFS
|
||||
EXT4
|
||||
NTFS
|
||||
}
|
||||
|
||||
type Disk implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""The device path of the disk (e.g. /dev/sdb)"""
|
||||
device: String!
|
||||
|
||||
"""The type of disk (e.g. SSD, HDD)"""
|
||||
type: String!
|
||||
|
||||
"""The model name of the disk"""
|
||||
name: String!
|
||||
|
||||
"""The manufacturer of the disk"""
|
||||
vendor: String!
|
||||
|
||||
"""The total size of the disk in bytes"""
|
||||
size: Float!
|
||||
|
||||
"""The number of bytes per sector"""
|
||||
bytesPerSector: Float!
|
||||
|
||||
"""The total number of cylinders on the disk"""
|
||||
totalCylinders: Float!
|
||||
|
||||
"""The total number of heads on the disk"""
|
||||
totalHeads: Float!
|
||||
|
||||
"""The total number of sectors on the disk"""
|
||||
totalSectors: Float!
|
||||
|
||||
"""The total number of tracks on the disk"""
|
||||
totalTracks: Float!
|
||||
|
||||
"""The number of tracks per cylinder"""
|
||||
tracksPerCylinder: Float!
|
||||
|
||||
"""The number of sectors per track"""
|
||||
sectorsPerTrack: Float!
|
||||
|
||||
"""The firmware revision of the disk"""
|
||||
firmwareRevision: String!
|
||||
|
||||
"""The serial number of the disk"""
|
||||
serialNum: String!
|
||||
|
||||
"""The interface type of the disk"""
|
||||
interfaceType: DiskInterfaceType!
|
||||
|
||||
"""The SMART status of the disk"""
|
||||
smartStatus: DiskSmartStatus!
|
||||
|
||||
"""The current temperature of the disk in Celsius"""
|
||||
temperature: Float
|
||||
|
||||
"""The partitions on the disk"""
|
||||
partitions: [DiskPartition!]!
|
||||
}
|
||||
|
||||
"""The type of interface the disk uses to connect to the system"""
|
||||
enum DiskInterfaceType {
|
||||
SAS
|
||||
SATA
|
||||
USB
|
||||
PCIE
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
"""
|
||||
The SMART (Self-Monitoring, Analysis and Reporting Technology) status of the disk
|
||||
"""
|
||||
enum DiskSmartStatus {
|
||||
OK
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
type InfoApps implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
@@ -1106,60 +1351,6 @@ type Owner {
|
||||
avatar: String!
|
||||
}
|
||||
|
||||
type KeyFile {
|
||||
location: String
|
||||
contents: String
|
||||
}
|
||||
|
||||
type Registration implements Node {
|
||||
id: PrefixedID!
|
||||
type: registrationType
|
||||
keyFile: KeyFile
|
||||
state: RegistrationState
|
||||
expiration: String
|
||||
updateExpiration: String
|
||||
}
|
||||
|
||||
enum registrationType {
|
||||
BASIC
|
||||
PLUS
|
||||
PRO
|
||||
STARTER
|
||||
UNLEASHED
|
||||
LIFETIME
|
||||
INVALID
|
||||
TRIAL
|
||||
}
|
||||
|
||||
enum RegistrationState {
|
||||
TRIAL
|
||||
BASIC
|
||||
PLUS
|
||||
PRO
|
||||
STARTER
|
||||
UNLEASHED
|
||||
LIFETIME
|
||||
EEXPIRED
|
||||
EGUID
|
||||
EGUID1
|
||||
ETRIAL
|
||||
ENOKEYFILE
|
||||
ENOKEYFILE1
|
||||
ENOKEYFILE2
|
||||
ENOFLASH
|
||||
ENOFLASH1
|
||||
ENOFLASH2
|
||||
ENOFLASH3
|
||||
ENOFLASH4
|
||||
ENOFLASH5
|
||||
ENOFLASH6
|
||||
ENOFLASH7
|
||||
EBLACKLISTED
|
||||
EBLACKLISTED1
|
||||
EBLACKLISTED2
|
||||
ENOCONN
|
||||
}
|
||||
|
||||
type ProfileModel implements Node {
|
||||
id: PrefixedID!
|
||||
username: String!
|
||||
@@ -1225,197 +1416,6 @@ type Settings implements Node {
|
||||
api: ApiConfig!
|
||||
}
|
||||
|
||||
type Vars implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Unraid version"""
|
||||
version: String
|
||||
maxArraysz: Int
|
||||
maxCachesz: Int
|
||||
|
||||
"""Machine hostname"""
|
||||
name: String
|
||||
timeZone: String
|
||||
comment: String
|
||||
security: String
|
||||
workgroup: String
|
||||
domain: String
|
||||
domainShort: String
|
||||
hideDotFiles: Boolean
|
||||
localMaster: Boolean
|
||||
enableFruit: String
|
||||
|
||||
"""Should a NTP server be used for time sync?"""
|
||||
useNtp: Boolean
|
||||
|
||||
"""NTP Server 1"""
|
||||
ntpServer1: String
|
||||
|
||||
"""NTP Server 2"""
|
||||
ntpServer2: String
|
||||
|
||||
"""NTP Server 3"""
|
||||
ntpServer3: String
|
||||
|
||||
"""NTP Server 4"""
|
||||
ntpServer4: String
|
||||
domainLogin: String
|
||||
sysModel: String
|
||||
sysArraySlots: Int
|
||||
sysCacheSlots: Int
|
||||
sysFlashSlots: Int
|
||||
useSsl: Boolean
|
||||
|
||||
"""Port for the webui via HTTP"""
|
||||
port: Int
|
||||
|
||||
"""Port for the webui via HTTPS"""
|
||||
portssl: Int
|
||||
localTld: String
|
||||
bindMgt: Boolean
|
||||
|
||||
"""Should telnet be enabled?"""
|
||||
useTelnet: Boolean
|
||||
porttelnet: Int
|
||||
useSsh: Boolean
|
||||
portssh: Int
|
||||
startPage: String
|
||||
startArray: Boolean
|
||||
spindownDelay: String
|
||||
queueDepth: String
|
||||
spinupGroups: Boolean
|
||||
defaultFormat: String
|
||||
defaultFsType: String
|
||||
shutdownTimeout: Int
|
||||
luksKeyfile: String
|
||||
pollAttributes: String
|
||||
pollAttributesDefault: String
|
||||
pollAttributesStatus: String
|
||||
nrRequests: Int
|
||||
nrRequestsDefault: Int
|
||||
nrRequestsStatus: String
|
||||
mdNumStripes: Int
|
||||
mdNumStripesDefault: Int
|
||||
mdNumStripesStatus: String
|
||||
mdSyncWindow: Int
|
||||
mdSyncWindowDefault: Int
|
||||
mdSyncWindowStatus: String
|
||||
mdSyncThresh: Int
|
||||
mdSyncThreshDefault: Int
|
||||
mdSyncThreshStatus: String
|
||||
mdWriteMethod: Int
|
||||
mdWriteMethodDefault: String
|
||||
mdWriteMethodStatus: String
|
||||
shareDisk: String
|
||||
shareUser: String
|
||||
shareUserInclude: String
|
||||
shareUserExclude: String
|
||||
shareSmbEnabled: Boolean
|
||||
shareNfsEnabled: Boolean
|
||||
shareAfpEnabled: Boolean
|
||||
shareInitialOwner: String
|
||||
shareInitialGroup: String
|
||||
shareCacheEnabled: Boolean
|
||||
shareCacheFloor: String
|
||||
shareMoverSchedule: String
|
||||
shareMoverLogging: Boolean
|
||||
fuseRemember: String
|
||||
fuseRememberDefault: String
|
||||
fuseRememberStatus: String
|
||||
fuseDirectio: String
|
||||
fuseDirectioDefault: String
|
||||
fuseDirectioStatus: String
|
||||
shareAvahiEnabled: Boolean
|
||||
shareAvahiSmbName: String
|
||||
shareAvahiSmbModel: String
|
||||
shareAvahiAfpName: String
|
||||
shareAvahiAfpModel: String
|
||||
safeMode: Boolean
|
||||
startMode: String
|
||||
configValid: Boolean
|
||||
configError: ConfigErrorState
|
||||
joinStatus: String
|
||||
deviceCount: Int
|
||||
flashGuid: String
|
||||
flashProduct: String
|
||||
flashVendor: String
|
||||
regCheck: String
|
||||
regFile: String
|
||||
regGuid: String
|
||||
regTy: registrationType
|
||||
regState: RegistrationState
|
||||
|
||||
"""Registration owner"""
|
||||
regTo: String
|
||||
regTm: String
|
||||
regTm2: String
|
||||
regGen: String
|
||||
sbName: String
|
||||
sbVersion: String
|
||||
sbUpdated: String
|
||||
sbEvents: Int
|
||||
sbState: String
|
||||
sbClean: Boolean
|
||||
sbSynced: Int
|
||||
sbSyncErrs: Int
|
||||
sbSynced2: Int
|
||||
sbSyncExit: String
|
||||
sbNumDisks: Int
|
||||
mdColor: String
|
||||
mdNumDisks: Int
|
||||
mdNumDisabled: Int
|
||||
mdNumInvalid: Int
|
||||
mdNumMissing: Int
|
||||
mdNumNew: Int
|
||||
mdNumErased: Int
|
||||
mdResync: Int
|
||||
mdResyncCorr: String
|
||||
mdResyncPos: String
|
||||
mdResyncDb: String
|
||||
mdResyncDt: String
|
||||
mdResyncAction: String
|
||||
mdResyncSize: Int
|
||||
mdState: String
|
||||
mdVersion: String
|
||||
cacheNumDevices: Int
|
||||
cacheSbNumDisks: Int
|
||||
fsState: String
|
||||
|
||||
"""Human friendly string of array events happening"""
|
||||
fsProgress: String
|
||||
|
||||
"""
|
||||
Percentage from 0 - 100 while upgrading a disk or swapping parity drives
|
||||
"""
|
||||
fsCopyPrcnt: Int
|
||||
fsNumMounted: Int
|
||||
fsNumUnmountable: Int
|
||||
fsUnmountableMask: String
|
||||
|
||||
"""Total amount of user shares"""
|
||||
shareCount: Int
|
||||
|
||||
"""Total amount shares with SMB enabled"""
|
||||
shareSmbCount: Int
|
||||
|
||||
"""Total amount shares with NFS enabled"""
|
||||
shareNfsCount: Int
|
||||
|
||||
"""Total amount shares with AFP enabled"""
|
||||
shareAfpCount: Int
|
||||
shareMoverActive: Boolean
|
||||
csrfToken: String
|
||||
}
|
||||
|
||||
"""Possible error states for configuration"""
|
||||
enum ConfigErrorState {
|
||||
UNKNOWN_ERROR
|
||||
INELIGIBLE
|
||||
INVALID
|
||||
NO_KEY_SERVER
|
||||
WITHDRAWN
|
||||
}
|
||||
|
||||
type VmDomain implements Node {
|
||||
"""The unique identifier for the vm (uuid)"""
|
||||
id: PrefixedID!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/api",
|
||||
"version": "4.10.0",
|
||||
"version": "4.9.4",
|
||||
"main": "src/cli/index.ts",
|
||||
"type": "module",
|
||||
"corepack": {
|
||||
@@ -10,7 +10,7 @@
|
||||
"author": "Lime Technology, Inc. <unraid.net>",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"engines": {
|
||||
"pnpm": "10.13.1"
|
||||
"pnpm": "10.12.4"
|
||||
},
|
||||
"scripts": {
|
||||
"// Development": "",
|
||||
@@ -57,7 +57,7 @@
|
||||
"@as-integrations/fastify": "2.1.1",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
"@fastify/helmet": "13.0.1",
|
||||
"@graphql-codegen/client-preset": "4.8.3",
|
||||
"@graphql-codegen/client-preset": "4.8.2",
|
||||
"@graphql-tools/load-files": "7.0.1",
|
||||
"@graphql-tools/merge": "9.0.24",
|
||||
"@graphql-tools/schema": "10.0.23",
|
||||
@@ -82,7 +82,7 @@
|
||||
"accesscontrol": "2.2.1",
|
||||
"bycontract": "2.0.11",
|
||||
"bytes": "3.1.2",
|
||||
"cache-manager": "7.0.1",
|
||||
"cache-manager": "7.0.0",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"camelcase-keys": "9.1.3",
|
||||
"casbin": "5.38.0",
|
||||
@@ -94,11 +94,11 @@
|
||||
"command-exists": "1.2.9",
|
||||
"convert": "5.12.0",
|
||||
"cookie": "1.0.2",
|
||||
"cron": "4.3.2",
|
||||
"cron": "4.3.1",
|
||||
"cross-fetch": "4.1.0",
|
||||
"diff": "8.0.2",
|
||||
"dockerode": "4.0.7",
|
||||
"dotenv": "17.2.0",
|
||||
"dotenv": "17.1.0",
|
||||
"execa": "9.6.0",
|
||||
"exit-hook": "4.0.0",
|
||||
"fastify": "5.4.0",
|
||||
@@ -112,7 +112,7 @@
|
||||
"graphql-scalars": "1.24.2",
|
||||
"graphql-subscriptions": "3.0.0",
|
||||
"graphql-tag": "2.12.6",
|
||||
"graphql-ws": "6.0.6",
|
||||
"graphql-ws": "6.0.5",
|
||||
"ini": "5.0.0",
|
||||
"ip": "2.0.1",
|
||||
"jose": "6.0.11",
|
||||
@@ -138,11 +138,11 @@
|
||||
"rxjs": "7.8.2",
|
||||
"semver": "7.7.2",
|
||||
"strftime": "0.10.3",
|
||||
"systeminformation": "5.27.7",
|
||||
"systeminformation": "5.27.6",
|
||||
"uuid": "11.1.0",
|
||||
"ws": "8.18.3",
|
||||
"ws": "8.18.2",
|
||||
"zen-observable-ts": "1.1.0",
|
||||
"zod": "3.25.76"
|
||||
"zod": "3.25.67"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"unraid-api-plugin-connect": "workspace:*"
|
||||
@@ -153,35 +153,35 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.31.0",
|
||||
"@eslint/js": "9.29.0",
|
||||
"@graphql-codegen/add": "5.0.3",
|
||||
"@graphql-codegen/cli": "5.0.7",
|
||||
"@graphql-codegen/fragment-matcher": "5.1.0",
|
||||
"@graphql-codegen/import-types-preset": "3.0.1",
|
||||
"@graphql-codegen/typed-document-node": "5.1.2",
|
||||
"@graphql-codegen/typed-document-node": "5.1.1",
|
||||
"@graphql-codegen/typescript": "4.1.6",
|
||||
"@graphql-codegen/typescript-operations": "4.6.1",
|
||||
"@graphql-codegen/typescript-resolvers": "4.5.1",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.5.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.4.2",
|
||||
"@nestjs/testing": "11.1.3",
|
||||
"@originjs/vite-plugin-commonjs": "1.0.3",
|
||||
"@rollup/plugin-node-resolve": "16.0.1",
|
||||
"@swc/core": "1.12.14",
|
||||
"@swc/core": "1.12.4",
|
||||
"@types/async-exit-hook": "2.0.2",
|
||||
"@types/bytes": "3.1.5",
|
||||
"@types/cli-table": "0.3.4",
|
||||
"@types/command-exists": "1.2.3",
|
||||
"@types/cors": "2.8.19",
|
||||
"@types/dockerode": "3.3.42",
|
||||
"@types/dockerode": "3.3.41",
|
||||
"@types/graphql-fields": "1.3.9",
|
||||
"@types/graphql-type-uuid": "0.2.6",
|
||||
"@types/ini": "4.1.1",
|
||||
"@types/ip": "1.1.3",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/lodash": "4.17.18",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/mustache": "4.2.6",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/node": "22.15.32",
|
||||
"@types/pify": "6.1.0",
|
||||
"@types/semver": "7.7.0",
|
||||
"@types/sendmail": "1.4.7",
|
||||
@@ -193,27 +193,27 @@
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vitest/ui": "3.2.4",
|
||||
"cz-conventional-changelog": "3.3.0",
|
||||
"eslint": "9.31.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-n": "17.21.0",
|
||||
"eslint": "9.29.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-n": "17.20.0",
|
||||
"eslint-plugin-no-relative-import-paths": "1.6.1",
|
||||
"eslint-plugin-prettier": "5.5.1",
|
||||
"eslint-plugin-prettier": "5.5.0",
|
||||
"graphql-codegen-typescript-validation-schema": "0.17.1",
|
||||
"jiti": "2.4.2",
|
||||
"nodemon": "3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"prettier": "3.5.3",
|
||||
"rollup-plugin-node-externals": "8.0.1",
|
||||
"commit-and-tag-version": "9.6.0",
|
||||
"commit-and-tag-version": "9.5.0",
|
||||
"tsx": "4.20.3",
|
||||
"type-fest": "4.41.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.37.0",
|
||||
"typescript-eslint": "8.34.1",
|
||||
"unplugin-swc": "1.5.5",
|
||||
"vite": "7.0.4",
|
||||
"vite": "7.0.3",
|
||||
"vite-plugin-node": "7.0.0",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.2.4",
|
||||
"zx": "8.7.1"
|
||||
"zx": "8.5.5"
|
||||
},
|
||||
"overrides": {
|
||||
"eslint": {
|
||||
@@ -228,5 +228,5 @@
|
||||
}
|
||||
},
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.13.1"
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ exports[`Returns paths 1`] = `
|
||||
"myservers-base",
|
||||
"myservers-config",
|
||||
"myservers-config-states",
|
||||
"myservers-env",
|
||||
"myservers-keepalive",
|
||||
"keyfile-base",
|
||||
"machine-id",
|
||||
|
||||
@@ -24,6 +24,7 @@ test('Returns paths', async () => {
|
||||
'myservers-base': '/boot/config/plugins/dynamix.my.servers/',
|
||||
'myservers-config': expect.stringContaining('api/dev/Unraid.net/myservers.cfg'),
|
||||
'myservers-config-states': expect.stringContaining('api/dev/states/myservers.cfg'),
|
||||
'myservers-env': '/boot/config/plugins/dynamix.my.servers/env',
|
||||
'myservers-keepalive': './dev/Unraid.net/fb_keepalive',
|
||||
'keyfile-base': expect.stringContaining('api/dev/Unraid.net'),
|
||||
'machine-id': expect.stringContaining('api/dev/data/machine-id'),
|
||||
|
||||
@@ -67,7 +67,6 @@ export const getPackageJsonDependencies = (): string[] | undefined => {
|
||||
|
||||
export const API_VERSION = process.env.npm_package_version ?? getPackageJson().version;
|
||||
|
||||
/** Controls how the app is built/run (i.e. in terms of optimization) */
|
||||
export const NODE_ENV =
|
||||
(process.env.NODE_ENV as 'development' | 'test' | 'staging' | 'production') ?? 'production';
|
||||
export const environment = {
|
||||
@@ -77,7 +76,6 @@ export const CHOKIDAR_USEPOLLING = process.env.CHOKIDAR_USEPOLLING === 'true';
|
||||
export const IS_DOCKER = process.env.IS_DOCKER === 'true';
|
||||
export const DEBUG = process.env.DEBUG === 'true';
|
||||
export const INTROSPECTION = process.env.INTROSPECTION === 'true';
|
||||
/** Determines the app-level & business logic environment (i.e. what data & infrastructure is used) */
|
||||
export const ENVIRONMENT = process.env.ENVIRONMENT
|
||||
? (process.env.ENVIRONMENT as 'production' | 'staging' | 'development')
|
||||
: 'production';
|
||||
|
||||
@@ -49,6 +49,7 @@ const initialState = {
|
||||
resolvePath(process.env.PATHS_STATES ?? ('/usr/local/emhttp/state/' as const)),
|
||||
'myservers.cfg' as const
|
||||
),
|
||||
'myservers-env': '/boot/config/plugins/dynamix.my.servers/env' as const,
|
||||
'myservers-keepalive':
|
||||
process.env.PATHS_MY_SERVERS_FB ??
|
||||
('/boot/config/plugins/dynamix.my.servers/fb_keepalive' as const),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ensureWriteSync } from '@unraid/shared/util/file.js';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import type { RootState } from '@app/store/index.js';
|
||||
@@ -27,11 +27,8 @@ export const startStoreSync = async () => {
|
||||
!isEqual(state, lastState) &&
|
||||
state.paths['myservers-config-states']
|
||||
) {
|
||||
ensureWriteSync(
|
||||
join(state.paths.states, 'config.log'),
|
||||
JSON.stringify(state.config, null, 2)
|
||||
);
|
||||
ensureWriteSync(
|
||||
writeFileSync(join(state.paths.states, 'config.log'), JSON.stringify(state.config, null, 2));
|
||||
writeFileSync(
|
||||
join(state.paths.states, 'graphql.log'),
|
||||
JSON.stringify(state.minigraph, null, 2)
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { copyFile } from 'fs/promises';
|
||||
import { copyFile, readFile, writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
|
||||
import { fileExistsSync } from '@app/core/utils/files/file-exists.js';
|
||||
import { ENVIRONMENT } from '@app/environment.js';
|
||||
import { cliLogger } from '@app/core/log.js';
|
||||
import { getters } from '@app/store/index.js';
|
||||
import { LogService } from '@app/unraid-api/cli/log.service.js';
|
||||
import { RestartCommand } from '@app/unraid-api/cli/restart.command.js';
|
||||
import { StartCommand } from '@app/unraid-api/cli/start.command.js';
|
||||
import { StopCommand } from '@app/unraid-api/cli/stop.command.js';
|
||||
|
||||
interface SwitchEnvOptions {
|
||||
environment?: 'staging' | 'production';
|
||||
@@ -31,43 +31,60 @@ export class SwitchEnvCommand extends CommandRunner {
|
||||
|
||||
constructor(
|
||||
private readonly logger: LogService,
|
||||
private readonly restartCommand: RestartCommand
|
||||
private readonly stopCommand: StopCommand,
|
||||
private readonly startCommand: StartCommand
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private async getEnvironmentFromFile(path: string): Promise<'production' | 'staging'> {
|
||||
const envFile = await readFile(path, 'utf-8').catch(() => '');
|
||||
this.logger.debug(`Checking ${path} for current ENV, found ${envFile}`);
|
||||
|
||||
// Match the env file env="production" which would be [0] = env="production", [1] = env and [2] = production
|
||||
const matchArray = /([a-zA-Z]+)=["]*([a-zA-Z]+)["]*/.exec(envFile);
|
||||
// Get item from index 2 of the regex match or return production
|
||||
const [, , currentEnvInFile] = matchArray && matchArray.length === 3 ? matchArray : [];
|
||||
return this.parseStringToEnv(currentEnvInFile);
|
||||
}
|
||||
|
||||
private switchToOtherEnv(environment: 'production' | 'staging'): 'production' | 'staging' {
|
||||
if (environment === 'production') {
|
||||
return 'staging';
|
||||
}
|
||||
return 'production';
|
||||
}
|
||||
|
||||
async run(_, options: SwitchEnvOptions): Promise<void> {
|
||||
const paths = getters.paths();
|
||||
const basePath = paths['unraid-api-base'];
|
||||
const currentEnvPath = join(basePath, '.env');
|
||||
const envFlashFilePath = paths['myservers-env'];
|
||||
|
||||
// Determine target environment
|
||||
const currentEnv = ENVIRONMENT;
|
||||
const targetEnv = options.environment ?? 'production';
|
||||
|
||||
this.logger.info(`Switching environment from ${currentEnv} to ${targetEnv}`);
|
||||
|
||||
// Check if target environment file exists
|
||||
const sourceEnvPath = join(basePath, `.env.${targetEnv}`);
|
||||
if (!fileExistsSync(sourceEnvPath)) {
|
||||
this.logger.error(
|
||||
`Environment file ${sourceEnvPath} does not exist. Cannot switch to ${targetEnv} environment.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Copy the target environment file to .env
|
||||
this.logger.debug(`Copying ${sourceEnvPath} to ${currentEnvPath}`);
|
||||
this.logger.warn('Stopping the Unraid API');
|
||||
try {
|
||||
await copyFile(sourceEnvPath, currentEnvPath);
|
||||
this.logger.info(`Successfully switched to ${targetEnv} environment`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to copy environment file: ${error}`);
|
||||
process.exit(1);
|
||||
await this.stopCommand.run([], { delete: false });
|
||||
} catch (err) {
|
||||
this.logger.warn('Failed to stop the Unraid API (maybe already stopped?)');
|
||||
}
|
||||
|
||||
// Restart the API to pick up the new environment
|
||||
this.logger.info('Restarting Unraid API to apply environment changes...');
|
||||
await this.restartCommand.run();
|
||||
const newEnv =
|
||||
options.environment ??
|
||||
this.switchToOtherEnv(await this.getEnvironmentFromFile(envFlashFilePath));
|
||||
this.logger.info(`Setting environment to ${newEnv}`);
|
||||
|
||||
// Write new env to flash
|
||||
const newEnvLine = `env="${newEnv}"`;
|
||||
this.logger.debug('Writing %s to %s', newEnvLine, envFlashFilePath);
|
||||
await writeFile(envFlashFilePath, newEnvLine);
|
||||
|
||||
// Copy the new env over to live location before restarting
|
||||
const source = join(basePath, `.env.${newEnv}`);
|
||||
const destination = join(basePath, '.env');
|
||||
|
||||
cliLogger.debug('Copying %s to %s', source, destination);
|
||||
await copyFile(source, destination);
|
||||
|
||||
cliLogger.info('Now using %s', newEnv);
|
||||
await this.startCommand.run([], {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ export class NginxService {
|
||||
async reload() {
|
||||
try {
|
||||
await execa('/etc/rc.d/rc.nginx', ['reload']);
|
||||
this.logger.log('Nginx reloaded');
|
||||
return true;
|
||||
} catch (err: unknown) {
|
||||
this.logger.warn('Failed to reload Nginx with error: ', err);
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ONE_SECOND_MS } from '@app/consts.js';
|
||||
import { NginxService } from '@app/unraid-api/nginx/nginx.service.js';
|
||||
import { ModificationEffect } from '@app/unraid-api/unraid-file-modifier/file-modification.js';
|
||||
|
||||
@Injectable()
|
||||
export class FileModificationEffectService {
|
||||
private readonly logger = new Logger(FileModificationEffectService.name);
|
||||
constructor(private readonly nginxService: NginxService) {}
|
||||
async runEffect(effect: ModificationEffect): Promise<void> {
|
||||
switch (effect) {
|
||||
case 'nginx:reload':
|
||||
this.logger.log('Reloading Nginx in 10 seconds...');
|
||||
await new Promise((resolve) => setTimeout(resolve, 10 * ONE_SECOND_MS));
|
||||
await this.nginxService.reload();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
1752524464371
|
||||
1751630630443
|
||||
@@ -1 +1 @@
|
||||
1752524464066
|
||||
1751630630198
|
||||
@@ -1 +1 @@
|
||||
1752524464213
|
||||
1751630630343
|
||||
@@ -1 +1 @@
|
||||
1752524464631
|
||||
1751630630571
|
||||
@@ -1 +1 @@
|
||||
1752524464761
|
||||
1751630630810
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "unraid-monorepo",
|
||||
"private": true,
|
||||
"version": "4.10.0",
|
||||
"version": "4.9.4",
|
||||
"scripts": {
|
||||
"build": "pnpm -r build",
|
||||
"build:watch": " pnpm -r --parallel build:watch",
|
||||
@@ -57,5 +57,5 @@
|
||||
"pnpm lint:fix"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@10.13.1"
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
"description": "Unraid Connect plugin for Unraid API",
|
||||
"devDependencies": {
|
||||
"@apollo/client": "3.13.8",
|
||||
"@faker-js/faker": "9.9.0",
|
||||
"@faker-js/faker": "9.8.0",
|
||||
"@graphql-codegen/cli": "5.0.7",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.5.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.4.2",
|
||||
"@jsonforms/core": "3.6.0",
|
||||
"@nestjs/apollo": "13.1.0",
|
||||
"@nestjs/common": "11.1.3",
|
||||
@@ -41,29 +41,29 @@
|
||||
"@types/ini": "4.1.1",
|
||||
"@types/ip": "1.1.3",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/node": "22.15.32",
|
||||
"@types/ws": "8.18.1",
|
||||
"camelcase-keys": "9.1.3",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.14.2",
|
||||
"execa": "9.6.0",
|
||||
"fast-check": "4.2.0",
|
||||
"fast-check": "4.1.1",
|
||||
"got": "14.4.7",
|
||||
"graphql": "16.11.0",
|
||||
"graphql-scalars": "1.24.2",
|
||||
"graphql-subscriptions": "3.0.0",
|
||||
"graphql-ws": "6.0.6",
|
||||
"graphql-ws": "6.0.5",
|
||||
"ini": "5.0.0",
|
||||
"jose": "6.0.11",
|
||||
"lodash-es": "4.17.21",
|
||||
"nest-authz": "2.17.0",
|
||||
"prettier": "3.6.2",
|
||||
"prettier": "3.5.3",
|
||||
"rimraf": "6.0.1",
|
||||
"rxjs": "7.8.2",
|
||||
"type-fest": "4.41.0",
|
||||
"typescript": "5.8.3",
|
||||
"vitest": "3.2.4",
|
||||
"ws": "8.18.3",
|
||||
"ws": "8.18.2",
|
||||
"zen-observable-ts": "1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -91,7 +91,7 @@
|
||||
"graphql": "16.11.0",
|
||||
"graphql-scalars": "1.24.2",
|
||||
"graphql-subscriptions": "3.0.0",
|
||||
"graphql-ws": "6.0.6",
|
||||
"graphql-ws": "6.0.5",
|
||||
"ini": "5.0.0",
|
||||
"jose": "6.0.11",
|
||||
"lodash-es": "4.17.21",
|
||||
|
||||
@@ -130,19 +130,11 @@ export class MothershipConnectionService implements OnModuleInit, OnModuleDestro
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
// Warn on startup if these config values are not set initially
|
||||
// Crash on startup if these config values are not set initially
|
||||
const { unraidVersion, flashGuid, apiVersion } = this.configKeys;
|
||||
const warnings: string[] = [];
|
||||
[unraidVersion, flashGuid, apiVersion].forEach((key) => {
|
||||
try {
|
||||
this.configService.getOrThrow(key);
|
||||
} catch (error) {
|
||||
warnings.push(`${key} is not set`);
|
||||
}
|
||||
this.configService.getOrThrow(key);
|
||||
});
|
||||
if (warnings.length > 0) {
|
||||
this.logger.warn('Missing config values: %s', warnings.join(', '));
|
||||
}
|
||||
// Setup IDENTITY_CHANGED & METADATA_CHANGED events
|
||||
this.setupIdentitySubscription();
|
||||
this.setupMetadataChangedEvent();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"commander": "14.0.0",
|
||||
"create-create-app": "7.3.0",
|
||||
"fs-extra": "11.3.0",
|
||||
"inquirer": "12.7.0",
|
||||
"inquirer": "12.6.3",
|
||||
"validate-npm-package-name": "6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -25,7 +25,7 @@
|
||||
"@nestjs/graphql": "13.1.0",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/inquirer": "9.0.8",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/node": "22.15.32",
|
||||
"@types/validate-npm-package-name": "4.0.2",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.14.2",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Justfile for unraid-shared
|
||||
|
||||
# Default recipe to run when just is called without arguments
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Watch for changes in src files and run clean + build
|
||||
watch:
|
||||
watchexec -r -e ts,tsx -w src -- pnpm build
|
||||
@@ -31,9 +31,9 @@
|
||||
"@jsonforms/core": "3.6.0",
|
||||
"@nestjs/common": "11.1.3",
|
||||
"@nestjs/graphql": "13.1.0",
|
||||
"@types/bun": "1.2.18",
|
||||
"@types/bun": "1.2.16",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/node": "22.15.32",
|
||||
"class-validator": "0.14.2",
|
||||
"graphql": "16.11.0",
|
||||
"graphql-scalars": "1.24.2",
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
import { accessSync } from 'fs';
|
||||
import { access, mkdir, writeFile } from 'fs/promises';
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { access } from 'fs/promises';
|
||||
import { F_OK } from 'node:constants';
|
||||
import { dirname } from 'path';
|
||||
|
||||
/**
|
||||
* Checks if a file exists asynchronously.
|
||||
* @param path - The file path to check
|
||||
* @returns Promise that resolves to true if file exists, false otherwise
|
||||
*/
|
||||
export const fileExists = async (path: string) =>
|
||||
access(path, F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
/**
|
||||
* Checks if a file exists synchronously.
|
||||
* @param path - The file path to check
|
||||
* @returns true if file exists, false otherwise
|
||||
*/
|
||||
export const fileExistsSync = (path: string) => {
|
||||
try {
|
||||
accessSync(path, F_OK);
|
||||
@@ -27,44 +14,3 @@ export const fileExistsSync = (path: string) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes data to a file, creating parent directories if they don't exist.
|
||||
*
|
||||
* This function ensures the directory structure exists before writing the file,
|
||||
* equivalent to `mkdir -p` followed by file writing.
|
||||
*
|
||||
* @param path - The file path to write to
|
||||
* @param data - The data to write (string or Buffer)
|
||||
* @throws {Error} If path is invalid (null, empty, or not a string)
|
||||
* @throws {Error} For any file system errors (EACCES, EPERM, ENOSPC, EISDIR, etc.)
|
||||
*/
|
||||
export const ensureWrite = async (path: string, data: string | Buffer) => {
|
||||
if (!path || typeof path !== 'string') {
|
||||
throw new Error(`Invalid path provided: ${path}`);
|
||||
}
|
||||
|
||||
await mkdir(dirname(path), { recursive: true });
|
||||
return await writeFile(path, data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes data to a file synchronously, creating parent directories if they don't exist.
|
||||
*
|
||||
* This function ensures the directory structure exists before writing the file,
|
||||
* equivalent to `mkdir -p` followed by file writing.
|
||||
*
|
||||
* @param path - The file path to write to
|
||||
* @param data - The data to write (string or Buffer)
|
||||
* @throws {Error} If path is invalid (null, empty, or not a string)
|
||||
* @throws {Error} For any file system errors (EACCES, EPERM, ENOSPC, EISDIR, etc.)
|
||||
*/
|
||||
export const ensureWriteSync = (path: string, data: string | Buffer) => {
|
||||
if (!path || typeof path !== 'string') {
|
||||
throw new Error(`Invalid path provided: ${path}`);
|
||||
}
|
||||
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
return writeFileSync(path, data);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@unraid/connect-plugin",
|
||||
"version": "4.10.0",
|
||||
"version": "4.9.4",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"commander": "14.0.0",
|
||||
"conventional-changelog": "6.0.0",
|
||||
"date-fns": "4.1.0",
|
||||
"glob": "11.0.3",
|
||||
"glob": "11.0.1",
|
||||
"html-sloppy-escaper": "0.1.0",
|
||||
"semver": "7.7.2",
|
||||
"tsx": "4.20.3",
|
||||
"zod": "3.25.76",
|
||||
"zx": "8.7.1"
|
||||
"semver": "7.7.1",
|
||||
"tsx": "4.19.3",
|
||||
"zod": "3.24.2",
|
||||
"zx": "8.3.2"
|
||||
},
|
||||
"type": "module",
|
||||
"license": "GPL-2.0-or-later",
|
||||
@@ -37,7 +37,7 @@
|
||||
"devDependencies": {
|
||||
"http-server": "14.1.1",
|
||||
"nodemon": "3.1.10",
|
||||
"vitest": "3.2.4"
|
||||
"vitest": "3.0.7"
|
||||
},
|
||||
"packageManager": "pnpm@10.13.1"
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
}
|
||||
|
||||
@@ -331,7 +331,8 @@ exit 0
|
||||
<![CDATA[
|
||||
SCRIPTS_DIR="/usr/local/share/dynamix.unraid.net/install/scripts"
|
||||
# Log file for debugging
|
||||
mkdir -p "/var/log/unraid-api"
|
||||
LOGFILE="/var/log/unraid-api/dynamix-unraid-install.log"
|
||||
mkdir -p "$(dirname "$LOGFILE")"
|
||||
|
||||
echo "Starting Unraid Connect installation..."
|
||||
|
||||
@@ -343,26 +344,26 @@ CFG_NEW=/boot/config/plugins/dynamix.my.servers
|
||||
# Setup the API (but don't start it yet)
|
||||
if [ -x "$SCRIPTS_DIR/setup_api.sh" ]; then
|
||||
echo "Setting up Unraid API..."
|
||||
echo "Running setup_api.sh"
|
||||
# Run and show output to user
|
||||
"$SCRIPTS_DIR/setup_api.sh"
|
||||
echo "Running setup_api.sh" >> "$LOGFILE"
|
||||
# Capture output and add to log file
|
||||
setup_output=$("$SCRIPTS_DIR/setup_api.sh")
|
||||
echo "$setup_output" >> "$LOGFILE"
|
||||
else
|
||||
echo "ERROR: setup_api.sh not found or not executable"
|
||||
echo "ERROR: setup_api.sh not found or not executable"
|
||||
echo "ERROR: setup_api.sh not found or not executable" >> "$LOGFILE"
|
||||
fi
|
||||
|
||||
# Run post-installation verification
|
||||
if [ -x "$SCRIPTS_DIR/verify_install.sh" ]; then
|
||||
echo "Running post-installation verification..."
|
||||
echo "Running verify_install.sh"
|
||||
# Run and show output to user
|
||||
"$SCRIPTS_DIR/verify_install.sh"
|
||||
echo "Running verify_install.sh" >> "$LOGFILE"
|
||||
# Capture output and add to log file
|
||||
verify_output=$("$SCRIPTS_DIR/verify_install.sh")
|
||||
echo "$verify_output" >> "$LOGFILE"
|
||||
else
|
||||
echo "ERROR: verify_install.sh not found or not executable"
|
||||
echo "ERROR: verify_install.sh not found or not executable"
|
||||
echo "ERROR: verify_install.sh not found or not executable" >> "$LOGFILE"
|
||||
fi
|
||||
|
||||
echo "Installation completed at $(date)"
|
||||
echo "Installation completed at $(date)" >> "$LOGFILE"
|
||||
]]>
|
||||
</INLINE>
|
||||
</FILE>
|
||||
@@ -378,18 +379,6 @@ echo "Installation completed at $(date)"
|
||||
/etc/rc.d/rc.unraid-api cleanup-dependencies
|
||||
|
||||
echo "Starting Unraid API service"
|
||||
echo "DEBUG: Checking PATH: $PATH"
|
||||
echo "DEBUG: Checking if unraid-api files exist:"
|
||||
ls -la /usr/local/unraid-api/dist/
|
||||
echo "DEBUG: Checking symlink:"
|
||||
ls -la /usr/local/bin/unraid-api
|
||||
echo "DEBUG: Checking Node.js version:"
|
||||
node --version
|
||||
echo "DEBUG: Checking if cli.js is executable:"
|
||||
ls -la /usr/local/unraid-api/dist/cli.js
|
||||
echo "DEBUG: Attempting to run unraid-api directly:"
|
||||
/usr/local/unraid-api/dist/cli.js version || echo "Direct execution failed"
|
||||
|
||||
echo "If no additional messages appear within 30 seconds, it is safe to refresh the page."
|
||||
/etc/rc.d/rc.unraid-api plugins add unraid-api-plugin-connect -b --no-restart
|
||||
/etc/rc.d/rc.unraid-api start
|
||||
|
||||
@@ -166,23 +166,22 @@ _enabled() {
|
||||
return 1
|
||||
}
|
||||
_connected() {
|
||||
local connect_config username status_cfg connection_status
|
||||
connect_config=$API_CONFIG_HOME/connect.json
|
||||
[[ ! -f "${connect_config}" ]] && return 1
|
||||
CFG=$API_CONFIG_HOME/connect.json
|
||||
[[ ! -f "${CFG}" ]] && return 1
|
||||
|
||||
# is the user signed in?
|
||||
username=$(jq -r '.username // empty' "${connect_config}" 2>/dev/null)
|
||||
username=$(jq -r '.username // empty' "${CFG}" 2>/dev/null)
|
||||
if [ -z "${username}" ]; then
|
||||
return 1
|
||||
fi
|
||||
# are we connected to mothership?
|
||||
status_cfg="/var/local/emhttp/connectStatus.json"
|
||||
[[ ! -f "${status_cfg}" ]] && return 1
|
||||
connection_status=$(jq -r '.connectionStatus // empty' "${status_cfg}" 2>/dev/null)
|
||||
if [[ "${connection_status}" != "CONNECTED" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# the minigraph status is no longer synced to the connect config file
|
||||
# to avoid a false negative, we'll omit this check for now.
|
||||
#
|
||||
# shellcheck disable=SC1090
|
||||
# source <(sed -nr '/\[connectionStatus\]/,/\[/{/minigraph/p}' "${CFG}" 2>/dev/null)
|
||||
# # ensure connected
|
||||
# if [[ -z "${minigraph}" || "${minigraph}" != "CONNECTED" ]]; then
|
||||
# return 1
|
||||
# fi
|
||||
return 0
|
||||
}
|
||||
_haserror() {
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
# shellcheck source=/dev/null
|
||||
source /etc/profile
|
||||
|
||||
flash="/boot/config/plugins/dynamix.my.servers"
|
||||
[[ ! -d "${flash}" ]] && echo "Please reinstall the Unraid Connect plugin" && exit 1
|
||||
[[ ! -f "${flash}/env" ]] && echo 'env=production' >"${flash}/env"
|
||||
unraid_binary_path="/usr/local/bin/unraid-api"
|
||||
api_base_dir="/usr/local/unraid-api"
|
||||
scripts_dir="/usr/local/share/dynamix.unraid.net/scripts"
|
||||
|
||||
@@ -18,9 +18,10 @@ $cli = php_sapi_name()=='cli';
|
||||
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/connect-config.php";
|
||||
|
||||
$isRegistered = ConnectConfig::isUserSignedIn();
|
||||
$myservers_flash_cfg_path='/boot/config/plugins/dynamix.my.servers/myservers.cfg';
|
||||
$myservers = file_exists($myservers_flash_cfg_path) ? @parse_ini_file($myservers_flash_cfg_path,true) : [];
|
||||
$isRegistered = !empty($myservers['remote']['username']);
|
||||
|
||||
// Read connection status from the new API status file
|
||||
$statusFilePath = '/var/local/emhttp/connectStatus.json';
|
||||
@@ -594,31 +595,9 @@ set_git_config('user.email', 'gitbot@unraid.net');
|
||||
set_git_config('user.name', 'gitbot');
|
||||
|
||||
// ensure dns can resolve backup.unraid.net
|
||||
$dnsResolved = false;
|
||||
|
||||
// Try multiple DNS resolution methods
|
||||
if (function_exists('dns_get_record')) {
|
||||
$dnsRecords = dns_get_record("backup.unraid.net", DNS_A);
|
||||
$dnsResolved = !empty($dnsRecords);
|
||||
}
|
||||
|
||||
// Fallback to gethostbyname if dns_get_record fails
|
||||
if (!$dnsResolved) {
|
||||
$ip = gethostbyname("backup.unraid.net");
|
||||
$dnsResolved = ($ip !== "backup.unraid.net");
|
||||
}
|
||||
|
||||
// Final fallback to system nslookup
|
||||
if (!$dnsResolved) {
|
||||
$output = [];
|
||||
$return_var = 0;
|
||||
exec('nslookup backup.unraid.net 2>/dev/null', $output, $return_var);
|
||||
$dnsResolved = ($return_var === 0 && !empty($output));
|
||||
}
|
||||
|
||||
if (!$dnsResolved) {
|
||||
if (! checkdnsrr("backup.unraid.net","A") ) {
|
||||
$arrState['loading'] = '';
|
||||
$arrState['error'] = 'DNS resolution failed for backup.unraid.net - PHP DNS functions (checkdnsrr, dns_get_record, gethostbyname) and system nslookup all failed to resolve the hostname. This indicates a DNS configuration issue on your Unraid server. Check your DNS settings in Settings > Network Settings.';
|
||||
$arrState['error'] = 'DNS is unable to resolve backup.unraid.net';
|
||||
response_complete(406, array('error' => $arrState['error']));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/api-config.php";
|
||||
|
||||
/**
|
||||
* Wrapper around the API's connect.json configuration file.
|
||||
*/
|
||||
class ConnectConfig
|
||||
{
|
||||
public const CONFIG_PATH = ApiConfig::CONFIG_DIR . '/connect.json';
|
||||
|
||||
public static function getConfig()
|
||||
{
|
||||
try {
|
||||
return json_decode(file_get_contents(self::CONFIG_PATH), true) ?? [];
|
||||
} catch (Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public static function isUserSignedIn()
|
||||
{
|
||||
$config = self::getConfig();
|
||||
return ApiConfig::isConnectPluginEnabled() && !empty($config['username'] ?? '');
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
# Unraid API Installation Verification Script
|
||||
# Checks that critical files are installed correctly
|
||||
|
||||
# Exit on errors
|
||||
set -e
|
||||
|
||||
echo "Performing comprehensive installation verification..."
|
||||
|
||||
# Define critical files to check (POSIX-compliant, no arrays)
|
||||
@@ -168,7 +171,7 @@ if [ $TOTAL_ERRORS -eq 0 ]; then
|
||||
else
|
||||
printf 'Found %d total errors.\n' "$TOTAL_ERRORS"
|
||||
echo "Installation verification completed with issues."
|
||||
echo "Please review the errors above and contact support if needed."
|
||||
echo "See log file for details: /var/log/unraid-api/dynamix-unraid-install.log"
|
||||
# We don't exit with error as this is just a verification script
|
||||
exit 0
|
||||
fi
|
||||
4361
pnpm-lock.yaml
generated
4361
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/ui",
|
||||
"version": "4.10.0",
|
||||
"version": "4.9.4",
|
||||
"private": true,
|
||||
"license": "GPL-2.0-or-later",
|
||||
"type": "module",
|
||||
@@ -54,68 +54,68 @@
|
||||
"@jsonforms/core": "3.6.0",
|
||||
"@jsonforms/vue": "3.6.0",
|
||||
"@jsonforms/vue-vanilla": "3.6.0",
|
||||
"@vueuse/core": "13.5.0",
|
||||
"@vueuse/core": "13.4.0",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"dompurify": "3.2.6",
|
||||
"kebab-case": "2.0.2",
|
||||
"lucide-vue-next": "0.525.0",
|
||||
"lucide-vue-next": "0.519.0",
|
||||
"marked": "16.0.0",
|
||||
"reka-ui": "2.3.2",
|
||||
"reka-ui": "2.3.1",
|
||||
"shadcn-vue": "2.2.0",
|
||||
"tailwind-merge": "2.6.0",
|
||||
"vue-sonner": "1.3.2"
|
||||
"vue-sonner": "1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.5.1",
|
||||
"@storybook/addon-docs": "9.0.17",
|
||||
"@storybook/addon-links": "9.0.17",
|
||||
"@storybook/builder-vite": "9.0.17",
|
||||
"@storybook/vue3-vite": "9.0.17",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.4.2",
|
||||
"@storybook/addon-docs": "9.0.16",
|
||||
"@storybook/addon-links": "9.0.16",
|
||||
"@storybook/builder-vite": "9.0.16",
|
||||
"@storybook/vue3-vite": "9.0.16",
|
||||
"@tailwindcss/typography": "0.5.16",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/node": "22.15.32",
|
||||
"@types/testing-library__vue": "5.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
||||
"@unraid/tailwind-rem-to-rem": "1.1.0",
|
||||
"@vitejs/plugin-vue": "6.0.0",
|
||||
"@vitejs/plugin-vue": "5.2.4",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vitest/ui": "3.2.4",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"@vue/tsconfig": "0.7.0",
|
||||
"autoprefixer": "10.4.21",
|
||||
"concurrently": "9.2.0",
|
||||
"eslint": "9.31.0",
|
||||
"concurrently": "9.1.2",
|
||||
"eslint": "9.29.0",
|
||||
"eslint-config-prettier": "10.1.5",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-no-relative-import-paths": "1.6.1",
|
||||
"eslint-plugin-prettier": "5.5.1",
|
||||
"eslint-plugin-storybook": "9.0.17",
|
||||
"eslint-plugin-vue": "10.3.0",
|
||||
"happy-dom": "18.0.1",
|
||||
"eslint-plugin-prettier": "5.5.0",
|
||||
"eslint-plugin-storybook": "9.0.16",
|
||||
"eslint-plugin-vue": "10.2.0",
|
||||
"happy-dom": "18.0.0",
|
||||
"jiti": "2.4.2",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-import": "16.1.1",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-tailwindcss": "0.6.14",
|
||||
"prettier": "3.5.3",
|
||||
"prettier-plugin-tailwindcss": "0.6.13",
|
||||
"rimraf": "6.0.1",
|
||||
"storybook": "9.0.17",
|
||||
"storybook": "9.0.16",
|
||||
"tailwind-rem-to-rem": "github:unraid/tailwind-rem-to-rem",
|
||||
"tailwindcss": "3.4.17",
|
||||
"tailwindcss-animate": "1.0.7",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.37.0",
|
||||
"vite": "7.0.4",
|
||||
"typescript-eslint": "8.34.1",
|
||||
"vite": "7.0.3",
|
||||
"vite-plugin-dts": "3.9.1",
|
||||
"vite-plugin-vue-devtools": "7.7.7",
|
||||
"vitest": "3.2.4",
|
||||
"vue": "3.5.17",
|
||||
"vue-tsc": "3.0.1",
|
||||
"wrangler": "4.24.3"
|
||||
"wrangler": "3.114.10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-linux-x64-gnu": "4.45.1"
|
||||
"@rollup/rollup-linux-x64-gnu": "4.44.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -139,5 +139,5 @@
|
||||
"import": "./dist/theme/preset.js"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.13.1"
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
}
|
||||
|
||||
@@ -764,323 +764,4 @@ describe('useServerStore', () => {
|
||||
expect(store.cloudError).toBeDefined();
|
||||
expect((store.cloudError as { message: string })?.message).toBe('Test error');
|
||||
});
|
||||
|
||||
describe('trial extension features', () => {
|
||||
it('should determine trial extension eligibility correctly', () => {
|
||||
const store = getStore();
|
||||
|
||||
// Add trialExtensionEligible property to the store
|
||||
Object.defineProperty(store, 'trialExtensionEligible', {
|
||||
get: () => !store.regGen || store.regGen < 2,
|
||||
});
|
||||
|
||||
// Eligible - no regGen
|
||||
store.setServer({ regGen: 0 });
|
||||
expect(store.trialExtensionEligible).toBe(true);
|
||||
|
||||
// Eligible - regGen = 1
|
||||
store.setServer({ regGen: 1 });
|
||||
expect(store.trialExtensionEligible).toBe(true);
|
||||
|
||||
// Not eligible - regGen = 2
|
||||
store.setServer({ regGen: 2 });
|
||||
expect(store.trialExtensionEligible).toBe(false);
|
||||
|
||||
// Not eligible - regGen > 2
|
||||
store.setServer({ regGen: 3 });
|
||||
expect(store.trialExtensionEligible).toBe(false);
|
||||
});
|
||||
|
||||
it('should calculate trial within 5 days of expiration correctly', () => {
|
||||
const store = getStore();
|
||||
|
||||
// Add properties to the store
|
||||
Object.defineProperty(store, 'expireTime', { value: 0, writable: true });
|
||||
Object.defineProperty(store, 'trialWithin5DaysOfExpiration', {
|
||||
get: () => {
|
||||
if (!store.expireTime || store.state !== 'TRIAL') {
|
||||
return false;
|
||||
}
|
||||
const today = dayjs();
|
||||
const expirationDate = dayjs(store.expireTime);
|
||||
const daysUntilExpiration = expirationDate.diff(today, 'day');
|
||||
return daysUntilExpiration <= 5 && daysUntilExpiration >= 0;
|
||||
},
|
||||
});
|
||||
|
||||
// Not a trial
|
||||
store.setServer({ state: 'PRO' as ServerState, expireTime: dayjs().add(3, 'day').unix() * 1000 });
|
||||
expect(store.trialWithin5DaysOfExpiration).toBe(false);
|
||||
|
||||
// Trial but no expireTime
|
||||
store.setServer({ state: 'TRIAL' as ServerState, expireTime: 0 });
|
||||
expect(store.trialWithin5DaysOfExpiration).toBe(false);
|
||||
|
||||
// Trial expiring in 3 days
|
||||
store.setServer({ state: 'TRIAL' as ServerState, expireTime: dayjs().add(3, 'day').unix() * 1000 });
|
||||
expect(store.trialWithin5DaysOfExpiration).toBe(true);
|
||||
|
||||
// Trial expiring in exactly 5 days
|
||||
store.setServer({ state: 'TRIAL' as ServerState, expireTime: dayjs().add(5, 'day').unix() * 1000 });
|
||||
expect(store.trialWithin5DaysOfExpiration).toBe(true);
|
||||
|
||||
// Trial expiring in 7 days (to ensure it's clearly outside the 5-day window)
|
||||
store.setServer({ state: 'TRIAL' as ServerState, expireTime: dayjs().add(7, 'day').unix() * 1000 });
|
||||
expect(store.trialWithin5DaysOfExpiration).toBe(false);
|
||||
|
||||
// Trial already expired
|
||||
store.setServer({ state: 'TRIAL' as ServerState, expireTime: dayjs().subtract(1, 'day').unix() * 1000 });
|
||||
expect(store.trialWithin5DaysOfExpiration).toBe(false);
|
||||
});
|
||||
|
||||
it('should calculate trial extension renewal window conditions correctly', () => {
|
||||
const store = getStore();
|
||||
|
||||
// Add all necessary properties
|
||||
Object.defineProperty(store, 'expireTime', { value: 0, writable: true });
|
||||
Object.defineProperty(store, 'trialExtensionEligible', {
|
||||
get: () => !store.regGen || store.regGen < 2,
|
||||
});
|
||||
Object.defineProperty(store, 'trialWithin5DaysOfExpiration', {
|
||||
get: () => {
|
||||
if (!store.expireTime || store.state !== 'TRIAL') {
|
||||
return false;
|
||||
}
|
||||
const today = dayjs();
|
||||
const expirationDate = dayjs(store.expireTime);
|
||||
const daysUntilExpiration = expirationDate.diff(today, 'day');
|
||||
return daysUntilExpiration <= 5 && daysUntilExpiration >= 0;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(store, 'trialExtensionEligibleInsideRenewalWindow', {
|
||||
get: () => store.trialExtensionEligible && store.trialWithin5DaysOfExpiration,
|
||||
});
|
||||
Object.defineProperty(store, 'trialExtensionEligibleOutsideRenewalWindow', {
|
||||
get: () => store.trialExtensionEligible && !store.trialWithin5DaysOfExpiration,
|
||||
});
|
||||
Object.defineProperty(store, 'trialExtensionIneligibleInsideRenewalWindow', {
|
||||
get: () => !store.trialExtensionEligible && store.trialWithin5DaysOfExpiration,
|
||||
});
|
||||
|
||||
// Eligible inside renewal window
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 1,
|
||||
expireTime: dayjs().add(3, 'day').unix() * 1000,
|
||||
});
|
||||
expect(store.trialExtensionEligibleInsideRenewalWindow).toBe(true);
|
||||
expect(store.trialExtensionEligibleOutsideRenewalWindow).toBe(false);
|
||||
expect(store.trialExtensionIneligibleInsideRenewalWindow).toBe(false);
|
||||
|
||||
// Eligible outside renewal window
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 1,
|
||||
expireTime: dayjs().add(10, 'day').unix() * 1000,
|
||||
});
|
||||
expect(store.trialExtensionEligibleInsideRenewalWindow).toBe(false);
|
||||
expect(store.trialExtensionEligibleOutsideRenewalWindow).toBe(true);
|
||||
expect(store.trialExtensionIneligibleInsideRenewalWindow).toBe(false);
|
||||
|
||||
// Ineligible inside renewal window
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 2,
|
||||
expireTime: dayjs().add(3, 'day').unix() * 1000,
|
||||
});
|
||||
expect(store.trialExtensionEligibleInsideRenewalWindow).toBe(false);
|
||||
expect(store.trialExtensionEligibleOutsideRenewalWindow).toBe(false);
|
||||
expect(store.trialExtensionIneligibleInsideRenewalWindow).toBe(true);
|
||||
|
||||
// Ineligible outside renewal window
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 2,
|
||||
expireTime: dayjs().add(10, 'day').unix() * 1000,
|
||||
});
|
||||
expect(store.trialExtensionEligibleInsideRenewalWindow).toBe(false);
|
||||
expect(store.trialExtensionEligibleOutsideRenewalWindow).toBe(false);
|
||||
expect(store.trialExtensionIneligibleInsideRenewalWindow).toBe(false);
|
||||
});
|
||||
|
||||
it('should display correct trial messages based on extension eligibility and renewal window', () => {
|
||||
const store = getStore();
|
||||
|
||||
// Add all necessary properties
|
||||
Object.defineProperty(store, 'expireTime', { value: 0, writable: true });
|
||||
Object.defineProperty(store, 'trialExtensionEligible', {
|
||||
get: () => !store.regGen || store.regGen < 2,
|
||||
});
|
||||
Object.defineProperty(store, 'trialWithin5DaysOfExpiration', {
|
||||
get: () => {
|
||||
if (!store.expireTime || store.state !== 'TRIAL') {
|
||||
return false;
|
||||
}
|
||||
const today = dayjs();
|
||||
const expirationDate = dayjs(store.expireTime);
|
||||
const daysUntilExpiration = expirationDate.diff(today, 'day');
|
||||
return daysUntilExpiration <= 5 && daysUntilExpiration >= 0;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(store, 'trialExtensionEligibleInsideRenewalWindow', {
|
||||
get: () => store.trialExtensionEligible && store.trialWithin5DaysOfExpiration,
|
||||
});
|
||||
Object.defineProperty(store, 'trialExtensionEligibleOutsideRenewalWindow', {
|
||||
get: () => store.trialExtensionEligible && !store.trialWithin5DaysOfExpiration,
|
||||
});
|
||||
Object.defineProperty(store, 'trialExtensionIneligibleInsideRenewalWindow', {
|
||||
get: () => !store.trialExtensionEligible && store.trialWithin5DaysOfExpiration,
|
||||
});
|
||||
|
||||
// Mock stateData getter to include trial message logic
|
||||
Object.defineProperty(store, 'stateData', {
|
||||
get: () => {
|
||||
if (store.state !== 'TRIAL') {
|
||||
return {
|
||||
humanReadable: '',
|
||||
heading: '',
|
||||
message: '',
|
||||
actions: [],
|
||||
};
|
||||
}
|
||||
|
||||
let trialMessage = '';
|
||||
if (store.trialExtensionEligibleInsideRenewalWindow) {
|
||||
trialMessage = '<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>Your trial is expiring soon. When it expires, <strong>the array will stop</strong>. You may extend your trial now, purchase a license key, or wait until expiration to take action.</p>';
|
||||
} else if (store.trialExtensionIneligibleInsideRenewalWindow) {
|
||||
trialMessage = '<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>Your trial is expiring soon and you have used all available extensions. When it expires, <strong>the array will stop</strong>. To continue using Unraid OS, you must purchase a license key.</p>';
|
||||
} else if (store.trialExtensionEligibleOutsideRenewalWindow) {
|
||||
trialMessage = '<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>When your <em>Trial</em> expires, <strong>the array will stop</strong>. At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>';
|
||||
} else {
|
||||
trialMessage = '<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>You have used all available trial extensions. When your <em>Trial</em> expires, <strong>the array will stop</strong>. To continue using Unraid OS after expiration, you must purchase a license key.</p>';
|
||||
}
|
||||
|
||||
return {
|
||||
humanReadable: 'Trial',
|
||||
heading: 'Thank you for choosing Unraid OS!',
|
||||
message: trialMessage,
|
||||
actions: [],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Test case 1: Eligible inside renewal window
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 1,
|
||||
expireTime: dayjs().add(3, 'day').unix() * 1000,
|
||||
});
|
||||
expect(store.stateData.message).toContain('Your trial is expiring soon');
|
||||
expect(store.stateData.message).toContain('You may extend your trial now');
|
||||
|
||||
// Test case 2: Ineligible inside renewal window
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 2,
|
||||
expireTime: dayjs().add(3, 'day').unix() * 1000,
|
||||
});
|
||||
expect(store.stateData.message).toContain('Your trial is expiring soon and you have used all available extensions');
|
||||
expect(store.stateData.message).toContain('To continue using Unraid OS, you must purchase a license key');
|
||||
|
||||
// Test case 3: Eligible outside renewal window
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 0,
|
||||
expireTime: dayjs().add(10, 'day').unix() * 1000,
|
||||
});
|
||||
expect(store.stateData.message).toContain('At that point you may either purchase a license key or request a <em>Trial</em> extension');
|
||||
|
||||
// Test case 4: Ineligible outside renewal window
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 2,
|
||||
expireTime: dayjs().add(10, 'day').unix() * 1000,
|
||||
});
|
||||
expect(store.stateData.message).toContain('You have used all available trial extensions');
|
||||
expect(store.stateData.message).toContain('To continue using Unraid OS after expiration, you must purchase a license key');
|
||||
});
|
||||
|
||||
it('should include trial extend action only when eligible inside renewal window', () => {
|
||||
const store = getStore();
|
||||
|
||||
// Add necessary properties
|
||||
Object.defineProperty(store, 'expireTime', { value: 0, writable: true });
|
||||
Object.defineProperty(store, 'trialExtensionEligible', {
|
||||
get: () => !store.regGen || store.regGen < 2,
|
||||
});
|
||||
Object.defineProperty(store, 'trialWithin5DaysOfExpiration', {
|
||||
get: () => {
|
||||
if (!store.expireTime || store.state !== 'TRIAL') {
|
||||
return false;
|
||||
}
|
||||
const today = dayjs();
|
||||
const expirationDate = dayjs(store.expireTime);
|
||||
const daysUntilExpiration = expirationDate.diff(today, 'day');
|
||||
return daysUntilExpiration <= 5 && daysUntilExpiration >= 0;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(store, 'trialExtensionEligibleInsideRenewalWindow', {
|
||||
get: () => store.trialExtensionEligible && store.trialWithin5DaysOfExpiration,
|
||||
});
|
||||
|
||||
// Mock the trialExtendAction
|
||||
const trialExtendAction = { name: 'trialExtend', text: 'Extend Trial' };
|
||||
|
||||
// Mock stateData getter to include actions logic
|
||||
Object.defineProperty(store, 'stateData', {
|
||||
get: () => {
|
||||
if (store.state !== 'TRIAL') {
|
||||
return {
|
||||
humanReadable: '',
|
||||
heading: '',
|
||||
message: '',
|
||||
actions: [],
|
||||
};
|
||||
}
|
||||
|
||||
const actions = [];
|
||||
if (store.trialExtensionEligibleInsideRenewalWindow) {
|
||||
actions.push(trialExtendAction);
|
||||
}
|
||||
|
||||
return {
|
||||
humanReadable: 'Trial',
|
||||
heading: 'Thank you for choosing Unraid OS!',
|
||||
message: '',
|
||||
actions,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Test case 1: Eligible inside renewal window - should include trialExtend action
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 1,
|
||||
expireTime: dayjs().add(3, 'day').unix() * 1000,
|
||||
registered: true,
|
||||
connectPluginInstalled: 'true' as ServerconnectPluginInstalled,
|
||||
});
|
||||
expect(store.stateData.actions?.some((action: { name: string }) => action.name === 'trialExtend')).toBe(true);
|
||||
|
||||
// Test case 2: Not eligible inside renewal window - should NOT include trialExtend action
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 2,
|
||||
expireTime: dayjs().add(3, 'day').unix() * 1000,
|
||||
registered: true,
|
||||
connectPluginInstalled: 'true' as ServerconnectPluginInstalled,
|
||||
});
|
||||
expect(store.stateData.actions?.some((action: { name: string }) => action.name === 'trialExtend')).toBe(false);
|
||||
|
||||
// Test case 3: Eligible outside renewal window - should NOT include trialExtend action
|
||||
store.setServer({
|
||||
state: 'TRIAL' as ServerState,
|
||||
regGen: 1,
|
||||
expireTime: dayjs().add(10, 'day').unix() * 1000,
|
||||
registered: true,
|
||||
connectPluginInstalled: 'true' as ServerconnectPluginInstalled,
|
||||
});
|
||||
expect(store.stateData.actions?.some((action: { name: string }) => action.name === 'trialExtend')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,10 +23,6 @@
|
||||
"<p>To support more storage devices as your server grows, click Upgrade Key.</p>": "<p>To support more storage devices as your server grows, click Upgrade Key.</p>",
|
||||
"<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>": "<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>": "<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>When your <em>Trial</em> expires, <strong>the array will stop</strong>. At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>": "<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>When your <em>Trial</em> expires, <strong>the array will stop</strong>. At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>Your trial is expiring soon. When it expires, <strong>the array will stop</strong>. You may extend your trial now, purchase a license key, or wait until expiration to take action.</p>": "<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>Your trial is expiring soon. When it expires, <strong>the array will stop</strong>. You may extend your trial now, purchase a license key, or wait until expiration to take action.</p>",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>Your trial is expiring soon and you have used all available extensions. When it expires, <strong>the array will stop</strong>. To continue using Unraid OS, you must purchase a license key.</p>": "<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>Your trial is expiring soon and you have used all available extensions. When it expires, <strong>the array will stop</strong>. To continue using Unraid OS, you must purchase a license key.</p>",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>You have used all available trial extensions. When your <em>Trial</em> expires, <strong>the array will stop</strong>. To continue using Unraid OS after expiration, you must purchase a license key.</p>": "<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>You have used all available trial extensions. When your <em>Trial</em> expires, <strong>the array will stop</strong>. To continue using Unraid OS after expiration, you must purchase a license key.</p>",
|
||||
"<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>": "<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>",
|
||||
"<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>": "<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>",
|
||||
"<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of an Unleashed Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>": "<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of an Unleashed Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/web",
|
||||
"version": "4.10.0",
|
||||
"version": "4.9.4",
|
||||
"private": true,
|
||||
"license": "GPL-2.0-or-later",
|
||||
"scripts": {
|
||||
@@ -39,13 +39,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.7",
|
||||
"@graphql-codegen/client-preset": "4.8.3",
|
||||
"@graphql-codegen/client-preset": "4.8.2",
|
||||
"@graphql-codegen/introspection": "4.0.3",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.5.1",
|
||||
"@nuxt/devtools": "2.6.2",
|
||||
"@nuxt/eslint": "1.5.2",
|
||||
"@nuxt/test-utils": "3.19.2",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.4.2",
|
||||
"@nuxt/devtools": "2.5.0",
|
||||
"@nuxt/eslint": "1.4.1",
|
||||
"@nuxt/test-utils": "3.19.1",
|
||||
"@nuxtjs/tailwindcss": "6.14.0",
|
||||
"@pinia/testing": "1.0.2",
|
||||
"@rollup/plugin-strip": "3.0.4",
|
||||
@@ -53,34 +53,34 @@
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/eslint-config-prettier": "6.11.3",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/node": "22.15.32",
|
||||
"@types/semver": "7.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
||||
"@unraid/tailwind-rem-to-rem": "1.1.0",
|
||||
"@vitejs/plugin-vue": "6.0.0",
|
||||
"@vitejs/plugin-vue": "5.2.4",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vue/apollo-util": "4.2.2",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"@vueuse/core": "13.5.0",
|
||||
"@vueuse/nuxt": "13.5.0",
|
||||
"eslint": "9.31.0",
|
||||
"@vueuse/core": "13.4.0",
|
||||
"@vueuse/nuxt": "13.4.0",
|
||||
"eslint": "9.29.0",
|
||||
"eslint-config-prettier": "10.1.5",
|
||||
"eslint-import-resolver-typescript": "4.4.4",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"happy-dom": "18.0.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"happy-dom": "18.0.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"nuxt": "3.17.7",
|
||||
"nuxt": "3.17.5",
|
||||
"nuxt-custom-elements": "2.0.0-beta.32",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-tailwindcss": "0.6.14",
|
||||
"prettier": "3.5.3",
|
||||
"prettier-plugin-tailwindcss": "0.6.13",
|
||||
"shadcn-nuxt": "2.2.0",
|
||||
"tailwindcss": "3.4.17",
|
||||
"tailwindcss-animate": "1.0.7",
|
||||
"terser": "5.43.1",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.0.4",
|
||||
"vite": "7.0.3",
|
||||
"vite-plugin-remove-console": "2.2.0",
|
||||
"vite-plugin-vue-tracer": "1.0.0",
|
||||
"vite-plugin-vue-tracer": "0.1.4",
|
||||
"vitest": "3.2.4",
|
||||
"vue": "3.5.17",
|
||||
"vue-tsc": "3.0.1",
|
||||
@@ -88,9 +88,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "3.13.8",
|
||||
"@floating-ui/dom": "1.7.2",
|
||||
"@floating-ui/utils": "0.2.10",
|
||||
"@floating-ui/vue": "1.1.7",
|
||||
"@floating-ui/dom": "1.7.1",
|
||||
"@floating-ui/utils": "0.2.9",
|
||||
"@floating-ui/vue": "1.1.6",
|
||||
"@headlessui/vue": "1.7.23",
|
||||
"@heroicons/vue": "2.2.0",
|
||||
"@jsonforms/core": "3.6.0",
|
||||
@@ -102,8 +102,8 @@
|
||||
"@unraid/shared-callbacks": "1.1.1",
|
||||
"@unraid/ui": "link:../unraid-ui",
|
||||
"@vue/apollo-composable": "4.2.2",
|
||||
"@vueuse/components": "13.5.0",
|
||||
"@vueuse/integrations": "13.5.0",
|
||||
"@vueuse/components": "13.4.0",
|
||||
"@vueuse/integrations": "13.4.0",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"crypto-js": "4.2.0",
|
||||
@@ -111,26 +111,26 @@
|
||||
"focus-trap": "7.6.5",
|
||||
"graphql": "16.11.0",
|
||||
"graphql-tag": "2.12.6",
|
||||
"graphql-ws": "6.0.6",
|
||||
"graphql-ws": "6.0.5",
|
||||
"hex-to-rgba": "2.0.1",
|
||||
"highlight.js": "11.11.1",
|
||||
"isomorphic-dompurify": "2.26.0",
|
||||
"lucide-vue-next": "0.525.0",
|
||||
"isomorphic-dompurify": "2.25.0",
|
||||
"lucide-vue-next": "0.519.0",
|
||||
"marked": "16.0.0",
|
||||
"marked-base-url": "1.1.7",
|
||||
"marked-base-url": "1.1.6",
|
||||
"pinia": "3.0.3",
|
||||
"semver": "7.7.2",
|
||||
"tailwind-merge": "2.6.0",
|
||||
"vue-i18n": "11.1.9",
|
||||
"vue-i18n": "11.1.6",
|
||||
"vue-web-component-wrapper": "1.7.7",
|
||||
"vuetify": "3.9.0",
|
||||
"vuetify": "3.8.10",
|
||||
"wretch": "2.11.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-linux-x64-gnu": "4.45.1"
|
||||
"@rollup/rollup-linux-x64-gnu": "4.44.0"
|
||||
},
|
||||
"overrides": {
|
||||
"vue": "latest"
|
||||
},
|
||||
"packageManager": "pnpm@10.13.1"
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
}
|
||||
|
||||
@@ -495,7 +495,6 @@ export const useServerStore = defineStore('server', () => {
|
||||
});
|
||||
|
||||
let messageEGUID = '';
|
||||
let trialMessage = '';
|
||||
const stateData = computed((): ServerStateData => {
|
||||
switch (state.value) {
|
||||
case 'ENOKEYFILE':
|
||||
@@ -511,26 +510,16 @@ export const useServerStore = defineStore('server', () => {
|
||||
'<p>Choose an option below, then use our <a href="https://unraid.net/getting-started" target="_blank" rel="noreffer noopener">Getting Started Guide</a> to configure your array in less than 15 minutes.</p>',
|
||||
};
|
||||
case 'TRIAL':
|
||||
if (trialExtensionEligibleInsideRenewalWindow.value) {
|
||||
trialMessage = '<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>Your trial is expiring soon. When it expires, <strong>the array will stop</strong>. You may extend your trial now, purchase a license key, or wait until expiration to take action.</p>';
|
||||
} else if (trialExtensionIneligibleInsideRenewalWindow.value) {
|
||||
trialMessage = '<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>Your trial is expiring soon and you have used all available extensions. When it expires, <strong>the array will stop</strong>. To continue using Unraid OS, you must purchase a license key.</p>';
|
||||
} else if (trialExtensionEligibleOutsideRenewalWindow.value) {
|
||||
trialMessage = '<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>When your <em>Trial</em> expires, <strong>the array will stop</strong>. At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>';
|
||||
} else { // would be trialExtensionIneligibleOutsideRenewalWindow if it wasn't an else conditionally
|
||||
trialMessage = '<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>You have used all available trial extensions. When your <em>Trial</em> expires, <strong>the array will stop</strong>. To continue using Unraid OS after expiration, you must purchase a license key.</p>';
|
||||
}
|
||||
|
||||
return {
|
||||
actions: [
|
||||
...(!registered.value && connectPluginInstalled.value ? [signInAction.value] : []),
|
||||
...[purchaseAction.value, redeemAction.value],
|
||||
...(trialExtensionEligibleInsideRenewalWindow.value ? [trialExtendAction.value] : []),
|
||||
...(registered.value && connectPluginInstalled.value ? [signOutAction.value] : []),
|
||||
],
|
||||
humanReadable: 'Trial',
|
||||
heading: 'Thank you for choosing Unraid OS!',
|
||||
message: trialMessage,
|
||||
message:
|
||||
'<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>',
|
||||
};
|
||||
case 'EEXPIRED':
|
||||
return {
|
||||
@@ -784,18 +773,6 @@ export const useServerStore = defineStore('server', () => {
|
||||
return stateData.value.actions.filter((action) => !authActionsNames.includes(action.name));
|
||||
});
|
||||
const trialExtensionEligible = computed(() => !regGen.value || regGen.value < 2);
|
||||
const trialWithin5DaysOfExpiration = computed(() => {
|
||||
if (!expireTime.value || state.value !== 'TRIAL') {
|
||||
return false;
|
||||
}
|
||||
const today = dayjs();
|
||||
const expirationDate = dayjs(expireTime.value);
|
||||
const daysUntilExpiration = expirationDate.diff(today, 'day');
|
||||
return daysUntilExpiration <= 5 && daysUntilExpiration >= 0;
|
||||
});
|
||||
const trialExtensionEligibleInsideRenewalWindow = computed(() => trialExtensionEligible.value && trialWithin5DaysOfExpiration.value);
|
||||
const trialExtensionEligibleOutsideRenewalWindow = computed(() => trialExtensionEligible.value && !trialWithin5DaysOfExpiration.value);
|
||||
const trialExtensionIneligibleInsideRenewalWindow = computed(() => !trialExtensionEligible.value && trialWithin5DaysOfExpiration.value);
|
||||
|
||||
const serverConfigError = computed((): Error | undefined => {
|
||||
if (!config.value?.valid && config.value?.error) {
|
||||
@@ -1198,9 +1175,7 @@ export const useServerStore = defineStore('server', () => {
|
||||
|
||||
setTimeout(() => {
|
||||
load();
|
||||
if (connectPluginInstalled.value) {
|
||||
loadCloudState();
|
||||
}
|
||||
loadCloudState();
|
||||
}, 500);
|
||||
|
||||
onResult((result) => {
|
||||
|
||||
Reference in New Issue
Block a user