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/.lint-incremental
|
||||||
src/proxmox-dark/theme-proxmox-dark.css
|
*.deb
|
||||||
src/proxmoxlib.js
|
*.changes
|
||||||
src/proxmoxlib.min.js
|
*.buildinfo
|
||||||
|
370
debian/changelog
vendored
370
debian/changelog
vendored
@ -1,382 +1,30 @@
|
|||||||
proxmox-widget-toolkit (4.3.7) bookworm; urgency=medium
|
proxmox-widget-toolkit (3.7.3) bullseye; 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
|
|
||||||
|
|
||||||
* apt repositories: fix typo for getting the default unknown text
|
* apt repositories: fix typo for getting the default unknown text
|
||||||
|
|
||||||
* apt repositories: avoid potential type error in classifyOrigin helper
|
* 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
|
proxmox-widget-toolkit (3.7.2) bullseye; 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
|
|
||||||
|
|
||||||
* apt repositories: actually ignore ignore-pre-upgrade-warning
|
* 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: just ignore unknown info rather than throwing an error
|
||||||
|
|
||||||
* apt repositories: detect mixed suites before major upgrade
|
* 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-widget-toolkit (3.7.1) bullseye; urgency=medium
|
||||||
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
* tfa: paperkey: cleanup iframes for printing after window close
|
* tfa: paperkey: cleanup iframes for printing after window close
|
||||||
|
|
||||||
* parser: adapt to calling convention of updated Markdown renderer library
|
* apt repositoires: allow major upgrade
|
||||||
"marked" has since its v4.0.0
|
|
||||||
|
|
||||||
* fix #4551: ui: translate byte unit in `format_size`
|
-- Proxmox Support Team <support@proxmox.com> Wed, 07 Jun 2023 17:25:57 +0200
|
||||||
|
|
||||||
-- 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-widget-toolkit (3.7.0) bullseye; urgency=medium
|
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
|
Section: web
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||||
Build-Depends: debhelper-compat (= 13),
|
Build-Depends: debhelper-compat (= 12),
|
||||||
libjs-marked,
|
libjs-marked,
|
||||||
pve-eslint (>= 7.28.0),
|
pve-eslint (>= 7.28.0),
|
||||||
sassc,
|
sassc,
|
||||||
uglifyjs,
|
Standards-Version: 4.5.1
|
||||||
Standards-Version: 4.6.2
|
|
||||||
Homepage: https://www.proxmox.com
|
Homepage: https://www.proxmox.com
|
||||||
|
|
||||||
Package: proxmox-widget-toolkit
|
Package: proxmox-widget-toolkit
|
||||||
|
36
src/Makefile
36
src/Makefile
@ -1,12 +1,10 @@
|
|||||||
include defines.mk
|
include defines.mk
|
||||||
|
|
||||||
ESLINT ?= $(if $(shell command -v pve-eslint), pve-eslint, eslint)
|
|
||||||
|
|
||||||
SUBDIRS= css images proxmox-dark
|
SUBDIRS= css images proxmox-dark
|
||||||
|
|
||||||
# bundle it for now from the libjs-marked debian package to avoid touching our proxies file mapper,
|
# 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
|
# 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= \
|
JSSRC= \
|
||||||
Utils.js \
|
Utils.js \
|
||||||
@ -22,7 +20,6 @@ JSSRC= \
|
|||||||
data/ObjectStore.js \
|
data/ObjectStore.js \
|
||||||
data/RRDStore.js \
|
data/RRDStore.js \
|
||||||
data/TimezoneStore.js \
|
data/TimezoneStore.js \
|
||||||
data/model/NotificationConfig.js \
|
|
||||||
data/model/Realm.js \
|
data/model/Realm.js \
|
||||||
data/model/Certificates.js \
|
data/model/Certificates.js \
|
||||||
data/model/ACME.js \
|
data/model/ACME.js \
|
||||||
@ -31,8 +28,6 @@ JSSRC= \
|
|||||||
form/ExpireDate.js \
|
form/ExpireDate.js \
|
||||||
form/IntegerField.js \
|
form/IntegerField.js \
|
||||||
form/TextField.js \
|
form/TextField.js \
|
||||||
form/TextAreaField.js \
|
|
||||||
form/VlanField.js \
|
|
||||||
form/DateTimeField.js \
|
form/DateTimeField.js \
|
||||||
form/Checkbox.js \
|
form/Checkbox.js \
|
||||||
form/KVComboBox.js \
|
form/KVComboBox.js \
|
||||||
@ -50,7 +45,6 @@ JSSRC= \
|
|||||||
form/ACME.js \
|
form/ACME.js \
|
||||||
form/UserSelector.js \
|
form/UserSelector.js \
|
||||||
form/ThemeSelector.js \
|
form/ThemeSelector.js \
|
||||||
form/FingerprintField.js \
|
|
||||||
button/Button.js \
|
button/Button.js \
|
||||||
button/AltText.js \
|
button/AltText.js \
|
||||||
button/HelpButton.js \
|
button/HelpButton.js \
|
||||||
@ -63,24 +57,18 @@ JSSRC= \
|
|||||||
panel/InfoWidget.js \
|
panel/InfoWidget.js \
|
||||||
panel/LogView.js \
|
panel/LogView.js \
|
||||||
panel/NodeInfoRepoStatus.js \
|
panel/NodeInfoRepoStatus.js \
|
||||||
panel/NotificationConfigView.js \
|
|
||||||
panel/JournalView.js \
|
panel/JournalView.js \
|
||||||
panel/PermissionView.js \
|
panel/PermissionView.js \
|
||||||
panel/PruneKeepPanel.js \
|
panel/PruneKeepPanel.js \
|
||||||
panel/RRDChart.js \
|
panel/RRDChart.js \
|
||||||
panel/GaugeWidget.js \
|
panel/GaugeWidget.js \
|
||||||
panel/GotifyEditPanel.js \
|
|
||||||
panel/Certificates.js \
|
panel/Certificates.js \
|
||||||
panel/ACMEAccount.js \
|
panel/ACMEAccount.js \
|
||||||
panel/ACMEPlugin.js \
|
panel/ACMEPlugin.js \
|
||||||
panel/ACMEDomains.js \
|
panel/ACMEDomains.js \
|
||||||
panel/EmailRecipientPanel.js \
|
|
||||||
panel/SendmailEditPanel.js \
|
|
||||||
panel/SmtpEditPanel.js \
|
|
||||||
panel/StatusView.js \
|
panel/StatusView.js \
|
||||||
panel/TfaView.js \
|
panel/TfaView.js \
|
||||||
panel/NotesView.js \
|
panel/NotesView.js \
|
||||||
panel/WebhookEditPanel.js \
|
|
||||||
window/Edit.js \
|
window/Edit.js \
|
||||||
window/PasswordEdit.js \
|
window/PasswordEdit.js \
|
||||||
window/SafeDestroy.js \
|
window/SafeDestroy.js \
|
||||||
@ -90,18 +78,13 @@ JSSRC= \
|
|||||||
window/DiskSmart.js \
|
window/DiskSmart.js \
|
||||||
window/ZFSDetail.js \
|
window/ZFSDetail.js \
|
||||||
window/Certificates.js \
|
window/Certificates.js \
|
||||||
window/ConsentModal.js \
|
|
||||||
window/ACMEAccount.js \
|
window/ACMEAccount.js \
|
||||||
window/ACMEPluginEdit.js \
|
window/ACMEPluginEdit.js \
|
||||||
window/ACMEDomains.js \
|
window/ACMEDomains.js \
|
||||||
window/EndpointEditBase.js \
|
|
||||||
window/NotificationMatcherEdit.js \
|
|
||||||
window/FileBrowser.js \
|
window/FileBrowser.js \
|
||||||
window/AuthEditBase.js \
|
window/AuthEditBase.js \
|
||||||
window/AuthEditOpenId.js \
|
window/AuthEditOpenId.js \
|
||||||
window/AuthEditLDAP.js \
|
window/AuthEditLDAP.js \
|
||||||
window/AuthEditAD.js \
|
|
||||||
window/AuthEditSimple.js \
|
|
||||||
window/TfaWindow.js \
|
window/TfaWindow.js \
|
||||||
window/AddTfaRecovery.js \
|
window/AddTfaRecovery.js \
|
||||||
window/AddTotp.js \
|
window/AddTotp.js \
|
||||||
@ -127,14 +110,14 @@ all: $(SUBDIRS)
|
|||||||
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i; done
|
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i; done
|
||||||
|
|
||||||
.lint-incremental: $(JSSRC)
|
.lint-incremental: $(JSSRC)
|
||||||
$(ESLINT) $?
|
eslint $?
|
||||||
touch "$@"
|
touch "$@"
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
check: lint
|
check: lint
|
||||||
$(ESLINT) --strict api-viewer/APIViewer.js
|
eslint --strict api-viewer/APIViewer.js
|
||||||
lint: $(JSSRC)
|
lint: $(JSSRC)
|
||||||
$(ESLINT) --strict $(JSSRC)
|
eslint --strict $(JSSRC)
|
||||||
touch ".lint-incremental"
|
touch ".lint-incremental"
|
||||||
|
|
||||||
BUILD_TIME=$(or $(SOURCE_DATE_EPOCH),$(shell date '+%s.%N'))
|
BUILD_TIME=$(or $(SOURCE_DATE_EPOCH),$(shell date '+%s.%N'))
|
||||||
@ -145,16 +128,11 @@ proxmoxlib.js: .lint-incremental $(JSSRC)
|
|||||||
cat $(JSSRC) $(MARKEDJS) >> $@.tmp
|
cat $(JSSRC) $(MARKEDJS) >> $@.tmp
|
||||||
mv $@.tmp $@
|
mv $@.tmp $@
|
||||||
|
|
||||||
proxmoxlib.min.js: proxmoxlib.js
|
install: proxmoxlib.js
|
||||||
uglifyjs $< -c -m -o $@.tmp
|
|
||||||
mv $@.tmp $@
|
|
||||||
|
|
||||||
install: proxmoxlib.js proxmoxlib.min.js
|
|
||||||
install -d -m 755 $(WWWBASEDIR)
|
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
|
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
$(MAKE) -C proxmox-dark $@
|
rm -f proxmoxlib.js
|
||||||
rm -f proxmoxlib.js* proxmoxlib.min.js* .lint-incremental
|
|
||||||
|
@ -24,33 +24,22 @@ Ext.define('Proxmox.Markdown', {
|
|||||||
for (let i=node.attributes.length; i--;) {
|
for (let i=node.attributes.length; i--;) {
|
||||||
const name = node.attributes[i].name;
|
const name = node.attributes[i].name;
|
||||||
const value = node.attributes[i].value;
|
const value = node.attributes[i].value;
|
||||||
const canonicalTagName = node.tagName.toLowerCase();
|
|
||||||
// TODO: we may want to also disallow class and id attrs
|
// TODO: we may want to also disallow class and id attrs
|
||||||
if (
|
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);
|
node.attributes.removeNamedItem(name);
|
||||||
} else if ((name === 'href' || name === 'src') && !_isHTTPLike(value)) {
|
} else if ((name === 'href' || name === 'src') && !_isHTTPLike(value)) {
|
||||||
let safeURL = false;
|
|
||||||
try {
|
try {
|
||||||
let url = new URL(value, window.location.origin);
|
let url = new URL(value, window.location.origin);
|
||||||
safeURL = _isHTTPLike(url.protocol);
|
if (_isHTTPLike(url.protocol) || (node.tagName === 'img' && url.protocol === 'data:')) {
|
||||||
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) {
|
|
||||||
node.attributes[i].value = url.href;
|
node.attributes[i].value = url.href;
|
||||||
} else {
|
} else {
|
||||||
node.attributes.removeNamedItem(name);
|
node.attributes.removeNamedItem(name);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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]);
|
for (let i=node.childNodes.length; i--;) _sanitize(node.childNodes[i]);
|
||||||
@ -66,7 +55,7 @@ Ext.define('Proxmox.Markdown', {
|
|||||||
|
|
||||||
parse: function(markdown) {
|
parse: function(markdown) {
|
||||||
/*global marked*/
|
/*global marked*/
|
||||||
let unsafeHTML = marked.parse(markdown);
|
let unsafeHTML = marked(markdown);
|
||||||
|
|
||||||
return `<div class="pmx-md">${this.sanitizeHTML(unsafeHTML)}</div>`;
|
return `<div class="pmx-md">${this.sanitizeHTML(unsafeHTML)}</div>`;
|
||||||
},
|
},
|
||||||
|
@ -4,13 +4,10 @@ Ext.define('Proxmox.Schema', { // a singleton
|
|||||||
authDomains: {
|
authDomains: {
|
||||||
pam: {
|
pam: {
|
||||||
name: 'Linux PAM',
|
name: 'Linux PAM',
|
||||||
ipanel: 'pmxAuthSimplePanel',
|
|
||||||
onlineHelp: 'user-realms-pam',
|
|
||||||
add: false,
|
add: false,
|
||||||
edit: true,
|
edit: false,
|
||||||
pwchange: true,
|
pwchange: true,
|
||||||
sync: false,
|
sync: false,
|
||||||
useTypeInUrl: false,
|
|
||||||
},
|
},
|
||||||
openid: {
|
openid: {
|
||||||
name: gettext('OpenID Connect Server'),
|
name: gettext('OpenID Connect Server'),
|
||||||
@ -21,7 +18,6 @@ Ext.define('Proxmox.Schema', { // a singleton
|
|||||||
pwchange: false,
|
pwchange: false,
|
||||||
sync: false,
|
sync: false,
|
||||||
iconCls: 'pmx-itype-icon-openid-logo',
|
iconCls: 'pmx-itype-icon-openid-logo',
|
||||||
useTypeInUrl: true,
|
|
||||||
},
|
},
|
||||||
ldap: {
|
ldap: {
|
||||||
name: gettext('LDAP Server'),
|
name: gettext('LDAP Server'),
|
||||||
@ -32,18 +28,6 @@ Ext.define('Proxmox.Schema', { // a singleton
|
|||||||
tfa: true,
|
tfa: true,
|
||||||
pwchange: false,
|
pwchange: false,
|
||||||
sync: true,
|
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
|
// 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: {
|
pxarFileTypes: {
|
||||||
b: { icon: 'cube', label: gettext('Block Device') },
|
b: { icon: 'cube', label: gettext('Block Device') },
|
||||||
c: { icon: 'tty', label: gettext('Character Device') },
|
c: { icon: 'tty', label: gettext('Character Device') },
|
||||||
|
132
src/Toolkit.js
132
src/Toolkit.js
@ -76,7 +76,7 @@ Ext.apply(Ext.form.field.VTypes, {
|
|||||||
MacPrefixText: gettext('Example') + ': 02:8f - ' + gettext('only unicast addresses are allowed'),
|
MacPrefixText: gettext('Example') + ': 02:8f - ' + gettext('only unicast addresses are allowed'),
|
||||||
|
|
||||||
BridgeName: function(v) {
|
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) {
|
VlanName: function(v) {
|
||||||
if (Proxmox.Utils.VlanInterface_match.test(v)) {
|
if (Proxmox.Utils.VlanInterface_match.test(v)) {
|
||||||
@ -86,7 +86,7 @@ Ext.apply(Ext.form.field.VTypes, {
|
|||||||
}
|
}
|
||||||
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) {
|
BondName: function(v) {
|
||||||
return (/^bond\d{1,4}$/).test(v);
|
return (/^bond\d{1,4}$/).test(v);
|
||||||
@ -129,12 +129,12 @@ Ext.apply(Ext.form.field.VTypes, {
|
|||||||
DnsName: function(v) {
|
DnsName: function(v) {
|
||||||
return Proxmox.Utils.DnsName_match.test(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) {
|
DnsNameOrWildcard: function(v) {
|
||||||
return Proxmox.Utils.DnsName_or_Wildcard_match.test(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
|
// email regex used by pve-common
|
||||||
proxmoxMail: function(v) {
|
proxmoxMail: function(v) {
|
||||||
@ -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
|
// 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
|
// since Ext.Msg is an object and not a prototype, we need to override it
|
||||||
// after the framework has been initiated
|
// 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', {
|
Ext.define('Ext.ux.IFrame', {
|
||||||
extend: 'Ext.Component',
|
extend: 'Ext.Component',
|
||||||
|
|
||||||
@ -771,10 +733,8 @@ Ext.define('Ext.ux.IFrame', {
|
|||||||
src: 'about:blank',
|
src: 'about:blank',
|
||||||
|
|
||||||
renderTpl: [
|
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'],
|
childEls: ['iframeEl'],
|
||||||
|
|
||||||
initComponent: function() {
|
initComponent: function() {
|
||||||
@ -785,7 +745,6 @@ Ext.define('Ext.ux.IFrame', {
|
|||||||
|
|
||||||
initEvents: function() {
|
initEvents: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
me.callParent();
|
me.callParent();
|
||||||
me.iframeEl.on('load', me.onLoad, me);
|
me.iframeEl.on('load', me.onLoad, me);
|
||||||
},
|
},
|
||||||
@ -799,7 +758,6 @@ Ext.define('Ext.ux.IFrame', {
|
|||||||
|
|
||||||
getBody: function() {
|
getBody: function() {
|
||||||
let doc = this.getDoc();
|
let doc = this.getDoc();
|
||||||
|
|
||||||
return doc.body || doc.documentElement;
|
return doc.body || doc.documentElement;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -814,22 +772,73 @@ Ext.define('Ext.ux.IFrame', {
|
|||||||
getWin: function() {
|
getWin: function() {
|
||||||
let me = this,
|
let me = this,
|
||||||
name = me.frameName,
|
name = me.frameName,
|
||||||
win = Ext.isIE ? me.iframeEl.dom.contentWindow : window.frames[name];
|
win = Ext.isIE
|
||||||
|
? me.iframeEl.dom.contentWindow
|
||||||
|
: window.frames[name];
|
||||||
return win;
|
return win;
|
||||||
},
|
},
|
||||||
|
|
||||||
getFrame: function() {
|
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() {
|
onLoad: function() {
|
||||||
let me = this,
|
let me = this,
|
||||||
doc = me.getDoc();
|
doc = me.getDoc(),
|
||||||
|
fn = me.onRelayedEvent;
|
||||||
|
|
||||||
if (doc) {
|
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.el.unmask();
|
||||||
this.fireEvent('load', this);
|
this.fireEvent('load', this);
|
||||||
} else if (me.src) {
|
} else if (me.src) {
|
||||||
@ -838,6 +847,29 @@ Ext.define('Ext.ux.IFrame', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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) {
|
load: function(src) {
|
||||||
let me = this,
|
let me = this,
|
||||||
text = me.loadMask,
|
text = me.loadMask,
|
||||||
|
131
src/Utils.js
131
src/Utils.js
@ -64,7 +64,6 @@ utilities: {
|
|||||||
|
|
||||||
language_map: { //language map is sorted alphabetically by iso 639-1
|
language_map: { //language map is sorted alphabetically by iso 639-1
|
||||||
ar: `العربية - ${gettext("Arabic")}`,
|
ar: `العربية - ${gettext("Arabic")}`,
|
||||||
bg: `Български - ${gettext("Bulgarian")}`,
|
|
||||||
ca: `Català - ${gettext("Catalan")}`,
|
ca: `Català - ${gettext("Catalan")}`,
|
||||||
da: `Dansk - ${gettext("Danish")}`,
|
da: `Dansk - ${gettext("Danish")}`,
|
||||||
de: `Deutsch - ${gettext("German")}`,
|
de: `Deutsch - ${gettext("German")}`,
|
||||||
@ -73,12 +72,10 @@ utilities: {
|
|||||||
eu: `Euskera (Basque) - ${gettext("Euskera (Basque)")}`,
|
eu: `Euskera (Basque) - ${gettext("Euskera (Basque)")}`,
|
||||||
fa: `فارسی - ${gettext("Persian (Farsi)")}`,
|
fa: `فارسی - ${gettext("Persian (Farsi)")}`,
|
||||||
fr: `Français - ${gettext("French")}`,
|
fr: `Français - ${gettext("French")}`,
|
||||||
hr: `Hrvatski - ${gettext("Croatian")}`,
|
|
||||||
he: `עברית - ${gettext("Hebrew")}`,
|
he: `עברית - ${gettext("Hebrew")}`,
|
||||||
it: `Italiano - ${gettext("Italian")}`,
|
it: `Italiano - ${gettext("Italian")}`,
|
||||||
ja: `日本語 - ${gettext("Japanese")}`,
|
ja: `日本語 - ${gettext("Japanese")}`,
|
||||||
ka: `ქართული - ${gettext("Georgian")}`,
|
kr: `한국어 - ${gettext("Korean")}`,
|
||||||
ko: `한국어 - ${gettext("Korean")}`,
|
|
||||||
nb: `Bokmål - ${gettext("Norwegian (Bokmal)")}`,
|
nb: `Bokmål - ${gettext("Norwegian (Bokmal)")}`,
|
||||||
nl: `Nederlands - ${gettext("Dutch")}`,
|
nl: `Nederlands - ${gettext("Dutch")}`,
|
||||||
nn: `Nynorsk - ${gettext("Norwegian (Nynorsk)")}`,
|
nn: `Nynorsk - ${gettext("Norwegian (Nynorsk)")}`,
|
||||||
@ -88,7 +85,6 @@ utilities: {
|
|||||||
sl: `Slovenščina - ${gettext("Slovenian")}`,
|
sl: `Slovenščina - ${gettext("Slovenian")}`,
|
||||||
sv: `Svenska - ${gettext("Swedish")}`,
|
sv: `Svenska - ${gettext("Swedish")}`,
|
||||||
tr: `Türkçe - ${gettext("Turkish")}`,
|
tr: `Türkçe - ${gettext("Turkish")}`,
|
||||||
ukr: `Українська - ${gettext("Ukrainian")}`,
|
|
||||||
zh_CN: `中文(简体)- ${gettext("Chinese (Simplified)")}`,
|
zh_CN: `中文(简体)- ${gettext("Chinese (Simplified)")}`,
|
||||||
zh_TW: `中文(繁體)- ${gettext("Chinese (Traditional)")}`,
|
zh_TW: `中文(繁體)- ${gettext("Chinese (Traditional)")}`,
|
||||||
},
|
},
|
||||||
@ -97,9 +93,6 @@ utilities: {
|
|||||||
if (!value || value === '__default__') {
|
if (!value || value === '__default__') {
|
||||||
return Proxmox.Utils.defaultText + ' (English)';
|
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];
|
let text = Proxmox.Utils.language_map[value];
|
||||||
if (text) {
|
if (text) {
|
||||||
return text + ' (' + value + ')';
|
return text + ' (' + value + ')';
|
||||||
@ -156,11 +149,8 @@ utilities: {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getNoSubKeyHtml: function(url) {
|
getNoSubKeyHtml: function(url) {
|
||||||
let html_url = Ext.String.format('<a target="_blank" href="{0}">www.proxmox.com</a>', url || 'https://www.proxmox.com');
|
// url http://www.proxmox.com/products/proxmox-ve/subscription-service-plans
|
||||||
return Ext.String.format(
|
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');
|
||||||
gettext('You do not have a valid subscription for this server. Please visit {0} to get a list of available options.'),
|
|
||||||
html_url,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
format_boolean_with_default: function(value) {
|
format_boolean_with_default: function(value) {
|
||||||
@ -318,7 +308,7 @@ utilities: {
|
|||||||
// that way the cookie gets deleted after the browser window is closed
|
// that way the cookie gets deleted after the browser window is closed
|
||||||
if (data.ticket) {
|
if (data.ticket) {
|
||||||
Proxmox.CSRFPreventionToken = data.CSRFPreventionToken;
|
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) {
|
if (data.token) {
|
||||||
@ -344,7 +334,7 @@ utilities: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ExtJS clear is basically the same, but browser may complain if any cookie isn't "secure"
|
// 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");
|
window.localStorage.removeItem("ProxmoxUser");
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -463,12 +453,6 @@ utilities: {
|
|||||||
newopts.url = '/api2/extjs' + newopts.url;
|
newopts.url = '/api2/extjs' + newopts.url;
|
||||||
}
|
}
|
||||||
delete newopts.callback;
|
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) {
|
let createWrapper = function(successFn, callbackFn, failureFn) {
|
||||||
Ext.apply(newopts, {
|
Ext.apply(newopts, {
|
||||||
@ -477,7 +461,7 @@ utilities: {
|
|||||||
if (Proxmox.Utils.toolkit === 'touch') {
|
if (Proxmox.Utils.toolkit === 'touch') {
|
||||||
options.waitMsgTarget.setMasked(false);
|
options.waitMsgTarget.setMasked(false);
|
||||||
} else {
|
} else {
|
||||||
unmask(options.waitMsgTarget);
|
options.waitMsgTarget.setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let result = Ext.decode(response.responseText);
|
let result = Ext.decode(response.responseText);
|
||||||
@ -499,7 +483,7 @@ utilities: {
|
|||||||
if (Proxmox.Utils.toolkit === 'touch') {
|
if (Proxmox.Utils.toolkit === 'touch') {
|
||||||
options.waitMsgTarget.setMasked(false);
|
options.waitMsgTarget.setMasked(false);
|
||||||
} else {
|
} else {
|
||||||
unmask(options.waitMsgTarget);
|
options.waitMsgTarget.setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response.result = {};
|
response.result = {};
|
||||||
@ -529,17 +513,10 @@ utilities: {
|
|||||||
if (target) {
|
if (target) {
|
||||||
if (Proxmox.Utils.toolkit === 'touch') {
|
if (Proxmox.Utils.toolkit === 'touch') {
|
||||||
target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg });
|
target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg });
|
||||||
} else if (target.rendered) {
|
|
||||||
target.waitMsgTargetCount = (target.waitMsgTargetCount ?? 0) + 1;
|
|
||||||
target.setLoading(newopts.waitMsg);
|
|
||||||
} else {
|
} else {
|
||||||
target.waitMsgTargetCount = (target.waitMsgTargetCount ?? 0) + 1;
|
// Note: ExtJS bug - this does not work when component is not rendered
|
||||||
target.on('afterlayout', function() {
|
|
||||||
if ((target.waitMsgTargetCount ?? 0) > 0) {
|
|
||||||
target.setLoading(newopts.waitMsg);
|
target.setLoading(newopts.waitMsg);
|
||||||
}
|
}
|
||||||
}, target, { single: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ext.Ajax.request(newopts);
|
Ext.Ajax.request(newopts);
|
||||||
},
|
},
|
||||||
@ -548,7 +525,12 @@ utilities: {
|
|||||||
// Proxmox.Async.api2({
|
// Proxmox.Async.api2({
|
||||||
// ...
|
// ...
|
||||||
// }).catch(Proxmox.Utils.alertResponseFailure);
|
// }).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) {
|
checked_command: function(orig_cmd) {
|
||||||
Proxmox.Utils.API2Request(
|
Proxmox.Utils.API2Request(
|
||||||
@ -665,37 +647,6 @@ utilities: {
|
|||||||
Proxmox.Utils.unknownText;
|
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
|
// NOTE: only add general, product agnostic, ones here! Else use override helper in product repos
|
||||||
task_desc_table: {
|
task_desc_table: {
|
||||||
aptupdate: ['', gettext('Update package database')],
|
aptupdate: ['', gettext('Update package database')],
|
||||||
@ -737,23 +688,21 @@ utilities: {
|
|||||||
},
|
},
|
||||||
|
|
||||||
format_size: function(size, useSI) {
|
format_size: function(size, useSI) {
|
||||||
let unitsSI = [gettext('B'), gettext('KB'), gettext('MB'), gettext('GB'),
|
let units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
||||||
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 order = 0;
|
let order = 0;
|
||||||
let commaDigits = 2;
|
|
||||||
const baseValue = useSI ? 1000 : 1024;
|
const baseValue = useSI ? 1000 : 1024;
|
||||||
while (size >= baseValue && order < unitsSI.length) {
|
while (size >= baseValue && order < units.length) {
|
||||||
size = size / baseValue;
|
size = size / baseValue;
|
||||||
order++;
|
order++;
|
||||||
}
|
}
|
||||||
|
|
||||||
let unit = useSI ? unitsSI[order] : unitsIEC[order];
|
let unit = units[order], commaDigits = 2;
|
||||||
if (order === 0) {
|
if (order === 0) {
|
||||||
commaDigits = 0;
|
commaDigits = 0;
|
||||||
|
} else if (!useSI) {
|
||||||
|
unit += 'i';
|
||||||
}
|
}
|
||||||
return `${size.toFixed(commaDigits)} ${unit}`;
|
return `${size.toFixed(commaDigits)} ${unit}B`;
|
||||||
},
|
},
|
||||||
|
|
||||||
SizeUnits: {
|
SizeUnits: {
|
||||||
@ -928,7 +877,7 @@ utilities: {
|
|||||||
let parsed = Proxmox.Utils.parse_task_status(status);
|
let parsed = Proxmox.Utils.parse_task_status(status);
|
||||||
switch (parsed) {
|
switch (parsed) {
|
||||||
case 'unknown': return Proxmox.Utils.unknownText;
|
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 'warning': return status.replace('WARNINGS', Proxmox.Utils.warningsText);
|
||||||
case 'ok': // fall-through
|
case 'ok': // fall-through
|
||||||
default: return status;
|
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) {
|
stringToRGB: function(string) {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
if (!string) {
|
if (!string) {
|
||||||
@ -1525,26 +1456,6 @@ utilities: {
|
|||||||
me.IP6_dotnotation_match = new RegExp("^(" + IPV6_REGEXP + ")(?:\\.(\\d+))?$");
|
me.IP6_dotnotation_match = new RegExp("^(" + IPV6_REGEXP + ")(?:\\.(\\d+))?$");
|
||||||
me.Vlan_match = /^vlan(\d+)/;
|
me.Vlan_match = /^vlan(\d+)/;
|
||||||
me.VlanInterface_match = /(\w+)\.(\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',
|
'name', 'type', 'typetext', 'description', 'verbose_description',
|
||||||
'enum', 'minimum', 'maximum', 'minLength', 'maxLength',
|
'enum', 'minimum', 'maximum', 'minLength', 'maxLength',
|
||||||
'pattern', 'title', 'requires', 'format', 'default',
|
'pattern', 'title', 'requires', 'format', 'default',
|
||||||
'disallow', 'extends', 'links', 'instance-types',
|
'disallow', 'extends', 'links',
|
||||||
{
|
{
|
||||||
name: 'optional',
|
name: 'optional',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
@ -214,10 +214,6 @@ Ext.onReady(function() {
|
|||||||
},
|
},
|
||||||
groupField: 'optional',
|
groupField: 'optional',
|
||||||
sorters: [
|
sorters: [
|
||||||
{
|
|
||||||
property: 'instance-types',
|
|
||||||
direction: 'ASC',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
property: 'name',
|
property: 'name',
|
||||||
direction: 'ASC',
|
direction: 'ASC',
|
||||||
@ -225,27 +221,9 @@ Ext.onReady(function() {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
let has_type_properties = false;
|
|
||||||
|
|
||||||
Ext.Object.each(info.parameters.properties, function(name, pdef) {
|
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;
|
pdef.name = name;
|
||||||
pstore.add(pdef);
|
pstore.add(pdef);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pstore.sort();
|
pstore.sort();
|
||||||
@ -277,12 +255,6 @@ Ext.onReady(function() {
|
|||||||
renderer: render_type,
|
renderer: render_type,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: 'For Types',
|
|
||||||
dataIndex: 'instance-types',
|
|
||||||
hidden: !has_type_properties,
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: 'Default',
|
header: 'Default',
|
||||||
dataIndex: 'default',
|
dataIndex: 'default',
|
||||||
|
@ -110,7 +110,6 @@ Ext.define('Proxmox.button.StdRemoveButton', {
|
|||||||
|
|
||||||
config: {
|
config: {
|
||||||
baseurl: undefined,
|
baseurl: undefined,
|
||||||
customConfirmationMessage: undefined,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getUrl: function(rec) {
|
getUrl: function(rec) {
|
||||||
@ -134,14 +133,7 @@ Ext.define('Proxmox.button.StdRemoveButton', {
|
|||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
let name = me.getRecordName(rec);
|
let name = me.getRecordName(rec);
|
||||||
|
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'), `'${name}'`);
|
||||||
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}'`));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handler: function(btn, event, rec) {
|
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-light,
|
||||||
.proxmox-tags-full .x-grid-cell-inner-treecolumn .proxmox-tag-dark,
|
.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 {
|
|
||||||
display: inherit;
|
display: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,10 +25,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.proxmox-tags-circle :not(span.proxmox-tags-full) > .proxmox-tag-light,
|
.proxmox-tags-circle .proxmox-tag-light,
|
||||||
.proxmox-tags-circle :not(span.proxmox-tags-full) > .proxmox-tag-dark,
|
.proxmox-tags-circle .proxmox-tag-dark {
|
||||||
.proxmox-tags-circle > .proxmox-tag-light,
|
|
||||||
.proxmox-tags-circle > .proxmox-tag-dark {
|
|
||||||
margin: 0px 1px;
|
margin: 0px 1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
@ -42,17 +38,13 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.proxmox-tags-none :not(span.proxmox-tags-full) > .proxmox-tag-light,
|
.proxmox-tags-none .proxmox-tag-light,
|
||||||
.proxmox-tags-none :not(span.proxmox-tags-full) > .proxmox-tag-dark,
|
.proxmox-tags-none .proxmox-tag-dark {
|
||||||
.proxmox-tags-none > .proxmox-tag-light,
|
|
||||||
.proxmox-tags-none > .proxmox-tag-dark {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.proxmox-tags-dense :not(span.proxmox-tags-full) > .proxmox-tag-light,
|
.proxmox-tags-dense .proxmox-tag-light,
|
||||||
.proxmox-tags-dense :not(span.proxmox-tags-full) > .proxmox-tag-dark,
|
.proxmox-tags-dense .proxmox-tag-dark {
|
||||||
.proxmox-tags-dense > .proxmox-tag-light,
|
|
||||||
.proxmox-tags-dense > .proxmox-tag-dark {
|
|
||||||
width: 6px;
|
width: 6px;
|
||||||
margin-right: 1px;
|
margin-right: 1px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -74,9 +66,6 @@
|
|||||||
.x-mask-msg-text {
|
.x-mask-msg-text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.x-window-header-default-top .x-mask {
|
|
||||||
background-color: #f5f5f5B4; /* ~ 0.7 opacity */
|
|
||||||
}
|
|
||||||
|
|
||||||
.proxmox-disabled-row, .proxmox-disabled-row td {
|
.proxmox-disabled-row, .proxmox-disabled-row td {
|
||||||
/*color: #a0a0a0;*/
|
/*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,
|
notFoundIsValid: false,
|
||||||
deleteEmpty: false,
|
deleteEmpty: false,
|
||||||
errorHeight: 100,
|
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.
|
// needed to trigger onKeyUp etc.
|
||||||
@ -57,7 +54,7 @@ Ext.define('Proxmox.form.ComboGrid', {
|
|||||||
setValue: function(value) {
|
setValue: function(value) {
|
||||||
let me = this;
|
let me = this;
|
||||||
let empty = Ext.isArray(value) ? !value.length : !value;
|
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]);
|
return me.callParent([value]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -403,7 +400,7 @@ Ext.define('Proxmox.form.ComboGrid', {
|
|||||||
matchFieldWidth: false,
|
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 });
|
Ext.applyIf(me.listConfig, { width: 400 });
|
||||||
|
|
||||||
|
@ -1,169 +1,151 @@
|
|||||||
Ext.define('Proxmox.DateTimeField', {
|
Ext.define('Proxmox.DateTimeField', {
|
||||||
extend: 'Ext.form.FieldContainer',
|
extend: 'Ext.form.FieldContainer',
|
||||||
// FIXME: remove once all use sites upgraded (with versioned depends on new WTK!)
|
xtype: 'promxoxDateTimeField',
|
||||||
alias: ['widget.promxoxDateTimeField'],
|
|
||||||
xtype: 'proxmoxDateTimeField',
|
|
||||||
|
|
||||||
layout: 'hbox',
|
layout: 'hbox',
|
||||||
|
|
||||||
viewModel: {
|
referenceHolder: true,
|
||||||
data: {
|
|
||||||
datetime: null,
|
|
||||||
minDatetime: null,
|
|
||||||
maxDatetime: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
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',
|
submitFormat: 'U',
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
setValue: function(value) {
|
|
||||||
this.getViewModel().set('datetime', value);
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue: function() {
|
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() {
|
getSubmitValue: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
let format = me.submitFormat;
|
||||||
let value = me.getValue();
|
let value = me.getValue();
|
||||||
return value ? Ext.Date.format(value, me.submitFormat) : null;
|
|
||||||
},
|
|
||||||
|
|
||||||
setMinValue: function(value) {
|
return value ? Ext.Date.format(value, format) : null;
|
||||||
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);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
xtype: 'datefield',
|
xtype: 'datefield',
|
||||||
editable: false,
|
editable: false,
|
||||||
|
reference: 'dateentry',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
format: 'Y-m-d',
|
format: 'Y-m-d',
|
||||||
bind: {
|
|
||||||
value: '{date}',
|
|
||||||
minValue: '{minDate}',
|
|
||||||
maxValue: '{maxDate}',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'timefield',
|
xtype: 'timefield',
|
||||||
|
reference: 'timeentry',
|
||||||
format: 'H:i',
|
format: 'H:i',
|
||||||
width: 80,
|
width: 80,
|
||||||
value: '00:00',
|
value: '00:00',
|
||||||
increment: 60,
|
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() {
|
getEditable: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
let vm = me.getViewModel();
|
let vm = me.getViewModel();
|
||||||
return vm.get('editable');
|
vm.get('editable');
|
||||||
},
|
},
|
||||||
|
|
||||||
setValue: function(value) {
|
setValue: function(value) {
|
||||||
@ -37,19 +37,9 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
|
|||||||
getValue: function() {
|
getValue: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
let vm = me.getViewModel();
|
let vm = me.getViewModel();
|
||||||
// FIXME: add return, but check all use-sites for regressions then
|
|
||||||
vm.get('value');
|
vm.get('value');
|
||||||
},
|
},
|
||||||
|
|
||||||
setEmptyText: function(emptyText) {
|
|
||||||
let me = this;
|
|
||||||
me.editField.setEmptyText(emptyText);
|
|
||||||
},
|
|
||||||
getEmptyText: function() {
|
|
||||||
let me = this;
|
|
||||||
return me.editField.getEmptyText();
|
|
||||||
},
|
|
||||||
|
|
||||||
layout: 'fit',
|
layout: 'fit',
|
||||||
defaults: {
|
defaults: {
|
||||||
hideLabel: true,
|
hideLabel: true,
|
||||||
@ -108,11 +98,6 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
|
|||||||
|
|
||||||
me.callParent();
|
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);
|
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) {
|
setValue: function(value) {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
value ??= [];
|
|
||||||
|
|
||||||
if (!Ext.isArray(value)) {
|
if (!Ext.isArray(value)) {
|
||||||
value = value.split(/;, /);
|
value = value.split(/;, /);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,10 @@ Ext.define('Proxmox.form.NetworkSelector', {
|
|||||||
networkSelectorStore.load();
|
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',
|
valueField: 'cidr',
|
||||||
displayField: 'cidr',
|
displayField: 'cidr',
|
||||||
store: {
|
store: {
|
||||||
@ -119,7 +123,6 @@ Ext.define('Proxmox.form.NetworkSelector', {
|
|||||||
header: gettext('Comment'),
|
header: gettext('Comment'),
|
||||||
flex: 2,
|
flex: 2,
|
||||||
dataIndex: 'comments',
|
dataIndex: 'comments',
|
||||||
renderer: Ext.String.htmlEncode,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,6 @@ Ext.define('Proxmox.form.RealmComboBox', {
|
|||||||
|
|
||||||
init: function(view) {
|
init: function(view) {
|
||||||
let store = view.getStore();
|
let store = view.getStore();
|
||||||
store.proxy.url = `/api2/json${view.baseUrl}`;
|
|
||||||
if (view.storeFilter) {
|
if (view.storeFilter) {
|
||||||
store.setFilters(view.storeFilter);
|
store.setFilters(view.storeFilter);
|
||||||
}
|
}
|
||||||
@ -46,7 +45,6 @@ Ext.define('Proxmox.form.RealmComboBox', {
|
|||||||
triggerAction: 'all',
|
triggerAction: 'all',
|
||||||
valueField: 'realm',
|
valueField: 'realm',
|
||||||
displayField: 'descr',
|
displayField: 'descr',
|
||||||
baseUrl: '/access/domains',
|
|
||||||
getState: function() {
|
getState: function() {
|
||||||
return { value: this.getValue() };
|
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,
|
skipEmptyText: true,
|
||||||
|
|
||||||
deleteEmpty: false,
|
deleteEmpty: false,
|
||||||
|
|
||||||
trimValue: false,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getSubmitData: function() {
|
getSubmitData: function() {
|
||||||
@ -31,9 +29,6 @@ Ext.define('Proxmox.form.field.Textfield', {
|
|||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
let value = this.processRawValue(this.getRawValue());
|
let value = this.processRawValue(this.getRawValue());
|
||||||
if (me.getTrimValue() && typeof value === 'string') {
|
|
||||||
value = value.trim();
|
|
||||||
}
|
|
||||||
if (value !== '') {
|
if (value !== '') {
|
||||||
return 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: {
|
editor: {
|
||||||
xtype: 'proxmoxWindowEdit',
|
xtype: 'proxmoxWindowEdit',
|
||||||
subject: text,
|
subject: text,
|
||||||
|
onlineHelp: opts.onlineHelp,
|
||||||
fieldDefaults: {
|
fieldDefaults: {
|
||||||
labelWidth: opts.labelWidth || 100,
|
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) {
|
add_text_row: function(name, text, opts) {
|
||||||
@ -102,6 +100,7 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
|||||||
editor: {
|
editor: {
|
||||||
xtype: 'proxmoxWindowEdit',
|
xtype: 'proxmoxWindowEdit',
|
||||||
subject: text,
|
subject: text,
|
||||||
|
onlineHelp: opts.onlineHelp,
|
||||||
fieldDefaults: {
|
fieldDefaults: {
|
||||||
labelWidth: opts.labelWidth || 100,
|
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) {
|
add_boolean_row: function(name, text, opts) {
|
||||||
@ -135,6 +131,7 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
|||||||
editor: {
|
editor: {
|
||||||
xtype: 'proxmoxWindowEdit',
|
xtype: 'proxmoxWindowEdit',
|
||||||
subject: text,
|
subject: text,
|
||||||
|
onlineHelp: opts.onlineHelp,
|
||||||
fieldDefaults: {
|
fieldDefaults: {
|
||||||
labelWidth: opts.labelWidth || 100,
|
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) {
|
add_integer_row: function(name, text, opts) {
|
||||||
@ -169,6 +163,7 @@ Ext.define('Proxmox.grid.ObjectGrid', {
|
|||||||
editor: {
|
editor: {
|
||||||
xtype: 'proxmoxWindowEdit',
|
xtype: 'proxmoxWindowEdit',
|
||||||
subject: text,
|
subject: text,
|
||||||
|
onlineHelp: opts.onlineHelp,
|
||||||
fieldDefaults: {
|
fieldDefaults: {
|
||||||
labelWidth: opts.labelWidth || 100,
|
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
|
editorConfig: {}, // default config passed to editor
|
||||||
|
@ -2,7 +2,7 @@ Ext.define('apt-pkglist', {
|
|||||||
extend: 'Ext.data.Model',
|
extend: 'Ext.data.Model',
|
||||||
fields: [
|
fields: [
|
||||||
'Package', 'Title', 'Description', 'Section', 'Arch', 'Priority', 'Version', 'OldVersion',
|
'Package', 'Title', 'Description', 'Section', 'Arch', 'Priority', 'Version', 'OldVersion',
|
||||||
'Origin',
|
'ChangeLogUrl', 'Origin',
|
||||||
],
|
],
|
||||||
idProperty: 'Package',
|
idProperty: 'Package',
|
||||||
});
|
});
|
||||||
@ -108,8 +108,8 @@ Ext.define('Proxmox.node.APT', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let show_changelog = function(rec) {
|
let show_changelog = function(rec) {
|
||||||
if (!rec?.data?.Package) {
|
if (!rec?.data?.ChangeLogUrl || !rec?.data?.Package) {
|
||||||
console.debug('cannot show changelog, missing Package', rec);
|
console.debug('cannot show changelog, missing Package and/or ChangeLogUrl', rec);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ Ext.define('Proxmox.node.APT', {
|
|||||||
text: gettext('Changelog'),
|
text: gettext('Changelog'),
|
||||||
selModel: sm,
|
selModel: sm,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
enableFn: rec => !!rec?.data?.Package,
|
enableFn: rec => !!rec?.data?.ChangeLogUrl && !!rec?.data?.Package,
|
||||||
handler: (b, e, rec) => show_changelog(rec),
|
handler: (b, e, rec) => show_changelog(rec),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
|
|||||||
let txt = [gettext('Warning')];
|
let txt = [gettext('Warning')];
|
||||||
record.data.warnings.forEach((warning) => {
|
record.data.warnings.forEach((warning) => {
|
||||||
if (warning.property === 'Suites') {
|
if (warning.property === 'Suites') {
|
||||||
txt.push(Ext.htmlEncode(warning.message));
|
txt.push(warning.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
metaData.tdAttr = `data-qtip="${Ext.htmlEncode(txt.join('<br>'))}"`;
|
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 no-subscription repository is NOT production-ready')
|
||||||
: gettext('The test repository may contain unstable updates')
|
: 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;
|
return components.join(' ') + err;
|
||||||
@ -361,7 +361,6 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
|
|||||||
header: gettext('Comment'),
|
header: gettext('Comment'),
|
||||||
dataIndex: 'Comment',
|
dataIndex: 'Comment',
|
||||||
flex: 2,
|
flex: 2,
|
||||||
renderer: Ext.String.htmlEncode,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -461,11 +460,6 @@ Ext.define('Proxmox.node.APTRepositories', {
|
|||||||
let enterprise = vm.get('enterpriseRepo');
|
let enterprise = vm.get('enterpriseRepo');
|
||||||
let nosubscription = vm.get('noSubscriptionRepo');
|
let nosubscription = vm.get('noSubscriptionRepo');
|
||||||
let test = vm.get('testRepo');
|
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 wrongSuites = vm.get('suitesWarning');
|
||||||
let mixedSuites = vm.get('mixedSuites');
|
let mixedSuites = vm.get('mixedSuites');
|
||||||
|
|
||||||
@ -489,33 +483,17 @@ Ext.define('Proxmox.node.APTRepositories', {
|
|||||||
addWarn(gettext('Detected mixed suites before upgrade'));
|
addWarn(gettext('Detected mixed suites before upgrade'));
|
||||||
}
|
}
|
||||||
|
|
||||||
let productionReadyCheck = (repos, type, noSubAlternateName) => {
|
if (!activeSubscription && enterprise) {
|
||||||
if (!activeSubscription && repos.enterprise) {
|
addWarn(gettext('The enterprise repository is enabled, but there is no active subscription!'));
|
||||||
addWarn(Ext.String.format(
|
|
||||||
gettext('The {0}enterprise repository is enabled, but there is no active subscription!'),
|
|
||||||
type,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repos.nosubscription) {
|
if (nosubscription) {
|
||||||
addWarn(Ext.String.format(
|
addWarn(gettext('The no-subscription repository is not recommended for production use!'));
|
||||||
gettext('The {0}no-subscription{1} repository is not recommended for production use!'),
|
|
||||||
type,
|
|
||||||
noSubAlternateName,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repos.test) {
|
if (test) {
|
||||||
addWarn(Ext.String.format(
|
addWarn(gettext('The test repository may pull in unstable updates and is not recommended for production use!'));
|
||||||
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 (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
text = gettext('Fatal parsing error for at least one repository');
|
text = gettext('Fatal parsing error for at least one repository');
|
||||||
@ -540,9 +518,6 @@ Ext.define('Proxmox.node.APTRepositories', {
|
|||||||
noSubscriptionRepo: '',
|
noSubscriptionRepo: '',
|
||||||
enterpriseRepo: '',
|
enterpriseRepo: '',
|
||||||
testRepo: '',
|
testRepo: '',
|
||||||
cephEnterpriseRepo: '',
|
|
||||||
cephNoSubscriptionRepo: '',
|
|
||||||
cephTestRepo: '',
|
|
||||||
selectionenabled: false,
|
selectionenabled: false,
|
||||||
state: {},
|
state: {},
|
||||||
},
|
},
|
||||||
@ -611,7 +586,7 @@ Ext.define('Proxmox.node.APTRepositories', {
|
|||||||
nodename: '{nodename}',
|
nodename: '{nodename}',
|
||||||
onlineHelp: '{onlineHelp}',
|
onlineHelp: '{onlineHelp}',
|
||||||
},
|
},
|
||||||
majorUpgradeAllowed: false, // TODO get release information from an API call?
|
majorUpgradeAllowed: true, // TODO get release information from an API call?
|
||||||
listeners: {
|
listeners: {
|
||||||
selectionchange: 'selectionChange',
|
selectionchange: 'selectionChange',
|
||||||
},
|
},
|
||||||
@ -652,12 +627,6 @@ Ext.define('Proxmox.node.APTRepositories', {
|
|||||||
vm.set('noSubscriptionRepo', status);
|
vm.set('noSubscriptionRepo', status);
|
||||||
} else if (handle === 'test') {
|
} else if (handle === 'test') {
|
||||||
vm.set('testRepo', status);
|
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();
|
me.getController().updateState();
|
||||||
|
|
||||||
|
@ -2,10 +2,6 @@ Ext.define('Proxmox.node.DNSEdit', {
|
|||||||
extend: 'Proxmox.window.Edit',
|
extend: 'Proxmox.window.Edit',
|
||||||
alias: ['widget.proxmoxNodeDNSEdit'],
|
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() {
|
initComponent: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
@ -25,7 +21,6 @@ Ext.define('Proxmox.node.DNSEdit', {
|
|||||||
fieldLabel: gettext('DNS server') + " 1",
|
fieldLabel: gettext('DNS server') + " 1",
|
||||||
vtype: 'IP64Address',
|
vtype: 'IP64Address',
|
||||||
skipEmptyText: true,
|
skipEmptyText: true,
|
||||||
deleteEmpty: me.deleteEmpty,
|
|
||||||
name: 'dns1',
|
name: 'dns1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -33,7 +28,6 @@ Ext.define('Proxmox.node.DNSEdit', {
|
|||||||
fieldLabel: gettext('DNS server') + " 2",
|
fieldLabel: gettext('DNS server') + " 2",
|
||||||
vtype: 'IP64Address',
|
vtype: 'IP64Address',
|
||||||
skipEmptyText: true,
|
skipEmptyText: true,
|
||||||
deleteEmpty: me.deleteEmpty,
|
|
||||||
name: 'dns2',
|
name: 'dns2',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -41,7 +35,6 @@ Ext.define('Proxmox.node.DNSEdit', {
|
|||||||
fieldLabel: gettext('DNS server') + " 3",
|
fieldLabel: gettext('DNS server') + " 3",
|
||||||
vtype: 'IP64Address',
|
vtype: 'IP64Address',
|
||||||
skipEmptyText: true,
|
skipEmptyText: true,
|
||||||
deleteEmpty: me.deleteEmpty,
|
|
||||||
name: 'dns3',
|
name: 'dns3',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -2,10 +2,6 @@ Ext.define('Proxmox.node.DNSView', {
|
|||||||
extend: 'Proxmox.grid.ObjectGrid',
|
extend: 'Proxmox.grid.ObjectGrid',
|
||||||
alias: ['widget.proxmoxNodeDNSView'],
|
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() {
|
initComponent: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
@ -16,7 +12,6 @@ Ext.define('Proxmox.node.DNSView', {
|
|||||||
let run_editor = () => Ext.create('Proxmox.node.DNSEdit', {
|
let run_editor = () => Ext.create('Proxmox.node.DNSEdit', {
|
||||||
autoShow: true,
|
autoShow: true,
|
||||||
nodename: me.nodename,
|
nodename: me.nodename,
|
||||||
deleteEmpty: me.deleteEmpty,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ext.apply(me, {
|
Ext.apply(me, {
|
||||||
@ -26,7 +21,7 @@ Ext.define('Proxmox.node.DNSView', {
|
|||||||
run_editor: run_editor,
|
run_editor: run_editor,
|
||||||
rows: {
|
rows: {
|
||||||
search: {
|
search: {
|
||||||
header: gettext('Search domain'),
|
header: 'Search domain',
|
||||||
required: true,
|
required: true,
|
||||||
renderer: Ext.htmlEncode,
|
renderer: Ext.htmlEncode,
|
||||||
},
|
},
|
||||||
|
@ -2,9 +2,6 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
|||||||
extend: 'Proxmox.window.Edit',
|
extend: 'Proxmox.window.Edit',
|
||||||
alias: ['widget.proxmoxNodeNetworkEdit'],
|
alias: ['widget.proxmoxNodeNetworkEdit'],
|
||||||
|
|
||||||
// Enable to show the VLAN ID field
|
|
||||||
enableBridgeVlanIds: false,
|
|
||||||
|
|
||||||
initComponent: function() {
|
initComponent: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
@ -60,53 +57,11 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (me.iftype === 'bridge') {
|
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({
|
column2.push({
|
||||||
xtype: 'proxmoxcheckbox',
|
xtype: 'proxmoxcheckbox',
|
||||||
fieldLabel: gettext('VLAN aware'),
|
fieldLabel: gettext('VLAN aware'),
|
||||||
name: 'bridge_vlan_aware',
|
name: 'bridge_vlan_aware',
|
||||||
deleteEmpty: !me.isCreate,
|
deleteEmpty: !me.isCreate,
|
||||||
listeners: {
|
|
||||||
change: function(f, newVal) {
|
|
||||||
if (vlanIdsField) {
|
|
||||||
vlanIdsField.setDisabled(!newVal);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
column2.push({
|
column2.push({
|
||||||
xtype: 'textfield',
|
xtype: 'textfield',
|
||||||
@ -117,9 +72,6 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
|||||||
'data-qtip': gettext('Space-separated list of interfaces, for example: enp0s0 enp1s0'),
|
'data-qtip': gettext('Space-separated list of interfaces, for example: enp0s0 enp1s0'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (vlanIdsField) {
|
|
||||||
advancedColumn2.push(vlanIdsField);
|
|
||||||
}
|
|
||||||
} else if (me.iftype === 'OVSBridge') {
|
} else if (me.iftype === 'OVSBridge') {
|
||||||
column2.push({
|
column2.push({
|
||||||
xtype: 'textfield',
|
xtype: 'textfield',
|
||||||
@ -145,7 +97,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
|||||||
name: 'ovs_bridge',
|
name: 'ovs_bridge',
|
||||||
});
|
});
|
||||||
column2.push({
|
column2.push({
|
||||||
xtype: 'proxmoxvlanfield',
|
xtype: 'pveVlanField',
|
||||||
deleteEmpty: !me.isCreate,
|
deleteEmpty: !me.isCreate,
|
||||||
name: 'ovs_tag',
|
name: 'ovs_tag',
|
||||||
value: '',
|
value: '',
|
||||||
@ -188,7 +140,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
column2.push({
|
column2.push({
|
||||||
xtype: 'proxmoxvlanfield',
|
xtype: 'pveVlanField',
|
||||||
name: 'vlan-id',
|
name: 'vlan-id',
|
||||||
value: me.vlanidvalue,
|
value: me.vlanidvalue,
|
||||||
disabled: me.disablevlanid,
|
disabled: me.disablevlanid,
|
||||||
@ -259,7 +211,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
|||||||
name: 'ovs_bridge',
|
name: 'ovs_bridge',
|
||||||
});
|
});
|
||||||
column2.push({
|
column2.push({
|
||||||
xtype: 'proxmoxvlanfield',
|
xtype: 'pveVlanField',
|
||||||
deleteEmpty: !me.isCreate,
|
deleteEmpty: !me.isCreate,
|
||||||
name: 'ovs_tag',
|
name: 'ovs_tag',
|
||||||
value: '',
|
value: '',
|
||||||
@ -302,7 +254,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
|
|||||||
value: me.iface,
|
value: me.iface,
|
||||||
vtype: iface_vtype,
|
vtype: iface_vtype,
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
maxLength: iface_vtype === 'BridgeName' ? 10 : 15,
|
maxLength: 15,
|
||||||
autoEl: {
|
autoEl: {
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
'data-qtip': gettext('For example, vmbr0.100, vmbr0, vlan0.100, vlan0'),
|
'data-qtip': gettext('For example, vmbr0.100, vmbr0, vlan0.100, vlan0'),
|
||||||
|
@ -33,9 +33,6 @@ Ext.define('Proxmox.node.NetworkView', {
|
|||||||
|
|
||||||
showApplyBtn: false,
|
showApplyBtn: false,
|
||||||
|
|
||||||
// for options passed down to the network edit window
|
|
||||||
editOptions: {},
|
|
||||||
|
|
||||||
initComponent: function() {
|
initComponent: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
@ -103,7 +100,6 @@ Ext.define('Proxmox.node.NetworkView', {
|
|||||||
nodename: me.nodename,
|
nodename: me.nodename,
|
||||||
iface: rec.data.iface,
|
iface: rec.data.iface,
|
||||||
iftype: rec.data.type,
|
iftype: rec.data.type,
|
||||||
...me.editOptions,
|
|
||||||
listeners: {
|
listeners: {
|
||||||
destroy: () => reload(),
|
destroy: () => reload(),
|
||||||
},
|
},
|
||||||
@ -174,7 +170,6 @@ Ext.define('Proxmox.node.NetworkView', {
|
|||||||
nodename: me.nodename,
|
nodename: me.nodename,
|
||||||
iftype: iType,
|
iftype: iType,
|
||||||
iface_default: findNextFreeInterfaceId(iDefault ?? iType),
|
iface_default: findNextFreeInterfaceId(iDefault ?? iType),
|
||||||
...me.editOptions,
|
|
||||||
onlineHelp: 'sysadmin_network_configuration',
|
onlineHelp: 'sysadmin_network_configuration',
|
||||||
listeners: {
|
listeners: {
|
||||||
destroy: () => reload(),
|
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', {
|
let store = Ext.create('Proxmox.data.DiffStore', {
|
||||||
rstore: rstore,
|
rstore: rstore,
|
||||||
sortAfterUpdate: true,
|
sortAfterUpdate: true,
|
||||||
@ -40,24 +38,6 @@ Ext.define('Proxmox.node.ServiceView', {
|
|||||||
direction: 'ASC',
|
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() {
|
let view_service_log = function() {
|
||||||
@ -186,8 +166,6 @@ Ext.define('Proxmox.node.ServiceView', {
|
|||||||
restart_btn,
|
restart_btn,
|
||||||
'-',
|
'-',
|
||||||
syslog_btn,
|
syslog_btn,
|
||||||
'->',
|
|
||||||
unHideCB,
|
|
||||||
],
|
],
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
Ext.define('Proxmox.panel.AuthView', {
|
Ext.define('Proxmox.panel.AuthView', {
|
||||||
extend: 'Ext.grid.GridPanel',
|
extend: 'Ext.grid.GridPanel',
|
||||||
alias: 'widget.pmxAuthView',
|
|
||||||
mixins: ['Proxmox.Mixin.CBind'],
|
|
||||||
|
|
||||||
showDefaultRealm: false,
|
alias: 'widget.pmxAuthView',
|
||||||
|
|
||||||
stateful: true,
|
stateful: true,
|
||||||
stateId: 'grid-authrealms',
|
stateId: 'grid-authrealms',
|
||||||
@ -13,7 +11,7 @@ Ext.define('Proxmox.panel.AuthView', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
baseUrl: '/access/domains',
|
baseUrl: '/access/domains',
|
||||||
storeBaseUrl: '/access/domains',
|
useTypeInUrl: false,
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
@ -28,17 +26,6 @@ Ext.define('Proxmox.panel.AuthView', {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
dataIndex: 'type',
|
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'),
|
header: gettext('Comment'),
|
||||||
sortable: false,
|
sortable: false,
|
||||||
@ -48,17 +35,21 @@ Ext.define('Proxmox.panel.AuthView', {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
store: {
|
||||||
|
model: 'pmx-domains',
|
||||||
|
sorters: {
|
||||||
|
property: 'realm',
|
||||||
|
direction: 'ASC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
openEditWindow: function(authType, realm) {
|
openEditWindow: function(authType, realm) {
|
||||||
let me = this;
|
let me = this;
|
||||||
const { useTypeInUrl, onlineHelp } = Proxmox.Schema.authDomains[authType];
|
|
||||||
|
|
||||||
Ext.create('Proxmox.window.AuthEditBase', {
|
Ext.create('Proxmox.window.AuthEditBase', {
|
||||||
baseUrl: me.baseUrl,
|
baseUrl: me.baseUrl,
|
||||||
useTypeInUrl,
|
useTypeInUrl: me.useTypeInUrl,
|
||||||
onlineHelp,
|
|
||||||
authType,
|
authType,
|
||||||
realm,
|
realm,
|
||||||
showDefaultRealm: me.showDefaultRealm,
|
|
||||||
listeners: {
|
listeners: {
|
||||||
destroy: () => me.reload(),
|
destroy: () => me.reload(),
|
||||||
},
|
},
|
||||||
@ -104,18 +95,6 @@ Ext.define('Proxmox.panel.AuthView', {
|
|||||||
initComponent: function() {
|
initComponent: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
me.store = {
|
|
||||||
model: 'pmx-domains',
|
|
||||||
sorters: {
|
|
||||||
property: 'realm',
|
|
||||||
direction: 'ASC',
|
|
||||||
},
|
|
||||||
proxy: {
|
|
||||||
type: 'proxmox',
|
|
||||||
url: `/api2/json${me.storeBaseUrl}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let menuitems = [];
|
let menuitems = [];
|
||||||
for (const [authType, config] of Object.entries(Proxmox.Schema.authDomains).sort()) {
|
for (const [authType, config] of Object.entries(Proxmox.Schema.authDomains).sort()) {
|
||||||
if (!config.add) { continue; }
|
if (!config.add) { continue; }
|
||||||
@ -144,7 +123,7 @@ Ext.define('Proxmox.panel.AuthView', {
|
|||||||
xtype: 'proxmoxStdRemoveButton',
|
xtype: 'proxmoxStdRemoveButton',
|
||||||
getUrl: (rec) => {
|
getUrl: (rec) => {
|
||||||
let url = me.baseUrl;
|
let url = me.baseUrl;
|
||||||
if (Proxmox.Schema.authDomains[rec.data.type].useTypeInUrl) {
|
if (me.useTypeInUrl) {
|
||||||
url += `/${rec.get('type')}`;
|
url += `/${rec.get('type')}`;
|
||||||
}
|
}
|
||||||
url += `/${rec.getId()}`;
|
url += `/${rec.getId()}`;
|
||||||
|
@ -85,7 +85,7 @@ Ext.define('Proxmox.panel.Certificates', {
|
|||||||
url: `/api2/extjs/${url}?restart=1`,
|
url: `/api2/extjs/${url}?restart=1`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
success: function(response, opt) {
|
success: function(response, opt) {
|
||||||
if (cert.reloadUi) {
|
if (cert.reloadUid) {
|
||||||
Ext.getBody().mask(
|
Ext.getBody().mask(
|
||||||
gettext('API server will be restarted to use new certificates, please reload web-interface!'),
|
gettext('API server will be restarted to use new certificates, please reload web-interface!'),
|
||||||
['pve-static-mask'],
|
['pve-static-mask'],
|
||||||
@ -237,16 +237,10 @@ Ext.define('Proxmox.panel.Certificates', {
|
|||||||
{
|
{
|
||||||
xtype: 'proxmoxButton',
|
xtype: 'proxmoxButton',
|
||||||
text: gettext('Delete Custom Certificate'),
|
text: gettext('Delete Custom Certificate'),
|
||||||
confirmMsg: rec => {
|
confirmMsg: rec => Ext.String.format(
|
||||||
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}'),
|
gettext('Are you sure you want to remove the certificate used for {0}'),
|
||||||
cert.name,
|
me.certById[rec.id].name,
|
||||||
);
|
),
|
||||||
}
|
|
||||||
return gettext('Are you sure you want to remove the certificate');
|
|
||||||
},
|
|
||||||
callback: () => me.reload(),
|
callback: () => me.reload(),
|
||||||
selModel: me.selModel,
|
selModel: me.selModel,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
|
@ -220,11 +220,7 @@ Ext.define('Proxmox.DiskList', {
|
|||||||
let extendedInfo = '';
|
let extendedInfo = '';
|
||||||
if (rec) {
|
if (rec) {
|
||||||
let types = [];
|
let types = [];
|
||||||
if (rec.data['osdid-list'] && rec.data['osdid-list'].length > 0) {
|
if (rec.data.osdid !== undefined && rec.data.osdid >= 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) {
|
|
||||||
types.push(`OSD.${rec.data.osdid.toString()}`);
|
types.push(`OSD.${rec.data.osdid.toString()}`);
|
||||||
}
|
}
|
||||||
if (rec.data.journals > 0) {
|
if (rec.data.journals > 0) {
|
||||||
@ -325,14 +321,14 @@ Ext.define('Proxmox.DiskList', {
|
|||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: gettext('Mounted'),
|
header: 'Mounted',
|
||||||
width: 60,
|
width: 60,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
renderer: Proxmox.Utils.format_boolean,
|
renderer: Proxmox.Utils.format_boolean,
|
||||||
dataIndex: 'mounted',
|
dataIndex: 'mounted',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: gettext('Wearout'),
|
header: 'Wearout',
|
||||||
width: 90,
|
width: 90,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
@ -341,7 +337,7 @@ Ext.define('Proxmox.DiskList', {
|
|||||||
if (Ext.isNumeric(value)) {
|
if (Ext.isNumeric(value)) {
|
||||||
return (100 - value).toString() + '%';
|
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() {
|
updateParams: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
let viewModel = me.getViewModel();
|
let viewModel = me.getViewModel();
|
||||||
|
|
||||||
if (viewModel.get('hide_timespan') || viewModel.get('livemode')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let since = viewModel.get('since');
|
let since = viewModel.get('since');
|
||||||
let until = viewModel.get('until');
|
let until = viewModel.get('until');
|
||||||
|
if (viewModel.get('hide_timespan')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (since > until) {
|
if (since > until) {
|
||||||
Ext.Msg.alert('Error', 'Since date must be less equal than Until date.');
|
Ext.Msg.alert('Error', 'Since date must be less equal than Until date.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let submitFormat = viewModel.get('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');
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
me.getView().loadTask.delay(200);
|
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) {
|
init: function(view) {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
@ -219,7 +189,6 @@ Ext.define('Proxmox.panel.LogView', {
|
|||||||
viewModel.set('since', since);
|
viewModel.set('since', since);
|
||||||
viewModel.set('params.limit', view.pageSize);
|
viewModel.set('params.limit', view.pageSize);
|
||||||
viewModel.set('hide_timespan', !view.log_select_timespan);
|
viewModel.set('hide_timespan', !view.log_select_timespan);
|
||||||
viewModel.set('submitFormat', view.submitFormat);
|
|
||||||
me.lookup('content').setStyle('line-height', `${view.lineHeight}px`);
|
me.lookup('content').setStyle('line-height', `${view.lineHeight}px`);
|
||||||
|
|
||||||
view.loadTask = new Ext.util.DelayedTask(me.doLoad, me);
|
view.loadTask = new Ext.util.DelayedTask(me.doLoad, me);
|
||||||
@ -255,8 +224,6 @@ Ext.define('Proxmox.panel.LogView', {
|
|||||||
data: {
|
data: {
|
||||||
until: null,
|
until: null,
|
||||||
since: null,
|
since: null,
|
||||||
submitFormat: 'Y-m-d',
|
|
||||||
livemode: true,
|
|
||||||
hide_timespan: false,
|
hide_timespan: false,
|
||||||
data: {
|
data: {
|
||||||
start: 0,
|
start: 0,
|
||||||
@ -296,70 +263,32 @@ Ext.define('Proxmox.panel.LogView', {
|
|||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
'->',
|
'->',
|
||||||
|
'Since: ',
|
||||||
{
|
{
|
||||||
xtype: 'segmentedbutton',
|
xtype: 'datefield',
|
||||||
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',
|
|
||||||
name: 'since_date',
|
name: 'since_date',
|
||||||
reference: 'since',
|
reference: 'since',
|
||||||
format: 'Y-m-d',
|
format: 'Y-m-d',
|
||||||
bind: {
|
bind: {
|
||||||
disabled: '{livemode}',
|
|
||||||
value: '{since}',
|
value: '{since}',
|
||||||
maxValue: '{until}',
|
maxValue: '{until}',
|
||||||
submitFormat: '{submitFormat}',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'Until: ',
|
||||||
{
|
{
|
||||||
xtype: 'box',
|
xtype: 'datefield',
|
||||||
autoEl: { cn: gettext('Until') + ':' },
|
|
||||||
bind: {
|
|
||||||
disabled: '{livemode}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
xtype: 'proxmoxDateTimeField',
|
|
||||||
name: 'until_date',
|
name: 'until_date',
|
||||||
reference: 'until',
|
reference: 'until',
|
||||||
format: 'Y-m-d',
|
format: 'Y-m-d',
|
||||||
bind: {
|
bind: {
|
||||||
disabled: '{livemode}',
|
|
||||||
value: '{until}',
|
value: '{until}',
|
||||||
minValue: '{since}',
|
minValue: '{since}',
|
||||||
submitFormat: '{submitFormat}',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'button',
|
xtype: 'button',
|
||||||
text: 'Update',
|
text: 'Update',
|
||||||
handler: 'updateParams',
|
handler: 'updateParams',
|
||||||
bind: {
|
|
||||||
disabled: '{livemode}',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -7,8 +7,6 @@ Ext.define('Proxmox.panel.NotesView', {
|
|||||||
bodyPadding: 10,
|
bodyPadding: 10,
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
animCollapse: false,
|
animCollapse: false,
|
||||||
collapseFirst: false,
|
|
||||||
|
|
||||||
maxLength: 64 * 1024,
|
maxLength: 64 * 1024,
|
||||||
enableTBar: false,
|
enableTBar: false,
|
||||||
onlineHelp: 'markdown_basics',
|
onlineHelp: 'markdown_basics',
|
||||||
@ -19,7 +17,6 @@ Ext.define('Proxmox.panel.NotesView', {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
text: gettext('Edit'),
|
text: gettext('Edit'),
|
||||||
iconCls: 'fa fa-pencil-square-o',
|
|
||||||
handler: function() {
|
handler: function() {
|
||||||
let view = this.up('panel');
|
let view = this.up('panel');
|
||||||
view.run_editor();
|
view.run_editor();
|
||||||
@ -112,23 +109,7 @@ Ext.define('Proxmox.panel.NotesView', {
|
|||||||
listeners: {
|
listeners: {
|
||||||
render: function(c) {
|
render: function(c) {
|
||||||
let me = this;
|
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);
|
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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
afterlayout: function() {
|
afterlayout: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
@ -141,11 +122,10 @@ Ext.define('Proxmox.panel.NotesView', {
|
|||||||
|
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
glyph: 'xf044@FontAwesome', // fa-pencil-square-o
|
type: 'gear',
|
||||||
tooltip: gettext('Edit notes'),
|
handler: function() {
|
||||||
callback: view => view.run_editor(),
|
let view = this.up('panel');
|
||||||
style: {
|
view.run_editor();
|
||||||
paddingRight: '5px',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -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) {
|
onLoad: function(store, data, success) {
|
||||||
if (!success) return;
|
if (!success) return;
|
||||||
|
|
||||||
let now = new Date().getTime() / 1000;
|
|
||||||
let records = [];
|
let records = [];
|
||||||
Ext.Array.each(data, user => {
|
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 => {
|
Ext.Array.each(user.data.entries, entry => {
|
||||||
records.push({
|
records.push({
|
||||||
fullid: `${user.id}/${entry.id}`,
|
fullid: `${user.id}/${entry.id}`,
|
||||||
@ -80,7 +77,6 @@ Ext.define('Proxmox.panel.TfaView', {
|
|||||||
description: entry.description,
|
description: entry.description,
|
||||||
created: entry.created,
|
created: entry.created,
|
||||||
enable: entry.enable,
|
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],
|
renderUser: fullid => fullid.split('/')[0],
|
||||||
|
|
||||||
renderEnabled: function(enabled, metaData, record) {
|
renderEnabled: enabled => {
|
||||||
if (record.data.locked) {
|
if (enabled === undefined) {
|
||||||
return gettext("Locked");
|
|
||||||
} else if (enabled === undefined) {
|
|
||||||
return Proxmox.Utils.yesText;
|
return Proxmox.Utils.yesText;
|
||||||
} else {
|
} else {
|
||||||
return Proxmox.Utils.format_boolean(enabled);
|
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,
|
// make the color transparent so the border doesn't appear visibly,
|
||||||
// like in crisp, but keep to keep the layout intact
|
// like in crisp, but keep to keep the layout intact
|
||||||
border-color: transparent;
|
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 {
|
.x-window-header-title-default {
|
||||||
|
@ -33,13 +33,6 @@
|
|||||||
color: $icon-color;
|
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
|
// but some are implement as background SVGs or PNGs. invert them via
|
||||||
// filters:
|
// filters:
|
||||||
.fa-ceph::before,
|
.fa-ceph::before,
|
||||||
@ -111,9 +104,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pbs show task log in longest task list column
|
// 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 {
|
.x-action-col-icon.fa-chevron-right::before {
|
||||||
filter: none;
|
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
|
// The usage icons dynamically displaying how full a storage is
|
||||||
.usage-wrapper {
|
.usage-wrapper {
|
||||||
border: 1px solid $icon-color;
|
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
|
// removes the gray line in the header of the mail gateway
|
||||||
div[id^="versioninfo-"] + div[id^="panel-"] > div[id^="panel-"][id$="-bodyWrap"] > div,
|
div[id^="versioninfo-"] + div[id^="panel-"] > div[id^="panel-"][id$="-bodyWrap"] > div {
|
||||||
div.eol-notice + div[id^="panel-"] > div[id^="panel-"][id$="-bodyWrap"] > div {
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
@ -95,14 +95,14 @@ Ext.define('Proxmox.window.ACMEPluginEdit', {
|
|||||||
let field = Ext.create({
|
let field = Ext.create({
|
||||||
xtype,
|
xtype,
|
||||||
name: `custom_${name}`,
|
name: `custom_${name}`,
|
||||||
fieldLabel: Ext.htmlEncode(label),
|
fieldLabel: label,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
labelWidth: 150,
|
labelWidth: 150,
|
||||||
labelSeparator: '=',
|
labelSeparator: '=',
|
||||||
emptyText: definition.default || '',
|
emptyText: definition.default || '',
|
||||||
autoEl: definition.description ? {
|
autoEl: definition.description ? {
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
'data-qtip': Ext.htmlEncode(Ext.htmlEncode(definition.description)),
|
'data-qtip': definition.description,
|
||||||
} : undefined,
|
} : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -127,7 +127,6 @@ Ext.define('Proxmox.window.ACMEPluginEdit', {
|
|||||||
if (me.createdFields[key]) {
|
if (me.createdFields[key]) {
|
||||||
me.createdFields[key].setValue(value);
|
me.createdFields[key].setValue(value);
|
||||||
me.createdFields[key].originalValue = me.originalValues[key];
|
me.createdFields[key].originalValue = me.originalValues[key];
|
||||||
me.createdFields[key].checkDirty();
|
|
||||||
} else {
|
} else {
|
||||||
extradata.push(`${key}=${value}`);
|
extradata.push(`${key}=${value}`);
|
||||||
}
|
}
|
||||||
|
@ -224,10 +224,10 @@ Ext.define('Proxmox.window.AddTotp', {
|
|||||||
visible: '{!secretEmpty}',
|
visible: '{!secretEmpty}',
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
margin: '16px auto',
|
margin: '5px auto',
|
||||||
padding: '16px',
|
padding: '5px',
|
||||||
width: '288px',
|
width: '266px',
|
||||||
height: '288px',
|
height: '266px',
|
||||||
'background-color': 'white',
|
'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', {
|
Ext.define('Proxmox.window.AuthEditBase', {
|
||||||
extend: 'Proxmox.window.Edit',
|
extend: 'Proxmox.window.Edit',
|
||||||
mixins: ['Proxmox.Mixin.CBind'],
|
|
||||||
|
|
||||||
showDefaultRealm: false,
|
|
||||||
|
|
||||||
isAdd: true,
|
isAdd: true,
|
||||||
|
|
||||||
@ -32,9 +29,9 @@ Ext.define('Proxmox.window.AuthEditBase', {
|
|||||||
|
|
||||||
let authConfig = Proxmox.Schema.authDomains[me.authType];
|
let authConfig = Proxmox.Schema.authDomains[me.authType];
|
||||||
if (!authConfig) {
|
if (!authConfig) {
|
||||||
throw `unknown auth type ${me.authType}`;
|
throw 'unknown auth type';
|
||||||
} else if (!authConfig.add && me.isCreate) {
|
} 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;
|
me.subject = authConfig.name;
|
||||||
@ -54,9 +51,7 @@ Ext.define('Proxmox.window.AuthEditBase', {
|
|||||||
realm: me.realm,
|
realm: me.realm,
|
||||||
xtype: authConfig.ipanel,
|
xtype: authConfig.ipanel,
|
||||||
isCreate: me.isCreate,
|
isCreate: me.isCreate,
|
||||||
useTypeInUrl: me.useTypeInUrl,
|
|
||||||
type: me.authType,
|
type: me.authType,
|
||||||
showDefaultRealm: me.showDefaultRealm,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: gettext('Sync Options'),
|
title: gettext('Sync Options'),
|
||||||
@ -72,9 +67,7 @@ Ext.define('Proxmox.window.AuthEditBase', {
|
|||||||
realm: me.realm,
|
realm: me.realm,
|
||||||
xtype: authConfig.ipanel,
|
xtype: authConfig.ipanel,
|
||||||
isCreate: me.isCreate,
|
isCreate: me.isCreate,
|
||||||
useTypeInUrl: me.useTypeInUrl,
|
|
||||||
type: me.authType,
|
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
|
// only check this when the type is not in the api path
|
||||||
if (!me.useTypeInUrl && data.type !== me.authType) {
|
if (!me.useTypeInUrl && data.type !== me.authType) {
|
||||||
me.close();
|
me.close();
|
||||||
throw `got wrong auth type '${me.authType}' for realm '${data.type}'`;
|
throw "got wrong auth type";
|
||||||
}
|
}
|
||||||
me.setValues(data);
|
me.setValues(data);
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
|
||||||
Ext.define('Proxmox.panel.LDAPInputPanelViewModel', {
|
Ext.define('Proxmox.panel.LDAPInputPanelViewModel', {
|
||||||
extend: 'Ext.app.ViewModel',
|
extend: 'Ext.app.ViewModel',
|
||||||
|
|
||||||
alias: 'viewmodel.pmxAuthLDAPPanel',
|
alias: 'viewmodel.pmxAuthLDAPPanel',
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
@ -21,8 +23,6 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
xtype: 'pmxAuthLDAPPanel',
|
xtype: 'pmxAuthLDAPPanel',
|
||||||
mixins: ['Proxmox.Mixin.CBind'],
|
mixins: ['Proxmox.Mixin.CBind'],
|
||||||
|
|
||||||
showDefaultRealm: false,
|
|
||||||
|
|
||||||
viewModel: {
|
viewModel: {
|
||||||
type: 'pmxAuthLDAPPanel',
|
type: 'pmxAuthLDAPPanel',
|
||||||
},
|
},
|
||||||
@ -32,11 +32,11 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
onlineHelp: 'user-realms-ldap',
|
onlineHelp: 'user-realms-ldap',
|
||||||
|
|
||||||
onGetValues: function(values) {
|
onGetValues: function(values) {
|
||||||
if (this.isCreate && !this.useTypeInUrl) {
|
if (this.isCreate) {
|
||||||
values.type = this.type;
|
values.type = this.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.anonymous_search && !this.isCreate) {
|
if (values.anonymous_search) {
|
||||||
if (!values.delete) {
|
if (!values.delete) {
|
||||||
values.delete = [];
|
values.delete = [];
|
||||||
}
|
}
|
||||||
@ -64,12 +64,6 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
return values;
|
return values;
|
||||||
},
|
},
|
||||||
|
|
||||||
cbindData: function(config) {
|
|
||||||
return {
|
|
||||||
isLdap: this.type === 'ldap',
|
|
||||||
isAd: this.type === 'ad',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
column1: [
|
column1: [
|
||||||
{
|
{
|
||||||
@ -82,40 +76,19 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
fieldLabel: gettext('Realm'),
|
fieldLabel: gettext('Realm'),
|
||||||
allowBlank: false,
|
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',
|
xtype: 'proxmoxtextfield',
|
||||||
fieldLabel: gettext('Base Domain Name'),
|
fieldLabel: gettext('Base Domain Name'),
|
||||||
name: 'base-dn',
|
name: 'base-dn',
|
||||||
|
allowBlank: false,
|
||||||
emptyText: 'cn=Users,dc=company,dc=net',
|
emptyText: 'cn=Users,dc=company,dc=net',
|
||||||
cbind: {
|
|
||||||
hidden: '{!isLdap}',
|
|
||||||
allowBlank: '{!isLdap}',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxtextfield',
|
xtype: 'proxmoxtextfield',
|
||||||
fieldLabel: gettext('User Attribute Name'),
|
fieldLabel: gettext('User Attribute Name'),
|
||||||
name: 'user-attr',
|
name: 'user-attr',
|
||||||
|
allowBlank: false,
|
||||||
emptyText: 'uid / sAMAccountName',
|
emptyText: 'uid / sAMAccountName',
|
||||||
cbind: {
|
|
||||||
hidden: '{!isLdap}',
|
|
||||||
allowBlank: '{!isLdap}',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxcheckbox',
|
xtype: 'proxmoxcheckbox',
|
||||||
@ -130,14 +103,7 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
fieldLabel: gettext('Bind Domain Name'),
|
fieldLabel: gettext('Bind Domain Name'),
|
||||||
name: 'bind-dn',
|
name: 'bind-dn',
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
cbind: {
|
emptyText: 'cn=user,dc=company,dc=net',
|
||||||
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'),
|
|
||||||
} : {},
|
|
||||||
},
|
|
||||||
bind: {
|
bind: {
|
||||||
disabled: "{anonymous_search}",
|
disabled: "{anonymous_search}",
|
||||||
},
|
},
|
||||||
@ -147,9 +113,9 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
inputType: 'password',
|
inputType: 'password',
|
||||||
fieldLabel: gettext('Bind Password'),
|
fieldLabel: gettext('Bind Password'),
|
||||||
name: 'password',
|
name: 'password',
|
||||||
|
allowBlank: true,
|
||||||
cbind: {
|
cbind: {
|
||||||
emptyText: get => !get('isCreate') ? gettext('Unchanged') : '',
|
emptyText: get => !get('isCreate') ? gettext('Unchanged') : '',
|
||||||
allowBlank: '{!isCreate}',
|
|
||||||
},
|
},
|
||||||
bind: {
|
bind: {
|
||||||
disabled: "{anonymous_search}",
|
disabled: "{anonymous_search}",
|
||||||
@ -181,9 +147,7 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
maxValue: 65535,
|
maxValue: 65535,
|
||||||
emptyText: gettext('Default'),
|
emptyText: gettext('Default'),
|
||||||
submitEmptyText: false,
|
submitEmptyText: false,
|
||||||
cbind: {
|
deleteEmpty: true,
|
||||||
deleteEmpty: '{!isCreate}',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxKVComboBox',
|
xtype: 'proxmoxKVComboBox',
|
||||||
@ -223,7 +187,7 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
|
|
||||||
columnB: [
|
columnB: [
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxtextfield',
|
xtype: 'textfield',
|
||||||
name: 'comment',
|
name: 'comment',
|
||||||
fieldLabel: gettext('Comment'),
|
fieldLabel: gettext('Comment'),
|
||||||
cbind: {
|
cbind: {
|
||||||
@ -231,6 +195,7 @@ Ext.define('Proxmox.panel.LDAPInputPanel', {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -330,30 +295,16 @@ Ext.define('Proxmox.panel.LDAPSyncInputPanel', {
|
|||||||
xtype: 'proxmoxtextfield',
|
xtype: 'proxmoxtextfield',
|
||||||
name: 'firstname',
|
name: 'firstname',
|
||||||
fieldLabel: gettext('First Name attribute'),
|
fieldLabel: gettext('First Name attribute'),
|
||||||
autoEl: {
|
|
||||||
tag: 'div',
|
|
||||||
'data-qtip': Ext.String.format(gettext('Often called {0}'), '`givenName`'),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxtextfield',
|
xtype: 'proxmoxtextfield',
|
||||||
name: 'lastname',
|
name: 'lastname',
|
||||||
fieldLabel: gettext('Last Name attribute'),
|
fieldLabel: gettext('Last Name attribute'),
|
||||||
autoEl: {
|
|
||||||
tag: 'div',
|
|
||||||
'data-qtip': Ext.String.format(gettext('Often called {0}'), '`sn`'),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxtextfield',
|
xtype: 'proxmoxtextfield',
|
||||||
name: 'email',
|
name: 'email',
|
||||||
fieldLabel: gettext('E-Mail attribute'),
|
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',
|
xtype: 'displayfield',
|
||||||
@ -385,9 +336,7 @@ Ext.define('Proxmox.panel.LDAPSyncInputPanel', {
|
|||||||
xtype: 'proxmoxtextfield',
|
xtype: 'proxmoxtextfield',
|
||||||
name: 'user-classes',
|
name: 'user-classes',
|
||||||
fieldLabel: gettext('User classes'),
|
fieldLabel: gettext('User classes'),
|
||||||
cbind: {
|
deleteEmpty: true,
|
||||||
deleteEmpty: '{!isCreate}',
|
|
||||||
},
|
|
||||||
emptyText: 'inetorgperson, posixaccount, person, user',
|
emptyText: 'inetorgperson, posixaccount, person, user',
|
||||||
autoEl: {
|
autoEl: {
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
@ -398,9 +347,7 @@ Ext.define('Proxmox.panel.LDAPSyncInputPanel', {
|
|||||||
xtype: 'proxmoxtextfield',
|
xtype: 'proxmoxtextfield',
|
||||||
name: 'filter',
|
name: 'filter',
|
||||||
fieldLabel: gettext('User Filter'),
|
fieldLabel: gettext('User Filter'),
|
||||||
cbind: {
|
deleteEmpty: true,
|
||||||
deleteEmpty: '{!isCreate}',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -3,14 +3,12 @@ Ext.define('Proxmox.panel.OpenIDInputPanel', {
|
|||||||
xtype: 'pmxAuthOpenIDPanel',
|
xtype: 'pmxAuthOpenIDPanel',
|
||||||
mixins: ['Proxmox.Mixin.CBind'],
|
mixins: ['Proxmox.Mixin.CBind'],
|
||||||
|
|
||||||
showDefaultRealm: false,
|
|
||||||
|
|
||||||
type: 'openid',
|
type: 'openid',
|
||||||
|
|
||||||
onGetValues: function(values) {
|
onGetValues: function(values) {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
if (me.isCreate && !me.useTypeInUrl) {
|
if (me.isCreate) {
|
||||||
values.type = me.type;
|
values.type = me.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,21 +35,6 @@ Ext.define('Proxmox.panel.OpenIDInputPanel', {
|
|||||||
fieldLabel: gettext('Realm'),
|
fieldLabel: gettext('Realm'),
|
||||||
allowBlank: false,
|
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',
|
xtype: 'proxmoxtextfield',
|
||||||
fieldLabel: gettext('Client ID'),
|
fieldLabel: gettext('Client ID'),
|
||||||
@ -129,7 +112,7 @@ Ext.define('Proxmox.panel.OpenIDInputPanel', {
|
|||||||
|
|
||||||
columnB: [
|
columnB: [
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxtextfield',
|
xtype: 'textfield',
|
||||||
name: 'comment',
|
name: 'comment',
|
||||||
fieldLabel: gettext('Comment'),
|
fieldLabel: gettext('Comment'),
|
||||||
cbind: {
|
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',
|
text: 'ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
width: 50,
|
width: 50,
|
||||||
align: 'right',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: gettext('Attribute'),
|
text: gettext('Attribute'),
|
||||||
@ -46,19 +45,16 @@ Ext.define('Proxmox.window.DiskSmart', {
|
|||||||
text: gettext('Normalized'),
|
text: gettext('Normalized'),
|
||||||
dataIndex: 'real-normalized',
|
dataIndex: 'real-normalized',
|
||||||
width: 60,
|
width: 60,
|
||||||
align: 'right',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: gettext('Threshold'),
|
text: gettext('Threshold'),
|
||||||
dataIndex: 'threshold',
|
dataIndex: 'threshold',
|
||||||
width: 60,
|
width: 60,
|
||||||
align: 'right',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: gettext('Worst'),
|
text: gettext('Worst'),
|
||||||
dataIndex: 'worst',
|
dataIndex: 'worst',
|
||||||
width: 60,
|
width: 60,
|
||||||
align: 'right',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: gettext('Flags'),
|
text: gettext('Flags'),
|
||||||
|
@ -31,9 +31,6 @@ Ext.define('Proxmox.window.Edit', {
|
|||||||
// custom submitText
|
// custom submitText
|
||||||
submitText: undefined,
|
submitText: undefined,
|
||||||
|
|
||||||
// custom options for the submit api call
|
|
||||||
submitOptions: {},
|
|
||||||
|
|
||||||
backgroundDelay: 0,
|
backgroundDelay: 0,
|
||||||
|
|
||||||
// string or function, called as (url, values) - useful if the ID of the
|
// 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 of our first item, if set.
|
||||||
onlineHelp: undefined,
|
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() {
|
isValid: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
@ -163,7 +151,7 @@ Ext.define('Proxmox.window.Edit', {
|
|||||||
values = undefined;
|
values = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestOptions = Ext.apply({
|
Proxmox.Utils.API2Request({
|
||||||
url: url,
|
url: url,
|
||||||
waitMsgTarget: me,
|
waitMsgTarget: me,
|
||||||
method: me.method || (me.backgroundDelay ? 'POST' : 'PUT'),
|
method: me.method || (me.backgroundDelay ? 'POST' : 'PUT'),
|
||||||
@ -203,8 +191,7 @@ Ext.define('Proxmox.window.Edit', {
|
|||||||
me.close();
|
me.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}, me.submitOptions ?? {});
|
});
|
||||||
Proxmox.Utils.API2Request(requestOptions);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
load: function(options) {
|
load: function(options) {
|
||||||
@ -318,21 +305,19 @@ Ext.define('Proxmox.window.Edit', {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let resetTool = Ext.create('Ext.panel.Tool', {
|
let resetBtn = Ext.create('Ext.Button', {
|
||||||
glyph: 'xf0e2@FontAwesome', // fa-undo
|
text: 'Reset',
|
||||||
tooltip: gettext('Reset form data'),
|
|
||||||
callback: () => form.reset(),
|
|
||||||
style: {
|
|
||||||
paddingRight: '2px', // just slightly more room to breathe
|
|
||||||
},
|
|
||||||
disabled: true,
|
disabled: true,
|
||||||
|
handler: function() {
|
||||||
|
form.reset();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let set_button_status = function() {
|
let set_button_status = function() {
|
||||||
let valid = form.isValid();
|
let valid = form.isValid();
|
||||||
let dirty = form.isDirty();
|
let dirty = form.isDirty();
|
||||||
submitBtn.setDisabled(!valid || !(dirty || me.isCreate));
|
submitBtn.setDisabled(!valid || !(dirty || me.isCreate));
|
||||||
resetTool.setDisabled(!dirty);
|
resetBtn.setDisabled(!dirty);
|
||||||
};
|
};
|
||||||
|
|
||||||
form.on('dirtychange', set_button_status);
|
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.title = Proxmox.Utils.dialog_title(me.subject, me.isCreate, me.isAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (me.isCreate || !me.showReset) {
|
||||||
me.buttons = [submitBtn];
|
me.buttons = [submitBtn];
|
||||||
|
} else {
|
||||||
if (!me.isCreate && me.showReset) {
|
me.buttons = [submitBtn, resetBtn];
|
||||||
me.tools = [resetTool];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputPanel && inputPanel.hasAdvanced) {
|
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
|
'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
|
// prefix to prepend to downloaded file names
|
||||||
downloadPrefix: '',
|
downloadPrefix: '',
|
||||||
},
|
},
|
||||||
@ -122,7 +126,7 @@ Ext.define("Proxmox.window.FileBrowser", {
|
|||||||
view.lookup('selectText').setText(st);
|
view.lookup('selectText').setText(st);
|
||||||
|
|
||||||
let canDownload = view.downloadURL && view.downloadableFileTypes[data.type];
|
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');
|
let downloadBtn = view.lookup('downloadBtn');
|
||||||
downloadBtn.setDisabled(!canDownload || enableMenu);
|
downloadBtn.setDisabled(!canDownload || enableMenu);
|
||||||
|
@ -12,12 +12,6 @@ Ext.define('Proxmox.window.LanguageEditWindow', {
|
|||||||
xclass: 'Ext.app.ViewController',
|
xclass: 'Ext.app.ViewController',
|
||||||
init: function(view) {
|
init: function(view) {
|
||||||
let language = Ext.util.Cookies.get(view.cookieName) || '__default__';
|
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);
|
this.getViewModel().set('language', language);
|
||||||
},
|
},
|
||||||
applyLanguage: function(button) {
|
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',
|
url: '/api2/extjs/access/password',
|
||||||
|
|
||||||
width: 380,
|
|
||||||
fieldDefaults: {
|
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: [
|
items: [
|
||||||
{
|
{
|
||||||
xtype: 'textfield',
|
xtype: 'textfield',
|
||||||
inputType: 'password',
|
inputType: 'password',
|
||||||
fieldLabel: gettext('Your Current Password'),
|
fieldLabel: gettext('Password'),
|
||||||
reference: 'confirmation-password',
|
minLength: 5,
|
||||||
name: 'confirmation-password',
|
|
||||||
allowBlank: false,
|
|
||||||
vtype: 'password',
|
|
||||||
cbind: {
|
|
||||||
hidden: '{!confirmCurrentPassword}',
|
|
||||||
disabled: '{!confirmCurrentPassword}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
xtype: 'textfield',
|
|
||||||
inputType: 'password',
|
|
||||||
fieldLabel: gettext('New Password'),
|
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
name: 'password',
|
name: 'password',
|
||||||
listeners: {
|
listeners: {
|
||||||
change: (field) => field.next().validate(),
|
change: (field) => field.next().validate(),
|
||||||
blur: (field) => field.next().validate(),
|
blur: (field) => field.next().validate(),
|
||||||
},
|
},
|
||||||
cbind: {
|
|
||||||
minLength: '{minLength}',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'textfield',
|
xtype: 'textfield',
|
||||||
inputType: 'password',
|
inputType: 'password',
|
||||||
fieldLabel: gettext('Confirm New Password'),
|
fieldLabel: gettext('Confirm password'),
|
||||||
name: 'verifypassword',
|
name: 'verifypassword',
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
vtype: 'password',
|
vtype: 'password',
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
// Pop-up a message window where the user has to manually enter the resource ID to enable the
|
/* Popup a message window
|
||||||
// destroy confirmation button to ensure that they got the correct resource selected for.
|
* where the user has to manually enter the resource ID
|
||||||
|
* to enable the destroy button
|
||||||
|
*/
|
||||||
Ext.define('Proxmox.window.SafeDestroy', {
|
Ext.define('Proxmox.window.SafeDestroy', {
|
||||||
extend: 'Ext.window.Window',
|
extend: 'Ext.window.Window',
|
||||||
alias: 'widget.proxmoxSafeDestroy',
|
alias: 'widget.proxmoxSafeDestroy',
|
||||||
|
@ -108,11 +108,11 @@ Ext.define('Proxmox.window.TaskViewer', {
|
|||||||
defaultValue: 'unknown',
|
defaultValue: 'unknown',
|
||||||
renderer: function(value) {
|
renderer: function(value) {
|
||||||
if (value !== 'stopped') {
|
if (value !== 'stopped') {
|
||||||
return Ext.htmlEncode(value);
|
return value;
|
||||||
}
|
}
|
||||||
let es = statgrid.getObjectValue('exitstatus');
|
let es = statgrid.getObjectValue('exitstatus');
|
||||||
if (es) {
|
if (es) {
|
||||||
return Ext.htmlEncode(`${value}: ${es}`);
|
return value + ': ' + es;
|
||||||
}
|
}
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
},
|
},
|
||||||
|
@ -45,17 +45,11 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
|||||||
|
|
||||||
let lastTabId = me.getLastTabUsed();
|
let lastTabId = me.getLastTabUsed();
|
||||||
let initialTab = -1, i = 0;
|
let initialTab = -1, i = 0;
|
||||||
let count2nd = 0;
|
|
||||||
let hasRecovery = false;
|
|
||||||
for (const k of ['webauthn', 'totp', 'recovery', 'u2f', 'yubico']) {
|
for (const k of ['webauthn', 'totp', 'recovery', 'u2f', 'yubico']) {
|
||||||
const available = !!challenge[k];
|
const available = !!challenge[k];
|
||||||
vm.set(`availableChallenge.${k}`, available);
|
vm.set(`availableChallenge.${k}`, available);
|
||||||
|
|
||||||
if (available) {
|
if (available) {
|
||||||
count2nd++;
|
|
||||||
if (k === 'recovery') {
|
|
||||||
hasRecovery = true;
|
|
||||||
}
|
|
||||||
if (i === lastTabId) {
|
if (i === lastTabId) {
|
||||||
initialTab = i;
|
initialTab = i;
|
||||||
} else if (initialTab < 0) {
|
} else if (initialTab < 0) {
|
||||||
@ -64,34 +58,17 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
|||||||
}
|
}
|
||||||
i++;
|
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);
|
view.down('tabpanel').setActiveTab(initialTab);
|
||||||
|
|
||||||
if (challenge.recovery) {
|
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(
|
me.lookup('availableRecovery').update(Ext.String.htmlEncode(
|
||||||
Ext.String.format(gettext('Available recovery keys: {0}'), idList),
|
gettext('Available recovery keys: ') + view.challenge.recovery.join(', '),
|
||||||
));
|
));
|
||||||
me.lookup('availableRecovery').setVisible(true);
|
me.lookup('availableRecovery').setVisible(true);
|
||||||
if (view.challenge.recovery.length <= 3) {
|
if (view.challenge.recovery.length <= 3) {
|
||||||
me.lookup('recoveryLow').setVisible(true);
|
me.lookup('recoveryLow').setVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (challenge.webauthn && initialTab === 0) {
|
if (challenge.webauthn && initialTab === 0) {
|
||||||
let _promise = me.loginWebauthn();
|
let _promise = me.loginWebauthn();
|
||||||
@ -374,7 +351,6 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
|||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
regex: /^[0-9]{2,16}$/,
|
regex: /^[0-9]{2,16}$/,
|
||||||
regexText: gettext('TOTP codes usually consist of six decimal digits'),
|
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}',
|
disabled: '{!availableChallenge.recovery}',
|
||||||
},
|
},
|
||||||
items: [
|
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',
|
xtype: 'box',
|
||||||
reference: 'availableRecovery',
|
reference: 'availableRecovery',
|
||||||
@ -433,6 +379,16 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
|||||||
regex: /^[0-9a-f]{4}(-[0-9a-f]{4}){3}$/,
|
regex: /^[0-9a-f]{4}(-[0-9a-f]{4}){3}$/,
|
||||||
regexText: gettext('Does not look like a valid recovery key'),
|
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