Compare commits
13 Commits
master
...
stable-bul
Author | SHA1 | Date | |
---|---|---|---|
![]() |
aa976ad3e5 | ||
![]() |
931d9bfdfa | ||
![]() |
ad53b6dd22 | ||
![]() |
03f664dd2d | ||
![]() |
13b101437c | ||
![]() |
07f7518735 | ||
![]() |
f7b2ed4f3a | ||
![]() |
083c6f1d1a | ||
![]() |
ae66538726 | ||
![]() |
a5106662fb | ||
![]() |
62de2763ed | ||
![]() |
622ba1f9e2 | ||
![]() |
93f8588e41 |
12
.gitignore
vendored
12
.gitignore
vendored
@ -1,10 +1,4 @@
|
||||
*.buildinfo
|
||||
*.changes
|
||||
*.deb
|
||||
*.dsc
|
||||
*.tar.xz
|
||||
/proxmox-widget-toolkit-[0-9]*/
|
||||
src/.lint-incremental
|
||||
src/proxmox-dark/theme-proxmox-dark.css
|
||||
src/proxmoxlib.js
|
||||
src/proxmoxlib.min.js
|
||||
*.deb
|
||||
*.changes
|
||||
*.buildinfo
|
||||
|
370
debian/changelog
vendored
370
debian/changelog
vendored
@ -1,382 +1,30 @@
|
||||
proxmox-widget-toolkit (4.3.7) bookworm; urgency=medium
|
||||
|
||||
* authentication realm edit: use correct property to derive the realm type.
|
||||
|
||||
* authentication view: allow downstream users to override the API path to
|
||||
query available authentication realms from.
|
||||
|
||||
* from: realm combobox: allow downstream users to override the API path to
|
||||
query available authentication realms from.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 26 Feb 2025 19:12:24 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.3.6) bookworm; urgency=medium
|
||||
|
||||
* object grid: fix onlineHelp setting from editorConfig for row editors
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 25 Feb 2025 18:08:50 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.3.5) bookworm; urgency=medium
|
||||
|
||||
* add form-field component for entering certificate fingerprints
|
||||
|
||||
* fix #6088: notification: matcher: use more descriptive strings
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 25 Feb 2025 17:08:11 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.3.4) bookworm; urgency=medium
|
||||
|
||||
* textarea field: add emptyText message to show markdown is supported
|
||||
|
||||
* add missing htmlEncode for some UI elements
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 20 Jan 2025 11:38:34 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.3.3) bookworm; urgency=medium
|
||||
|
||||
* display-edit field: add emptyText getter and setter to support binding to
|
||||
the property of the underlying edit-field directly.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 27 Nov 2024 12:25:44 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.3.2) bookworm; urgency=medium
|
||||
|
||||
* node: service state: restore original behavior
|
||||
|
||||
* window: add consent modal widget
|
||||
|
||||
* object grid: add support for multiline textarea widget
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 25 Nov 2024 18:32:21 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.3.1) bookworm; urgency=medium
|
||||
|
||||
* peraration to fix #5379: panel: authentication realm view: add opt-in
|
||||
column displaying whether the realm is default and allow enabling it for a
|
||||
realm
|
||||
|
||||
* various UX improvements for the webhook edit window:
|
||||
- improve layout and component hierarchy
|
||||
- use type in add button text
|
||||
- show empty-text to key-value fields
|
||||
- display validity for added key/value fields immediately
|
||||
|
||||
* add Bulgarian as available language
|
||||
|
||||
* dark theme: make icons in the permissions tree in Proxmox VE UI dark
|
||||
|
||||
* fix #3892: network: add bridge VIDs field for Linux bridge and enable them
|
||||
if VLAN-aware is enabled.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 19 Nov 2024 12:40:04 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.3.0) bookworm; urgency=medium
|
||||
|
||||
* css: add some conditions to the tag classes for the tag view
|
||||
|
||||
* utils: add base64 conversion helper
|
||||
|
||||
* notification: add UI for adding/updating webhook targets
|
||||
|
||||
* fix #5836: ui: translate systemd states in system service view
|
||||
|
||||
* fix #5611: node service view: hide non-installed system services by
|
||||
default
|
||||
|
||||
* password edit: allow one to override the minimum length parameter
|
||||
|
||||
* fix #5831: ui: right-align s.m.a.r.t numerical table data
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 11 Nov 2024 21:57:31 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.2.4) bookworm; urgency=medium
|
||||
|
||||
* notification: matcher: match-field: show known fields/values
|
||||
|
||||
* notification: matcher: move match-severity and match-calendar fields to
|
||||
panel
|
||||
|
||||
* css: dark theme: fix panel borders for the Proxmox Mail Gateway's EOL
|
||||
notice widget
|
||||
|
||||
* fix opening a link from another site to a web UI of our products by
|
||||
setting the auth-cookie's 'SameSite' attribute to lax, which is the safe
|
||||
default in modern browsers.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 16 Oct 2024 18:54:37 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.2.3) bookworm; urgency=medium
|
||||
|
||||
* realm edit: don't send type as extra parameter when 'useTypeInUrl' is set
|
||||
|
||||
* realm edit: don't send 'delete' parameter when creating new entry
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 25 Apr 2024 11:45:12 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.2.2) bookworm; urgency=medium
|
||||
|
||||
* form: move network VLAN field widget over from PVE
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 24 Apr 2024 21:44:12 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.2.1) bookworm; urgency=medium
|
||||
|
||||
* fix #5251: tfa: set one-time-code auto-complete hint on TOTP input field
|
||||
|
||||
* sendmail: smtp: allow one to override the default mail author
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 23 Apr 2024 19:25:06 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.2.0) bookworm; urgency=medium
|
||||
|
||||
* window: add widget for Active Directory specific LDAP-like authentication
|
||||
|
||||
* window: ldap: add tooltips for firstname, lastname and email attributes
|
||||
|
||||
* dark-mode: set intentionally black icons to `$icon-color`
|
||||
|
||||
* window: edit: avoid sharing custom config objects between subclasses
|
||||
|
||||
* i18n: make various user-facing strings translatable
|
||||
|
||||
* remove button: allow one to set custom confirmation message
|
||||
|
||||
* notify: change 'Remove' button to 'Reset' for built-in targets
|
||||
|
||||
* css: correctly mask disabled elements inside headers
|
||||
|
||||
* fix #5277: move reset button into window header toolbar. The form reset
|
||||
and the form submit button where located besides each other with the exact
|
||||
same styling. This made it easy to click the wrong one by accident, while
|
||||
most of the time not a huge issue, it's quite annoying and just
|
||||
unnecessary to do it this way. Moving the reset functionality into the
|
||||
header, besides the close tool, avoid this potential mis-click and makes
|
||||
the form simpler in general. Mis-clicks between the close and the reset
|
||||
tool in the header are not an issue because they both have the same level
|
||||
of destructiveness w.r.t. pending data. Using the font-awesome undo icon
|
||||
makes it quite clear and a tooltip, which is also exposed as ARIA label,
|
||||
helps to further improve accessibility.
|
||||
|
||||
* notes view:
|
||||
- place collapse tool on the right
|
||||
- use pencil-square-o icon for opening the editor
|
||||
- make opening the editor on double-click opt-in to avoid interfering with
|
||||
word-boundary selection of text by default.
|
||||
|
||||
* network edit: allow bridges to have any valid interface name
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sun, 21 Apr 2024 12:31:26 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.1.5) bookworm; urgency=medium
|
||||
|
||||
* dns: provide option to change behavior from sending the new, full actual
|
||||
state to what actually changed. This fixes deleting entries in APIs like
|
||||
the one from PBS.
|
||||
|
||||
* edit window: add optional custom submit options
|
||||
|
||||
* certificates: removal prompt: don't display name if there is no name
|
||||
|
||||
* utils: api request: defer masking after layout
|
||||
|
||||
* window: password edit: add opt-in config to show a confirmation-password
|
||||
field for the current user
|
||||
|
||||
* window: password edit: clarify labels
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 21 Mar 2024 17:40:54 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.1.4) bookworm; urgency=medium
|
||||
|
||||
* fix #5074: notify: sendmail smtp: fix mailto/mailto-user parameter
|
||||
deletion
|
||||
|
||||
* i18n: use correct ISO 639-1 code for Korean with backward compat
|
||||
|
||||
* form: date time: fix changing date to end of month from a longer to a
|
||||
shorter month
|
||||
|
||||
* form: combo grid: allow one to force showing a clear trigger through a new
|
||||
showClearTrigger config value
|
||||
|
||||
* utils: add mechanism to add and override translatable notification event
|
||||
descriptions in the product specific UIs
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 28 Feb 2024 11:46:31 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.1.3) bookworm; urgency=medium
|
||||
|
||||
* notification ui: change icon for match-field tree nodes to avoid clash
|
||||
with use for LXC containers
|
||||
|
||||
* notification ui: display yellow warning triangle instead of red icon if
|
||||
the repesctive entry is not valid
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 23 Nov 2023 10:12:50 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.1.2) bookworm; urgency=medium
|
||||
|
||||
* notification matcher: fix inverted match modes
|
||||
|
||||
* notification ui: add appropriate onlineHelp anchors
|
||||
|
||||
* notification ui: add 'unknown' to match-severity dropdown
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 21 Nov 2023 21:35:56 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.1.1) bookworm; urgency=medium
|
||||
|
||||
* api-viewer: implement basic oneOf support
|
||||
|
||||
* form: displaye-edit: add one of the two missing returns
|
||||
|
||||
* notification ui: rework for new matcher based approach, drop old filter
|
||||
and grouping widgets
|
||||
|
||||
* notification: matcher: add UI for matcher editing
|
||||
|
||||
* panel: notification: add gui for SMTP endpoints
|
||||
|
||||
* notification ui: add enable checkbox for targets/matchers
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 17 Nov 2023 16:56:06 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.1.0) bookworm; urgency=medium
|
||||
|
||||
* text field: add trimValue option to auto-trim leading and trailing
|
||||
whitespace from the to be submitted value
|
||||
|
||||
* schema: endpoint types: don't translate endpoint type names
|
||||
|
||||
* adapt the date time field to be more declarative
|
||||
|
||||
* fix #4442: extend the log view for firewall to allow filtering by a
|
||||
date-time range
|
||||
|
||||
* disk list: render osdid-list if present
|
||||
|
||||
* file-level restore: enable the download-as-tar button by default, all use
|
||||
sites support that feature now.
|
||||
|
||||
* apt updates: drop handling the ChangeLogUrl, it's not returned anymore
|
||||
|
||||
* combo grid: initialize value with [] by default to avoid glitches where
|
||||
ExtJS sometimes does not finishes with initializing everything
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 14 Nov 2023 09:11:23 +0100
|
||||
|
||||
proxmox-widget-toolkit (4.0.9) bookworm; urgency=medium
|
||||
|
||||
* fix using gettext with parameter in various newly added notification and
|
||||
sendmail widgets
|
||||
|
||||
* parser: split checking IMG and A tags, make the latter more strict
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 03 Oct 2023 10:39:31 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.8) bookworm; urgency=medium
|
||||
|
||||
* fix #4531: acme plugins: correct change detection of dirty form fields
|
||||
|
||||
* fix #4951: accept undefined as value for the multi-disk selector
|
||||
|
||||
* auth: ldap: openid: use our proxmox textfield variant for the comment
|
||||
field to avoid that an empty comments are saved in the config
|
||||
|
||||
* utils: language map: add entry for new Croatian translation
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 13 Sep 2023 17:16:01 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.7) bookworm; urgency=medium
|
||||
|
||||
* fix #4874: improve error message for invalid hostname
|
||||
|
||||
* add entry for Georgian translation
|
||||
|
||||
* fix the UI not refreshing after successful certificate deletion
|
||||
|
||||
* add missing htmlEncode calls to network selector and apt repository panel
|
||||
|
||||
* add panels for notification system:
|
||||
- sendmail endpoint panel
|
||||
- gotify endpoint panel
|
||||
- notification group management panel
|
||||
- notification filter management panel
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 16 Aug 2023 10:43:02 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.6) bookworm; urgency=medium
|
||||
|
||||
* LDAP realm edit: forbid specifying a bind_dn without a password
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 26 Jun 2023 20:24:57 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.5) bookworm; urgency=medium
|
||||
|
||||
* add Українська - Ukrainian to language map
|
||||
|
||||
* apt repositories: add production ready warnings for Ceph repositories
|
||||
|
||||
* add TOTP second factor: increase the size of the quiet zone for the QR
|
||||
code, following general recommendation to make scanning it more robust
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 16 Jun 2023 15:58:27 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.4) bookworm; urgency=medium
|
||||
proxmox-widget-toolkit (3.7.3) bullseye; urgency=medium
|
||||
|
||||
* apt repositories: fix typo for getting the default unknown text
|
||||
|
||||
* apt repositories: avoid potential type error in classifyOrigin helper
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jun 2023 17:29:44 +0200
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 13 Jun 2023 12:25:28 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.3) bookworm; urgency=medium
|
||||
|
||||
* date time field: fix typo in xtype name but add alias for backward compat
|
||||
|
||||
* set 'SameSite' attr of auth cookie to 'strict', which modern browsers
|
||||
already ensured in practice
|
||||
proxmox-widget-toolkit (3.7.2) bullseye; urgency=medium
|
||||
|
||||
* apt repositories: actually ignore ignore-pre-upgrade-warning
|
||||
|
||||
* apt repositories: add classifyOrigin helper
|
||||
|
||||
* apt repositories: just ignore unknown info rather than throwing an error
|
||||
|
||||
* apt repositories: detect mixed suites before major upgrade
|
||||
|
||||
* tfa: improve UX for recovery keys and when none are left
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jun 2023 13:00:41 +0200
|
||||
|
||||
* tfa: show 'Locked' in 'Enabled' column if tfa is locked
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jun 2023 08:07:01 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.2) bookworm; urgency=medium
|
||||
|
||||
* markdown parser: allow setting "target" attribute for links
|
||||
|
||||
* fix #4756: markdown parser: allow any valid URL for link (a) tags,
|
||||
allowing one to add short-cuts like a RDP url to quickly open the remote
|
||||
viewer.
|
||||
|
||||
* ship a minified version of the widget-toolkit JS library
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sat, 03 Jun 2023 13:45:27 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.1) bookworm; urgency=medium
|
||||
proxmox-widget-toolkit (3.7.1) bullseye; urgency=medium
|
||||
|
||||
* tfa: paperkey: cleanup iframes for printing after window close
|
||||
|
||||
* parser: adapt to calling convention of updated Markdown renderer library
|
||||
"marked" has since its v4.0.0
|
||||
* apt repositoires: allow major upgrade
|
||||
|
||||
* fix #4551: ui: translate byte unit in `format_size`
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 01 Jun 2023 16:35:32 +0200
|
||||
|
||||
proxmox-widget-toolkit (4.0.0) bookworm; urgency=medium
|
||||
|
||||
* re-build for Debian 12 Bookworm based releases
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 25 May 2023 09:13:29 +0200
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 07 Jun 2023 17:25:57 +0200
|
||||
|
||||
proxmox-widget-toolkit (3.7.0) bullseye; urgency=medium
|
||||
|
||||
|
5
debian/control
vendored
5
debian/control
vendored
@ -2,12 +2,11 @@ Source: proxmox-widget-toolkit
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
Build-Depends: debhelper-compat (= 12),
|
||||
libjs-marked,
|
||||
pve-eslint (>= 7.28.0),
|
||||
sassc,
|
||||
uglifyjs,
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.5.1
|
||||
Homepage: https://www.proxmox.com
|
||||
|
||||
Package: proxmox-widget-toolkit
|
||||
|
38
src/Makefile
38
src/Makefile
@ -1,12 +1,10 @@
|
||||
include defines.mk
|
||||
|
||||
ESLINT ?= $(if $(shell command -v pve-eslint), pve-eslint, eslint)
|
||||
|
||||
SUBDIRS= css images proxmox-dark
|
||||
|
||||
# bundle it for now from the libjs-marked debian package to avoid touching our proxies file mapper,
|
||||
# we could also just ship a link to the packages file and load from same path as the widget-toolkit
|
||||
MARKEDJS=/usr/share/javascript/marked/marked.js
|
||||
MARKEDJS=/usr/share/javascript/marked/marked.min.js
|
||||
|
||||
JSSRC= \
|
||||
Utils.js \
|
||||
@ -22,7 +20,6 @@ JSSRC= \
|
||||
data/ObjectStore.js \
|
||||
data/RRDStore.js \
|
||||
data/TimezoneStore.js \
|
||||
data/model/NotificationConfig.js \
|
||||
data/model/Realm.js \
|
||||
data/model/Certificates.js \
|
||||
data/model/ACME.js \
|
||||
@ -31,8 +28,6 @@ JSSRC= \
|
||||
form/ExpireDate.js \
|
||||
form/IntegerField.js \
|
||||
form/TextField.js \
|
||||
form/TextAreaField.js \
|
||||
form/VlanField.js \
|
||||
form/DateTimeField.js \
|
||||
form/Checkbox.js \
|
||||
form/KVComboBox.js \
|
||||
@ -50,7 +45,6 @@ JSSRC= \
|
||||
form/ACME.js \
|
||||
form/UserSelector.js \
|
||||
form/ThemeSelector.js \
|
||||
form/FingerprintField.js \
|
||||
button/Button.js \
|
||||
button/AltText.js \
|
||||
button/HelpButton.js \
|
||||
@ -63,24 +57,18 @@ JSSRC= \
|
||||
panel/InfoWidget.js \
|
||||
panel/LogView.js \
|
||||
panel/NodeInfoRepoStatus.js \
|
||||
panel/NotificationConfigView.js \
|
||||
panel/JournalView.js \
|
||||
panel/PermissionView.js \
|
||||
panel/PruneKeepPanel.js \
|
||||
panel/RRDChart.js \
|
||||
panel/GaugeWidget.js \
|
||||
panel/GotifyEditPanel.js \
|
||||
panel/Certificates.js \
|
||||
panel/ACMEAccount.js \
|
||||
panel/ACMEPlugin.js \
|
||||
panel/ACMEDomains.js \
|
||||
panel/EmailRecipientPanel.js \
|
||||
panel/SendmailEditPanel.js \
|
||||
panel/SmtpEditPanel.js \
|
||||
panel/StatusView.js \
|
||||
panel/TfaView.js \
|
||||
panel/NotesView.js \
|
||||
panel/WebhookEditPanel.js \
|
||||
window/Edit.js \
|
||||
window/PasswordEdit.js \
|
||||
window/SafeDestroy.js \
|
||||
@ -90,18 +78,13 @@ JSSRC= \
|
||||
window/DiskSmart.js \
|
||||
window/ZFSDetail.js \
|
||||
window/Certificates.js \
|
||||
window/ConsentModal.js \
|
||||
window/ACMEAccount.js \
|
||||
window/ACMEPluginEdit.js \
|
||||
window/ACMEDomains.js \
|
||||
window/EndpointEditBase.js \
|
||||
window/NotificationMatcherEdit.js \
|
||||
window/FileBrowser.js \
|
||||
window/AuthEditBase.js \
|
||||
window/AuthEditOpenId.js \
|
||||
window/AuthEditLDAP.js \
|
||||
window/AuthEditAD.js \
|
||||
window/AuthEditSimple.js \
|
||||
window/TfaWindow.js \
|
||||
window/AddTfaRecovery.js \
|
||||
window/AddTotp.js \
|
||||
@ -127,34 +110,29 @@ all: $(SUBDIRS)
|
||||
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i; done
|
||||
|
||||
.lint-incremental: $(JSSRC)
|
||||
$(ESLINT) $?
|
||||
eslint $?
|
||||
touch "$@"
|
||||
|
||||
.PHONY: lint
|
||||
check: lint
|
||||
$(ESLINT) --strict api-viewer/APIViewer.js
|
||||
eslint --strict api-viewer/APIViewer.js
|
||||
lint: $(JSSRC)
|
||||
$(ESLINT) --strict $(JSSRC)
|
||||
eslint --strict $(JSSRC)
|
||||
touch ".lint-incremental"
|
||||
|
||||
BUILD_TIME=$(or $(SOURCE_DATE_EPOCH),$(shell date '+%s.%N'))
|
||||
BUILD_VERSION=$(or $(DEB_VERSION),$(shell git rev-parse HEAD),unknown version)
|
||||
proxmoxlib.js: .lint-incremental $(JSSRC)
|
||||
# add the version as comment in the file
|
||||
echo "// v$(BUILD_VERSION)-t$(BUILD_TIME)" > $@.tmp
|
||||
echo "// v$(BUILD_VERSION) - t$(BUILD_TIME)" > $@.tmp
|
||||
cat $(JSSRC) $(MARKEDJS) >> $@.tmp
|
||||
mv $@.tmp $@
|
||||
|
||||
proxmoxlib.min.js: proxmoxlib.js
|
||||
uglifyjs $< -c -m -o $@.tmp
|
||||
mv $@.tmp $@
|
||||
|
||||
install: proxmoxlib.js proxmoxlib.min.js
|
||||
install: proxmoxlib.js
|
||||
install -d -m 755 $(WWWBASEDIR)
|
||||
install -m 0644 proxmoxlib.js proxmoxlib.min.js $(WWWBASEDIR)
|
||||
install -m 0644 proxmoxlib.js $(WWWBASEDIR)
|
||||
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(MAKE) -C proxmox-dark $@
|
||||
rm -f proxmoxlib.js* proxmoxlib.min.js* .lint-incremental
|
||||
rm -f proxmoxlib.js
|
||||
|
@ -24,33 +24,22 @@ Ext.define('Proxmox.Markdown', {
|
||||
for (let i=node.attributes.length; i--;) {
|
||||
const name = node.attributes[i].name;
|
||||
const value = node.attributes[i].value;
|
||||
const canonicalTagName = node.tagName.toLowerCase();
|
||||
// TODO: we may want to also disallow class and id attrs
|
||||
if (
|
||||
!/^(class|id|name|href|src|alt|align|valign|disabled|checked|start|type|target)$/i.test(name)
|
||||
!/^(class|id|name|href|src|alt|align|valign|disabled|checked|start|type)$/i.test(name)
|
||||
) {
|
||||
node.attributes.removeNamedItem(name);
|
||||
} else if ((name === 'href' || name === 'src') && !_isHTTPLike(value)) {
|
||||
let safeURL = false;
|
||||
try {
|
||||
let url = new URL(value, window.location.origin);
|
||||
safeURL = _isHTTPLike(url.protocol);
|
||||
if (canonicalTagName === 'img' && url.protocol.toLowerCase() === 'data:') {
|
||||
safeURL = true;
|
||||
} else if (canonicalTagName === 'a') {
|
||||
// allow most link protocols so admins can use short-cuts to, e.g., RDP
|
||||
safeURL = url.protocol.toLowerCase() !== 'javascript:'; // eslint-disable-line no-script-url
|
||||
}
|
||||
if (safeURL) {
|
||||
if (_isHTTPLike(url.protocol) || (node.tagName === 'img' && url.protocol === 'data:')) {
|
||||
node.attributes[i].value = url.href;
|
||||
} else {
|
||||
node.attributes.removeNamedItem(name);
|
||||
}
|
||||
} catch (e) {
|
||||
node.attributes.removeNamedItem(name);
|
||||
node.attributes[i].removeNamedItem(name);
|
||||
}
|
||||
} else if (name === 'target' && canonicalTagName !== 'a') {
|
||||
node.attributes.removeNamedItem(name);
|
||||
}
|
||||
}
|
||||
for (let i=node.childNodes.length; i--;) _sanitize(node.childNodes[i]);
|
||||
@ -66,7 +55,7 @@ Ext.define('Proxmox.Markdown', {
|
||||
|
||||
parse: function(markdown) {
|
||||
/*global marked*/
|
||||
let unsafeHTML = marked.parse(markdown);
|
||||
let unsafeHTML = marked(markdown);
|
||||
|
||||
return `<div class="pmx-md">${this.sanitizeHTML(unsafeHTML)}</div>`;
|
||||
},
|
||||
|
@ -4,13 +4,10 @@ Ext.define('Proxmox.Schema', { // a singleton
|
||||
authDomains: {
|
||||
pam: {
|
||||
name: 'Linux PAM',
|
||||
ipanel: 'pmxAuthSimplePanel',
|
||||
onlineHelp: 'user-realms-pam',
|
||||
add: false,
|
||||
edit: true,
|
||||
edit: false,
|
||||
pwchange: true,
|
||||
sync: false,
|
||||
useTypeInUrl: false,
|
||||
},
|
||||
openid: {
|
||||
name: gettext('OpenID Connect Server'),
|
||||
@ -21,7 +18,6 @@ Ext.define('Proxmox.Schema', { // a singleton
|
||||
pwchange: false,
|
||||
sync: false,
|
||||
iconCls: 'pmx-itype-icon-openid-logo',
|
||||
useTypeInUrl: true,
|
||||
},
|
||||
ldap: {
|
||||
name: gettext('LDAP Server'),
|
||||
@ -32,18 +28,6 @@ Ext.define('Proxmox.Schema', { // a singleton
|
||||
tfa: true,
|
||||
pwchange: false,
|
||||
sync: true,
|
||||
useTypeInUrl: true,
|
||||
},
|
||||
ad: {
|
||||
name: gettext('Active Directory Server'),
|
||||
ipanel: 'pmxAuthADPanel',
|
||||
syncipanel: 'pmxAuthADSyncPanel',
|
||||
add: true,
|
||||
edit: true,
|
||||
tfa: true,
|
||||
pwchange: false,
|
||||
sync: true,
|
||||
useTypeInUrl: true,
|
||||
},
|
||||
},
|
||||
// to add or change existing for product specific ones
|
||||
@ -53,38 +37,6 @@ Ext.define('Proxmox.Schema', { // a singleton
|
||||
}
|
||||
},
|
||||
|
||||
notificationEndpointTypes: {
|
||||
sendmail: {
|
||||
name: 'Sendmail',
|
||||
ipanel: 'pmxSendmailEditPanel',
|
||||
iconCls: 'fa-envelope-o',
|
||||
defaultMailAuthor: 'Proxmox VE',
|
||||
},
|
||||
smtp: {
|
||||
name: 'SMTP',
|
||||
ipanel: 'pmxSmtpEditPanel',
|
||||
iconCls: 'fa-envelope-o',
|
||||
defaultMailAuthor: 'Proxmox VE',
|
||||
},
|
||||
gotify: {
|
||||
name: 'Gotify',
|
||||
ipanel: 'pmxGotifyEditPanel',
|
||||
iconCls: 'fa-bell-o',
|
||||
},
|
||||
webhook: {
|
||||
name: 'Webhook',
|
||||
ipanel: 'pmxWebhookEditPanel',
|
||||
iconCls: 'fa-bell-o',
|
||||
},
|
||||
},
|
||||
|
||||
// to add or change existing for product specific ones
|
||||
overrideEndpointTypes: function(extra) {
|
||||
for (const [key, value] of Object.entries(extra)) {
|
||||
Proxmox.Schema.notificationEndpointTypes[key] = value;
|
||||
}
|
||||
},
|
||||
|
||||
pxarFileTypes: {
|
||||
b: { icon: 'cube', label: gettext('Block Device') },
|
||||
c: { icon: 'tty', label: gettext('Character Device') },
|
||||
|
476
src/Toolkit.js
476
src/Toolkit.js
@ -21,7 +21,7 @@ Ext.apply(Ext.form.field.VTypes, {
|
||||
IPCIDRAddressMask: /[\d./]/i,
|
||||
|
||||
IP6Address: function(v) {
|
||||
return Proxmox.Utils.IP6_match.test(v);
|
||||
return Proxmox.Utils.IP6_match.test(v);
|
||||
},
|
||||
IP6AddressText: gettext('Example') + ': 2001:DB8::42',
|
||||
IP6AddressMask: /[A-Fa-f0-9:]/,
|
||||
@ -42,7 +42,7 @@ Ext.apply(Ext.form.field.VTypes, {
|
||||
IP6PrefixLengthMask: /[0-9]/,
|
||||
|
||||
IP64Address: function(v) {
|
||||
return Proxmox.Utils.IP64_match.test(v);
|
||||
return Proxmox.Utils.IP64_match.test(v);
|
||||
},
|
||||
IP64AddressText: gettext('Example') + ': 192.168.1.1 2001:DB8::42',
|
||||
IP64AddressMask: /[A-Fa-f0-9.:]/,
|
||||
@ -76,25 +76,25 @@ Ext.apply(Ext.form.field.VTypes, {
|
||||
MacPrefixText: gettext('Example') + ': 02:8f - ' + gettext('only unicast addresses are allowed'),
|
||||
|
||||
BridgeName: function(v) {
|
||||
return (/^[a-zA-Z][a-zA-Z0-9_]{0,9}$/).test(v);
|
||||
return (/^vmbr\d{1,4}$/).test(v);
|
||||
},
|
||||
VlanName: function(v) {
|
||||
if (Proxmox.Utils.VlanInterface_match.test(v)) {
|
||||
return true;
|
||||
return true;
|
||||
} else if (Proxmox.Utils.Vlan_match.test(v)) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
BridgeNameText: gettext('Format') + ': alphanumeric string starting with a character',
|
||||
BridgeNameText: gettext('Format') + ': vmbr<b>N</b>, where 0 <= <b>N</b> <= 9999',
|
||||
|
||||
BondName: function(v) {
|
||||
return (/^bond\d{1,4}$/).test(v);
|
||||
return (/^bond\d{1,4}$/).test(v);
|
||||
},
|
||||
BondNameText: gettext('Format') + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999',
|
||||
|
||||
InterfaceName: function(v) {
|
||||
return (/^[a-z][a-z0-9_]{1,20}$/).test(v);
|
||||
return (/^[a-z][a-z0-9_]{1,20}$/).test(v);
|
||||
},
|
||||
InterfaceNameText: gettext("Allowed characters") + ": 'a-z', '0-9', '_'<br />" +
|
||||
gettext("Minimum characters") + ": 2<br />" +
|
||||
@ -102,7 +102,7 @@ Ext.apply(Ext.form.field.VTypes, {
|
||||
gettext("Must start with") + ": 'a-z'",
|
||||
|
||||
StorageId: function(v) {
|
||||
return (/^[a-z][a-z0-9\-_.]*[a-z0-9]$/i).test(v);
|
||||
return (/^[a-z][a-z0-9\-_.]*[a-z0-9]$/i).test(v);
|
||||
},
|
||||
StorageIdText: gettext("Allowed characters") + ": 'A-Z', 'a-z', '0-9', '-', '_', '.'<br />" +
|
||||
gettext("Minimum characters") + ": 2<br />" +
|
||||
@ -110,14 +110,14 @@ Ext.apply(Ext.form.field.VTypes, {
|
||||
gettext("Must end with") + ": 'A-Z', 'a-z', '0-9'<br />",
|
||||
|
||||
ConfigId: function(v) {
|
||||
return (/^[a-z][a-z0-9_-]+$/i).test(v);
|
||||
return (/^[a-z][a-z0-9_-]+$/i).test(v);
|
||||
},
|
||||
ConfigIdText: gettext("Allowed characters") + ": 'A-Z', 'a-z', '0-9', '_'<br />" +
|
||||
gettext("Minimum characters") + ": 2<br />" +
|
||||
gettext("Must start with") + ": " + gettext("letter"),
|
||||
|
||||
HttpProxy: function(v) {
|
||||
return (/^http:\/\/.*$/).test(v);
|
||||
return (/^http:\/\/.*$/).test(v);
|
||||
},
|
||||
HttpProxyText: gettext('Example') + ": http://username:password@host:port/",
|
||||
|
||||
@ -129,16 +129,16 @@ Ext.apply(Ext.form.field.VTypes, {
|
||||
DnsName: function(v) {
|
||||
return Proxmox.Utils.DnsName_match.test(v);
|
||||
},
|
||||
DnsNameText: gettext('This is not a valid hostname'),
|
||||
DnsNameText: gettext('This is not a valid DNS name'),
|
||||
|
||||
DnsNameOrWildcard: function(v) {
|
||||
return Proxmox.Utils.DnsName_or_Wildcard_match.test(v);
|
||||
},
|
||||
DnsNameOrWildcardText: gettext('This is not a valid hostname'),
|
||||
DnsNameOrWildcardText: gettext('This is not a valid DNS name'),
|
||||
|
||||
// email regex used by pve-common
|
||||
proxmoxMail: function(v) {
|
||||
return (/^[\w+-~]+(\.[\w+-~]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$/).test(v);
|
||||
return (/^[\w+-~]+(\.[\w+-~]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$/).test(v);
|
||||
},
|
||||
proxmoxMailText: gettext('Example') + ": user@example.com",
|
||||
|
||||
@ -179,11 +179,11 @@ Ext.apply(Ext.form.field.VTypes, {
|
||||
HostListText: gettext('Not a valid list of hosts'),
|
||||
|
||||
password: function(val, field) {
|
||||
if (field.initialPassField) {
|
||||
let pwd = field.up('form').down(`[name=${field.initialPassField}]`);
|
||||
return val === pwd.getValue();
|
||||
}
|
||||
return true;
|
||||
if (field.initialPassField) {
|
||||
let pwd = field.up('form').down(`[name=${field.initialPassField}]`);
|
||||
return val === pwd.getValue();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
passwordText: gettext('Passwords do not match'),
|
||||
@ -216,30 +216,30 @@ Ext.define('Proxmox.UnderlayPool', {
|
||||
override: 'Ext.dom.UnderlayPool',
|
||||
|
||||
checkOut: function() {
|
||||
let cache = this.cache,
|
||||
len = cache.length,
|
||||
el;
|
||||
let cache = this.cache,
|
||||
len = cache.length,
|
||||
el;
|
||||
|
||||
// do cleanup because some of the objects might have been destroyed
|
||||
// do cleanup because some of the objects might have been destroyed
|
||||
while (len--) {
|
||||
if (cache[len].destroyed) {
|
||||
cache.splice(len, 1);
|
||||
}
|
||||
}
|
||||
// end do cleanup
|
||||
if (cache[len].destroyed) {
|
||||
cache.splice(len, 1);
|
||||
}
|
||||
}
|
||||
// end do cleanup
|
||||
|
||||
el = cache.shift();
|
||||
|
||||
if (!el) {
|
||||
el = Ext.Element.create(this.elementConfig);
|
||||
el.setVisibilityMode(2);
|
||||
//<debug>
|
||||
// tell the spec runner to ignore this element when checking if the dom is clean
|
||||
if (!el) {
|
||||
el = Ext.Element.create(this.elementConfig);
|
||||
el.setVisibilityMode(2);
|
||||
//<debug>
|
||||
// tell the spec runner to ignore this element when checking if the dom is clean
|
||||
el.dom.setAttribute('data-sticky', true);
|
||||
//</debug>
|
||||
//</debug>
|
||||
}
|
||||
|
||||
return el;
|
||||
return el;
|
||||
},
|
||||
});
|
||||
|
||||
@ -515,28 +515,28 @@ Ext.define('Proxmox.selection.CheckboxModel', {
|
||||
// [ P: optimized to remove all records at once as single remove is O(n^3) slow ]
|
||||
// records can be an index, a record or an array of records
|
||||
doDeselect: function(records, suppressEvent) {
|
||||
var me = this,
|
||||
selected = me.selected,
|
||||
i = 0,
|
||||
len, record,
|
||||
commit;
|
||||
if (me.locked || !me.store) {
|
||||
return false;
|
||||
}
|
||||
if (typeof records === "number") {
|
||||
// No matching record, jump out
|
||||
record = me.store.getAt(records);
|
||||
if (!record) {
|
||||
return false;
|
||||
}
|
||||
records = [
|
||||
record,
|
||||
];
|
||||
} else if (!Ext.isArray(records)) {
|
||||
records = [
|
||||
records,
|
||||
];
|
||||
}
|
||||
var me = this,
|
||||
selected = me.selected,
|
||||
i = 0,
|
||||
len, record,
|
||||
commit;
|
||||
if (me.locked || !me.store) {
|
||||
return false;
|
||||
}
|
||||
if (typeof records === "number") {
|
||||
// No matching record, jump out
|
||||
record = me.store.getAt(records);
|
||||
if (!record) {
|
||||
return false;
|
||||
}
|
||||
records = [
|
||||
record,
|
||||
];
|
||||
} else if (!Ext.isArray(records)) {
|
||||
records = [
|
||||
records,
|
||||
];
|
||||
}
|
||||
// [P] a beforedeselection, triggered by me.onSelectChange below, can block removal by
|
||||
// returning false, thus the original implementation removed only here in the commit fn,
|
||||
// which has an abysmal performance O(n^3). As blocking removal is not the norm, go do the
|
||||
@ -550,119 +550,119 @@ Ext.define('Proxmox.selection.CheckboxModel', {
|
||||
}
|
||||
};
|
||||
let removalBlocked = [];
|
||||
len = records.length;
|
||||
me.suspendChanges();
|
||||
for (; i < len; i++) {
|
||||
record = records[i];
|
||||
if (me.isSelected(record)) {
|
||||
len = records.length;
|
||||
me.suspendChanges();
|
||||
for (; i < len; i++) {
|
||||
record = records[i];
|
||||
if (me.isSelected(record)) {
|
||||
committed = false;
|
||||
me.onSelectChange(record, false, suppressEvent, commit);
|
||||
me.onSelectChange(record, false, suppressEvent, commit);
|
||||
if (!committed) {
|
||||
removalBlocked.push(record);
|
||||
}
|
||||
if (me.destroyed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (me.destroyed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removalBlocked.length > 0) {
|
||||
records.remove(removalBlocked);
|
||||
}
|
||||
selected.remove(records); // [P] FAST(er)
|
||||
me.lastSelected = selected.last();
|
||||
me.resumeChanges();
|
||||
// fire selchange if there was a change and there is no suppressEvent flag
|
||||
me.resumeChanges();
|
||||
// fire selchange if there was a change and there is no suppressEvent flag
|
||||
me.maybeFireSelectionChange(records.length > 0 && !suppressEvent);
|
||||
return records.length;
|
||||
},
|
||||
|
||||
|
||||
doMultiSelect: function(records, keepExisting, suppressEvent) {
|
||||
var me = this,
|
||||
selected = me.selected,
|
||||
change = false,
|
||||
result, i, len, record, commit;
|
||||
var me = this,
|
||||
selected = me.selected,
|
||||
change = false,
|
||||
result, i, len, record, commit;
|
||||
|
||||
if (me.locked) {
|
||||
return;
|
||||
}
|
||||
if (me.locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
records = !Ext.isArray(records) ? [records] : records;
|
||||
len = records.length;
|
||||
if (!keepExisting && selected.getCount() > 0) {
|
||||
result = me.deselectDuringSelect(records, suppressEvent);
|
||||
if (me.destroyed) {
|
||||
return;
|
||||
}
|
||||
if (result[0]) {
|
||||
// We had a failure during selection, so jump out
|
||||
// Fire selection change if we did deselect anything
|
||||
me.maybeFireSelectionChange(result[1] > 0 && !suppressEvent);
|
||||
return;
|
||||
} else {
|
||||
// Means something has been deselected, so we've had a change
|
||||
change = result[1] > 0;
|
||||
}
|
||||
}
|
||||
records = !Ext.isArray(records) ? [records] : records;
|
||||
len = records.length;
|
||||
if (!keepExisting && selected.getCount() > 0) {
|
||||
result = me.deselectDuringSelect(records, suppressEvent);
|
||||
if (me.destroyed) {
|
||||
return;
|
||||
}
|
||||
if (result[0]) {
|
||||
// We had a failure during selection, so jump out
|
||||
// Fire selection change if we did deselect anything
|
||||
me.maybeFireSelectionChange(result[1] > 0 && !suppressEvent);
|
||||
return;
|
||||
} else {
|
||||
// Means something has been deselected, so we've had a change
|
||||
change = result[1] > 0;
|
||||
}
|
||||
}
|
||||
|
||||
let gotBlocked, blockedRecords = [];
|
||||
commit = function() {
|
||||
if (!selected.getCount()) {
|
||||
me.selectionStart = record;
|
||||
}
|
||||
commit = function() {
|
||||
if (!selected.getCount()) {
|
||||
me.selectionStart = record;
|
||||
}
|
||||
gotBlocked = false;
|
||||
change = true;
|
||||
};
|
||||
change = true;
|
||||
};
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
record = records[i];
|
||||
if (me.isSelected(record)) {
|
||||
continue;
|
||||
}
|
||||
for (i = 0; i < len; i++) {
|
||||
record = records[i];
|
||||
if (me.isSelected(record)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
gotBlocked = true;
|
||||
me.onSelectChange(record, true, suppressEvent, commit);
|
||||
if (me.destroyed) {
|
||||
return;
|
||||
}
|
||||
me.onSelectChange(record, true, suppressEvent, commit);
|
||||
if (me.destroyed) {
|
||||
return;
|
||||
}
|
||||
if (gotBlocked) {
|
||||
blockedRecords.push(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blockedRecords.length > 0) {
|
||||
records.remove(blockedRecords);
|
||||
}
|
||||
selected.add(records);
|
||||
me.lastSelected = record;
|
||||
selected.add(records);
|
||||
me.lastSelected = record;
|
||||
|
||||
// fire selchange if there was a change and there is no suppressEvent flag
|
||||
me.maybeFireSelectionChange(change && !suppressEvent);
|
||||
// fire selchange if there was a change and there is no suppressEvent flag
|
||||
me.maybeFireSelectionChange(change && !suppressEvent);
|
||||
},
|
||||
deselectDuringSelect: function(toSelect, suppressEvent) {
|
||||
var me = this,
|
||||
selected = me.selected.getRange(),
|
||||
changed = 0,
|
||||
failed = false;
|
||||
// Prevent selection change events from firing, will happen during select
|
||||
me.suspendChanges();
|
||||
me.deselectingDuringSelect = true;
|
||||
var me = this,
|
||||
selected = me.selected.getRange(),
|
||||
changed = 0,
|
||||
failed = false;
|
||||
// Prevent selection change events from firing, will happen during select
|
||||
me.suspendChanges();
|
||||
me.deselectingDuringSelect = true;
|
||||
let toDeselect = selected.filter(item => !Ext.Array.contains(toSelect, item));
|
||||
if (toDeselect.length > 0) {
|
||||
changed = me.doDeselect(toDeselect, suppressEvent);
|
||||
if (!changed) {
|
||||
failed = true;
|
||||
}
|
||||
if (me.destroyed) {
|
||||
failed = true;
|
||||
changed = 0;
|
||||
}
|
||||
}
|
||||
me.deselectingDuringSelect = false;
|
||||
me.resumeChanges();
|
||||
return [
|
||||
failed,
|
||||
changed,
|
||||
];
|
||||
}
|
||||
if (me.destroyed) {
|
||||
failed = true;
|
||||
changed = 0;
|
||||
}
|
||||
}
|
||||
me.deselectingDuringSelect = false;
|
||||
me.resumeChanges();
|
||||
return [
|
||||
failed,
|
||||
changed,
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
@ -678,11 +678,11 @@ Ext.define('Proxmox.view.DragZone', {
|
||||
override: 'Ext.view.DragZone',
|
||||
|
||||
onItemMouseDown: function(view, record, item, index, e) {
|
||||
// Ignore touchstart.
|
||||
// For touch events, we use longpress.
|
||||
if (e.pointerType !== 'touch') {
|
||||
this.onTriggerGesture(view, record, item, index, e);
|
||||
}
|
||||
// Ignore touchstart.
|
||||
// For touch events, we use longpress.
|
||||
if (e.pointerType !== 'touch') {
|
||||
this.onTriggerGesture(view, record, item, index, e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -702,39 +702,6 @@ Ext.define('Proxmox.dd.DragDropManager', {
|
||||
},
|
||||
});
|
||||
|
||||
// make it possible to set the SameSite attribute on cookies
|
||||
Ext.define('Proxmox.Cookies', {
|
||||
override: 'Ext.util.Cookies',
|
||||
|
||||
set: function(name, value, expires, path, domain, secure, samesite) {
|
||||
let attrs = [];
|
||||
|
||||
if (expires) {
|
||||
attrs.push("expires=" + expires.toUTCString());
|
||||
}
|
||||
|
||||
if (path === undefined) { // mimic original function's behaviour
|
||||
attrs.push("path=/");
|
||||
} else if (path) {
|
||||
attrs.push("path=" + path);
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
attrs.push("domain=" + domain);
|
||||
}
|
||||
|
||||
if (secure === true) {
|
||||
attrs.push("secure");
|
||||
}
|
||||
|
||||
if (samesite && ["lax", "none", "strict"].includes(samesite.toLowerCase())) {
|
||||
attrs.push("samesite=" + samesite);
|
||||
}
|
||||
|
||||
document.cookie = name + "=" + escape(value) + "; " + attrs.join("; ");
|
||||
},
|
||||
});
|
||||
|
||||
// force alert boxes to be rendered with an Error Icon
|
||||
// since Ext.Msg is an object and not a prototype, we need to override it
|
||||
// after the framework has been initiated
|
||||
@ -756,11 +723,6 @@ Ext.onReady(function() {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// add allowfullscreen to render template to allow the noVNC/xterm.js embedded UIs to go fullscreen
|
||||
//
|
||||
// The rest is the same as in the separate ux package (extjs/build/packages/ux/classic/ux-debug.js),
|
||||
// which we do not load as it's rather big and most of the widgets there are not useful for our UIs
|
||||
Ext.define('Ext.ux.IFrame', {
|
||||
extend: 'Ext.Component',
|
||||
|
||||
@ -771,84 +733,154 @@ Ext.define('Ext.ux.IFrame', {
|
||||
src: 'about:blank',
|
||||
|
||||
renderTpl: [
|
||||
// eslint-disable-next-line max-len
|
||||
'<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0" allowfullscreen="true"></iframe>',
|
||||
'<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0" allowfullscreen="true"></iframe>',
|
||||
],
|
||||
|
||||
childEls: ['iframeEl'],
|
||||
|
||||
initComponent: function() {
|
||||
this.callParent();
|
||||
this.callParent();
|
||||
|
||||
this.frameName = this.frameName || this.id + '-frame';
|
||||
this.frameName = this.frameName || this.id + '-frame';
|
||||
},
|
||||
|
||||
initEvents: function() {
|
||||
let me = this;
|
||||
|
||||
me.callParent();
|
||||
me.iframeEl.on('load', me.onLoad, me);
|
||||
let me = this;
|
||||
me.callParent();
|
||||
me.iframeEl.on('load', me.onLoad, me);
|
||||
},
|
||||
|
||||
initRenderData: function() {
|
||||
return Ext.apply(this.callParent(), {
|
||||
src: this.src,
|
||||
frameName: this.frameName,
|
||||
});
|
||||
return Ext.apply(this.callParent(), {
|
||||
src: this.src,
|
||||
frameName: this.frameName,
|
||||
});
|
||||
},
|
||||
|
||||
getBody: function() {
|
||||
let doc = this.getDoc();
|
||||
|
||||
return doc.body || doc.documentElement;
|
||||
let doc = this.getDoc();
|
||||
return doc.body || doc.documentElement;
|
||||
},
|
||||
|
||||
getDoc: function() {
|
||||
try {
|
||||
return this.getWin().document;
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return this.getWin().document;
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
getWin: function() {
|
||||
let me = this,
|
||||
name = me.frameName,
|
||||
win = Ext.isIE ? me.iframeEl.dom.contentWindow : window.frames[name];
|
||||
|
||||
return win;
|
||||
let me = this,
|
||||
name = me.frameName,
|
||||
win = Ext.isIE
|
||||
? me.iframeEl.dom.contentWindow
|
||||
: window.frames[name];
|
||||
return win;
|
||||
},
|
||||
|
||||
getFrame: function() {
|
||||
let me = this;
|
||||
let me = this;
|
||||
return me.iframeEl.dom;
|
||||
},
|
||||
|
||||
return me.iframeEl.dom;
|
||||
beforeDestroy: function() {
|
||||
this.cleanupListeners(true);
|
||||
this.callParent();
|
||||
},
|
||||
|
||||
cleanupListeners: function(destroying) {
|
||||
let doc, prop;
|
||||
|
||||
if (this.rendered) {
|
||||
try {
|
||||
doc = this.getDoc();
|
||||
if (doc) {
|
||||
Ext.get(doc).un(this._docListeners);
|
||||
if (destroying && doc.hasOwnProperty) {
|
||||
for (prop in doc) {
|
||||
if (Object.prototype.hasOwnProperty.call(doc, prop)) {
|
||||
delete doc[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLoad: function() {
|
||||
let me = this,
|
||||
doc = me.getDoc();
|
||||
let me = this,
|
||||
doc = me.getDoc(),
|
||||
fn = me.onRelayedEvent;
|
||||
|
||||
if (doc) {
|
||||
this.el.unmask();
|
||||
this.fireEvent('load', this);
|
||||
} else if (me.src) {
|
||||
this.el.unmask();
|
||||
this.fireEvent('error', this);
|
||||
}
|
||||
if (doc) {
|
||||
try {
|
||||
// These events need to be relayed from the inner document (where they stop
|
||||
// bubbling) up to the outer document. This has to be done at the DOM level so
|
||||
// the event reaches listeners on elements like the document body. The effected
|
||||
// mechanisms that depend on this bubbling behavior are listed to the right
|
||||
// of the event.
|
||||
Ext.get(doc).on(
|
||||
me._docListeners = {
|
||||
mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
|
||||
mousemove: fn, // window resize drag detection
|
||||
mouseup: fn, // window resize termination
|
||||
click: fn, // not sure, but just to be safe
|
||||
dblclick: fn, // not sure again
|
||||
scope: me,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// cannot do this xss
|
||||
}
|
||||
|
||||
// We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
|
||||
Ext.get(this.getWin()).on('beforeunload', me.cleanupListeners, me);
|
||||
|
||||
this.el.unmask();
|
||||
this.fireEvent('load', this);
|
||||
} else if (me.src) {
|
||||
this.el.unmask();
|
||||
this.fireEvent('error', this);
|
||||
}
|
||||
},
|
||||
|
||||
onRelayedEvent: function(event) {
|
||||
// relay event from the iframe's document to the document that owns the iframe...
|
||||
|
||||
let iframeEl = this.iframeEl,
|
||||
|
||||
// Get the left-based iframe position
|
||||
iframeXY = iframeEl.getTrueXY(),
|
||||
originalEventXY = event.getXY(),
|
||||
|
||||
// Get the left-based XY position.
|
||||
// This is because the consumer of the injected event will
|
||||
// perform its own RTL normalization.
|
||||
eventXY = event.getTrueXY();
|
||||
|
||||
// the event from the inner document has XY relative to that document's origin,
|
||||
// so adjust it to use the origin of the iframe in the outer document:
|
||||
event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
|
||||
|
||||
event.injectEvent(iframeEl); // blame the iframe for the event...
|
||||
|
||||
event.xy = originalEventXY; // restore the original XY (just for safety)
|
||||
},
|
||||
|
||||
load: function(src) {
|
||||
let me = this,
|
||||
text = me.loadMask,
|
||||
frame = me.getFrame();
|
||||
let me = this,
|
||||
text = me.loadMask,
|
||||
frame = me.getFrame();
|
||||
|
||||
if (me.fireEvent('beforeload', me, src) !== false) {
|
||||
if (text && me.el) {
|
||||
me.el.mask(text);
|
||||
}
|
||||
if (me.fireEvent('beforeload', me, src) !== false) {
|
||||
if (text && me.el) {
|
||||
me.el.mask(text);
|
||||
}
|
||||
|
||||
frame.src = me.src = src || me.src;
|
||||
}
|
||||
frame.src = me.src = src || me.src;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
135
src/Utils.js
135
src/Utils.js
@ -64,7 +64,6 @@ utilities: {
|
||||
|
||||
language_map: { //language map is sorted alphabetically by iso 639-1
|
||||
ar: `العربية - ${gettext("Arabic")}`,
|
||||
bg: `Български - ${gettext("Bulgarian")}`,
|
||||
ca: `Català - ${gettext("Catalan")}`,
|
||||
da: `Dansk - ${gettext("Danish")}`,
|
||||
de: `Deutsch - ${gettext("German")}`,
|
||||
@ -73,12 +72,10 @@ utilities: {
|
||||
eu: `Euskera (Basque) - ${gettext("Euskera (Basque)")}`,
|
||||
fa: `فارسی - ${gettext("Persian (Farsi)")}`,
|
||||
fr: `Français - ${gettext("French")}`,
|
||||
hr: `Hrvatski - ${gettext("Croatian")}`,
|
||||
he: `עברית - ${gettext("Hebrew")}`,
|
||||
it: `Italiano - ${gettext("Italian")}`,
|
||||
ja: `日本語 - ${gettext("Japanese")}`,
|
||||
ka: `ქართული - ${gettext("Georgian")}`,
|
||||
ko: `한국어 - ${gettext("Korean")}`,
|
||||
kr: `한국어 - ${gettext("Korean")}`,
|
||||
nb: `Bokmål - ${gettext("Norwegian (Bokmal)")}`,
|
||||
nl: `Nederlands - ${gettext("Dutch")}`,
|
||||
nn: `Nynorsk - ${gettext("Norwegian (Nynorsk)")}`,
|
||||
@ -88,7 +85,6 @@ utilities: {
|
||||
sl: `Slovenščina - ${gettext("Slovenian")}`,
|
||||
sv: `Svenska - ${gettext("Swedish")}`,
|
||||
tr: `Türkçe - ${gettext("Turkish")}`,
|
||||
ukr: `Українська - ${gettext("Ukrainian")}`,
|
||||
zh_CN: `中文(简体)- ${gettext("Chinese (Simplified)")}`,
|
||||
zh_TW: `中文(繁體)- ${gettext("Chinese (Traditional)")}`,
|
||||
},
|
||||
@ -97,9 +93,6 @@ utilities: {
|
||||
if (!value || value === '__default__') {
|
||||
return Proxmox.Utils.defaultText + ' (English)';
|
||||
}
|
||||
if (value === 'kr') {
|
||||
value = 'ko'; // fix-up wrongly used Korean code. FIXME: remove with trixie releases
|
||||
}
|
||||
let text = Proxmox.Utils.language_map[value];
|
||||
if (text) {
|
||||
return text + ' (' + value + ')';
|
||||
@ -156,11 +149,8 @@ utilities: {
|
||||
},
|
||||
|
||||
getNoSubKeyHtml: function(url) {
|
||||
let html_url = Ext.String.format('<a target="_blank" href="{0}">www.proxmox.com</a>', url || 'https://www.proxmox.com');
|
||||
return Ext.String.format(
|
||||
gettext('You do not have a valid subscription for this server. Please visit {0} to get a list of available options.'),
|
||||
html_url,
|
||||
);
|
||||
// url http://www.proxmox.com/products/proxmox-ve/subscription-service-plans
|
||||
return Ext.String.format('You do not have a valid subscription for this server. Please visit <a target="_blank" href="{0}">www.proxmox.com</a> to get a list of available options.', url || 'https://www.proxmox.com');
|
||||
},
|
||||
|
||||
format_boolean_with_default: function(value) {
|
||||
@ -318,7 +308,7 @@ utilities: {
|
||||
// that way the cookie gets deleted after the browser window is closed
|
||||
if (data.ticket) {
|
||||
Proxmox.CSRFPreventionToken = data.CSRFPreventionToken;
|
||||
Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, data.ticket, null, '/', null, true, "lax");
|
||||
Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, data.ticket, null, '/', null, true);
|
||||
}
|
||||
|
||||
if (data.token) {
|
||||
@ -344,7 +334,7 @@ utilities: {
|
||||
return;
|
||||
}
|
||||
// ExtJS clear is basically the same, but browser may complain if any cookie isn't "secure"
|
||||
Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, "", new Date(0), null, null, true, "lax");
|
||||
Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, "", new Date(0), null, null, true);
|
||||
window.localStorage.removeItem("ProxmoxUser");
|
||||
},
|
||||
|
||||
@ -463,12 +453,6 @@ utilities: {
|
||||
newopts.url = '/api2/extjs' + newopts.url;
|
||||
}
|
||||
delete newopts.callback;
|
||||
let unmask = (target) => {
|
||||
if (target.waitMsgTargetCount === undefined || --target.waitMsgTargetCount <= 0) {
|
||||
target.setLoading(false);
|
||||
delete target.waitMsgTargetCount;
|
||||
}
|
||||
};
|
||||
|
||||
let createWrapper = function(successFn, callbackFn, failureFn) {
|
||||
Ext.apply(newopts, {
|
||||
@ -477,7 +461,7 @@ utilities: {
|
||||
if (Proxmox.Utils.toolkit === 'touch') {
|
||||
options.waitMsgTarget.setMasked(false);
|
||||
} else {
|
||||
unmask(options.waitMsgTarget);
|
||||
options.waitMsgTarget.setLoading(false);
|
||||
}
|
||||
}
|
||||
let result = Ext.decode(response.responseText);
|
||||
@ -499,7 +483,7 @@ utilities: {
|
||||
if (Proxmox.Utils.toolkit === 'touch') {
|
||||
options.waitMsgTarget.setMasked(false);
|
||||
} else {
|
||||
unmask(options.waitMsgTarget);
|
||||
options.waitMsgTarget.setLoading(false);
|
||||
}
|
||||
}
|
||||
response.result = {};
|
||||
@ -529,16 +513,9 @@ utilities: {
|
||||
if (target) {
|
||||
if (Proxmox.Utils.toolkit === 'touch') {
|
||||
target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg });
|
||||
} else if (target.rendered) {
|
||||
target.waitMsgTargetCount = (target.waitMsgTargetCount ?? 0) + 1;
|
||||
target.setLoading(newopts.waitMsg);
|
||||
} else {
|
||||
target.waitMsgTargetCount = (target.waitMsgTargetCount ?? 0) + 1;
|
||||
target.on('afterlayout', function() {
|
||||
if ((target.waitMsgTargetCount ?? 0) > 0) {
|
||||
target.setLoading(newopts.waitMsg);
|
||||
}
|
||||
}, target, { single: true });
|
||||
// Note: ExtJS bug - this does not work when component is not rendered
|
||||
target.setLoading(newopts.waitMsg);
|
||||
}
|
||||
}
|
||||
Ext.Ajax.request(newopts);
|
||||
@ -548,7 +525,12 @@ utilities: {
|
||||
// Proxmox.Async.api2({
|
||||
// ...
|
||||
// }).catch(Proxmox.Utils.alertResponseFailure);
|
||||
alertResponseFailure: res => Ext.Msg.alert(gettext('Error'), res.htmlStatus || res.result.message),
|
||||
alertResponseFailure: (response) => {
|
||||
Ext.Msg.alert(
|
||||
gettext('Error'),
|
||||
response.htmlStatus || response.result.message,
|
||||
);
|
||||
},
|
||||
|
||||
checked_command: function(orig_cmd) {
|
||||
Proxmox.Utils.API2Request(
|
||||
@ -583,7 +565,7 @@ utilities: {
|
||||
},
|
||||
|
||||
assemble_field_data: function(values, data) {
|
||||
if (!Ext.isObject(data)) {
|
||||
if (!Ext.isObject(data)) {
|
||||
return;
|
||||
}
|
||||
Ext.Object.each(data, function(name, val) {
|
||||
@ -665,37 +647,6 @@ utilities: {
|
||||
Proxmox.Utils.unknownText;
|
||||
},
|
||||
|
||||
// Only add product-agnostic fields here!
|
||||
notificationFieldName: {
|
||||
'type': gettext('Notification type'),
|
||||
'hostname': gettext('Hostname'),
|
||||
},
|
||||
|
||||
formatNotificationFieldName: (value) =>
|
||||
Proxmox.Utils.notificationFieldName[value] || value,
|
||||
|
||||
// to add or change existing for product specific ones
|
||||
overrideNotificationFieldName: function(extra) {
|
||||
for (const [key, value] of Object.entries(extra)) {
|
||||
Proxmox.Utils.notificationFieldName[key] = value;
|
||||
}
|
||||
},
|
||||
|
||||
// Only add product-agnostic fields here!
|
||||
notificationFieldValue: {
|
||||
'system-mail': gettext('Forwarded mails to the local root user'),
|
||||
},
|
||||
|
||||
formatNotificationFieldValue: (value) =>
|
||||
Proxmox.Utils.notificationFieldValue[value] || value,
|
||||
|
||||
// to add or change existing for product specific ones
|
||||
overrideNotificationFieldValue: function(extra) {
|
||||
for (const [key, value] of Object.entries(extra)) {
|
||||
Proxmox.Utils.notificationFieldValue[key] = value;
|
||||
}
|
||||
},
|
||||
|
||||
// NOTE: only add general, product agnostic, ones here! Else use override helper in product repos
|
||||
task_desc_table: {
|
||||
aptupdate: ['', gettext('Update package database')],
|
||||
@ -737,23 +688,21 @@ utilities: {
|
||||
},
|
||||
|
||||
format_size: function(size, useSI) {
|
||||
let unitsSI = [gettext('B'), gettext('KB'), gettext('MB'), gettext('GB'),
|
||||
gettext('TB'), gettext('PB'), gettext('EB'), gettext('ZB'), gettext('YB')];
|
||||
let unitsIEC = [gettext('B'), gettext('KiB'), gettext('MiB'), gettext('GiB'),
|
||||
gettext('TiB'), gettext('PiB'), gettext('EiB'), gettext('ZiB'), gettext('YiB')];
|
||||
let units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
||||
let order = 0;
|
||||
let commaDigits = 2;
|
||||
const baseValue = useSI ? 1000 : 1024;
|
||||
while (size >= baseValue && order < unitsSI.length) {
|
||||
while (size >= baseValue && order < units.length) {
|
||||
size = size / baseValue;
|
||||
order++;
|
||||
}
|
||||
|
||||
let unit = useSI ? unitsSI[order] : unitsIEC[order];
|
||||
let unit = units[order], commaDigits = 2;
|
||||
if (order === 0) {
|
||||
commaDigits = 0;
|
||||
} else if (!useSI) {
|
||||
unit += 'i';
|
||||
}
|
||||
return `${size.toFixed(commaDigits)} ${unit}`;
|
||||
return `${size.toFixed(commaDigits)} ${unit}B`;
|
||||
},
|
||||
|
||||
SizeUnits: {
|
||||
@ -928,7 +877,7 @@ utilities: {
|
||||
let parsed = Proxmox.Utils.parse_task_status(status);
|
||||
switch (parsed) {
|
||||
case 'unknown': return Proxmox.Utils.unknownText;
|
||||
case 'error': return Proxmox.Utils.errorText + ': ' + Ext.htmlEncode(status);
|
||||
case 'error': return Proxmox.Utils.errorText + ': ' + status;
|
||||
case 'warning': return status.replace('WARNINGS', Proxmox.Utils.warningsText);
|
||||
case 'ok': // fall-through
|
||||
default: return status;
|
||||
@ -1357,24 +1306,6 @@ utilities: {
|
||||
);
|
||||
},
|
||||
|
||||
// Convert utf-8 string to base64.
|
||||
// This also escapes unicode characters such as emojis.
|
||||
utf8ToBase64: function(string) {
|
||||
let bytes = new TextEncoder().encode(string);
|
||||
const escapedString = Array.from(bytes, (byte) =>
|
||||
String.fromCodePoint(byte),
|
||||
).join("");
|
||||
return btoa(escapedString);
|
||||
},
|
||||
|
||||
// Converts a base64 string into a utf8 string.
|
||||
// Decodes escaped unicode characters correctly.
|
||||
base64ToUtf8: function(b64_string) {
|
||||
let string = atob(b64_string);
|
||||
let bytes = Uint8Array.from(string, (m) => m.codePointAt(0));
|
||||
return new TextDecoder().decode(bytes);
|
||||
},
|
||||
|
||||
stringToRGB: function(string) {
|
||||
let hash = 0;
|
||||
if (!string) {
|
||||
@ -1525,26 +1456,6 @@ utilities: {
|
||||
me.IP6_dotnotation_match = new RegExp("^(" + IPV6_REGEXP + ")(?:\\.(\\d+))?$");
|
||||
me.Vlan_match = /^vlan(\d+)/;
|
||||
me.VlanInterface_match = /(\w+)\.(\d+)/;
|
||||
|
||||
|
||||
// Taken from proxmox-schema and ported to JS
|
||||
let PORT_REGEX_STR = "(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])";
|
||||
let IPRE_BRACKET_STR = "(?:" + IPV4_REGEXP + "|\\[(?:" + IPV6_REGEXP + ")\\])";
|
||||
let DNS_NAME_STR = "(?:(?:" + DnsName_REGEXP + "\\.)*" + DnsName_REGEXP + ")";
|
||||
let HTTP_URL_REGEX = "^https?://(?:(?:(?:"
|
||||
+ DNS_NAME_STR
|
||||
+ "|"
|
||||
+ IPRE_BRACKET_STR
|
||||
+ ")(?::"
|
||||
+ PORT_REGEX_STR
|
||||
+ ")?)|"
|
||||
+ IPV6_REGEXP
|
||||
+ ")(?:/[^\x00-\x1F\x7F]*)?$";
|
||||
|
||||
me.httpUrlRegex = new RegExp(HTTP_URL_REGEX);
|
||||
|
||||
// Same as SAFE_ID_REGEX in proxmox-schema
|
||||
me.safeIdRegex = /^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -7,7 +7,7 @@ Ext.onReady(function() {
|
||||
'name', 'type', 'typetext', 'description', 'verbose_description',
|
||||
'enum', 'minimum', 'maximum', 'minLength', 'maxLength',
|
||||
'pattern', 'title', 'requires', 'format', 'default',
|
||||
'disallow', 'extends', 'links', 'instance-types',
|
||||
'disallow', 'extends', 'links',
|
||||
{
|
||||
name: 'optional',
|
||||
type: 'boolean',
|
||||
@ -214,10 +214,6 @@ Ext.onReady(function() {
|
||||
},
|
||||
groupField: 'optional',
|
||||
sorters: [
|
||||
{
|
||||
property: 'instance-types',
|
||||
direction: 'ASC',
|
||||
},
|
||||
{
|
||||
property: 'name',
|
||||
direction: 'ASC',
|
||||
@ -225,27 +221,9 @@ Ext.onReady(function() {
|
||||
],
|
||||
});
|
||||
|
||||
let has_type_properties = false;
|
||||
|
||||
Ext.Object.each(info.parameters.properties, function(name, pdef) {
|
||||
if (pdef.oneOf) {
|
||||
pdef.oneOf.forEach((alternative) => {
|
||||
alternative.name = name;
|
||||
pstore.add(alternative);
|
||||
has_type_properties = true;
|
||||
});
|
||||
} else if (pdef['instance-types']) {
|
||||
pdef['instance-types'].forEach((type) => {
|
||||
let typePdef = Ext.apply({}, pdef);
|
||||
typePdef.name = name;
|
||||
typePdef['instance-types'] = [type];
|
||||
pstore.add(typePdef);
|
||||
has_type_properties = true;
|
||||
});
|
||||
} else {
|
||||
pdef.name = name;
|
||||
pstore.add(pdef);
|
||||
}
|
||||
pdef.name = name;
|
||||
pstore.add(pdef);
|
||||
});
|
||||
|
||||
pstore.sort();
|
||||
@ -277,12 +255,6 @@ Ext.onReady(function() {
|
||||
renderer: render_type,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
header: 'For Types',
|
||||
dataIndex: 'instance-types',
|
||||
hidden: !has_type_properties,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
header: 'Default',
|
||||
dataIndex: 'default',
|
||||
|
@ -110,7 +110,6 @@ Ext.define('Proxmox.button.StdRemoveButton', {
|
||||
|
||||
config: {
|
||||
baseurl: undefined,
|
||||
customConfirmationMessage: undefined,
|
||||
},
|
||||
|
||||
getUrl: function(rec) {
|
||||
@ -134,14 +133,7 @@ Ext.define('Proxmox.button.StdRemoveButton', {
|
||||
let me = this;
|
||||
|
||||
let name = me.getRecordName(rec);
|
||||
|
||||
let text;
|
||||
if (me.customConfirmationMessage) {
|
||||
text = me.customConfirmationMessage;
|
||||
} else {
|
||||
text = gettext('Are you sure you want to remove entry {0}');
|
||||
}
|
||||
return Ext.String.format(text, Ext.htmlEncode(`'${name}'`));
|
||||
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'), `'${name}'`);
|
||||
},
|
||||
|
||||
handler: function(btn, event, rec) {
|
||||
|
@ -15,9 +15,7 @@
|
||||
}
|
||||
|
||||
.proxmox-tags-full .x-grid-cell-inner-treecolumn .proxmox-tag-light,
|
||||
.proxmox-tags-full .x-grid-cell-inner-treecolumn .proxmox-tag-dark,
|
||||
.x-grid-cell-inner-treecolumn .proxmox-tags-full .proxmox-tag-light,
|
||||
.x-grid-cell-inner-treecolumn .proxmox-tags-full .proxmox-tag-dark {
|
||||
.proxmox-tags-full .x-grid-cell-inner-treecolumn .proxmox-tag-dark {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
@ -27,10 +25,8 @@
|
||||
}
|
||||
|
||||
|
||||
.proxmox-tags-circle :not(span.proxmox-tags-full) > .proxmox-tag-light,
|
||||
.proxmox-tags-circle :not(span.proxmox-tags-full) > .proxmox-tag-dark,
|
||||
.proxmox-tags-circle > .proxmox-tag-light,
|
||||
.proxmox-tags-circle > .proxmox-tag-dark {
|
||||
.proxmox-tags-circle .proxmox-tag-light,
|
||||
.proxmox-tags-circle .proxmox-tag-dark {
|
||||
margin: 0px 1px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
@ -42,17 +38,13 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.proxmox-tags-none :not(span.proxmox-tags-full) > .proxmox-tag-light,
|
||||
.proxmox-tags-none :not(span.proxmox-tags-full) > .proxmox-tag-dark,
|
||||
.proxmox-tags-none > .proxmox-tag-light,
|
||||
.proxmox-tags-none > .proxmox-tag-dark {
|
||||
.proxmox-tags-none .proxmox-tag-light,
|
||||
.proxmox-tags-none .proxmox-tag-dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.proxmox-tags-dense :not(span.proxmox-tags-full) > .proxmox-tag-light,
|
||||
.proxmox-tags-dense :not(span.proxmox-tags-full) > .proxmox-tag-dark,
|
||||
.proxmox-tags-dense > .proxmox-tag-light,
|
||||
.proxmox-tags-dense > .proxmox-tag-dark {
|
||||
.proxmox-tags-dense .proxmox-tag-light,
|
||||
.proxmox-tags-dense .proxmox-tag-dark {
|
||||
width: 6px;
|
||||
margin-right: 1px;
|
||||
display: inline-block;
|
||||
@ -74,9 +66,6 @@
|
||||
.x-mask-msg-text {
|
||||
text-align: center;
|
||||
}
|
||||
.x-window-header-default-top .x-mask {
|
||||
background-color: #f5f5f5B4; /* ~ 0.7 opacity */
|
||||
}
|
||||
|
||||
.proxmox-disabled-row, .proxmox-disabled-row td {
|
||||
/*color: #a0a0a0;*/
|
||||
|
@ -1,29 +0,0 @@
|
||||
Ext.define('proxmox-notification-endpoints', {
|
||||
extend: 'Ext.data.Model',
|
||||
fields: ['name', 'type', 'comment', 'disable', 'origin'],
|
||||
proxy: {
|
||||
type: 'proxmox',
|
||||
},
|
||||
idProperty: 'name',
|
||||
});
|
||||
|
||||
Ext.define('proxmox-notification-matchers', {
|
||||
extend: 'Ext.data.Model',
|
||||
fields: ['name', 'comment', 'disable', 'origin'],
|
||||
proxy: {
|
||||
type: 'proxmox',
|
||||
},
|
||||
idProperty: 'name',
|
||||
});
|
||||
|
||||
Ext.define('proxmox-notification-fields', {
|
||||
extend: 'Ext.data.Model',
|
||||
fields: ['name', 'description'],
|
||||
idProperty: 'name',
|
||||
});
|
||||
|
||||
Ext.define('proxmox-notification-field-values', {
|
||||
extend: 'Ext.data.Model',
|
||||
fields: ['value', 'comment', 'field'],
|
||||
idProperty: 'value',
|
||||
});
|
@ -32,9 +32,6 @@ Ext.define('Proxmox.form.ComboGrid', {
|
||||
notFoundIsValid: false,
|
||||
deleteEmpty: false,
|
||||
errorHeight: 100,
|
||||
// NOTE: the trigger will always be shown if allowBlank is true, setting showClearTrigger
|
||||
// to false cannot change that
|
||||
showClearTrigger: false,
|
||||
},
|
||||
|
||||
// needed to trigger onKeyUp etc.
|
||||
@ -57,7 +54,7 @@ Ext.define('Proxmox.form.ComboGrid', {
|
||||
setValue: function(value) {
|
||||
let me = this;
|
||||
let empty = Ext.isArray(value) ? !value.length : !value;
|
||||
me.triggers.clear.setVisible(!empty && (me.allowBlank || me.showClearTrigger));
|
||||
me.triggers.clear.setVisible(!empty && me.allowBlank);
|
||||
return me.callParent([value]);
|
||||
},
|
||||
|
||||
@ -403,7 +400,7 @@ Ext.define('Proxmox.form.ComboGrid', {
|
||||
matchFieldWidth: false,
|
||||
});
|
||||
|
||||
Ext.applyIf(me, { value: [] }); // hack: avoid ExtJS validate() bug
|
||||
Ext.applyIf(me, { value: '' }); // hack: avoid ExtJS validate() bug
|
||||
|
||||
Ext.applyIf(me.listConfig, { width: 400 });
|
||||
|
||||
|
@ -1,169 +1,151 @@
|
||||
Ext.define('Proxmox.DateTimeField', {
|
||||
extend: 'Ext.form.FieldContainer',
|
||||
// FIXME: remove once all use sites upgraded (with versioned depends on new WTK!)
|
||||
alias: ['widget.promxoxDateTimeField'],
|
||||
xtype: 'proxmoxDateTimeField',
|
||||
xtype: 'promxoxDateTimeField',
|
||||
|
||||
layout: 'hbox',
|
||||
|
||||
viewModel: {
|
||||
data: {
|
||||
datetime: null,
|
||||
minDatetime: null,
|
||||
maxDatetime: null,
|
||||
},
|
||||
referenceHolder: true,
|
||||
|
||||
formulas: {
|
||||
date: {
|
||||
get: function(get) {
|
||||
return get('datetime');
|
||||
},
|
||||
set: function(date) {
|
||||
if (!date) {
|
||||
this.set('datetime', null);
|
||||
return;
|
||||
}
|
||||
let datetime = new Date(this.get('datetime'));
|
||||
datetime.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
this.set('datetime', datetime);
|
||||
},
|
||||
},
|
||||
|
||||
time: {
|
||||
get: function(get) {
|
||||
return get('datetime');
|
||||
},
|
||||
set: function(time) {
|
||||
if (!time) {
|
||||
this.set('datetime', null);
|
||||
return;
|
||||
}
|
||||
let datetime = new Date(this.get('datetime'));
|
||||
datetime.setHours(time.getHours());
|
||||
datetime.setMinutes(time.getMinutes());
|
||||
datetime.setSeconds(time.getSeconds());
|
||||
datetime.setMilliseconds(time.getMilliseconds());
|
||||
this.set('datetime', datetime);
|
||||
},
|
||||
},
|
||||
|
||||
minDate: {
|
||||
get: function(get) {
|
||||
let datetime = get('minDatetime');
|
||||
return datetime ? new Date(datetime) : null;
|
||||
},
|
||||
},
|
||||
|
||||
maxDate: {
|
||||
get: function(get) {
|
||||
let datetime = get('maxDatetime');
|
||||
return datetime ? new Date(datetime) : null;
|
||||
},
|
||||
},
|
||||
|
||||
minTime: {
|
||||
get: function(get) {
|
||||
let current = get('datetime');
|
||||
let min = get('minDatetime');
|
||||
if (min && current && !this.isSameDay(current, min)) {
|
||||
return new Date(min).setHours('00', '00', '00', '000');
|
||||
}
|
||||
return min;
|
||||
},
|
||||
},
|
||||
|
||||
maxTime: {
|
||||
get: function(get) {
|
||||
let current = get('datetime');
|
||||
let max = get('maxDatetime');
|
||||
if (max && current && !this.isSameDay(current, max)) {
|
||||
return new Date(max).setHours('23', '59', '59', '999');
|
||||
}
|
||||
return max;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Helper function to check if dates are the same day of the year
|
||||
isSameDay: function(date1, date2) {
|
||||
return date1.getDate() === date2.getDate() &&
|
||||
date1.getMonth() === date2.getMonth() &&
|
||||
date1.getFullYear() === date2.getFullYear();
|
||||
},
|
||||
},
|
||||
|
||||
config: {
|
||||
value: null,
|
||||
submitFormat: 'U',
|
||||
disabled: false,
|
||||
},
|
||||
|
||||
setValue: function(value) {
|
||||
this.getViewModel().set('datetime', value);
|
||||
},
|
||||
submitFormat: 'U',
|
||||
|
||||
getValue: function() {
|
||||
return this.getViewModel().get('datetime');
|
||||
let me = this;
|
||||
let d = me.lookupReference('dateentry').getValue();
|
||||
|
||||
if (d === undefined || d === null) { return null; }
|
||||
|
||||
let t = me.lookupReference('timeentry').getValue();
|
||||
|
||||
if (t === undefined || t === null) { return null; }
|
||||
|
||||
let offset = (t.getHours() * 3600 + t.getMinutes() * 60) * 1000;
|
||||
|
||||
return new Date(d.getTime() + offset);
|
||||
},
|
||||
|
||||
getSubmitValue: function() {
|
||||
let me = this;
|
||||
let value = me.getValue();
|
||||
return value ? Ext.Date.format(value, me.submitFormat) : null;
|
||||
},
|
||||
let me = this;
|
||||
let format = me.submitFormat;
|
||||
let value = me.getValue();
|
||||
|
||||
setMinValue: function(value) {
|
||||
this.getViewModel().set('minDatetime', value);
|
||||
},
|
||||
|
||||
getMinValue: function() {
|
||||
return this.getViewModel().get('minDatetime');
|
||||
},
|
||||
|
||||
setMaxValue: function(value) {
|
||||
this.getViewModel().set('maxDatetime', value);
|
||||
},
|
||||
|
||||
getMaxValue: function() {
|
||||
return this.getViewModel().get('maxDatetime');
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
me.callParent();
|
||||
|
||||
let vm = me.getViewModel();
|
||||
vm.set('datetime', me.config.value);
|
||||
// Propagate state change to binding
|
||||
vm.bind('{datetime}', function(value) {
|
||||
me.publishState('value', value);
|
||||
me.fireEvent('change', value);
|
||||
});
|
||||
return value ? Ext.Date.format(value, format) : null;
|
||||
},
|
||||
|
||||
items: [
|
||||
{
|
||||
xtype: 'datefield',
|
||||
editable: false,
|
||||
reference: 'dateentry',
|
||||
flex: 1,
|
||||
format: 'Y-m-d',
|
||||
bind: {
|
||||
value: '{date}',
|
||||
minValue: '{minDate}',
|
||||
maxValue: '{maxDate}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'timefield',
|
||||
reference: 'timeentry',
|
||||
format: 'H:i',
|
||||
width: 80,
|
||||
value: '00:00',
|
||||
increment: 60,
|
||||
bind: {
|
||||
value: '{time}',
|
||||
minValue: '{minTime}',
|
||||
maxValue: '{maxTime}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
setMinValue: function(value) {
|
||||
let me = this;
|
||||
let current = me.getValue();
|
||||
if (!value || !current) {
|
||||
return;
|
||||
}
|
||||
|
||||
let minhours = value.getHours();
|
||||
let minminutes = value.getMinutes();
|
||||
|
||||
let hours = current.getHours();
|
||||
let minutes = current.getMinutes();
|
||||
|
||||
value.setHours(0);
|
||||
value.setMinutes(0);
|
||||
value.setSeconds(0);
|
||||
current.setHours(0);
|
||||
current.setMinutes(0);
|
||||
current.setSeconds(0);
|
||||
|
||||
let time = new Date();
|
||||
if (current-value > 0) {
|
||||
time.setHours(0);
|
||||
time.setMinutes(0);
|
||||
time.setSeconds(0);
|
||||
time.setMilliseconds(0);
|
||||
} else {
|
||||
time.setHours(minhours);
|
||||
time.setMinutes(minminutes);
|
||||
}
|
||||
me.lookup('timeentry').setMinValue(time);
|
||||
|
||||
// current time is smaller than the time part of the new minimum
|
||||
// so we have to add 1 to the day
|
||||
if (minhours*60+minminutes > hours*60+minutes) {
|
||||
value.setDate(value.getDate()+1);
|
||||
}
|
||||
me.lookup('dateentry').setMinValue(value);
|
||||
},
|
||||
|
||||
setMaxValue: function(value) {
|
||||
let me = this;
|
||||
let current = me.getValue();
|
||||
if (!value || !current) {
|
||||
return;
|
||||
}
|
||||
|
||||
let maxhours = value.getHours();
|
||||
let maxminutes = value.getMinutes();
|
||||
|
||||
let hours = current.getHours();
|
||||
let minutes = current.getMinutes();
|
||||
|
||||
value.setHours(0);
|
||||
value.setMinutes(0);
|
||||
current.setHours(0);
|
||||
current.setMinutes(0);
|
||||
|
||||
let time = new Date();
|
||||
if (value-current > 0) {
|
||||
time.setHours(23);
|
||||
time.setMinutes(59);
|
||||
time.setSeconds(59);
|
||||
} else {
|
||||
time.setHours(maxhours);
|
||||
time.setMinutes(maxminutes);
|
||||
}
|
||||
me.lookup('timeentry').setMaxValue(time);
|
||||
|
||||
// current time is biger than the time part of the new maximum
|
||||
// so we have to subtract 1 to the day
|
||||
if (maxhours*60+maxminutes < hours*60+minutes) {
|
||||
value.setDate(value.getDate()-1);
|
||||
}
|
||||
|
||||
me.lookup('dateentry').setMaxValue(value);
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
me.callParent();
|
||||
|
||||
let value = me.value || new Date();
|
||||
|
||||
me.lookupReference('dateentry').setValue(value);
|
||||
me.lookupReference('timeentry').setValue(value);
|
||||
|
||||
if (me.minValue) {
|
||||
me.setMinValue(me.minValue);
|
||||
}
|
||||
|
||||
if (me.maxValue) {
|
||||
me.setMaxValue(me.maxValue);
|
||||
}
|
||||
|
||||
me.relayEvents(me.lookupReference('dateentry'), ['change']);
|
||||
me.relayEvents(me.lookupReference('timeentry'), ['change']);
|
||||
},
|
||||
});
|
||||
|
@ -24,7 +24,7 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
|
||||
getEditable: function() {
|
||||
let me = this;
|
||||
let vm = me.getViewModel();
|
||||
return vm.get('editable');
|
||||
vm.get('editable');
|
||||
},
|
||||
|
||||
setValue: function(value) {
|
||||
@ -37,19 +37,9 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
|
||||
getValue: function() {
|
||||
let me = this;
|
||||
let vm = me.getViewModel();
|
||||
// FIXME: add return, but check all use-sites for regressions then
|
||||
vm.get('value');
|
||||
},
|
||||
|
||||
setEmptyText: function(emptyText) {
|
||||
let me = this;
|
||||
me.editField.setEmptyText(emptyText);
|
||||
},
|
||||
getEmptyText: function() {
|
||||
let me = this;
|
||||
return me.editField.getEmptyText();
|
||||
},
|
||||
|
||||
layout: 'fit',
|
||||
defaults: {
|
||||
hideLabel: true,
|
||||
@ -108,11 +98,6 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
|
||||
|
||||
me.callParent();
|
||||
|
||||
// save a reference to make it easier when one needs to operate on the underlying fields,
|
||||
// like when creating a passthrough getter/setter to allow easy data-binding.
|
||||
me.editField = me.down(editConfig.xtype);
|
||||
me.displayField = me.down(displayConfig.xtype);
|
||||
|
||||
me.getViewModel().set('editable', me.editable);
|
||||
},
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
Ext.define('Proxmox.form.field.FingerprintField', {
|
||||
extend: 'Proxmox.form.field.Textfield',
|
||||
alias: ['widget.pmxFingerprintField'],
|
||||
|
||||
config: {
|
||||
fieldLabel: gettext('Fingerprint'),
|
||||
emptyText: gettext('Server certificate SHA-256 fingerprint, required for self-signed certificates'),
|
||||
|
||||
regex: /[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){31}/,
|
||||
regexText: gettext('Example') + ': AB:CD:EF:...',
|
||||
|
||||
allowBlank: true,
|
||||
},
|
||||
});
|
@ -39,8 +39,6 @@ Ext.define('Proxmox.form.MultiDiskSelector', {
|
||||
setValue: function(value) {
|
||||
let me = this;
|
||||
|
||||
value ??= [];
|
||||
|
||||
if (!Ext.isArray(value)) {
|
||||
value = value.split(/;, /);
|
||||
}
|
||||
|
@ -45,6 +45,10 @@ Ext.define('Proxmox.form.NetworkSelector', {
|
||||
networkSelectorStore.load();
|
||||
}
|
||||
},
|
||||
// set default value to empty array, else it inits it with
|
||||
// null and after the store load it is an empty array,
|
||||
// triggering dirtychange
|
||||
value: [],
|
||||
valueField: 'cidr',
|
||||
displayField: 'cidr',
|
||||
store: {
|
||||
@ -119,7 +123,6 @@ Ext.define('Proxmox.form.NetworkSelector', {
|
||||
header: gettext('Comment'),
|
||||
flex: 2,
|
||||
dataIndex: 'comments',
|
||||
renderer: Ext.String.htmlEncode,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -7,7 +7,6 @@ Ext.define('Proxmox.form.RealmComboBox', {
|
||||
|
||||
init: function(view) {
|
||||
let store = view.getStore();
|
||||
store.proxy.url = `/api2/json${view.baseUrl}`;
|
||||
if (view.storeFilter) {
|
||||
store.setFilters(view.storeFilter);
|
||||
}
|
||||
@ -46,7 +45,6 @@ Ext.define('Proxmox.form.RealmComboBox', {
|
||||
triggerAction: 'all',
|
||||
valueField: 'realm',
|
||||
displayField: 'descr',
|
||||
baseUrl: '/access/domains',
|
||||
getState: function() {
|
||||
return { value: this.getValue() };
|
||||
},
|
||||
|
@ -1,61 +0,0 @@
|
||||
Ext.define('Proxmox.form.field.Base64TextArea', {
|
||||
extend: 'Ext.form.field.TextArea',
|
||||
alias: ['widget.proxmoxBase64TextArea'],
|
||||
|
||||
config: {
|
||||
skipEmptyText: false,
|
||||
deleteEmpty: false,
|
||||
trimValue: false,
|
||||
editable: true,
|
||||
width: 600,
|
||||
height: 400,
|
||||
scrollable: 'y',
|
||||
emptyText: gettext('You can use Markdown for rich text formatting.'),
|
||||
},
|
||||
|
||||
setValue: function(value) {
|
||||
// We want to edit the decoded version of the text
|
||||
this.callParent([Proxmox.Utils.base64ToUtf8(value)]);
|
||||
},
|
||||
|
||||
processRawValue: function(value) {
|
||||
// The field could contain multi-line values
|
||||
return Proxmox.Utils.utf8ToBase64(value);
|
||||
},
|
||||
|
||||
getSubmitData: function() {
|
||||
let me = this,
|
||||
data = null,
|
||||
val;
|
||||
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
|
||||
val = me.getSubmitValue();
|
||||
if (val !== null) {
|
||||
data = {};
|
||||
data[me.getName()] = val;
|
||||
} else if (me.getDeleteEmpty()) {
|
||||
data = {};
|
||||
data.delete = me.getName();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
getSubmitValue: function() {
|
||||
let me = this;
|
||||
|
||||
let value = this.processRawValue(this.getRawValue());
|
||||
if (me.getTrimValue() && typeof value === 'string') {
|
||||
value = value.trim();
|
||||
}
|
||||
if (value !== '') {
|
||||
return value;
|
||||
}
|
||||
|
||||
return me.getSkipEmptyText() ? null: value;
|
||||
},
|
||||
|
||||
setAllowBlank: function(allowBlank) {
|
||||
this.allowBlank = allowBlank;
|
||||
this.validate();
|
||||
},
|
||||
});
|
@ -6,8 +6,6 @@ Ext.define('Proxmox.form.field.Textfield', {
|
||||
skipEmptyText: true,
|
||||
|
||||
deleteEmpty: false,
|
||||
|
||||
trimValue: false,
|
||||
},
|
||||
|
||||
getSubmitData: function() {
|
||||
@ -31,9 +29,6 @@ Ext.define('Proxmox.form.field.Textfield', {
|
||||
let me = this;
|
||||
|
||||
let value = this.processRawValue(this.getRawValue());
|
||||
if (me.getTrimValue() && typeof value === 'string') {
|
||||
value = value.trim();
|
||||
}
|
||||
if (value !== '') {
|
||||
return value;
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
Ext.define('Proxmox.form.field.VlanField', {
|
||||
extend: 'Ext.form.field.Number',
|
||||
alias: ['widget.proxmoxvlanfield'],
|
||||
|
||||
deleteEmpty: false,
|
||||
|
||||
emptyText: gettext('no VLAN'),
|
||||
|
||||
fieldLabel: gettext('VLAN Tag'),
|
||||
|
||||
allowBlank: true,
|
||||
|
||||
getSubmitData: function() {
|
||||
var me = this,
|
||||
data = null,
|
||||
val;
|
||||
if (!me.disabled && me.submitValue) {
|
||||
val = me.getSubmitValue();
|
||||
if (val) {
|
||||
data = {};
|
||||
data[me.getName()] = val;
|
||||
} else if (me.deleteEmpty) {
|
||||
data = {};
|
||||
data.delete = me.getName();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
Ext.apply(me, {
|
||||
minValue: 1,
|
||||
maxValue: 4094,
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
},
|
||||
});
|
@ -67,6 +67,7 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
||||
editor: {
|
||||
xtype: 'proxmoxWindowEdit',
|
||||
subject: text,
|
||||
onlineHelp: opts.onlineHelp,
|
||||
fieldDefaults: {
|
||||
labelWidth: opts.labelWidth || 100,
|
||||
},
|
||||
@ -83,9 +84,6 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (opts.onlineHelp) {
|
||||
me.rows[name].editor.onlineHelp = opts.onlineHelp;
|
||||
}
|
||||
},
|
||||
|
||||
add_text_row: function(name, text, opts) {
|
||||
@ -102,6 +100,7 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
||||
editor: {
|
||||
xtype: 'proxmoxWindowEdit',
|
||||
subject: text,
|
||||
onlineHelp: opts.onlineHelp,
|
||||
fieldDefaults: {
|
||||
labelWidth: opts.labelWidth || 100,
|
||||
},
|
||||
@ -116,9 +115,6 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (opts.onlineHelp) {
|
||||
me.rows[name].editor.onlineHelp = opts.onlineHelp;
|
||||
}
|
||||
},
|
||||
|
||||
add_boolean_row: function(name, text, opts) {
|
||||
@ -135,6 +131,7 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
||||
editor: {
|
||||
xtype: 'proxmoxWindowEdit',
|
||||
subject: text,
|
||||
onlineHelp: opts.onlineHelp,
|
||||
fieldDefaults: {
|
||||
labelWidth: opts.labelWidth || 100,
|
||||
},
|
||||
@ -150,9 +147,6 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (opts.onlineHelp) {
|
||||
me.rows[name].editor.onlineHelp = opts.onlineHelp;
|
||||
}
|
||||
},
|
||||
|
||||
add_integer_row: function(name, text, opts) {
|
||||
@ -169,6 +163,7 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
||||
editor: {
|
||||
xtype: 'proxmoxWindowEdit',
|
||||
subject: text,
|
||||
onlineHelp: opts.onlineHelp,
|
||||
fieldDefaults: {
|
||||
labelWidth: opts.labelWidth || 100,
|
||||
},
|
||||
@ -185,40 +180,6 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (opts.onlineHelp) {
|
||||
me.rows[name].editor.onlineHelp = opts.onlineHelp;
|
||||
}
|
||||
},
|
||||
|
||||
// adds a row that allows editing in a full TextArea that transparently de/encodes as Base64
|
||||
add_textareafield_row: function(name, text, opts) {
|
||||
let me = this;
|
||||
|
||||
opts = opts || {};
|
||||
me.rows = me.rows || {};
|
||||
let fieldOpts = opts.fieldOpts || {};
|
||||
|
||||
me.rows[name] = {
|
||||
required: true,
|
||||
defaultValue: "",
|
||||
header: text,
|
||||
renderer: value => Ext.htmlEncode(Proxmox.Utils.base64ToUtf8(value)),
|
||||
editor: {
|
||||
xtype: 'proxmoxWindowEdit',
|
||||
subject: text,
|
||||
fieldDefaults: {
|
||||
labelWidth: opts.labelWidth || 600,
|
||||
},
|
||||
items: {
|
||||
xtype: 'proxmoxBase64TextArea',
|
||||
...fieldOpts,
|
||||
name,
|
||||
},
|
||||
},
|
||||
};
|
||||
if (opts.onlineHelp) {
|
||||
me.rows[name].editor.onlineHelp = opts.onlineHelp;
|
||||
}
|
||||
},
|
||||
|
||||
editorConfig: {}, // default config passed to editor
|
||||
|
@ -2,7 +2,7 @@ Ext.define('apt-pkglist', {
|
||||
extend: 'Ext.data.Model',
|
||||
fields: [
|
||||
'Package', 'Title', 'Description', 'Section', 'Arch', 'Priority', 'Version', 'OldVersion',
|
||||
'Origin',
|
||||
'ChangeLogUrl', 'Origin',
|
||||
],
|
||||
idProperty: 'Package',
|
||||
});
|
||||
@ -108,8 +108,8 @@ Ext.define('Proxmox.node.APT', {
|
||||
});
|
||||
|
||||
let show_changelog = function(rec) {
|
||||
if (!rec?.data?.Package) {
|
||||
console.debug('cannot show changelog, missing Package', rec);
|
||||
if (!rec?.data?.ChangeLogUrl || !rec?.data?.Package) {
|
||||
console.debug('cannot show changelog, missing Package and/or ChangeLogUrl', rec);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -154,7 +154,7 @@ Ext.define('Proxmox.node.APT', {
|
||||
text: gettext('Changelog'),
|
||||
selModel: sm,
|
||||
disabled: true,
|
||||
enableFn: rec => !!rec?.data?.Package,
|
||||
enableFn: rec => !!rec?.data?.ChangeLogUrl && !!rec?.data?.Package,
|
||||
handler: (b, e, rec) => show_changelog(rec),
|
||||
});
|
||||
|
||||
|
@ -270,7 +270,7 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
|
||||
let txt = [gettext('Warning')];
|
||||
record.data.warnings.forEach((warning) => {
|
||||
if (warning.property === 'Suites') {
|
||||
txt.push(Ext.htmlEncode(warning.message));
|
||||
txt.push(warning.message);
|
||||
}
|
||||
});
|
||||
metaData.tdAttr = `data-qtip="${Ext.htmlEncode(txt.join('<br>'))}"`;
|
||||
@ -305,7 +305,7 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
|
||||
? gettext('The no-subscription repository is NOT production-ready')
|
||||
: gettext('The test repository may contain unstable updates')
|
||||
;
|
||||
metaData.tdAttr = `data-qtip="${Ext.htmlEncode(Ext.htmlEncode(qtip))}"`;
|
||||
metaData.tdAttr = `data-qtip="${Ext.htmlEncode(qtip)}"`;
|
||||
}
|
||||
}
|
||||
return components.join(' ') + err;
|
||||
@ -361,7 +361,6 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
|
||||
header: gettext('Comment'),
|
||||
dataIndex: 'Comment',
|
||||
flex: 2,
|
||||
renderer: Ext.String.htmlEncode,
|
||||
},
|
||||
],
|
||||
|
||||
@ -461,11 +460,6 @@ Ext.define('Proxmox.node.APTRepositories', {
|
||||
let enterprise = vm.get('enterpriseRepo');
|
||||
let nosubscription = vm.get('noSubscriptionRepo');
|
||||
let test = vm.get('testRepo');
|
||||
let cephRepos = {
|
||||
enterprise: vm.get('cephEnterpriseRepo'),
|
||||
nosubscription: vm.get('cephNoSubscriptionRepo'),
|
||||
test: vm.get('cephTestRepo'),
|
||||
};
|
||||
let wrongSuites = vm.get('suitesWarning');
|
||||
let mixedSuites = vm.get('mixedSuites');
|
||||
|
||||
@ -489,33 +483,17 @@ Ext.define('Proxmox.node.APTRepositories', {
|
||||
addWarn(gettext('Detected mixed suites before upgrade'));
|
||||
}
|
||||
|
||||
let productionReadyCheck = (repos, type, noSubAlternateName) => {
|
||||
if (!activeSubscription && repos.enterprise) {
|
||||
addWarn(Ext.String.format(
|
||||
gettext('The {0}enterprise repository is enabled, but there is no active subscription!'),
|
||||
type,
|
||||
));
|
||||
}
|
||||
if (!activeSubscription && enterprise) {
|
||||
addWarn(gettext('The enterprise repository is enabled, but there is no active subscription!'));
|
||||
}
|
||||
|
||||
if (repos.nosubscription) {
|
||||
addWarn(Ext.String.format(
|
||||
gettext('The {0}no-subscription{1} repository is not recommended for production use!'),
|
||||
type,
|
||||
noSubAlternateName,
|
||||
));
|
||||
}
|
||||
if (nosubscription) {
|
||||
addWarn(gettext('The no-subscription repository is not recommended for production use!'));
|
||||
}
|
||||
|
||||
if (repos.test) {
|
||||
addWarn(Ext.String.format(
|
||||
gettext('The {0}test repository may pull in unstable updates and is not recommended for production use!'),
|
||||
type,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
productionReadyCheck({ enterprise, nosubscription, test }, '', '');
|
||||
// TODO drop alternate 'main' name when no longer relevant
|
||||
productionReadyCheck(cephRepos, 'Ceph ', '/main');
|
||||
if (test) {
|
||||
addWarn(gettext('The test repository may pull in unstable updates and is not recommended for production use!'));
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
text = gettext('Fatal parsing error for at least one repository');
|
||||
@ -540,9 +518,6 @@ Ext.define('Proxmox.node.APTRepositories', {
|
||||
noSubscriptionRepo: '',
|
||||
enterpriseRepo: '',
|
||||
testRepo: '',
|
||||
cephEnterpriseRepo: '',
|
||||
cephNoSubscriptionRepo: '',
|
||||
cephTestRepo: '',
|
||||
selectionenabled: false,
|
||||
state: {},
|
||||
},
|
||||
@ -611,7 +586,7 @@ Ext.define('Proxmox.node.APTRepositories', {
|
||||
nodename: '{nodename}',
|
||||
onlineHelp: '{onlineHelp}',
|
||||
},
|
||||
majorUpgradeAllowed: false, // TODO get release information from an API call?
|
||||
majorUpgradeAllowed: true, // TODO get release information from an API call?
|
||||
listeners: {
|
||||
selectionchange: 'selectionChange',
|
||||
},
|
||||
@ -652,12 +627,6 @@ Ext.define('Proxmox.node.APTRepositories', {
|
||||
vm.set('noSubscriptionRepo', status);
|
||||
} else if (handle === 'test') {
|
||||
vm.set('testRepo', status);
|
||||
} else if (handle.match(/^ceph-[a-zA-Z]+-enterprise$/)) {
|
||||
vm.set('cephEnterpriseRepo', status);
|
||||
} else if (handle.match(/^ceph-[a-zA-Z]+-no-subscription$/)) {
|
||||
vm.set('cephNoSubscriptionRepo', status);
|
||||
} else if (handle.match(/^ceph-[a-zA-Z]+-test$/)) {
|
||||
vm.set('cephTestRepo', status);
|
||||
}
|
||||
me.getController().updateState();
|
||||
|
||||
|
@ -2,10 +2,6 @@ Ext.define('Proxmox.node.DNSEdit', {
|
||||
extend: 'Proxmox.window.Edit',
|
||||
alias: ['widget.proxmoxNodeDNSEdit'],
|
||||
|
||||
// Some longer existing APIs use a brittle "replace whole config" style, you can set this option
|
||||
// if the DNSEdit component is used in an API that has more modern, granular update semantics.
|
||||
deleteEmpty: false,
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
@ -25,7 +21,6 @@ Ext.define('Proxmox.node.DNSEdit', {
|
||||
fieldLabel: gettext('DNS server') + " 1",
|
||||
vtype: 'IP64Address',
|
||||
skipEmptyText: true,
|
||||
deleteEmpty: me.deleteEmpty,
|
||||
name: 'dns1',
|
||||
},
|
||||
{
|
||||
@ -33,7 +28,6 @@ Ext.define('Proxmox.node.DNSEdit', {
|
||||
fieldLabel: gettext('DNS server') + " 2",
|
||||
vtype: 'IP64Address',
|
||||
skipEmptyText: true,
|
||||
deleteEmpty: me.deleteEmpty,
|
||||
name: 'dns2',
|
||||
},
|
||||
{
|
||||
@ -41,7 +35,6 @@ Ext.define('Proxmox.node.DNSEdit', {
|
||||
fieldLabel: gettext('DNS server') + " 3",
|
||||
vtype: 'IP64Address',
|
||||
skipEmptyText: true,
|
||||
deleteEmpty: me.deleteEmpty,
|
||||
name: 'dns3',
|
||||
},
|
||||
];
|
||||
|
@ -2,10 +2,6 @@ Ext.define('Proxmox.node.DNSView', {
|
||||
extend: 'Proxmox.grid.ObjectGrid',
|
||||
alias: ['widget.proxmoxNodeDNSView'],
|
||||
|
||||
// Some longer existing APIs use a brittle "replace whole config" style, you can set this option
|
||||
// if the DNSView component is used in an API that has more modern, granular update semantics.
|
||||
deleteEmpty: false,
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
@ -16,7 +12,6 @@ Ext.define('Proxmox.node.DNSView', {
|
||||
let run_editor = () => Ext.create('Proxmox.node.DNSEdit', {
|
||||
autoShow: true,
|
||||
nodename: me.nodename,
|
||||
deleteEmpty: me.deleteEmpty,
|
||||
});
|
||||
|
||||
Ext.apply(me, {
|
||||
@ -26,7 +21,7 @@ Ext.define('Proxmox.node.DNSView', {
|
||||
run_editor: run_editor,
|
||||
rows: {
|
||||
search: {
|
||||
header: gettext('Search domain'),
|
||||
header: 'Search domain',
|
||||
required: true,
|
||||
renderer: Ext.htmlEncode,
|
||||
},
|
||||
|
@ -2,9 +2,6 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
||||
extend: 'Proxmox.window.Edit',
|
||||
alias: ['widget.proxmoxNodeNetworkEdit'],
|
||||
|
||||
// Enable to show the VLAN ID field
|
||||
enableBridgeVlanIds: false,
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
@ -60,53 +57,11 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
||||
}
|
||||
|
||||
if (me.iftype === 'bridge') {
|
||||
let vlanIdsField = !me.enableBridgeVlanIds ? undefined : Ext.create('Ext.form.field.Text', {
|
||||
fieldLabel: gettext('VLAN IDs'),
|
||||
name: 'bridge_vids',
|
||||
emptyText: '2-4094',
|
||||
disabled: true,
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': gettext("List of VLAN IDs and ranges, useful for NICs with restricted VLAN offloading support. For example: '2 4 100-200'"),
|
||||
},
|
||||
validator: function(value) {
|
||||
if (!value) { // empty
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const vid of value.split(/\s+[,;]?/)) {
|
||||
if (!vid) {
|
||||
continue;
|
||||
}
|
||||
let res = vid.match(/^(\d+)(?:-(\d+))?$/);
|
||||
if (!res) {
|
||||
return Ext.String.format(gettext("not a valid bridge VLAN ID entry: {0}"), vid);
|
||||
}
|
||||
let start = Number(res[1]), end = Number(res[2] ?? res[1]); // end=start for single IDs
|
||||
|
||||
if (Number.isNaN(start) || Number.isNaN(end)) {
|
||||
return Ext.String.format(gettext('VID range includes not-a-number: {0}'), vid);
|
||||
} else if (start > end) {
|
||||
return Ext.String.format(gettext('VID range must go from lower to higher tag: {0}'), vid);
|
||||
} else if (start < 2 || end > 4094) { // check just one each, we already ensured start < end
|
||||
return Ext.String.format(gettext('VID range outside of allowed 2 and 4094 limit: {0}'), vid);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
column2.push({
|
||||
xtype: 'proxmoxcheckbox',
|
||||
fieldLabel: gettext('VLAN aware'),
|
||||
name: 'bridge_vlan_aware',
|
||||
deleteEmpty: !me.isCreate,
|
||||
listeners: {
|
||||
change: function(f, newVal) {
|
||||
if (vlanIdsField) {
|
||||
vlanIdsField.setDisabled(!newVal);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
column2.push({
|
||||
xtype: 'textfield',
|
||||
@ -117,9 +72,6 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
||||
'data-qtip': gettext('Space-separated list of interfaces, for example: enp0s0 enp1s0'),
|
||||
},
|
||||
});
|
||||
if (vlanIdsField) {
|
||||
advancedColumn2.push(vlanIdsField);
|
||||
}
|
||||
} else if (me.iftype === 'OVSBridge') {
|
||||
column2.push({
|
||||
xtype: 'textfield',
|
||||
@ -145,7 +97,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
||||
name: 'ovs_bridge',
|
||||
});
|
||||
column2.push({
|
||||
xtype: 'proxmoxvlanfield',
|
||||
xtype: 'pveVlanField',
|
||||
deleteEmpty: !me.isCreate,
|
||||
name: 'ovs_tag',
|
||||
value: '',
|
||||
@ -188,7 +140,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
||||
});
|
||||
|
||||
column2.push({
|
||||
xtype: 'proxmoxvlanfield',
|
||||
xtype: 'pveVlanField',
|
||||
name: 'vlan-id',
|
||||
value: me.vlanidvalue,
|
||||
disabled: me.disablevlanid,
|
||||
@ -259,7 +211,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
||||
name: 'ovs_bridge',
|
||||
});
|
||||
column2.push({
|
||||
xtype: 'proxmoxvlanfield',
|
||||
xtype: 'pveVlanField',
|
||||
deleteEmpty: !me.isCreate,
|
||||
name: 'ovs_tag',
|
||||
value: '',
|
||||
@ -302,7 +254,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
||||
value: me.iface,
|
||||
vtype: iface_vtype,
|
||||
allowBlank: false,
|
||||
maxLength: iface_vtype === 'BridgeName' ? 10 : 15,
|
||||
maxLength: 15,
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': gettext('For example, vmbr0.100, vmbr0, vlan0.100, vlan0'),
|
||||
|
@ -33,9 +33,6 @@ Ext.define('Proxmox.node.NetworkView', {
|
||||
|
||||
showApplyBtn: false,
|
||||
|
||||
// for options passed down to the network edit window
|
||||
editOptions: {},
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
@ -103,7 +100,6 @@ Ext.define('Proxmox.node.NetworkView', {
|
||||
nodename: me.nodename,
|
||||
iface: rec.data.iface,
|
||||
iftype: rec.data.type,
|
||||
...me.editOptions,
|
||||
listeners: {
|
||||
destroy: () => reload(),
|
||||
},
|
||||
@ -174,7 +170,6 @@ Ext.define('Proxmox.node.NetworkView', {
|
||||
nodename: me.nodename,
|
||||
iftype: iType,
|
||||
iface_default: findNextFreeInterfaceId(iDefault ?? iType),
|
||||
...me.editOptions,
|
||||
onlineHelp: 'sysadmin_network_configuration',
|
||||
listeners: {
|
||||
destroy: () => reload(),
|
||||
|
@ -29,8 +29,6 @@ Ext.define('Proxmox.node.ServiceView', {
|
||||
},
|
||||
});
|
||||
|
||||
let filterInstalledOnly = record => record.get('unit-state') !== 'not-found';
|
||||
|
||||
let store = Ext.create('Proxmox.data.DiffStore', {
|
||||
rstore: rstore,
|
||||
sortAfterUpdate: true,
|
||||
@ -40,24 +38,6 @@ Ext.define('Proxmox.node.ServiceView', {
|
||||
direction: 'ASC',
|
||||
},
|
||||
],
|
||||
filters: [
|
||||
filterInstalledOnly,
|
||||
],
|
||||
});
|
||||
|
||||
let unHideCB = Ext.create('Ext.form.field.Checkbox', {
|
||||
boxLabel: gettext('Show only installed services'),
|
||||
value: true,
|
||||
boxLabelAlign: 'before',
|
||||
listeners: {
|
||||
change: function(_cb, value) {
|
||||
if (value) {
|
||||
store.addFilter([filterInstalledOnly]);
|
||||
} else {
|
||||
store.clearFilter();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let view_service_log = function() {
|
||||
@ -186,8 +166,6 @@ Ext.define('Proxmox.node.ServiceView', {
|
||||
restart_btn,
|
||||
'-',
|
||||
syslog_btn,
|
||||
'->',
|
||||
unHideCB,
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
|
@ -1,9 +1,7 @@
|
||||
Ext.define('Proxmox.panel.AuthView', {
|
||||
extend: 'Ext.grid.GridPanel',
|
||||
alias: 'widget.pmxAuthView',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
|
||||
showDefaultRealm: false,
|
||||
alias: 'widget.pmxAuthView',
|
||||
|
||||
stateful: true,
|
||||
stateId: 'grid-authrealms',
|
||||
@ -13,7 +11,7 @@ Ext.define('Proxmox.panel.AuthView', {
|
||||
},
|
||||
|
||||
baseUrl: '/access/domains',
|
||||
storeBaseUrl: '/access/domains',
|
||||
useTypeInUrl: false,
|
||||
|
||||
columns: [
|
||||
{
|
||||
@ -28,17 +26,6 @@ Ext.define('Proxmox.panel.AuthView', {
|
||||
sortable: true,
|
||||
dataIndex: 'type',
|
||||
},
|
||||
{
|
||||
header: gettext('Default'),
|
||||
width: 80,
|
||||
sortable: true,
|
||||
dataIndex: 'default',
|
||||
renderer: isDefault => isDefault ? Proxmox.Utils.renderEnabledIcon(true) : '',
|
||||
align: 'center',
|
||||
cbind: {
|
||||
hidden: '{!showDefaultRealm}',
|
||||
},
|
||||
},
|
||||
{
|
||||
header: gettext('Comment'),
|
||||
sortable: false,
|
||||
@ -48,17 +35,21 @@ Ext.define('Proxmox.panel.AuthView', {
|
||||
},
|
||||
],
|
||||
|
||||
store: {
|
||||
model: 'pmx-domains',
|
||||
sorters: {
|
||||
property: 'realm',
|
||||
direction: 'ASC',
|
||||
},
|
||||
},
|
||||
|
||||
openEditWindow: function(authType, realm) {
|
||||
let me = this;
|
||||
const { useTypeInUrl, onlineHelp } = Proxmox.Schema.authDomains[authType];
|
||||
|
||||
Ext.create('Proxmox.window.AuthEditBase', {
|
||||
baseUrl: me.baseUrl,
|
||||
useTypeInUrl,
|
||||
onlineHelp,
|
||||
useTypeInUrl: me.useTypeInUrl,
|
||||
authType,
|
||||
realm,
|
||||
showDefaultRealm: me.showDefaultRealm,
|
||||
listeners: {
|
||||
destroy: () => me.reload(),
|
||||
},
|
||||
@ -104,18 +95,6 @@ Ext.define('Proxmox.panel.AuthView', {
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
me.store = {
|
||||
model: 'pmx-domains',
|
||||
sorters: {
|
||||
property: 'realm',
|
||||
direction: 'ASC',
|
||||
},
|
||||
proxy: {
|
||||
type: 'proxmox',
|
||||
url: `/api2/json${me.storeBaseUrl}`,
|
||||
},
|
||||
};
|
||||
|
||||
let menuitems = [];
|
||||
for (const [authType, config] of Object.entries(Proxmox.Schema.authDomains).sort()) {
|
||||
if (!config.add) { continue; }
|
||||
@ -144,7 +123,7 @@ Ext.define('Proxmox.panel.AuthView', {
|
||||
xtype: 'proxmoxStdRemoveButton',
|
||||
getUrl: (rec) => {
|
||||
let url = me.baseUrl;
|
||||
if (Proxmox.Schema.authDomains[rec.data.type].useTypeInUrl) {
|
||||
if (me.useTypeInUrl) {
|
||||
url += `/${rec.get('type')}`;
|
||||
}
|
||||
url += `/${rec.getId()}`;
|
||||
|
@ -85,7 +85,7 @@ Ext.define('Proxmox.panel.Certificates', {
|
||||
url: `/api2/extjs/${url}?restart=1`,
|
||||
method: 'DELETE',
|
||||
success: function(response, opt) {
|
||||
if (cert.reloadUi) {
|
||||
if (cert.reloadUid) {
|
||||
Ext.getBody().mask(
|
||||
gettext('API server will be restarted to use new certificates, please reload web-interface!'),
|
||||
['pve-static-mask'],
|
||||
@ -237,16 +237,10 @@ Ext.define('Proxmox.panel.Certificates', {
|
||||
{
|
||||
xtype: 'proxmoxButton',
|
||||
text: gettext('Delete Custom Certificate'),
|
||||
confirmMsg: rec => {
|
||||
let cert = me.certById[rec.id];
|
||||
if (cert.name) {
|
||||
return Ext.String.format(
|
||||
gettext('Are you sure you want to remove the certificate used for {0}'),
|
||||
cert.name,
|
||||
);
|
||||
}
|
||||
return gettext('Are you sure you want to remove the certificate');
|
||||
},
|
||||
confirmMsg: rec => Ext.String.format(
|
||||
gettext('Are you sure you want to remove the certificate used for {0}'),
|
||||
me.certById[rec.id].name,
|
||||
),
|
||||
callback: () => me.reload(),
|
||||
selModel: me.selModel,
|
||||
disabled: true,
|
||||
|
@ -220,11 +220,7 @@ Ext.define('Proxmox.DiskList', {
|
||||
let extendedInfo = '';
|
||||
if (rec) {
|
||||
let types = [];
|
||||
if (rec.data['osdid-list'] && rec.data['osdid-list'].length > 0) {
|
||||
for (const id of rec.data['osdid-list'].sort()) {
|
||||
types.push(`OSD.${id.toString()}`);
|
||||
}
|
||||
} else if (rec.data.osdid !== undefined && rec.data.osdid >= 0) {
|
||||
if (rec.data.osdid !== undefined && rec.data.osdid >= 0) {
|
||||
types.push(`OSD.${rec.data.osdid.toString()}`);
|
||||
}
|
||||
if (rec.data.journals > 0) {
|
||||
@ -325,14 +321,14 @@ Ext.define('Proxmox.DiskList', {
|
||||
dataIndex: 'status',
|
||||
},
|
||||
{
|
||||
header: gettext('Mounted'),
|
||||
header: 'Mounted',
|
||||
width: 60,
|
||||
align: 'right',
|
||||
renderer: Proxmox.Utils.format_boolean,
|
||||
dataIndex: 'mounted',
|
||||
},
|
||||
{
|
||||
header: gettext('Wearout'),
|
||||
header: 'Wearout',
|
||||
width: 90,
|
||||
sortable: true,
|
||||
align: 'right',
|
||||
@ -341,7 +337,7 @@ Ext.define('Proxmox.DiskList', {
|
||||
if (Ext.isNumeric(value)) {
|
||||
return (100 - value).toString() + '%';
|
||||
}
|
||||
return gettext('N/A');
|
||||
return 'N/A';
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1,91 +0,0 @@
|
||||
Ext.define('Proxmox.panel.EmailRecipientPanel', {
|
||||
extend: 'Ext.panel.Panel',
|
||||
xtype: 'pmxEmailRecipientPanel',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
border: false,
|
||||
|
||||
mailValidator: function() {
|
||||
let mailto_user = this.down(`[name=mailto-user]`);
|
||||
let mailto = this.down(`[name=mailto]`);
|
||||
|
||||
if (!mailto_user.getValue()?.length && !mailto.getValue()) {
|
||||
return gettext('Either mailto or mailto-user must be set');
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
items: [
|
||||
{
|
||||
layout: 'anchor',
|
||||
border: false,
|
||||
cbind: {
|
||||
isCreate: '{isCreate}',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
xtype: 'pmxUserSelector',
|
||||
name: 'mailto-user',
|
||||
multiSelect: true,
|
||||
allowBlank: true,
|
||||
editable: false,
|
||||
skipEmptyText: true,
|
||||
fieldLabel: gettext('Recipient(s)'),
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
validator: function() {
|
||||
return this.up('pmxEmailRecipientPanel').mailValidator();
|
||||
},
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': gettext('The notification will be sent to the user\'s configured mail address'),
|
||||
},
|
||||
listConfig: {
|
||||
width: 600,
|
||||
columns: [
|
||||
{
|
||||
header: gettext('User'),
|
||||
sortable: true,
|
||||
dataIndex: 'userid',
|
||||
renderer: Ext.String.htmlEncode,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
header: gettext('E-Mail'),
|
||||
sortable: true,
|
||||
dataIndex: 'email',
|
||||
renderer: Ext.String.htmlEncode,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
header: gettext('Comment'),
|
||||
sortable: false,
|
||||
dataIndex: 'comment',
|
||||
renderer: Ext.String.htmlEncode,
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('Additional Recipient(s)'),
|
||||
name: 'mailto',
|
||||
allowBlank: true,
|
||||
emptyText: 'user@example.com, ...',
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': gettext('Multiple recipients must be separated by spaces, commas or semicolons'),
|
||||
},
|
||||
validator: function() {
|
||||
return this.up('pmxEmailRecipientPanel').mailValidator();
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
@ -1,75 +0,0 @@
|
||||
Ext.define('Proxmox.panel.GotifyEditPanel', {
|
||||
extend: 'Proxmox.panel.InputPanel',
|
||||
xtype: 'pmxGotifyEditPanel',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
onlineHelp: 'notification_targets_gotify',
|
||||
|
||||
type: 'gotify',
|
||||
|
||||
items: [
|
||||
{
|
||||
xtype: 'pmxDisplayEditField',
|
||||
name: 'name',
|
||||
cbind: {
|
||||
value: '{name}',
|
||||
editable: '{isCreate}',
|
||||
},
|
||||
fieldLabel: gettext('Endpoint Name'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
name: 'enable',
|
||||
fieldLabel: gettext('Enable'),
|
||||
allowBlank: false,
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('Server URL'),
|
||||
name: 'server',
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
inputType: 'password',
|
||||
fieldLabel: gettext('API Token'),
|
||||
name: 'token',
|
||||
cbind: {
|
||||
emptyText: get => !get('isCreate') ? gettext('Unchanged') : '',
|
||||
allowBlank: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'comment',
|
||||
fieldLabel: gettext('Comment'),
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onSetValues: (values) => {
|
||||
values.enable = !values.disable;
|
||||
|
||||
delete values.disable;
|
||||
return values;
|
||||
},
|
||||
|
||||
onGetValues: function(values) {
|
||||
let me = this;
|
||||
|
||||
if (values.enable) {
|
||||
if (!me.isCreate) {
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'disable' });
|
||||
}
|
||||
} else {
|
||||
values.disable = 1;
|
||||
}
|
||||
|
||||
delete values.enable;
|
||||
|
||||
return values;
|
||||
},
|
||||
});
|
@ -22,28 +22,19 @@ Ext.define('Proxmox.panel.LogView', {
|
||||
updateParams: function() {
|
||||
let me = this;
|
||||
let viewModel = me.getViewModel();
|
||||
|
||||
if (viewModel.get('hide_timespan') || viewModel.get('livemode')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let since = viewModel.get('since');
|
||||
let until = viewModel.get('until');
|
||||
if (viewModel.get('hide_timespan')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (since > until) {
|
||||
Ext.Msg.alert('Error', 'Since date must be less equal than Until date.');
|
||||
return;
|
||||
}
|
||||
|
||||
let submitFormat = viewModel.get('submitFormat');
|
||||
|
||||
viewModel.set('params.since', Ext.Date.format(since, submitFormat));
|
||||
if (submitFormat === 'Y-m-d') {
|
||||
viewModel.set('params.until', Ext.Date.format(until, submitFormat) + ' 23:59:59');
|
||||
} else {
|
||||
viewModel.set('params.until', Ext.Date.format(until, submitFormat));
|
||||
}
|
||||
|
||||
viewModel.set('params.since', Ext.Date.format(since, 'Y-m-d'));
|
||||
viewModel.set('params.until', Ext.Date.format(until, 'Y-m-d') + ' 23:59:59');
|
||||
me.getView().loadTask.delay(200);
|
||||
},
|
||||
|
||||
@ -184,27 +175,6 @@ Ext.define('Proxmox.panel.LogView', {
|
||||
}
|
||||
},
|
||||
|
||||
onLiveMode: function() {
|
||||
let me = this;
|
||||
let viewModel = me.getViewModel();
|
||||
viewModel.set('livemode', true);
|
||||
viewModel.set('params', { start: 0, limit: 510 });
|
||||
|
||||
let view = me.getView();
|
||||
delete view.content;
|
||||
view.scrollToEnd = true;
|
||||
me.updateView([], true, false);
|
||||
},
|
||||
|
||||
onTimespan: function() {
|
||||
let me = this;
|
||||
me.getViewModel().set('livemode', false);
|
||||
me.updateView([], false);
|
||||
// Directly apply currently selected values without update
|
||||
// button click.
|
||||
me.updateParams();
|
||||
},
|
||||
|
||||
init: function(view) {
|
||||
let me = this;
|
||||
|
||||
@ -219,7 +189,6 @@ Ext.define('Proxmox.panel.LogView', {
|
||||
viewModel.set('since', since);
|
||||
viewModel.set('params.limit', view.pageSize);
|
||||
viewModel.set('hide_timespan', !view.log_select_timespan);
|
||||
viewModel.set('submitFormat', view.submitFormat);
|
||||
me.lookup('content').setStyle('line-height', `${view.lineHeight}px`);
|
||||
|
||||
view.loadTask = new Ext.util.DelayedTask(me.doLoad, me);
|
||||
@ -255,8 +224,6 @@ Ext.define('Proxmox.panel.LogView', {
|
||||
data: {
|
||||
until: null,
|
||||
since: null,
|
||||
submitFormat: 'Y-m-d',
|
||||
livemode: true,
|
||||
hide_timespan: false,
|
||||
data: {
|
||||
start: 0,
|
||||
@ -296,70 +263,32 @@ Ext.define('Proxmox.panel.LogView', {
|
||||
},
|
||||
items: [
|
||||
'->',
|
||||
'Since: ',
|
||||
{
|
||||
xtype: 'segmentedbutton',
|
||||
items: [
|
||||
{
|
||||
text: gettext('Live Mode'),
|
||||
bind: {
|
||||
pressed: '{livemode}',
|
||||
},
|
||||
handler: 'onLiveMode',
|
||||
},
|
||||
{
|
||||
text: gettext('Select Timespan'),
|
||||
bind: {
|
||||
pressed: '{!livemode}',
|
||||
},
|
||||
handler: 'onTimespan',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
xtype: 'box',
|
||||
autoEl: { cn: gettext('Since') + ':' },
|
||||
bind: {
|
||||
disabled: '{livemode}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxDateTimeField',
|
||||
xtype: 'datefield',
|
||||
name: 'since_date',
|
||||
reference: 'since',
|
||||
format: 'Y-m-d',
|
||||
bind: {
|
||||
disabled: '{livemode}',
|
||||
value: '{since}',
|
||||
maxValue: '{until}',
|
||||
submitFormat: '{submitFormat}',
|
||||
},
|
||||
},
|
||||
'Until: ',
|
||||
{
|
||||
xtype: 'box',
|
||||
autoEl: { cn: gettext('Until') + ':' },
|
||||
bind: {
|
||||
disabled: '{livemode}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxDateTimeField',
|
||||
xtype: 'datefield',
|
||||
name: 'until_date',
|
||||
reference: 'until',
|
||||
format: 'Y-m-d',
|
||||
bind: {
|
||||
disabled: '{livemode}',
|
||||
value: '{until}',
|
||||
minValue: '{since}',
|
||||
submitFormat: '{submitFormat}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'button',
|
||||
text: 'Update',
|
||||
handler: 'updateParams',
|
||||
bind: {
|
||||
disabled: '{livemode}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -7,8 +7,6 @@ Ext.define('Proxmox.panel.NotesView', {
|
||||
bodyPadding: 10,
|
||||
scrollable: true,
|
||||
animCollapse: false,
|
||||
collapseFirst: false,
|
||||
|
||||
maxLength: 64 * 1024,
|
||||
enableTBar: false,
|
||||
onlineHelp: 'markdown_basics',
|
||||
@ -19,7 +17,6 @@ Ext.define('Proxmox.panel.NotesView', {
|
||||
items: [
|
||||
{
|
||||
text: gettext('Edit'),
|
||||
iconCls: 'fa fa-pencil-square-o',
|
||||
handler: function() {
|
||||
let view = this.up('panel');
|
||||
view.run_editor();
|
||||
@ -112,23 +109,7 @@ Ext.define('Proxmox.panel.NotesView', {
|
||||
listeners: {
|
||||
render: function(c) {
|
||||
let me = this;
|
||||
let sp = Ext.state.Manager.getProvider();
|
||||
// to cover live changes to the browser setting
|
||||
me.mon(sp, 'statechange', function(provider, key, value) {
|
||||
if (value === null || key !== 'edit-notes-on-double-click') {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
me.getEl().on('dblclick', me.run_editor, me);
|
||||
} else {
|
||||
// there's only the me.run_editor listener, and removing just that did not work
|
||||
me.getEl().clearListeners();
|
||||
}
|
||||
});
|
||||
// to cover initial setting value
|
||||
if (sp.get('edit-notes-on-double-click', false)) {
|
||||
me.getEl().on('dblclick', me.run_editor, me);
|
||||
}
|
||||
me.getEl().on('dblclick', me.run_editor, me);
|
||||
},
|
||||
afterlayout: function() {
|
||||
let me = this;
|
||||
@ -141,11 +122,10 @@ Ext.define('Proxmox.panel.NotesView', {
|
||||
|
||||
tools: [
|
||||
{
|
||||
glyph: 'xf044@FontAwesome', // fa-pencil-square-o
|
||||
tooltip: gettext('Edit notes'),
|
||||
callback: view => view.run_editor(),
|
||||
style: {
|
||||
paddingRight: '5px',
|
||||
type: 'gear',
|
||||
handler: function() {
|
||||
let view = this.up('panel');
|
||||
view.run_editor();
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1,410 +0,0 @@
|
||||
Ext.define('Proxmox.panel.NotificationConfigViewModel', {
|
||||
extend: 'Ext.app.ViewModel',
|
||||
|
||||
alias: 'viewmodel.pmxNotificationConfigPanel',
|
||||
|
||||
formulas: {
|
||||
builtinSelected: function(get) {
|
||||
let origin = get('selection')?.get('origin');
|
||||
return origin === 'modified-builtin' || origin === 'builtin';
|
||||
},
|
||||
removeButtonText: get => get('builtinSelected') ? gettext('Reset') : gettext('Remove'),
|
||||
removeButtonConfirmMessage: function(get) {
|
||||
if (get('builtinSelected')) {
|
||||
return gettext('Do you want to reset {0} to its default settings?');
|
||||
} else {
|
||||
// Use default message provided by the button
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
Ext.define('Proxmox.panel.NotificationConfigView', {
|
||||
extend: 'Ext.panel.Panel',
|
||||
alias: 'widget.pmxNotificationConfigView',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
onlineHelp: 'chapter_notifications',
|
||||
layout: {
|
||||
type: 'border',
|
||||
},
|
||||
|
||||
items: [
|
||||
{
|
||||
region: 'center',
|
||||
border: false,
|
||||
xtype: 'pmxNotificationEndpointView',
|
||||
cbind: {
|
||||
baseUrl: '{baseUrl}',
|
||||
},
|
||||
},
|
||||
{
|
||||
region: 'south',
|
||||
height: '50%',
|
||||
border: false,
|
||||
collapsible: true,
|
||||
animCollapse: false,
|
||||
xtype: 'pmxNotificationMatcherView',
|
||||
cbind: {
|
||||
baseUrl: '{baseUrl}',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
Ext.define('Proxmox.panel.NotificationEndpointView', {
|
||||
extend: 'Ext.grid.Panel',
|
||||
alias: 'widget.pmxNotificationEndpointView',
|
||||
|
||||
title: gettext('Notification Targets'),
|
||||
|
||||
viewModel: {
|
||||
type: 'pmxNotificationConfigPanel',
|
||||
},
|
||||
|
||||
bind: {
|
||||
selection: '{selection}',
|
||||
},
|
||||
|
||||
controller: {
|
||||
xclass: 'Ext.app.ViewController',
|
||||
|
||||
openEditWindow: function(endpointType, endpoint) {
|
||||
let me = this;
|
||||
|
||||
Ext.create('Proxmox.window.EndpointEditBase', {
|
||||
baseUrl: me.getView().baseUrl,
|
||||
type: endpointType,
|
||||
|
||||
name: endpoint,
|
||||
autoShow: true,
|
||||
listeners: {
|
||||
destroy: () => me.reload(),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
openEditForSelectedItem: function() {
|
||||
let me = this;
|
||||
let view = me.getView();
|
||||
|
||||
let selection = view.getSelection();
|
||||
if (selection.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
me.openEditWindow(selection[0].data.type, selection[0].data.name);
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
let me = this;
|
||||
let view = me.getView();
|
||||
view.getStore().rstore.load();
|
||||
this.getView().setSelection(null);
|
||||
},
|
||||
|
||||
testEndpoint: function() {
|
||||
let me = this;
|
||||
let view = me.getView();
|
||||
|
||||
let selection = view.getSelection();
|
||||
if (selection.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = selection[0].data.name;
|
||||
|
||||
Ext.Msg.confirm(
|
||||
gettext("Notification Target Test"),
|
||||
Ext.String.format(gettext("Do you want to send a test notification to '{0}'?"), target),
|
||||
function(decision) {
|
||||
if (decision !== "yes") {
|
||||
return;
|
||||
}
|
||||
|
||||
Proxmox.Utils.API2Request({
|
||||
method: 'POST',
|
||||
url: `${view.baseUrl}/targets/${target}/test`,
|
||||
|
||||
success: function(response, opt) {
|
||||
Ext.Msg.show({
|
||||
title: gettext('Notification Target Test'),
|
||||
message: Ext.String.format(
|
||||
gettext("Sent test notification to '{0}'."),
|
||||
target,
|
||||
),
|
||||
buttons: Ext.Msg.OK,
|
||||
icon: Ext.Msg.INFO,
|
||||
});
|
||||
},
|
||||
autoErrorAlert: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
itemdblclick: 'openEditForSelectedItem',
|
||||
activate: 'reload',
|
||||
},
|
||||
|
||||
emptyText: gettext('No notification targets configured'),
|
||||
|
||||
columns: [
|
||||
{
|
||||
dataIndex: 'disable',
|
||||
text: gettext('Enable'),
|
||||
renderer: (disable) => Proxmox.Utils.renderEnabledIcon(!disable),
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
text: gettext('Target Name'),
|
||||
renderer: Ext.String.htmlEncode,
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
dataIndex: 'type',
|
||||
text: gettext('Type'),
|
||||
renderer: Ext.String.htmlEncode,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
dataIndex: 'comment',
|
||||
text: gettext('Comment'),
|
||||
renderer: Ext.String.htmlEncode,
|
||||
flex: 3,
|
||||
},
|
||||
{
|
||||
dataIndex: 'origin',
|
||||
text: gettext('Origin'),
|
||||
renderer: (origin) => {
|
||||
switch (origin) {
|
||||
case 'user-created': return gettext('Custom');
|
||||
case 'modified-builtin': return gettext('Built-In (modified)');
|
||||
case 'builtin': return gettext('Built-In');
|
||||
}
|
||||
|
||||
// Should not happen...
|
||||
return 'unknown';
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
store: {
|
||||
type: 'diff',
|
||||
autoDestroy: true,
|
||||
autoDestroyRstore: true,
|
||||
rstore: {
|
||||
type: 'update',
|
||||
storeid: 'proxmox-notification-endpoints',
|
||||
model: 'proxmox-notification-endpoints',
|
||||
autoStart: true,
|
||||
},
|
||||
sorters: 'name',
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
if (!me.baseUrl) {
|
||||
throw "baseUrl is not set!";
|
||||
}
|
||||
|
||||
let menuItems = [];
|
||||
for (const [endpointType, config] of Object.entries(
|
||||
Proxmox.Schema.notificationEndpointTypes).sort()) {
|
||||
menuItems.push({
|
||||
text: config.name,
|
||||
iconCls: 'fa fa-fw ' + (config.iconCls || 'fa-bell-o'),
|
||||
handler: () => me.controller.openEditWindow(endpointType),
|
||||
});
|
||||
}
|
||||
|
||||
Ext.apply(me, {
|
||||
tbar: [
|
||||
{
|
||||
text: gettext('Add'),
|
||||
menu: menuItems,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxButton',
|
||||
text: gettext('Modify'),
|
||||
handler: 'openEditForSelectedItem',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxStdRemoveButton',
|
||||
callback: 'reload',
|
||||
bind: {
|
||||
text: '{removeButtonText}',
|
||||
customConfirmationMessage: '{removeButtonConfirmMessage}',
|
||||
},
|
||||
getUrl: function(rec) {
|
||||
return `${me.baseUrl}/endpoints/${rec.data.type}/${rec.getId()}`;
|
||||
},
|
||||
enableFn: (rec) => {
|
||||
let origin = rec.get('origin');
|
||||
return origin === 'user-created' || origin === 'modified-builtin';
|
||||
},
|
||||
},
|
||||
'-',
|
||||
{
|
||||
xtype: 'proxmoxButton',
|
||||
text: gettext('Test'),
|
||||
handler: 'testEndpoint',
|
||||
disabled: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
me.store.rstore.proxy.setUrl(`/api2/json/${me.baseUrl}/targets`);
|
||||
},
|
||||
});
|
||||
|
||||
Ext.define('Proxmox.panel.NotificationMatcherView', {
|
||||
extend: 'Ext.grid.Panel',
|
||||
alias: 'widget.pmxNotificationMatcherView',
|
||||
|
||||
title: gettext('Notification Matchers'),
|
||||
|
||||
controller: {
|
||||
xclass: 'Ext.app.ViewController',
|
||||
|
||||
openEditWindow: function(matcher) {
|
||||
let me = this;
|
||||
|
||||
Ext.create('Proxmox.window.NotificationMatcherEdit', {
|
||||
baseUrl: me.getView().baseUrl,
|
||||
name: matcher,
|
||||
autoShow: true,
|
||||
listeners: {
|
||||
destroy: () => me.reload(),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
openEditForSelectedItem: function() {
|
||||
let me = this;
|
||||
let view = me.getView();
|
||||
|
||||
let selection = view.getSelection();
|
||||
if (selection.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
me.openEditWindow(selection[0].data.name);
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
this.getView().getStore().rstore.load();
|
||||
this.getView().setSelection(null);
|
||||
},
|
||||
},
|
||||
|
||||
viewModel: {
|
||||
type: 'pmxNotificationConfigPanel',
|
||||
},
|
||||
|
||||
bind: {
|
||||
selection: '{selection}',
|
||||
},
|
||||
|
||||
listeners: {
|
||||
itemdblclick: 'openEditForSelectedItem',
|
||||
activate: 'reload',
|
||||
},
|
||||
|
||||
emptyText: gettext('No notification matchers configured'),
|
||||
|
||||
columns: [
|
||||
{
|
||||
dataIndex: 'disable',
|
||||
text: gettext('Enable'),
|
||||
renderer: (disable) => Proxmox.Utils.renderEnabledIcon(!disable),
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
text: gettext('Matcher Name'),
|
||||
renderer: Ext.String.htmlEncode,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
dataIndex: 'comment',
|
||||
text: gettext('Comment'),
|
||||
renderer: Ext.String.htmlEncode,
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
dataIndex: 'origin',
|
||||
text: gettext('Origin'),
|
||||
renderer: (origin) => {
|
||||
switch (origin) {
|
||||
case 'user-created': return gettext('Custom');
|
||||
case 'modified-builtin': return gettext('Built-In (modified)');
|
||||
case 'builtin': return gettext('Built-In');
|
||||
}
|
||||
|
||||
// Should not happen...
|
||||
return 'unknown';
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
store: {
|
||||
type: 'diff',
|
||||
autoDestroy: true,
|
||||
autoDestroyRstore: true,
|
||||
rstore: {
|
||||
type: 'update',
|
||||
storeid: 'proxmox-notification-matchers',
|
||||
model: 'proxmox-notification-matchers',
|
||||
autoStart: true,
|
||||
},
|
||||
sorters: 'name',
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
if (!me.baseUrl) {
|
||||
throw "baseUrl is not set!";
|
||||
}
|
||||
|
||||
Ext.apply(me, {
|
||||
tbar: [
|
||||
{
|
||||
xtype: 'proxmoxButton',
|
||||
text: gettext('Add'),
|
||||
handler: () => me.getController().openEditWindow(),
|
||||
selModel: false,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxButton',
|
||||
text: gettext('Modify'),
|
||||
handler: 'openEditForSelectedItem',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxStdRemoveButton',
|
||||
callback: 'reload',
|
||||
bind: {
|
||||
text: '{removeButtonText}',
|
||||
customConfirmationMessage: '{removeButtonConfirmMessage}',
|
||||
},
|
||||
baseurl: `${me.baseUrl}/matchers`,
|
||||
enableFn: (rec) => {
|
||||
let origin = rec.get('origin');
|
||||
return origin === 'user-created' || origin === 'modified-builtin';
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
me.store.rstore.proxy.setUrl(`/api2/json/${me.baseUrl}/matchers`);
|
||||
},
|
||||
});
|
@ -1,103 +0,0 @@
|
||||
Ext.define('Proxmox.panel.SendmailEditPanel', {
|
||||
extend: 'Proxmox.panel.InputPanel',
|
||||
xtype: 'pmxSendmailEditPanel',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
|
||||
type: 'sendmail',
|
||||
onlineHelp: 'notification_targets_sendmail',
|
||||
|
||||
mailValidator: function() {
|
||||
let mailto_user = this.down(`[name=mailto-user]`);
|
||||
let mailto = this.down(`[name=mailto]`);
|
||||
|
||||
if (!mailto_user.getValue()?.length && !mailto.getValue()) {
|
||||
return gettext('Either mailto or mailto-user must be set');
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
items: [
|
||||
{
|
||||
xtype: 'pmxDisplayEditField',
|
||||
name: 'name',
|
||||
cbind: {
|
||||
value: '{name}',
|
||||
editable: '{isCreate}',
|
||||
},
|
||||
fieldLabel: gettext('Endpoint Name'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
name: 'enable',
|
||||
fieldLabel: gettext('Enable'),
|
||||
allowBlank: false,
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
// provides 'mailto' and 'mailto-user' fields
|
||||
xtype: 'pmxEmailRecipientPanel',
|
||||
cbind: {
|
||||
isCreate: '{isCreate}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'comment',
|
||||
fieldLabel: gettext('Comment'),
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
advancedItems: [
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('Author'),
|
||||
name: 'author',
|
||||
allowBlank: true,
|
||||
cbind: {
|
||||
emptyText: '{defaultMailAuthor}',
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('From Address'),
|
||||
name: 'from-address',
|
||||
allowBlank: true,
|
||||
emptyText: gettext('Defaults to datacenter configuration, or root@$hostname'),
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onSetValues: (values) => {
|
||||
values.enable = !values.disable;
|
||||
|
||||
delete values.disable;
|
||||
return values;
|
||||
},
|
||||
|
||||
onGetValues: function(values) {
|
||||
let me = this;
|
||||
|
||||
if (values.enable) {
|
||||
if (!me.isCreate) {
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'disable' });
|
||||
}
|
||||
} else {
|
||||
values.disable = 1;
|
||||
}
|
||||
|
||||
delete values.enable;
|
||||
|
||||
if (values.mailto) {
|
||||
values.mailto = values.mailto.split(/[\s,;]+/);
|
||||
}
|
||||
return values;
|
||||
},
|
||||
});
|
@ -1,205 +0,0 @@
|
||||
Ext.define('Proxmox.panel.SmtpEditPanel', {
|
||||
extend: 'Proxmox.panel.InputPanel',
|
||||
xtype: 'pmxSmtpEditPanel',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
onlineHelp: 'notification_targets_smtp',
|
||||
|
||||
type: 'smtp',
|
||||
|
||||
viewModel: {
|
||||
xtype: 'viewmodel',
|
||||
cbind: {
|
||||
isCreate: "{isCreate}",
|
||||
},
|
||||
data: {
|
||||
mode: 'tls',
|
||||
authentication: true,
|
||||
},
|
||||
formulas: {
|
||||
portEmptyText: function(get) {
|
||||
let port;
|
||||
|
||||
switch (get('mode')) {
|
||||
case 'insecure':
|
||||
port = 25;
|
||||
break;
|
||||
case 'starttls':
|
||||
port = 587;
|
||||
break;
|
||||
case 'tls':
|
||||
port = 465;
|
||||
break;
|
||||
}
|
||||
return `${Proxmox.Utils.defaultText} (${port})`;
|
||||
},
|
||||
passwordEmptyText: function(get) {
|
||||
let isCreate = this.isCreate;
|
||||
return get('authentication') && !isCreate ? gettext('Unchanged') : '';
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
columnT: [
|
||||
{
|
||||
xtype: 'pmxDisplayEditField',
|
||||
name: 'name',
|
||||
cbind: {
|
||||
value: '{name}',
|
||||
editable: '{isCreate}',
|
||||
},
|
||||
fieldLabel: gettext('Endpoint Name'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
name: 'enable',
|
||||
fieldLabel: gettext('Enable'),
|
||||
allowBlank: false,
|
||||
checked: true,
|
||||
},
|
||||
],
|
||||
|
||||
column1: [
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('Server'),
|
||||
name: 'server',
|
||||
allowBlank: false,
|
||||
emptyText: gettext('mail.example.com'),
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxKVComboBox',
|
||||
name: 'mode',
|
||||
fieldLabel: gettext('Encryption'),
|
||||
editable: false,
|
||||
comboItems: [
|
||||
['insecure', Proxmox.Utils.noneText + ' (' + gettext('insecure') + ')'],
|
||||
['starttls', 'STARTTLS'],
|
||||
['tls', 'TLS'],
|
||||
],
|
||||
bind: "{mode}",
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxintegerfield',
|
||||
name: 'port',
|
||||
fieldLabel: gettext('Port'),
|
||||
minValue: 1,
|
||||
maxValue: 65535,
|
||||
bind: {
|
||||
emptyText: "{portEmptyText}",
|
||||
},
|
||||
submitEmptyText: false,
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
],
|
||||
column2: [
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
fieldLabel: gettext('Authenticate'),
|
||||
name: 'authentication',
|
||||
bind: {
|
||||
value: '{authentication}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('Username'),
|
||||
name: 'username',
|
||||
allowBlank: false,
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
bind: {
|
||||
disabled: '{!authentication}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
inputType: 'password',
|
||||
fieldLabel: gettext('Password'),
|
||||
name: 'password',
|
||||
allowBlank: true,
|
||||
bind: {
|
||||
disabled: '{!authentication}',
|
||||
emptyText: '{passwordEmptyText}',
|
||||
},
|
||||
},
|
||||
],
|
||||
columnB: [
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('From Address'),
|
||||
name: 'from-address',
|
||||
allowBlank: false,
|
||||
emptyText: gettext('user@example.com'),
|
||||
},
|
||||
{
|
||||
// provides 'mailto' and 'mailto-user' fields
|
||||
xtype: 'pmxEmailRecipientPanel',
|
||||
cbind: {
|
||||
isCreate: '{isCreate}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'comment',
|
||||
fieldLabel: gettext('Comment'),
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
advancedColumnB: [
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('Author'),
|
||||
name: 'author',
|
||||
allowBlank: true,
|
||||
cbind: {
|
||||
emptyText: '{defaultMailAuthor}',
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onGetValues: function(values) {
|
||||
let me = this;
|
||||
|
||||
if (values.mailto) {
|
||||
values.mailto = values.mailto.split(/[\s,;]+/);
|
||||
}
|
||||
|
||||
if (!values.authentication && !me.isCreate) {
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'username' });
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'password' });
|
||||
}
|
||||
|
||||
if (values.enable) {
|
||||
if (!me.isCreate) {
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'disable' });
|
||||
}
|
||||
} else {
|
||||
values.disable = 1;
|
||||
}
|
||||
|
||||
delete values.enable;
|
||||
|
||||
delete values.authentication;
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
onSetValues: function(values) {
|
||||
values.authentication = !!values.username;
|
||||
values.enable = !values.disable;
|
||||
delete values.disable;
|
||||
|
||||
return values;
|
||||
},
|
||||
});
|
@ -67,11 +67,8 @@ Ext.define('Proxmox.panel.TfaView', {
|
||||
onLoad: function(store, data, success) {
|
||||
if (!success) return;
|
||||
|
||||
let now = new Date().getTime() / 1000;
|
||||
let records = [];
|
||||
Ext.Array.each(data, user => {
|
||||
let tfa_locked = (user.data['tfa-locked-until'] || 0) > now;
|
||||
let totp_locked = user.data['totp-locked'];
|
||||
Ext.Array.each(user.data.entries, entry => {
|
||||
records.push({
|
||||
fullid: `${user.id}/${entry.id}`,
|
||||
@ -80,7 +77,6 @@ Ext.define('Proxmox.panel.TfaView', {
|
||||
description: entry.description,
|
||||
created: entry.created,
|
||||
enable: entry.enable,
|
||||
locked: tfa_locked || (entry.type === 'totp' && totp_locked),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -158,10 +154,8 @@ Ext.define('Proxmox.panel.TfaView', {
|
||||
|
||||
renderUser: fullid => fullid.split('/')[0],
|
||||
|
||||
renderEnabled: function(enabled, metaData, record) {
|
||||
if (record.data.locked) {
|
||||
return gettext("Locked");
|
||||
} else if (enabled === undefined) {
|
||||
renderEnabled: enabled => {
|
||||
if (enabled === undefined) {
|
||||
return Proxmox.Utils.yesText;
|
||||
} else {
|
||||
return Proxmox.Utils.format_boolean(enabled);
|
||||
|
@ -1,423 +0,0 @@
|
||||
Ext.define('Proxmox.panel.WebhookEditPanel', {
|
||||
extend: 'Proxmox.panel.InputPanel',
|
||||
xtype: 'pmxWebhookEditPanel',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
onlineHelp: 'notification_targets_webhook',
|
||||
|
||||
type: 'webhook',
|
||||
|
||||
columnT: [
|
||||
|
||||
],
|
||||
|
||||
column1: [
|
||||
{
|
||||
xtype: 'pmxDisplayEditField',
|
||||
name: 'name',
|
||||
cbind: {
|
||||
value: '{name}',
|
||||
editable: '{isCreate}',
|
||||
},
|
||||
fieldLabel: gettext('Endpoint Name'),
|
||||
regex: Proxmox.Utils.safeIdRegex,
|
||||
allowBlank: false,
|
||||
},
|
||||
],
|
||||
|
||||
column2: [
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
name: 'enable',
|
||||
fieldLabel: gettext('Enable'),
|
||||
allowBlank: false,
|
||||
checked: true,
|
||||
},
|
||||
],
|
||||
|
||||
columnB: [
|
||||
{
|
||||
xtype: 'fieldcontainer',
|
||||
fieldLabel: gettext('Method/URL'),
|
||||
layout: 'hbox',
|
||||
border: false,
|
||||
margin: '0 0 5 0',
|
||||
items: [
|
||||
{
|
||||
xtype: 'proxmoxKVComboBox',
|
||||
name: 'method',
|
||||
editable: false,
|
||||
value: 'post',
|
||||
comboItems: [
|
||||
['post', 'POST'],
|
||||
['put', 'PUT'],
|
||||
['get', 'GET'],
|
||||
],
|
||||
width: 80,
|
||||
margin: '0 5 0 0',
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'url',
|
||||
allowBlank: false,
|
||||
emptyText: "https://example.com/hook",
|
||||
regex: Proxmox.Utils.httpUrlRegex,
|
||||
regexText: gettext('Must be a valid URL'),
|
||||
flex: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
xtype: 'pmxWebhookKeyValueList',
|
||||
name: 'header',
|
||||
fieldLabel: gettext('Headers'),
|
||||
addLabel: gettext('Add Header'),
|
||||
maskValues: false,
|
||||
cbind: {
|
||||
isCreate: '{isCreate}',
|
||||
},
|
||||
margin: '0 0 10 0',
|
||||
},
|
||||
{
|
||||
xtype: 'textarea',
|
||||
fieldLabel: gettext('Body'),
|
||||
name: 'body',
|
||||
allowBlank: true,
|
||||
minHeight: '150',
|
||||
fieldStyle: {
|
||||
'font-family': 'monospace',
|
||||
},
|
||||
margin: '0 0 5 0',
|
||||
},
|
||||
{
|
||||
xtype: 'pmxWebhookKeyValueList',
|
||||
name: 'secret',
|
||||
fieldLabel: gettext('Secrets'),
|
||||
addLabel: gettext('Add Secret'),
|
||||
maskValues: true,
|
||||
cbind: {
|
||||
isCreate: '{isCreate}',
|
||||
},
|
||||
margin: '0 0 10 0',
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'comment',
|
||||
fieldLabel: gettext('Comment'),
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onSetValues: (values) => {
|
||||
values.enable = !values.disable;
|
||||
|
||||
if (values.body) {
|
||||
values.body = Proxmox.Utils.base64ToUtf8(values.body);
|
||||
}
|
||||
|
||||
delete values.disable;
|
||||
return values;
|
||||
},
|
||||
|
||||
onGetValues: function(values) {
|
||||
let me = this;
|
||||
|
||||
if (values.enable) {
|
||||
if (!me.isCreate) {
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'disable' });
|
||||
}
|
||||
} else {
|
||||
values.disable = 1;
|
||||
}
|
||||
|
||||
if (values.body) {
|
||||
values.body = Proxmox.Utils.utf8ToBase64(values.body);
|
||||
} else {
|
||||
delete values.body;
|
||||
if (!me.isCreate) {
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'body' });
|
||||
}
|
||||
}
|
||||
|
||||
if (Ext.isArray(values.header) && !values.header.length) {
|
||||
delete values.header;
|
||||
if (!me.isCreate) {
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'header' });
|
||||
}
|
||||
}
|
||||
|
||||
if (Ext.isArray(values.secret) && !values.secret.length) {
|
||||
delete values.secret;
|
||||
if (!me.isCreate) {
|
||||
Proxmox.Utils.assemble_field_data(values, { 'delete': 'secret' });
|
||||
}
|
||||
}
|
||||
delete values.enable;
|
||||
|
||||
return values;
|
||||
},
|
||||
});
|
||||
|
||||
Ext.define('Proxmox.form.WebhookKeyValueList', {
|
||||
extend: 'Ext.form.FieldContainer',
|
||||
alias: 'widget.pmxWebhookKeyValueList',
|
||||
|
||||
mixins: [
|
||||
'Ext.form.field.Field',
|
||||
],
|
||||
|
||||
// override for column header
|
||||
fieldTitle: gettext('Item'),
|
||||
|
||||
// label displayed in the "Add" button
|
||||
addLabel: undefined,
|
||||
|
||||
// will be applied to the textfields
|
||||
maskRe: undefined,
|
||||
|
||||
allowBlank: true,
|
||||
selectAll: false,
|
||||
isFormField: true,
|
||||
deleteEmpty: false,
|
||||
config: {
|
||||
deleteEmpty: false,
|
||||
maskValues: false,
|
||||
},
|
||||
|
||||
setValue: function(list) {
|
||||
let me = this;
|
||||
|
||||
list = Ext.isArray(list) ? list : (list ?? '').split(';').filter(t => t !== '');
|
||||
|
||||
let store = me.lookup('grid').getStore();
|
||||
if (list.length > 0) {
|
||||
store.setData(list.map(item => {
|
||||
let properties = Proxmox.Utils.parsePropertyString(item);
|
||||
|
||||
// decode base64
|
||||
let value = me.maskValues ? '' : Proxmox.Utils.base64ToUtf8(properties.value);
|
||||
|
||||
let obj = {
|
||||
headerName: properties.name,
|
||||
headerValue: value,
|
||||
};
|
||||
|
||||
if (!me.isCreate && me.maskValues) {
|
||||
obj.emptyText = gettext('Unchanged');
|
||||
}
|
||||
|
||||
return obj;
|
||||
}));
|
||||
} else {
|
||||
store.removeAll();
|
||||
}
|
||||
me.checkChange();
|
||||
return me;
|
||||
},
|
||||
|
||||
getValue: function() {
|
||||
let me = this;
|
||||
let values = [];
|
||||
me.lookup('grid').getStore().each((rec) => {
|
||||
if (rec.data.headerName) {
|
||||
let obj = {
|
||||
name: rec.data.headerName,
|
||||
value: Proxmox.Utils.utf8ToBase64(rec.data.headerValue),
|
||||
};
|
||||
|
||||
values.push(Proxmox.Utils.printPropertyString(obj));
|
||||
}
|
||||
});
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
getErrors: function(value) {
|
||||
let me = this;
|
||||
let empty = false;
|
||||
|
||||
me.lookup('grid').getStore().each((rec) => {
|
||||
if (!rec.data.headerName) {
|
||||
empty = true;
|
||||
}
|
||||
|
||||
if (!rec.data.headerValue && rec.data.newValue) {
|
||||
empty = true;
|
||||
}
|
||||
|
||||
if (!rec.data.headerValue && !me.maskValues) {
|
||||
empty = true;
|
||||
}
|
||||
});
|
||||
if (empty) {
|
||||
return [gettext('Name/value must not be empty.')];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
// override framework function to implement deleteEmpty behaviour
|
||||
getSubmitData: function() {
|
||||
let me = this,
|
||||
data = null,
|
||||
val;
|
||||
if (!me.disabled && me.submitValue) {
|
||||
val = me.getValue();
|
||||
if (val !== null && val !== '') {
|
||||
data = {};
|
||||
data[me.getName()] = val;
|
||||
} else if (me.getDeleteEmpty()) {
|
||||
data = {};
|
||||
data.delete = me.getName();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
controller: {
|
||||
xclass: 'Ext.app.ViewController',
|
||||
|
||||
addLine: function() {
|
||||
let me = this;
|
||||
me.lookup('grid').getStore().add({
|
||||
headerName: '',
|
||||
headerValue: '',
|
||||
emptyText: gettext('Value'),
|
||||
newValue: true,
|
||||
});
|
||||
},
|
||||
|
||||
removeSelection: function(field) {
|
||||
let me = this;
|
||||
let view = me.getView();
|
||||
let grid = me.lookup('grid');
|
||||
|
||||
let record = field.getWidgetRecord();
|
||||
if (record === undefined) {
|
||||
// this is sometimes called before a record/column is initialized
|
||||
return;
|
||||
}
|
||||
|
||||
grid.getStore().remove(record);
|
||||
view.checkChange();
|
||||
view.validate();
|
||||
},
|
||||
|
||||
itemChange: function(field, newValue) {
|
||||
let rec = field.getWidgetRecord();
|
||||
if (!rec) {
|
||||
return;
|
||||
}
|
||||
|
||||
let column = field.getWidgetColumn();
|
||||
rec.set(column.dataIndex, newValue);
|
||||
let list = field.up('pmxWebhookKeyValueList');
|
||||
list.checkChange();
|
||||
list.validate();
|
||||
},
|
||||
|
||||
control: {
|
||||
'grid button': {
|
||||
click: 'removeSelection',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
let items = [
|
||||
{
|
||||
xtype: 'grid',
|
||||
reference: 'grid',
|
||||
minHeight: 100,
|
||||
maxHeight: 100,
|
||||
scrollable: 'vertical',
|
||||
|
||||
viewConfig: {
|
||||
deferEmptyText: false,
|
||||
},
|
||||
|
||||
store: {
|
||||
listeners: {
|
||||
update: function() {
|
||||
this.commitChanges();
|
||||
},
|
||||
},
|
||||
},
|
||||
margin: '5 0 5 0',
|
||||
columns: [
|
||||
{
|
||||
header: me.fieldTtitle,
|
||||
dataIndex: 'headerName',
|
||||
xtype: 'widgetcolumn',
|
||||
widget: {
|
||||
xtype: 'textfield',
|
||||
isFormField: false,
|
||||
maskRe: me.maskRe,
|
||||
allowBlank: false,
|
||||
queryMode: 'local',
|
||||
emptyText: gettext('Key'),
|
||||
listeners: {
|
||||
change: 'itemChange',
|
||||
},
|
||||
},
|
||||
onWidgetAttach: function(_col, widget) {
|
||||
widget.isValid();
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
header: me.fieldTtitle,
|
||||
dataIndex: 'headerValue',
|
||||
xtype: 'widgetcolumn',
|
||||
widget: {
|
||||
xtype: 'proxmoxtextfield',
|
||||
inputType: me.maskValues ? 'password' : 'text',
|
||||
isFormField: false,
|
||||
maskRe: me.maskRe,
|
||||
queryMode: 'local',
|
||||
listeners: {
|
||||
change: 'itemChange',
|
||||
},
|
||||
allowBlank: !me.isCreate && me.maskValues,
|
||||
|
||||
bind: {
|
||||
emptyText: '{record.emptyText}',
|
||||
},
|
||||
},
|
||||
onWidgetAttach: function(_col, widget) {
|
||||
widget.isValid();
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
xtype: 'widgetcolumn',
|
||||
width: 40,
|
||||
widget: {
|
||||
xtype: 'button',
|
||||
iconCls: 'fa fa-trash-o',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
xtype: 'button',
|
||||
text: me.addLabel ? me.addLabel : gettext('Add'),
|
||||
iconCls: 'fa fa-plus-circle',
|
||||
handler: 'addLine',
|
||||
},
|
||||
];
|
||||
|
||||
for (const [key, value] of Object.entries(me.gridConfig ?? {})) {
|
||||
items[0][key] = value;
|
||||
}
|
||||
|
||||
Ext.apply(me, {
|
||||
items,
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
me.initField();
|
||||
},
|
||||
});
|
@ -12,12 +12,6 @@
|
||||
// make the color transparent so the border doesn't appear visibly,
|
||||
// like in crisp, but keep to keep the layout intact
|
||||
border-color: transparent;
|
||||
|
||||
// relevant for (disabled) elements that are rendered directly on the header bar like tools
|
||||
// As otherwise, a too light mask is used, which then makes the element stand out in an odd way
|
||||
.x-mask {
|
||||
background-color: rgba($background-darker, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.x-window-header-title-default {
|
||||
|
@ -33,13 +33,6 @@
|
||||
color: $icon-color;
|
||||
}
|
||||
|
||||
// pve needs to set `color` on this pseudo element, but the api viewer and pbs
|
||||
// add a filter to the parent. so invert it here again, to fix this for pve.
|
||||
.x-tree-icon-parent:not(.x-tree-icon-custom)::before,
|
||||
.x-tree-icon-parent-expanded:not(.x-tree-icon-custom)::before {
|
||||
filter: invert(90%);
|
||||
}
|
||||
|
||||
// but some are implement as background SVGs or PNGs. invert them via
|
||||
// filters:
|
||||
.fa-ceph::before,
|
||||
@ -111,9 +104,6 @@
|
||||
}
|
||||
|
||||
// pbs show task log in longest task list column
|
||||
.fa.black,
|
||||
.fa.black::after,
|
||||
.fa.black::before,
|
||||
.x-action-col-icon.fa-chevron-right::before {
|
||||
filter: none;
|
||||
}
|
||||
@ -232,12 +222,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// set icon color of intentional black icons (e.g.: pencil icon for
|
||||
// quickly changing the ACME account)
|
||||
.fa.black {
|
||||
color: $icon-color;
|
||||
}
|
||||
|
||||
// The usage icons dynamically displaying how full a storage is
|
||||
.usage-wrapper {
|
||||
border: 1px solid $icon-color;
|
||||
|
@ -4,8 +4,7 @@ img[id^="proxmoxlogo-"][id$="-img"] {
|
||||
}
|
||||
|
||||
// removes the gray line in the header of the mail gateway
|
||||
div[id^="versioninfo-"] + div[id^="panel-"] > div[id^="panel-"][id$="-bodyWrap"] > div,
|
||||
div.eol-notice + div[id^="panel-"] > div[id^="panel-"][id$="-bodyWrap"] > div {
|
||||
div[id^="versioninfo-"] + div[id^="panel-"] > div[id^="panel-"][id$="-bodyWrap"] > div {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
@ -95,14 +95,14 @@ Ext.define('Proxmox.window.ACMEPluginEdit', {
|
||||
let field = Ext.create({
|
||||
xtype,
|
||||
name: `custom_${name}`,
|
||||
fieldLabel: Ext.htmlEncode(label),
|
||||
fieldLabel: label,
|
||||
width: '100%',
|
||||
labelWidth: 150,
|
||||
labelSeparator: '=',
|
||||
emptyText: definition.default || '',
|
||||
autoEl: definition.description ? {
|
||||
tag: 'div',
|
||||
'data-qtip': Ext.htmlEncode(Ext.htmlEncode(definition.description)),
|
||||
'data-qtip': definition.description,
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
@ -127,7 +127,6 @@ Ext.define('Proxmox.window.ACMEPluginEdit', {
|
||||
if (me.createdFields[key]) {
|
||||
me.createdFields[key].setValue(value);
|
||||
me.createdFields[key].originalValue = me.originalValues[key];
|
||||
me.createdFields[key].checkDirty();
|
||||
} else {
|
||||
extradata.push(`${key}=${value}`);
|
||||
}
|
||||
|
@ -224,10 +224,10 @@ Ext.define('Proxmox.window.AddTotp', {
|
||||
visible: '{!secretEmpty}',
|
||||
},
|
||||
style: {
|
||||
margin: '16px auto',
|
||||
padding: '16px',
|
||||
width: '288px',
|
||||
height: '288px',
|
||||
margin: '5px auto',
|
||||
padding: '5px',
|
||||
width: '266px',
|
||||
height: '266px',
|
||||
'background-color': 'white',
|
||||
},
|
||||
},
|
||||
|
@ -1,14 +0,0 @@
|
||||
Ext.define('Proxmox.panel.ADInputPanel', {
|
||||
extend: 'Proxmox.panel.LDAPInputPanel',
|
||||
xtype: 'pmxAuthADPanel',
|
||||
|
||||
type: 'ad',
|
||||
onlineHelp: 'user-realms-ad',
|
||||
});
|
||||
|
||||
Ext.define('Proxmox.panel.ADSyncInputPanel', {
|
||||
extend: 'Proxmox.panel.LDAPSyncInputPanel',
|
||||
xtype: 'pmxAuthADSyncPanel',
|
||||
|
||||
type: 'ad',
|
||||
});
|
@ -1,8 +1,5 @@
|
||||
Ext.define('Proxmox.window.AuthEditBase', {
|
||||
extend: 'Proxmox.window.Edit',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
|
||||
showDefaultRealm: false,
|
||||
|
||||
isAdd: true,
|
||||
|
||||
@ -32,9 +29,9 @@ Ext.define('Proxmox.window.AuthEditBase', {
|
||||
|
||||
let authConfig = Proxmox.Schema.authDomains[me.authType];
|
||||
if (!authConfig) {
|
||||
throw `unknown auth type ${me.authType}`;
|
||||
throw 'unknown auth type';
|
||||
} else if (!authConfig.add && me.isCreate) {
|
||||
throw `trying to add non addable realm of type ${me.authType}`;
|
||||
throw 'trying to add non addable realm';
|
||||
}
|
||||
|
||||
me.subject = authConfig.name;
|
||||
@ -54,9 +51,7 @@ Ext.define('Proxmox.window.AuthEditBase', {
|
||||
realm: me.realm,
|
||||
xtype: authConfig.ipanel,
|
||||
isCreate: me.isCreate,
|
||||
useTypeInUrl: me.useTypeInUrl,
|
||||
type: me.authType,
|
||||
showDefaultRealm: me.showDefaultRealm,
|
||||
},
|
||||
{
|
||||
title: gettext('Sync Options'),
|
||||
@ -72,9 +67,7 @@ Ext.define('Proxmox.window.AuthEditBase', {
|
||||
realm: me.realm,
|
||||
xtype: authConfig.ipanel,
|
||||
isCreate: me.isCreate,
|
||||
useTypeInUrl: me.useTypeInUrl,
|
||||
type: me.authType,
|
||||
showDefaultRealm: me.showDefaultRealm,
|
||||
}];
|
||||
}
|
||||
|
||||
@ -93,7 +86,7 @@ Ext.define('Proxmox.window.AuthEditBase', {
|
||||
// only check this when the type is not in the api path
|
||||
if (!me.useTypeInUrl && data.type !== me.authType) {
|
||||
me.close();
|
||||
throw `got wrong auth type '${me.authType}' for realm '${data.type}'`;
|
||||
throw "got wrong auth type";
|
||||
}
|
||||
me.setValues(data);
|
||||
},
|
||||
|
@ -1,5 +1,7 @@
|
||||
|
||||
Ext.define('Proxmox.panel.LDAPInputPanelViewModel', {
|
||||
extend: 'Ext.app.ViewModel',
|
||||
|
||||
alias: 'viewmodel.pmxAuthLDAPPanel',
|
||||
|
||||
data: {
|
||||
@ -21,8 +23,6 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
xtype: 'pmxAuthLDAPPanel',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
|
||||
showDefaultRealm: false,
|
||||
|
||||
viewModel: {
|
||||
type: 'pmxAuthLDAPPanel',
|
||||
},
|
||||
@ -32,11 +32,11 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
onlineHelp: 'user-realms-ldap',
|
||||
|
||||
onGetValues: function(values) {
|
||||
if (this.isCreate && !this.useTypeInUrl) {
|
||||
if (this.isCreate) {
|
||||
values.type = this.type;
|
||||
}
|
||||
|
||||
if (values.anonymous_search && !this.isCreate) {
|
||||
if (values.anonymous_search) {
|
||||
if (!values.delete) {
|
||||
values.delete = [];
|
||||
}
|
||||
@ -64,12 +64,6 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
return values;
|
||||
},
|
||||
|
||||
cbindData: function(config) {
|
||||
return {
|
||||
isLdap: this.type === 'ldap',
|
||||
isAd: this.type === 'ad',
|
||||
};
|
||||
},
|
||||
|
||||
column1: [
|
||||
{
|
||||
@ -82,40 +76,19 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
fieldLabel: gettext('Realm'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
fieldLabel: gettext('Default realm'),
|
||||
name: 'default',
|
||||
value: 0,
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
hidden: '{!showDefaultRealm}',
|
||||
disabled: '{!showDefaultRealm}',
|
||||
},
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': gettext('Set realm as default for login'),
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('Base Domain Name'),
|
||||
name: 'base-dn',
|
||||
allowBlank: false,
|
||||
emptyText: 'cn=Users,dc=company,dc=net',
|
||||
cbind: {
|
||||
hidden: '{!isLdap}',
|
||||
allowBlank: '{!isLdap}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('User Attribute Name'),
|
||||
name: 'user-attr',
|
||||
allowBlank: false,
|
||||
emptyText: 'uid / sAMAccountName',
|
||||
cbind: {
|
||||
hidden: '{!isLdap}',
|
||||
allowBlank: '{!isLdap}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
@ -130,14 +103,7 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
fieldLabel: gettext('Bind Domain Name'),
|
||||
name: 'bind-dn',
|
||||
allowBlank: false,
|
||||
cbind: {
|
||||
emptyText: get => get('isAd') ? 'user@company.net' : 'cn=user,dc=company,dc=net',
|
||||
autoEl: get => get('isAd') ? {
|
||||
tag: 'div',
|
||||
'data-qtip':
|
||||
gettext('LDAP DN syntax can be used as well, e.g. cn=user,dc=company,dc=net'),
|
||||
} : {},
|
||||
},
|
||||
emptyText: 'cn=user,dc=company,dc=net',
|
||||
bind: {
|
||||
disabled: "{anonymous_search}",
|
||||
},
|
||||
@ -147,9 +113,9 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
inputType: 'password',
|
||||
fieldLabel: gettext('Bind Password'),
|
||||
name: 'password',
|
||||
allowBlank: true,
|
||||
cbind: {
|
||||
emptyText: get => !get('isCreate') ? gettext('Unchanged') : '',
|
||||
allowBlank: '{!isCreate}',
|
||||
},
|
||||
bind: {
|
||||
disabled: "{anonymous_search}",
|
||||
@ -181,9 +147,7 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
maxValue: 65535,
|
||||
emptyText: gettext('Default'),
|
||||
submitEmptyText: false,
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
deleteEmpty: true,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxKVComboBox',
|
||||
@ -223,7 +187,7 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
|
||||
columnB: [
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
xtype: 'textfield',
|
||||
name: 'comment',
|
||||
fieldLabel: gettext('Comment'),
|
||||
cbind: {
|
||||
@ -231,6 +195,7 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -330,30 +295,16 @@ Ext.define('Proxmox.panel.LDAPSyncInputPanel', {
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'firstname',
|
||||
fieldLabel: gettext('First Name attribute'),
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': Ext.String.format(gettext('Often called {0}'), '`givenName`'),
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'lastname',
|
||||
fieldLabel: gettext('Last Name attribute'),
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': Ext.String.format(gettext('Often called {0}'), '`sn`'),
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'email',
|
||||
fieldLabel: gettext('E-Mail attribute'),
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': get => get('isAd')
|
||||
? Ext.String.format(gettext('Often called {0} or {1}'), '`userPrincipalName`', '`mail`')
|
||||
: Ext.String.format(gettext('Often called {0}'), '`mail`'),
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'displayfield',
|
||||
@ -385,9 +336,7 @@ Ext.define('Proxmox.panel.LDAPSyncInputPanel', {
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'user-classes',
|
||||
fieldLabel: gettext('User classes'),
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
deleteEmpty: true,
|
||||
emptyText: 'inetorgperson, posixaccount, person, user',
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
@ -398,9 +347,7 @@ Ext.define('Proxmox.panel.LDAPSyncInputPanel', {
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'filter',
|
||||
fieldLabel: gettext('User Filter'),
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
},
|
||||
deleteEmpty: true,
|
||||
},
|
||||
],
|
||||
|
||||
|
@ -3,14 +3,12 @@ Ext.define('Proxmox.panel.OpenIDInputPanel', {
|
||||
xtype: 'pmxAuthOpenIDPanel',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
|
||||
showDefaultRealm: false,
|
||||
|
||||
type: 'openid',
|
||||
|
||||
onGetValues: function(values) {
|
||||
let me = this;
|
||||
|
||||
if (me.isCreate && !me.useTypeInUrl) {
|
||||
if (me.isCreate) {
|
||||
values.type = me.type;
|
||||
}
|
||||
|
||||
@ -37,21 +35,6 @@ Ext.define('Proxmox.panel.OpenIDInputPanel', {
|
||||
fieldLabel: gettext('Realm'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
fieldLabel: gettext('Default realm'),
|
||||
name: 'default',
|
||||
value: 0,
|
||||
cbind: {
|
||||
deleteEmpty: '{!isCreate}',
|
||||
hidden: '{!showDefaultRealm}',
|
||||
disabled: '{!showDefaultRealm}',
|
||||
},
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': gettext('Set realm as default for login'),
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
fieldLabel: gettext('Client ID'),
|
||||
@ -129,7 +112,7 @@ Ext.define('Proxmox.panel.OpenIDInputPanel', {
|
||||
|
||||
columnB: [
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
xtype: 'textfield',
|
||||
name: 'comment',
|
||||
fieldLabel: gettext('Comment'),
|
||||
cbind: {
|
||||
|
@ -1,45 +0,0 @@
|
||||
Ext.define('Proxmox.panel.SimpleRealmInputPanel', {
|
||||
extend: 'Proxmox.panel.InputPanel',
|
||||
xtype: 'pmxAuthSimplePanel',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
|
||||
showDefaultRealm: false,
|
||||
|
||||
column1: [
|
||||
{
|
||||
xtype: 'pmxDisplayEditField',
|
||||
name: 'realm',
|
||||
cbind: {
|
||||
value: '{realm}',
|
||||
},
|
||||
fieldLabel: gettext('Realm'),
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
fieldLabel: gettext('Default realm'),
|
||||
name: 'default',
|
||||
value: 0,
|
||||
deleteEmpty: true,
|
||||
autoEl: {
|
||||
tag: 'div',
|
||||
'data-qtip': gettext('Set realm as default for login'),
|
||||
},
|
||||
cbind: {
|
||||
hidden: '{!showDefaultRealm}',
|
||||
disabled: '{!showDefaultRealm}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
column2: [],
|
||||
|
||||
columnB: [
|
||||
{
|
||||
xtype: 'proxmoxtextfield',
|
||||
name: 'comment',
|
||||
fieldLabel: gettext('Comment'),
|
||||
allowBlank: true,
|
||||
deleteEmpty: true,
|
||||
},
|
||||
],
|
||||
});
|
@ -1,36 +0,0 @@
|
||||
Ext.define('Proxmox.window.ConsentModal', {
|
||||
extend: 'Ext.window.Window',
|
||||
alias: ['widget.pmxConsentModal'],
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
|
||||
maxWidth: 1000,
|
||||
maxHeight: 1000,
|
||||
minWidth: 600,
|
||||
minHeight: 400,
|
||||
scrollable: true,
|
||||
modal: true,
|
||||
closable: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: true,
|
||||
title: gettext('Consent'),
|
||||
|
||||
items: [
|
||||
{
|
||||
xtype: 'displayfield',
|
||||
padding: 10,
|
||||
scrollable: true,
|
||||
cbind: {
|
||||
value: '{consent}',
|
||||
},
|
||||
},
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
handler: function() {
|
||||
this.up('window').close();
|
||||
},
|
||||
text: gettext('OK'),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -29,7 +29,6 @@ Ext.define('Proxmox.window.DiskSmart', {
|
||||
text: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 50,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
text: gettext('Attribute'),
|
||||
@ -46,19 +45,16 @@ Ext.define('Proxmox.window.DiskSmart', {
|
||||
text: gettext('Normalized'),
|
||||
dataIndex: 'real-normalized',
|
||||
width: 60,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
text: gettext('Threshold'),
|
||||
dataIndex: 'threshold',
|
||||
width: 60,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
text: gettext('Worst'),
|
||||
dataIndex: 'worst',
|
||||
width: 60,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
text: gettext('Flags'),
|
||||
|
@ -31,9 +31,6 @@ Ext.define('Proxmox.window.Edit', {
|
||||
// custom submitText
|
||||
submitText: undefined,
|
||||
|
||||
// custom options for the submit api call
|
||||
submitOptions: {},
|
||||
|
||||
backgroundDelay: 0,
|
||||
|
||||
// string or function, called as (url, values) - useful if the ID of the
|
||||
@ -69,15 +66,6 @@ Ext.define('Proxmox.window.Edit', {
|
||||
// onlineHelp of our first item, if set.
|
||||
onlineHelp: undefined,
|
||||
|
||||
constructor: function(conf) {
|
||||
let me = this;
|
||||
// make copies in order to prevent subclasses from accidentally writing
|
||||
// to objects that are shared with other edit window subclasses
|
||||
me.extraRequestParams = Object.assign({}, me.extraRequestParams);
|
||||
me.submitOptions = Object.assign({}, me.submitOptions);
|
||||
me.callParent(arguments);
|
||||
},
|
||||
|
||||
isValid: function() {
|
||||
let me = this;
|
||||
|
||||
@ -163,7 +151,7 @@ Ext.define('Proxmox.window.Edit', {
|
||||
values = undefined;
|
||||
}
|
||||
|
||||
let requestOptions = Ext.apply({
|
||||
Proxmox.Utils.API2Request({
|
||||
url: url,
|
||||
waitMsgTarget: me,
|
||||
method: me.method || (me.backgroundDelay ? 'POST' : 'PUT'),
|
||||
@ -203,8 +191,7 @@ Ext.define('Proxmox.window.Edit', {
|
||||
me.close();
|
||||
}
|
||||
},
|
||||
}, me.submitOptions ?? {});
|
||||
Proxmox.Utils.API2Request(requestOptions);
|
||||
});
|
||||
},
|
||||
|
||||
load: function(options) {
|
||||
@ -318,21 +305,19 @@ Ext.define('Proxmox.window.Edit', {
|
||||
},
|
||||
});
|
||||
|
||||
let resetTool = Ext.create('Ext.panel.Tool', {
|
||||
glyph: 'xf0e2@FontAwesome', // fa-undo
|
||||
tooltip: gettext('Reset form data'),
|
||||
callback: () => form.reset(),
|
||||
style: {
|
||||
paddingRight: '2px', // just slightly more room to breathe
|
||||
},
|
||||
let resetBtn = Ext.create('Ext.Button', {
|
||||
text: 'Reset',
|
||||
disabled: true,
|
||||
handler: function() {
|
||||
form.reset();
|
||||
},
|
||||
});
|
||||
|
||||
let set_button_status = function() {
|
||||
let valid = form.isValid();
|
||||
let dirty = form.isDirty();
|
||||
submitBtn.setDisabled(!valid || !(dirty || me.isCreate));
|
||||
resetTool.setDisabled(!dirty);
|
||||
resetBtn.setDisabled(!dirty);
|
||||
};
|
||||
|
||||
form.on('dirtychange', set_button_status);
|
||||
@ -349,10 +334,10 @@ Ext.define('Proxmox.window.Edit', {
|
||||
me.title = Proxmox.Utils.dialog_title(me.subject, me.isCreate, me.isAdd);
|
||||
}
|
||||
|
||||
me.buttons = [submitBtn];
|
||||
|
||||
if (!me.isCreate && me.showReset) {
|
||||
me.tools = [resetTool];
|
||||
if (me.isCreate || !me.showReset) {
|
||||
me.buttons = [submitBtn];
|
||||
} else {
|
||||
me.buttons = [submitBtn, resetBtn];
|
||||
}
|
||||
|
||||
if (inputPanel && inputPanel.hasAdvanced) {
|
||||
|
@ -1,58 +0,0 @@
|
||||
Ext.define('Proxmox.window.EndpointEditBase', {
|
||||
extend: 'Proxmox.window.Edit',
|
||||
|
||||
isAdd: true,
|
||||
|
||||
fieldDefaults: {
|
||||
labelWidth: 120,
|
||||
},
|
||||
|
||||
width: 700,
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
me.isCreate = !me.name;
|
||||
|
||||
if (!me.baseUrl) {
|
||||
throw "baseUrl not set";
|
||||
}
|
||||
|
||||
if (me.type === 'group') {
|
||||
me.url = `/api2/extjs${me.baseUrl}/groups`;
|
||||
} else {
|
||||
me.url = `/api2/extjs${me.baseUrl}/endpoints/${me.type}`;
|
||||
}
|
||||
|
||||
if (me.isCreate) {
|
||||
me.method = 'POST';
|
||||
} else {
|
||||
me.url += `/${me.name}`;
|
||||
me.method = 'PUT';
|
||||
}
|
||||
|
||||
let endpointConfig = Proxmox.Schema.notificationEndpointTypes[me.type];
|
||||
if (!endpointConfig) {
|
||||
throw 'unknown endpoint type';
|
||||
}
|
||||
|
||||
me.subject = endpointConfig.name;
|
||||
|
||||
Ext.apply(me, {
|
||||
items: [{
|
||||
name: me.name,
|
||||
xtype: endpointConfig.ipanel,
|
||||
isCreate: me.isCreate,
|
||||
baseUrl: me.baseUrl,
|
||||
type: me.type,
|
||||
defaultMailAuthor: endpointConfig.defaultMailAuthor,
|
||||
}],
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
|
||||
if (!me.isCreate) {
|
||||
me.load();
|
||||
}
|
||||
},
|
||||
});
|
@ -61,6 +61,10 @@ Ext.define("Proxmox.window.FileBrowser", {
|
||||
'd': true, // directories
|
||||
},
|
||||
|
||||
// enable tar download, this will add a menu to the "Download" button when the selection
|
||||
// can be downloaded as `.tar` files
|
||||
enableTar: false,
|
||||
|
||||
// prefix to prepend to downloaded file names
|
||||
downloadPrefix: '',
|
||||
},
|
||||
@ -122,7 +126,7 @@ Ext.define("Proxmox.window.FileBrowser", {
|
||||
view.lookup('selectText').setText(st);
|
||||
|
||||
let canDownload = view.downloadURL && view.downloadableFileTypes[data.type];
|
||||
let enableMenu = data.type === 'd';
|
||||
let enableMenu = view.enableTar && data.type === 'd';
|
||||
|
||||
let downloadBtn = view.lookup('downloadBtn');
|
||||
downloadBtn.setDisabled(!canDownload || enableMenu);
|
||||
|
@ -12,12 +12,6 @@ Ext.define('Proxmox.window.LanguageEditWindow', {
|
||||
xclass: 'Ext.app.ViewController',
|
||||
init: function(view) {
|
||||
let language = Ext.util.Cookies.get(view.cookieName) || '__default__';
|
||||
if (language === 'kr') {
|
||||
// fix-up wrongly used Korean code before FIXME: remove with trixie releases
|
||||
language = 'ko';
|
||||
let expire = Ext.Date.add(new Date(), Ext.Date.YEAR, 10);
|
||||
Ext.util.Cookies.set(view.cookieName, language, expire);
|
||||
}
|
||||
this.getViewModel().set('language', language);
|
||||
},
|
||||
applyLanguage: function(button) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,50 +7,27 @@ Ext.define('Proxmox.window.PasswordEdit', {
|
||||
|
||||
url: '/api2/extjs/access/password',
|
||||
|
||||
width: 380,
|
||||
fieldDefaults: {
|
||||
labelWidth: 150,
|
||||
labelWidth: 120,
|
||||
},
|
||||
|
||||
// specifies the minimum length of *new* passwords so this can be
|
||||
// adapted by each product as limits are changed there.
|
||||
minLength: 5,
|
||||
|
||||
// allow products to opt-in as their API gains support for this.
|
||||
confirmCurrentPassword: false,
|
||||
|
||||
items: [
|
||||
{
|
||||
xtype: 'textfield',
|
||||
inputType: 'password',
|
||||
fieldLabel: gettext('Your Current Password'),
|
||||
reference: 'confirmation-password',
|
||||
name: 'confirmation-password',
|
||||
allowBlank: false,
|
||||
vtype: 'password',
|
||||
cbind: {
|
||||
hidden: '{!confirmCurrentPassword}',
|
||||
disabled: '{!confirmCurrentPassword}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'textfield',
|
||||
inputType: 'password',
|
||||
fieldLabel: gettext('New Password'),
|
||||
fieldLabel: gettext('Password'),
|
||||
minLength: 5,
|
||||
allowBlank: false,
|
||||
name: 'password',
|
||||
listeners: {
|
||||
change: (field) => field.next().validate(),
|
||||
blur: (field) => field.next().validate(),
|
||||
},
|
||||
cbind: {
|
||||
minLength: '{minLength}',
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: 'textfield',
|
||||
inputType: 'password',
|
||||
fieldLabel: gettext('Confirm New Password'),
|
||||
fieldLabel: gettext('Confirm password'),
|
||||
name: 'verifypassword',
|
||||
allowBlank: false,
|
||||
vtype: 'password',
|
||||
|
@ -1,5 +1,7 @@
|
||||
// Pop-up a message window where the user has to manually enter the resource ID to enable the
|
||||
// destroy confirmation button to ensure that they got the correct resource selected for.
|
||||
/* Popup a message window
|
||||
* where the user has to manually enter the resource ID
|
||||
* to enable the destroy button
|
||||
*/
|
||||
Ext.define('Proxmox.window.SafeDestroy', {
|
||||
extend: 'Ext.window.Window',
|
||||
alias: 'widget.proxmoxSafeDestroy',
|
||||
|
@ -108,11 +108,11 @@ Ext.define('Proxmox.window.TaskViewer', {
|
||||
defaultValue: 'unknown',
|
||||
renderer: function(value) {
|
||||
if (value !== 'stopped') {
|
||||
return Ext.htmlEncode(value);
|
||||
return value;
|
||||
}
|
||||
let es = statgrid.getObjectValue('exitstatus');
|
||||
if (es) {
|
||||
return Ext.htmlEncode(`${value}: ${es}`);
|
||||
return value + ': ' + es;
|
||||
}
|
||||
return 'unknown';
|
||||
},
|
||||
|
@ -45,17 +45,11 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
||||
|
||||
let lastTabId = me.getLastTabUsed();
|
||||
let initialTab = -1, i = 0;
|
||||
let count2nd = 0;
|
||||
let hasRecovery = false;
|
||||
for (const k of ['webauthn', 'totp', 'recovery', 'u2f', 'yubico']) {
|
||||
const available = !!challenge[k];
|
||||
vm.set(`availableChallenge.${k}`, available);
|
||||
|
||||
if (available) {
|
||||
count2nd++;
|
||||
if (k === 'recovery') {
|
||||
hasRecovery = true;
|
||||
}
|
||||
if (i === lastTabId) {
|
||||
initialTab = i;
|
||||
} else if (initialTab < 0) {
|
||||
@ -64,32 +58,15 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!count2nd || (count2nd === 1 && hasRecovery && !challenge.recovery.length)) {
|
||||
// no 2nd factors available (and if recovery keys are configured they're empty)
|
||||
me.lookup('cannotLogin').setVisible(true);
|
||||
me.lookup('recoveryKey').setVisible(false);
|
||||
view.down('tabpanel').setActiveTab(2); // recovery
|
||||
return;
|
||||
}
|
||||
view.down('tabpanel').setActiveTab(initialTab);
|
||||
|
||||
if (challenge.recovery) {
|
||||
if (!view.challenge.recovery.length) {
|
||||
me.lookup('recoveryEmpty').setVisible(true);
|
||||
me.lookup('recoveryKey').setVisible(false);
|
||||
} else {
|
||||
let idList = view
|
||||
.challenge
|
||||
.recovery
|
||||
.map((id) => Ext.String.format(gettext('ID {0}'), id))
|
||||
.join(', ');
|
||||
me.lookup('availableRecovery').update(Ext.String.htmlEncode(
|
||||
Ext.String.format(gettext('Available recovery keys: {0}'), idList),
|
||||
));
|
||||
me.lookup('availableRecovery').setVisible(true);
|
||||
if (view.challenge.recovery.length <= 3) {
|
||||
me.lookup('recoveryLow').setVisible(true);
|
||||
}
|
||||
me.lookup('availableRecovery').update(Ext.String.htmlEncode(
|
||||
gettext('Available recovery keys: ') + view.challenge.recovery.join(', '),
|
||||
));
|
||||
me.lookup('availableRecovery').setVisible(true);
|
||||
if (view.challenge.recovery.length <= 3) {
|
||||
me.lookup('recoveryLow').setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,7 +351,6 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
||||
allowBlank: false,
|
||||
regex: /^[0-9]{2,16}$/,
|
||||
regexText: gettext('TOTP codes usually consist of six decimal digits'),
|
||||
inputAttrTpl: 'autocomplete=one-time-code',
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -387,36 +363,6 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
||||
disabled: '{!availableChallenge.recovery}',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
xtype: 'box',
|
||||
reference: 'cannotLogin',
|
||||
hidden: true,
|
||||
html: '<i class="fa fa-exclamation-triangle warning"></i>'
|
||||
+ Ext.String.format(
|
||||
gettext('No second factor left! Please contact an administrator!'),
|
||||
4,
|
||||
),
|
||||
},
|
||||
{
|
||||
xtype: 'box',
|
||||
reference: 'recoveryEmpty',
|
||||
hidden: true,
|
||||
html: '<i class="fa fa-exclamation-triangle warning"></i>'
|
||||
+ Ext.String.format(
|
||||
gettext('No more recovery keys left! Please generate a new set!'),
|
||||
4,
|
||||
),
|
||||
},
|
||||
{
|
||||
xtype: 'box',
|
||||
reference: 'recoveryLow',
|
||||
hidden: true,
|
||||
html: '<i class="fa fa-exclamation-triangle warning"></i>'
|
||||
+ Ext.String.format(
|
||||
gettext('Less than {0} recovery keys available. Please generate a new set after login!'),
|
||||
4,
|
||||
),
|
||||
},
|
||||
{
|
||||
xtype: 'box',
|
||||
reference: 'availableRecovery',
|
||||
@ -433,6 +379,16 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
||||
regex: /^[0-9a-f]{4}(-[0-9a-f]{4}){3}$/,
|
||||
regexText: gettext('Does not look like a valid recovery key'),
|
||||
},
|
||||
{
|
||||
xtype: 'box',
|
||||
reference: 'recoveryLow',
|
||||
hidden: true,
|
||||
html: '<i class="fa fa-exclamation-triangle warning"></i>'
|
||||
+ Ext.String.format(
|
||||
gettext('Less than {0} recovery keys available. Please generate a new set after login!'),
|
||||
4,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user