Compare commits

..

594 Commits

Author SHA1 Message Date
Thomas Lamprecht
68a3518d13 bump version to 4.3.7
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-02-26 19:16:22 +01:00
Markus Frank
a37094b0fa form: RealmComboBox: add option to change the API path
Signed-off-by: Markus Frank <m.frank@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-02-26 18:50:34 +01:00
Markus Frank
b4304e6691 panel: AuthView: change API path in pmx-domains model
Currently is always using the default path because it was hardcoded into
the model 'pmx-domains'. Introduce new variable storeBaseUrl because PBS
uses two different paths to access the realm/domains API.

Co-authored-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Markus Frank <m.frank@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-02-26 18:50:29 +01:00
Markus Frank
ee9d92e37e fix: window: AuthEditBase: rename variable 'realm' to 'type'
PVE/PMG API returns a variable called 'type' instead of 'realm'

Fixes: 3822a03 ("window: AuthEditBase: include more information in thrown errors")
Signed-off-by: Markus Frank <m.frank@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-02-26 18:50:29 +01:00
Thomas Lamprecht
2df5c28d3f bump version to 4.3.6
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-02-25 18:10:10 +01:00
Dominik Csapak
f6440d3ee1 object grid: fix onlineHelp setting from editorConfig for row editors
In our row editors helpers, we unconditionally set onlineHelp from
'opts.onlineHelp', even if it's undefined.

Later we use 'Ext.apply' to set first the editorConfig defaults, then
the 'rowdef.editor' settings. In javascript, the objects

{} and
{ foo: undefined }

are not the same, so Ext.apply overwrites the default from editorConfig
with that from the row definition, also for undefined.

This means if we have a default onlineHelp in editorConfig and none in
the add_*_row options, we would not show it.

To fix it, check if 'opts.onlineHelp' is truthy before setting it in
the row definition. This should not happen for other options used
from the row helper options, since those are nested
(Ext.apply does not work recursively)

This fixes a regression in pmg-gui, where we set a default onlineHelp
for e.g. the Mail Proxy Options which would not show up anymore.

Note: PMG is the only product where we used this pattern, so this
was not visible anywhere in PVE or PBS.

Fixes: 7d16f8b (object grid: allow to pass online help to row editors)
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Reviewed-By: Stoiko Ivanov <s.ivanov@proxmox.com>
Tested-By: Stoiko Ivanov <s.ivanov@proxmox.com>
2025-02-25 18:07:02 +01:00
Thomas Lamprecht
15a62419a1 bump version to 4.3.5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-02-25 17:08:17 +01:00
Hannes Duerr
23289264bd form: add field component for certificate fingerprints
Create a new component for (SHA256) certificate fingerprints as we can
reuse this in several places.

Signed-off-by: Hannes Duerr <h.duerr@proxmox.com>
 [TL: drop filenames from commit message and fix widget alias name]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-02-25 17:04:22 +01:00
Maximiliano Sandoval
41c844b511 fix #6088: notification: matcher: use more descriptive strings
Add separate text for inverted and non-inverted cases, this should
reduce some confusion about how inversion works and allow for better
translations.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
 [TL: expand commit message.]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-01-28 15:21:02 +01:00
Fabian Grünbichler
82260afcdd changelog: s/UNRELEASED/bookworm
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-01-20 15:09:06 +01:00
Fabian Grünbichler
80b01f27bc bump version to 4.3.4
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-01-20 11:40:02 +01:00
Friedrich Weber
88d62e5386 acme: plugin: add htmlEncodes
to avoid interpreting html tags in the values.

When setting a tooltip via the `data-qtip` attribute, the contents
needs to be double-encoded. Ext.js internally uses `innerHTML` to
apply the generated HTML, which undoes one layer of encoding in case
of `data-qtip`.

Signed-off-by: Friedrich Weber <f.weber@proxmox.com>
2025-01-20 11:37:06 +01:00
Friedrich Weber
f26346a597 apt: add missing htmlEncode to qtip
to avoid interpreting html tags in the values.

When setting a tooltip via the `data-qtip` attribute, the contents
needs to be double-encoded. Ext.js internally uses `innerHTML` to
apply the generated HTML, which undoes one layer of encoding in case
of `data-qtip`.

Signed-off-by: Friedrich Weber <f.weber@proxmox.com>
2025-01-20 11:37:06 +01:00
Dominik Csapak
9d5d6a7815 task viewer: htmlEncode the status
so we don't accidentally interpret html tags from the task status

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2025-01-20 11:37:01 +01:00
Dominik Csapak
7bb124c036 button: htmlEncode the name/id for the confirm message
so we don't accidentally interpret html tags

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2025-01-20 11:37:01 +01:00
Dominik Csapak
867bf7e6f5 utils: htmlEncode status text
so we don't accidentally interpret html tags in the output

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2025-01-20 11:37:01 +01:00
Maximiliano Sandoval
d710ceeeb6 webhook edit: do not split translatable strings
Splitting translatable strings deprives translators for context which
might be needed for getting the correct tenses or genders. See [1] for
example.

[1] https://wiki.gnome.org/TranslationProject(2f)DevGuidelines(2f)Never(20)split(20)sentences.html

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2025-01-15 17:19:59 +01:00
Gabriel Goller
d6d173f05d textarea field: add emptyText message to show markdown is supported
Just like in our `Notes` fields show a emptyText message that explains
that markdown can be used.

Reported-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-11-27 15:31:57 +01:00
Thomas Lamprecht
a5fb3afcf9 bump version to 4.3.3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-27 12:25:47 +01:00
Fiona Ebner
ac10db95ef display-edit field: add emptyText getter and setter to support data-bind
To access the underlying fields a reference to them is now explicitly
saved after component initialisation.

First user is intended to be the path field for datastores where the
emptyText should dynamically be for a relative or absolute path.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
 [ TL: switch over from saving the xtype to query in the getter or
   setter to directly savign a reference to the underlying fields. ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-27 12:17:10 +01:00
Thomas Lamprecht
c806b73ff7 bump version to 4.3.2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 18:32:26 +01:00
Gabriel Goller
1d07d61a18 form: add support for multiline textarea
This adds support for a editable multiline textarea in the ObjectGrid.
Now we can add a textarea row, which will open a textarea popup, and
encode the multi-line text into an base64 string (with utf8 support).

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
 [ TL: various style, naming fixes and add fieldOps to allow passing,
   e.g., an empty text]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 18:16:12 +01:00
Gabriel Goller
70382e111a window: add consent modal
Add consentModal that gets displayed before the login. Simply shows the
text in a scrollable box and contains a single button "OK".

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-11-25 16:32:25 +01:00
Thomas Lamprecht
e79a20a8cc node: service state: restore original behavior
This reverts commit 461298d80a and
commit 4ca542b811 to get back to the
original state, which is already good enough here, as systemd unit
states are simply not translatable well, and are never translated by
systemd, so doing that will make most users life actually harder.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-19 15:39:41 +01:00
Christian Ebner
461298d80a utils: never translate systemd unit states
Translating unit states might be counter productive in case of
debugging, opt for not translating them and drop usage of the
translation helper.

This was spotted by several devs on doing translations in languages
they know, so it really seems not right to add.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
 [ TL: add last sentence to add some weight to not doing that ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-19 15:29:38 +01:00
Thomas Lamprecht
2686922392 bump version to 4.3.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-19 12:40:09 +01:00
Thomas Lamprecht
3375d11ea4 auth view/edit: make default-view checkbox opt-in
To avoid suggesting that a project has a feature that is not
implemented in the backend.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-19 12:37:26 +01:00
Thomas Lamprecht
0988db8ffc network view: pass generic editOptions config to edit window
Avoid the need to loop through every product specific feature
enablement, rather allow one to pass a generic object including them.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-16 16:37:15 +01:00
Thomas Lamprecht
358b98bf4f network edit: rename bridge_set_vids to enableBridgeVlanIds
we use camelCase in the JavaScript code and besides casing the old
name wasn't really telling and suggested that it was there to set a
specific VID not to enable setting them.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-16 16:35:51 +01:00
Thomas Lamprecht
2aa5d7e7ba network edit: shorten bridge VLAN ID label and tooltip
This is certainly a bit subjective, but IMO not much information is
lost, and the "space-separated" in the tooltip was never true (at
least for the backend), so might be even confusing; the example should
be enough to direct users in the right direction, real docs in the
documentation would be way better anyway than all those subtle UI
hints.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-16 16:33:15 +01:00
Thomas Lamprecht
ffe41ad5e3 network edit: shorten and improve bridge vlan ID validator
This does a few things, but all affecting the same validator and there
would not be much value with separate commits, so just use one.

Namely:
- reduce code by a lot, mostly by having a explicit ordered if-else
  chain that avoids the need for some extra checks as further branches
  can assume that former did not evaluate to true, thus we cans safe
  the closure that checked invalidity for a range-atom.
  While this allows entering "useless" ranges like "2-2" it's not
  clear why that should be a disallowed range, it's perfectly clear
  about what it represents.
- use Number.isNaN to avoid oddities from global isNaN that MDN warns
  against [0]
- give explicit error messages for different failure cases like
  out-of-range or not-a-number
- place error messages under gettext, our translators frequently ask
  to avoid untranslatable literals.
- support the same lists as the backend does, i.e. allow multiple
  whitespace and comma and semicolon as separators.

[0]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN#description

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-16 16:30:30 +01:00
Aaron Lauterer
b1a3eb7195 fix #3892: network: add bridge vids field for bridge_vids
The new optional bridge_vids field allows to set that property via the
GUI. Since the backend needs to support it, the field needs to be
explicitly enabled.

For now, Proxmox VE (PVE) is the use case.

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
2024-11-15 18:57:28 +01:00
Shannon Sterz
9d2fc36f61 dark theme: make icons in the permissions tree in pve dark
these icons are multiply style because they seem to be handled
differently across products. so this in essence "double inverts" them
in the context where that is needed

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-11-15 16:49:17 +01:00
Thomas Lamprecht
afb219dce0 add Bulgarian as available language
Got recently contributed to proxmox-i18n in commit 8ee24fd ("add
Bulgarian translations")

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-14 22:23:50 +01:00
Dominik Csapak
e84f80271d webhook edit: display validity for added key/value fields
by calling 'isValid()' once the widget is attached to the grid

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-12 17:42:36 +01:00
Dominik Csapak
e872ed40f7 webhook edit: add emptytext to key-value fields
namely 'Key' and 'Value'

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-12 17:42:36 +01:00
Dominik Csapak
b0eafb68a9 webhook edit: use type in add button text
so one can more easily see what gets added.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-12 17:42:36 +01:00
Dominik Csapak
3052c4dfc8 webhook edit: make items config not static
modifying static elements from the class, like done here with e.g.

 me.items[0][key] = value;

is dangerous, since it directly modifies the class definition of those
arrays/objects.

Instead move the definition in initComponent, which uses a fresh
declaration each time the component is initialized.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-12 17:42:36 +01:00
Dominik Csapak
20002254e6 webhook edit: improve layout and component hierarchy
* instead of manually setting margin/paddings and the fieldLabel, just
  use a FieldContainer instead of Container. That implements the
  Ext.form.Labelable mixin, which correctly positions the label. This
  also has the effect that the labels are now styled correctly.

* modify the margins to get a consistent spacing between fields

* reverse the order of grid/button, to be consistent with our other
  grids with this input pattern

* make the label of the textarea a proper fieldLabel with a
  FieldContainer, which gets rid of the ':' in the gettext and
  styles the label correctly.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-12 17:42:36 +01:00
Christoph Heiss
0f8d38b5de fix #5379: window: AuthEdit{LDAP, OpenId}: add 'Default realm' checkbox
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-11-11 23:58:34 +01:00
Christoph Heiss
6fe81f0cef fix #5379: panel: AuthView: add column displaying whether the realm is default
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-11-11 23:58:34 +01:00
Christoph Heiss
4b3850bf45 schema: make PAM realm editable using new AuthSimple panel
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-11-11 23:58:34 +01:00
Christoph Heiss
a285fec7e4 window: add panel for editing simple, built-in realms
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-11-11 23:58:34 +01:00
Christoph Heiss
b4b36ef29a panel: AuthView: use help link from schema if set
This can be used to set the `onlineHelp` identifier in the schema as
opposed to in the panel directly. Needed e.g. to share a panel between
PAM and PBS realm.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-11-11 23:57:46 +01:00
Christoph Heiss
be8cb1efb4 panel: AuthView: make useTypeInUrl property per-realm
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-11-11 23:57:46 +01:00
Christoph Heiss
3822a031dd window: AuthEditBase: include more information in thrown errors
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-11-11 23:57:46 +01:00
Thomas Lamprecht
5156cd1164 bump version to 4.3.0
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-11 21:58:01 +01:00
Timothy Nicholson
8f2c09235c fix #5831: ui: right-align s.m.a.r.t numerical table data
Signed-off-by: Timothy Nicholson <t.nicholson@proxmox.com>
2024-11-11 21:39:49 +01:00
Shannon Sterz
96fb6cfb72 password edit: add a minimum length parameter
so products can independently specify the minimum length of new
passwords

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-11-11 21:38:34 +01:00
Timothy Nicholson
4ca542b811 fix #5836: ui: translate systemd states in ServiceView
Signed-off-by: Timothy Nicholson <t.nicholson@proxmox.com>
2024-11-11 21:34:54 +01:00
Daniel Herzig
a351d3d36d fix #5611: node service view: hide non-installed services
This patch adds a filter to identify services, which are reported as
'not-found' by the api. By default they will not be shown in the UI
anymore, but visibility can still be toggled using a new checkbox.

Signed-off-by: Daniel Herzig <d.herzig@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-11 21:28:34 +01:00
Lukas Wagner
0ad5712ac7 notification: add UI for adding/updating webhook targets
The widgets for editing the headers/secrets were adapted from
the 'Tag Edit' dialog from PVE's datacenter options.

Apart from that, the new dialog is rather standard. I've decided
to put the http method and url in a single row, mostly to
save space and also to make it analogous to how an actual http request
is structured (VERB URL, followed by headers, followed by the body).

The secrets are a mechanism to store tokens/passwords in the
protected notification config. Secrets are accessible via
templating in the URL, headers and body via {{ secrets.NAME }}.
Secrets can only be set/updated, but not retrieved/displayed.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-By: Stefan Hanreich <s.hanreich@proxmox.com>
2024-11-10 18:03:15 +01:00
Gabriel Goller
ffeb8004fb utils: add base64 conversion helper
Add helper functions to convert from a utf8 string to a base64 string
and vice-versa. Using the TextEncoder/TextDecoder we can support unicode
such as emojis as well [0].

[0]: https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
Reviewed-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-10 18:01:44 +01:00
Dominik Csapak
f18c0d3636 css: add some conditions to the tag classes for the tag view
in the tag view, we have a custom 'full' style in a place where we
can have another tagstyle class above. to compensate for that, we have
to add another condition to those styles, namely that there is not the
'proxmox-tags-full' in between.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-10 11:30:00 +01:00
Thomas Lamprecht
6af66f9626 bump version to 4.2.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-16 18:55:24 +02:00
Dominik Csapak
9e69d726e0 fix external linking to products by setting cookie SameSite attribute to lax
We introduced the 'strict' setting when browsers warned about our
cookies not having any SameSite setting [0]. While this works in
general, it had an unforeseen side effect:

When opening a link to the web UI of Proxmox projects, any existing
cookie does not get sent on the initial page load due to coming from
another origin. This then leads to the username and CSRF prevention
token not being set in the index response.
The UI code interprets this as the user being logged out (e.g. because
the ticket is not valid) and clears the cookie, displaying the login
window, even if the cookie's ticket value was still valid.

The MDN reference[1] says that setting it to 'lax' is similar to
'strict', but sends the cookie when navigating *to* our origin even
from other sites, which is what we want when linking from elsewhere.
(This would have also been the default if we wouldn't have set any
attribute).

[0]: https://lore.proxmox.com/pve-devel/20230315162630.289768-1-m.carrara@proxmox.com/
[1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_attribute

Fixes: aec7e8d ("toolkit/utils: set SameSite attr of auth cookie to 'strict'")
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-10-15 15:25:14 +02:00
Dominik Csapak
51e64f26ec css: dark theme: fix panel borders for pmg eol theme
since the header markup is different than what we use in pve, the
'spacer' elements in the pmg header get a (different colored) border by
default.

We already overwrite it for the first spacer, but when we show an EOL
notice, we have two of them, so add the second one to the rule too.
This also uses the 'css next sibling selector', just for the sibling of
the div with the 'eol-notice' class instead of the 'versioninfo' one.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-09-26 11:14:28 +02:00
Lukas Wagner
f420a35cb7 notification: matcher: move match-severity fields to panel
Also introduce a local viewModel that is linked to a parent viewModel,
allowing us to move the formulas to the panel.
This should make the code more cohesive and easier to follow.

No functional changes.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
2024-09-23 17:09:32 +02:00
Lukas Wagner
fa315d842b notification: matcher: move match-calendar fields to panel
Also introduce a local viewModel that is linked to a parent viewModel,
allowing us to move the formulas to the panel.
This should make the code more cohesive and easier to follow.

No functional changes.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
2024-09-23 17:09:32 +02:00
Lukas Wagner
e91269d513 notification: matcher: move match-field formulas to local viewModel
This should make the code more cohesive and easier to follow.

No functional changes.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
2024-09-23 17:09:32 +02:00
Lukas Wagner
043ce82954 notification: matcher: match-field: show known fields/values
These changes introduce combogrid pickers for the 'field' and 'value'
form elements for 'match-field' match rules. The 'field' picker shows
a list of all known metadata fields, while the 'value' picker shows a
list of all known values, filtered depending on the current value of
'field'.

The list of known fields/values is retrieved from new API endpoints.
Some values are marked 'internal' by the backend. This means that the
'value' field was not user-created (counter example: backup job
IDs) and can therefore be used as a base for translations.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
2024-09-23 17:09:32 +02:00
Thomas Lamprecht
1ed4b715bc bump version to 4.2.3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-25 11:45:15 +02:00
Dominik Csapak
68cea10fa7 realm edit: don't send 'delete' parameter when creating new entry
since tha api does not expect a 'delete' parameter here

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-04-25 11:42:51 +02:00
Dominik Csapak
a5fa465796 realm edit: don't send type as extra parameter when 'useTypeInUrl' is set
in that case, the type is already part of the url and we must not send
it additionally as an parameter

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-04-25 11:42:51 +02:00
Thomas Lamprecht
10f804941c bump version to 4.2.2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-24 21:44:17 +02:00
Stefan Lendl
3251f4042b form: include vlan field widget from PVE
Copied from PVE to use in PBS network configuration.

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
2024-04-24 21:43:06 +02:00
Thomas Lamprecht
2d98e03d7c bump version to 4.2.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-23 19:33:24 +02:00
Lukas Wagner
f95fcc26b6 sendmail: smtp: allow one to override the default mail author
In PBS, we obviously don't have "Proxmox VE" as a default sender, so
we need a mechanism to change the default author.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-23 19:33:10 +02:00
Maximiliano Sandoval
3a4432a6e4 fix #5251: tfa: set autocomplete on tfa input form
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-04-23 17:10:43 +02:00
Thomas Lamprecht
af27e81747 bump version to 4.2.0
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-21 12:31:57 +02:00
Stefan Hanreich
9ffe9009fb network edit: allow bridges to have any valid interface name
Allow the web UI to accept bridge interfaces with any valid interface
name, rather than being limited to the arbitrary "vmbr" prefix.

Limiting to at most 10 characters, since SDN possibly adds a .XXXX
prefix for Vlans. Since the hard limit for network interface names is
15 characters, limiting it to 10 characters here enables SDN to append
the VLAN prefix in any case.

Originally-by: Jillian Morgan <jillian.morgan@primordial.ca>
Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
 [ TL: avoid far away intermediate variable ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-21 11:54:05 +02:00
Thomas Lamprecht
2e454014e0 gitignore: add more build artefacts to ignore list
ignore the assembled widget toolkit, the CSS generated from SCSS and
some more packaging related files

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-21 09:56:55 +02:00
Thomas Lamprecht
82bb667f05 edit window: fixup syntax mis-hap
argh, it's was a bit to late for the previous clean-up and I forgot to
drop a closing parenthesis after reworking the if expression (and did
test again...).

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 20:43:21 +02:00
Thomas Lamprecht
1689e53f6f window: edit: simplify setting submit button
Since the reset-form functionality got moved into the header tools in
commit 046ec35 ("fix #5277: move reset button into window header
toolbar") we can unconditionally set the initial buttons, as those
will always just contain the submit one now.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 20:41:11 +02:00
Thomas Lamprecht
f646c22a67 notes view: make opening the editor on double-click opt-in
One can get some smart-selection behavior when double clicking text in
browsers, e.g., whole-word selection, and the notes view is generally
for having some text that is often copied, like hostnames or IP
addresses.

Opening the notes editor on double click is interfering with that
select+copy workflow, so instead of hard-coding that make it opt-in,
controlled by a setting from the browser-local storage.

Add some handling to cope with live-changes to that setting, as having
to re-open a panel to make it take effect is annoying and might make
people believe that this is buggy.

This new setting has (currently) to be handled by the per-product UI.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 17:26:48 +02:00
Thomas Lamprecht
bb5cc876ef notes view: use pencil-square-o icon for opening the editor
The gears one from the native tool with the same name is not really
telling and is normally rather used for options, not editing a
specific (notes) field.

So go for the edit pencil that indicates editing some field and use
that for both the edit button in the top bar and the tool one in the
title header bar.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 17:19:33 +02:00
Thomas Lamprecht
d958d57e0d notes view: place collapse tool on the right
so it's always at the end for both the collapsed and expanded state

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 17:19:24 +02:00
Thomas Lamprecht
046ec35b11 fix #5277: move reset button into window header toolbar
The 'Reset' button, which can be used to reset the form to its
original values when editing an existing resource or property, was
located at the right to the submit button since its initial addition
(before the git epoch started at Proxmox).

As it had the exact same form and color as the 'OK' submit button, it
is easy to press by accident, which then resets the pending changes
one wanted to submit – while not catastrophic it's just needlessly bad
UX.

As this UX-mishap is something one gets used too relatively fast,
especially as developer due to frequently opening such dialogues to
test changes, its something that mostly newer users will run into.
Luckily one took the effort to actually open an enhancement request,
providing ample resources to underline their point.

While there where quite a few proposals to improve this, most of them
had some (smaller) disadvantage (e.g., potentially jumping location,
confusion with other buttons like the help one).

Moving the reset functionality as as icon-only + tooltip button into
the window header title bar was the proposal that had no real
disadvantage and solved the underlying UX issue by cleanly separating
submit from reset. Having reset near the close-window tool has no
negative implications, as both have a similar effect, the discard the
current pending changes that the user did not yet submit, so if one
mistakenly hits close instead of reset, or vice-versa, nothing is
lost.
A nice side-benefit of that option is that the change is really small
code wise.

Closes: #5277
Reported-by:  Tristan Harward <trisweb@gmail.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 17:19:24 +02:00
Thomas Lamprecht
b1df1f0efb css: correctly mask disabled elements inside headers
The mask ExtJS uses to add a disabled look is using the general
default background color of panels as base color, i.e. white for light
mode and almost black for the dark mode.

But as the top header of windows uses a darker variant, having a mask
applied on some icons that is rendered directly in the header, without
any (button) element that provides its own background color, will make
that element show off.

This mostly happens for Tools, which we do not have many besides the
"Close" on, which is almost never disabled.

This was noticed when trying out to move the reset button inside the
window header tool bar, when that was disabled (e.g., form was not
dirty) it stuck out quite a bit in an odd way.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 17:19:24 +02:00
Thomas Lamprecht
48830b9713 window: whitespace indentation fix
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 17:19:24 +02:00
Thomas Lamprecht
43b66893a8 buildsys: also clean proxmox-dark folder in src clean target
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 17:19:24 +02:00
Thomas Lamprecht
9afdea76cf safe destroy window: rework top-level comment
avoid odd wrap, this isn't a poem, and add some more context.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-20 17:19:24 +02:00
Thomas Lamprecht
537702e12f eol notice: escalate to warning only shortly before EOL
The EOL message is positioned quite noticeable already in all our
products, being always visible. So use a notice-style for the icon
and its color until three weeks before EOL, when we switch to a
critical warning.

As it can be OK to run a, e.g., PVE 7 setup shortly before its EOL,
if, for example, one plans to replace it completely and decommission
the old one (so upgrade before EOL would be just extra work).

Using three weeks for the cut-off has no in-depth, heavily thought
out Good Reason™, but was rather chosen as it's likely to be noticed
before the actual EOL in somewhat actively maintained setups (e.g.,
admin checking in every week or two) and can give admins further
means to escalate things with higher ups. Also weeks are always 7
days, while months aren't uniform, so the former is easier to
communicate.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-16 13:04:12 +02:00
Thomas Lamprecht
0493654c85 eol notice: surpress highlighting if link was visited
add a class to the whole outer div to manage the CSS rules for the
EOL widget and use this to keep the original color for links even if
they got visited already.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-16 13:04:12 +02:00
Thomas Lamprecht
120b530a31 notify: shorten text to "reset" for built-in targets
While the difference between Reset and Remove is a bit more subtle
this also leads to less jumping around of UI elements on the right to
it (we normally avoid such size-changes that cause layout changes
completely).

Also, the confirmation message is quite telling, so this is not too
bad.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-10 12:18:58 +02:00
Lukas Wagner
83bae06cae notify: change 'Remove' button to 'Reset to default' for built-ins
A HTTP DELETE for a built-in target/matcher acts as a reset to its
defaults. This patch changes the 'Remove' button text based on the
selected target/matcher. If it is a built-in, the button text is
changed to 'Reset to default'. Also, if the built-in is not actually
modified, the button is disabled.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-04-10 12:12:30 +02:00
Lukas Wagner
26e2aa7ce4 remove button: allow to set custom confirmation message
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-04-10 12:12:30 +02:00
Maximiliano Sandoval
2834a05d2b i18n: mark strings as translatable
Note that N/A is already translatable in other places.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-04-10 12:09:04 +02:00
Friedrich Weber
502c84b1ee window: edit: avoid sharing custom config objects between subclasses
Currently, `Proxmox.window.Edit` initializes `extraRequestParams` and
`submitOptions` to two objects that, if not overwritten, are shared
between all instances of subclasses. This bears the danger of
modifying the shared object in a subclass instead of overwriting it,
which affects all edit windows of the current session and can cause
hard-to-catch GUI bugs.

One such bug is the following: Currently, the `PVE.pool.AddStorage`
component inadvertently adds `poolid` to an `extraRequestParams`
object that is shared between all instances of `Proxmox.window.Edit`.
As a result, after adding a storage to a pool, opening any edit window
will send a GET request with a superfluous `poolid` parameter and
cause an error in the GUI:

> Parameter verification failed. (400)
> poolid: property is not defined in schema and the schema does not
> allow additional properties

This breaks all edit windows of the current session. A workaround is
to reload the current browser session.

To avoid this class of bugs in the future, implement a constructor
that makes copies of `extraRequestParams` and `submitOptions`. This
ensures that any subclass instance modifies only its own copies, and
modifications do not leak to other subclass instances.

Suggested-by: Stefan Sterz <s.sterz@proxmox.com>
Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Friedrich Weber <f.weber@proxmox.com>
Tested-by: Stefan Sterz <s.sterz@proxmox>
2024-04-10 10:35:18 +02:00
Stefan Sterz
2b6e8c67a6 dark-mode: set intentionally black icons to $icon-color
some icons intentionally use black as their color in the light theme.
this includes the little pencil and check mark icon in the acme
overview. change their color to the regular dark-mode icon-color. for
this to work the filter inversion needed for some other icons needs to
be removed too.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-04-10 10:28:02 +02:00
Thomas Lamprecht
4a5dd69899 update IFrame class to ExtJS 7.0 implantation
To get rid of some outdated event handlers like `beforeunload` which
Chromium based browsers are deprecating this year [0].

For those wondering about why we do not use ExtJS implementation
directly here it might be worth adding that the `Ext.ux` name space is
build to a separate file that has a (minified!) size of almost 160
KiB, and we only use a handful of those, so copying is a lot cheaper.

[0]: https://developer.chrome.com/docs/web-platform/deprecating-unload

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-01 16:27:18 +02:00
Christoph Heiss
da210b58a7 window: ldap: add tooltips for firstname, lastname and email attributes
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-03-25 17:15:24 +01:00
Christoph Heiss
4aff870f6c window: add Active Directory auth panel
As AD realms are mostly just LDAP, reuse the LDAP panel and just
show/hide some elements based on the type.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-03-25 17:15:24 +01:00
Thomas Lamprecht
f60fe4ad99 bump version to 4.1.5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-03-21 17:40:57 +01:00
Thomas Lamprecht
a5d6c92f60 window: password edit: clarify labels
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-03-21 17:33:24 +01:00
Wolfgang Bumiller
be5b9a4393 window: password edit: add opt-in confirmation-password field
For when the product UI using this component wants to show an extra
confirmation field where the user that executes the password change,
have to confirm their own password.

Reported-by: Wouter Arts <security@wth-security.nl>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
 [ TL: use already included CBind mixin instead of constructor ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-03-21 17:30:27 +01:00
Dominik Csapak
d35cb8aa7c utils: api request: defer masking after layout
Since recently (not sure when exactly), the 'load()' method of the
edit window did not correctly mask the window anymore

The reason seems to be that the API2Request tries to mask the
component before it's rendered, and that did never work correctly.

Instead of simply calling `setLoading`, test if the component is
rendered, and if not, mask it after it has finished it's layout.

Since we cannot guarantee that there is only one API2Request with the
waitMsgTarget set to it, nor that the 'afterlayout' and api call
responses come in a specific order, we count the loads, and only
ever unmask the component when the counter reaches zero again.

Since we're strictly in non-async code here and JavaScript is
single-threaded, this should not result in a data race.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-03-19 18:05:13 +01:00
Maximiliano Sandoval
461bb2e56c certificates: removal prompt: don't display name if there is no name
The default certificate does not have a name, which caused this to
display an undefined text in the prompt.

Reported-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
 [ TL: drop useless instance of calling format, keep arrow-fn ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-03-07 15:30:50 +01:00
Stefan Lendl
8d95122d85 gitignore: sort gitignore
Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
2024-03-07 15:23:31 +01:00
Stefan Lendl
a5b07f65ca gitignore: build outputs
Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
2024-03-07 15:23:31 +01:00
Dominik Csapak
03e44f5b60 edit window: add optional custom submit options
sometimes it's necessary or handy to add custom options to the submit
api call (e.g. timeout). So just expose a `submitOptions` where users
of the edit window can put their custom options.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-03-07 15:08:34 +01:00
Thomas Lamprecht
8d161ac19c dns: update comment to avoid coupling to downstream dependency
Not much of use, better comment why this exist, other products could
change or new ones get added with new semantic used there too, so the
previous comment would be guaranteed to become outdated rather sooner
than later.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-03-01 12:41:41 +01:00
Dominik Csapak
e3cf77e177 dns: optionally send delete for optional values
pbs only deletes the optional values here when they are sent with the
'delete' parameter, in contrast to pve/pmg that don't have a delete
parameter currently and always use the parameters as source of truth.

So to handle that, optionally set deleteEmpty if set from outside

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-03-01 12:34:55 +01:00
Thomas Lamprecht
9c69fc1b8f bump version to 4.1.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-02-28 11:46:43 +01:00
Lukas Wagner
d2c8fbfe0c utils: add extendable, translatable notification event descriptions
Add a similar mechanism like we have for adding and overriding task
description per product UI.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-02-28 11:40:48 +01:00
Thomas Lamprecht
36d167d629 form: combo grid: clarify that showClearTrigger cannot actively hide them
As getting a good setting name is a bit hard here, the current one
might me interpreted such that setting it to false will always hide
the trigger, but that's not the case, this is mostly a "force show
trigger even if allowBlank is set to false", and that's a bit of a
long name ;-)
So just add a comment and reevaluate if this really causes confusion.

While at it simplify the boolean expression to make it shorter and
easier to read.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-02-28 11:40:48 +01:00
Lukas Wagner
4bee6fb074 combogrid: add 'showClearTrigger' config
This allows one configure the clear trigger to be shown, even if
'allowBlank' is set false. This can be useful if one has a
non-editable combogrid where the value is set to something not
present in the store. Example: Match rule editing, one selects
a backup job to be match. If the backup job is removed and the match
rule edit window is opened again, then the old, deleted value cannot
be removed from the combogrid if there is no clear trigger.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-02-28 11:40:48 +01:00
Dominik Csapak
43b978658e datetimefield: fix changing date to end of month from short months
When selecting a new date, we get a date object from the currently
selected date before the change. If that month has less days than what
was selected for the new month, `setDate` will wrap that to the
following month since the old month is still selected there.

For example:

select any date in april (has 30 days)
then select the 31th of january

this will actually select the 1st of january since we first get

setDate: 20xx-04-XX -> 20xx-04-31 (wrap) -> 20xx-05-01
setMonth: 20xx-05-01 -> 20xx-01-01

To fix this, use the additional parameters of setFullYear[0] to set
all of them simultaneously

0: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setFullYear

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-02-27 14:33:41 +01:00
Thomas Lamprecht
bbb7ecbe36 d/changelog: drop duplicate 'for'
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-01-18 12:59:56 +01:00
Thomas Lamprecht
fc792d6411 i18n: use correct ISO 639-1 code for Korean with backward compat
recently the proxmox-i18n repo got a fix where we moved the files for
Korean to the correct language code, i.e., from previously wrong used
kr (Kanuri) to the correct ko (Korean).

This loads the correct ExtJS locale and is less confusing for our
Korean speakers, but we still want a clean transition for those that
have still the 'kr' value set in their language cookie.
Note that this transition only happens when the user opens the
language selector, as otherwise we do not have the product-specific
cookie name available, so a better transition would need to happen in
the per-product UIs.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-01-15 18:40:59 +01:00
Fiona Ebner
905528a28d fix #5074: notify: sendmail: smtp: fix mailto/mailto-user parameter deletion
by doing a cbind of isCreate to the top-level widget so that cbind in
the nested widgets for deleteEmpty works.

In the GUI, when a sendmail/smtp target is edited and either
'Additional Recipients' or 'Recipients' is completely removed (only
possible if the other field contains a value), parameter deletion did
not work properly. After applying the changes, the old value would
still be in place.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-05 10:33:26 +01:00
Thomas Lamprecht
115165bec2 bump version to 4.1.3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-23 11:16:28 +01:00
Lukas Wagner
1fc3d8cd1c notification ui: display yellow warning triangle instead of red icon
Instead of coloring the entire icon red, show a yellow warning
triangle containing an exclamation mark in case of validation errors.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-23 10:10:10 +01:00
Lukas Wagner
64821a108c notification ui: change icon for for match-field tree nodes
The old icon was slightly ambiguous since we also use it for LXC
containers.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-23 10:10:10 +01:00
Thomas Lamprecht
a5630fd2dd schema: do not translate SMTP
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-23 09:09:59 +01:00
Thomas Lamprecht
55f8555f18 bump version to 4.1.2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-21 21:36:07 +01:00
Lukas Wagner
6ed92b735f notification ui: add 'unknown' to match-severity dropdown
This is the severity used for forwarded mails, since we cannot
reasonably infer a priority here.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-21 17:38:52 +01:00
Lukas Wagner
c972de233a notification ui: add appropriate onlineHelp anchors
This links the dialog windows to the correct help section (different
target types, matchers).

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-21 17:38:40 +01:00
Fiona Ebner
edd98f946b notification matcher: fix inverted match modes
The 'not' prefix is already stripped in the set() method of the view
model's 'rootMode' and not present anymore when updating the store.
The information about whether the mode is inverted or not is present
in the 'invert' data member.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-11-20 16:17:42 +01:00
Thomas Lamprecht
49f7549b75 bump version to 4.1.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-17 16:56:10 +01:00
Dominik Csapak
1f8bfa3b30 notification matcher: improve wording for mode
by removing the 'invert' checkbox and instead show the 4 modes possible,
we still assemble/parse the invert for the backend

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-11-17 15:11:25 +01:00
Dominik Csapak
be5329512c notification matcher: improve handling empty and invalid values
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-11-17 15:11:25 +01:00
Lukas Wagner
6a0b0b376c notification ui: add column for 'origin'
This column shows whether a matcher/target was provided as a built-in
default config or if it was created by the user. For built-ins, it
also shows whether the built-in settings have been changed.

To reset a built-in entry to its defaults, one can simply delete it.
For best UX, the 'delete' button should change its text to 'reset
defaults' when a built-in target/matcher is selected. This will be
added in another patch.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
5f7b28cb19 notification ui: add enable checkbox for targets/matchers
Add a 'enable' checkbox for targets and matchers in their edit
windows. Also show a new 'enable' column in the overview panel.
The parameter in the config is actually called 'disable', so
the UI needs to invert the setting in the appropriate
on{Get,Set}Values hooks.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
3003f37779 panel: notification: add gui for SMTP endpoints
This new endpoint configuration panel is embedded in the existing
EndpointEditBase dialog window. This commit also factors out some of
the non-trivial common form elements that are shared between the new
panel and the already existing SendmailEditPanel into a separate panel
EmailRecipientPanel.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
de0cec409a noficiation: matcher edit: make 'field' an editable combobox
For now with fixed options that are shared between most notification
events - later, once we have a notification registry, this should be
filled dynamically.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
5bd3ad4e90 notification ui: unprotected mailto-root target
A default notification config will now be created in pve-manager's
postinst hook - which is not magic in any way and can be modified
and deleted as desired.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
03a54ebd29 notification: matcher: add UI for matcher editing
This modifies the old filter edit window in the following ways:
  - Split content into multiple panels
    - Name and comment in the first tab
    - Match rules in a tree-structure in the second tab
    - Targets to notify in the third tab

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
e8f1954c55 notification ui: rename filter to matcher
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
feacab72ca notification ui: remove notification groups
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
159fec230d notification ui: remove filter setting for targets
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Lukas Wagner
78d21b71d2 notification ui: add target selector for matcher
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 15:04:39 +01:00
Thomas Lamprecht
15f0d99534 form: displaye-edit: add one of the two missing returns
the other one _should_ not be problematic, as field-container itself
isn't picked up as a "real" field itself, but we might bind to that
somewhere, where enabling could break this.

The editable one seems to not be used yet, according to Dominik, so
fix that now already.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-17 15:00:32 +01:00
Dominik Csapak
e34b0f4a12 api-viewer: implement basic oneOf support
for parameters only for now, also only implement the basic use case we
want to have currently: use in section config apis where we have more
than one type.

we could improve upon that, e.g. by properly grouping the type relevant
options, and also implementing that for return types.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-11-17 09:58:17 +01:00
Thomas Lamprecht
60e1c56233 bump version to 4.1.0
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-14 09:19:18 +01:00
Dominik Csapak
4ecad70c66 combogrid: initialze value with [] by default
we have to initialize the value of a combogrid to something (else extjs
does not initialize everything in the object *sometimes* for yet unknown
reasons), but the empty string is wrong.

we already have at least two places where we set the default value to []
(namely NodeSelector and ha GroupSelector) with the comment:

 // set default value to empty array, else it inits it with
 // null and after the store load it is an empty array,
 // triggering dirtychange

so it makes sense to always set it to that by default. This only ever is
relevant when the combogrid has `allowBlank: true`, since if it does not
it's either invalid (and thus "dirty") or it has a selected value anyway

this should make the manual setting of

 value: [],

unnecessary in the child classes. We can even remove it direcly in the
NetworkSelector.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-11-14 09:03:59 +01:00
Fabian Grünbichler
7605e43eaa apt: drop ChangeLogUrl
it's not returned anymore by the corresponding backends, since fetching
changelogs is now fully delegated to `apt`.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-11-13 18:16:30 +01:00
Dominik Csapak
64ffc0378e window/FileBrowser: enable tar button by default
all endpoints now can handle the 'tar' parameter, so add it for all

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-11-13 16:45:55 +01:00
Aaron Lauterer
d981e33b1f DiskList: render osdid-list if present
Render all OSD IDs in 'osdid-list' if the parameter is present.

It is possible to have multiple OSD daemons on a disk. We want to list
them all in the UI.
Fall back to the 'osdid' parameter if 'osdid-list' is not available.

We check rec.data['osdid-list'] against its general truthiness as it
might not be present at all or null.

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
2023-11-13 15:33:55 +01:00
Christian Ebner
40e341792e fix #4442: Extend LogView for firewall datetime filtering
Extends the current panels date filtering capability to date-time
based filtering, and adds a config option to switch between livemode
and filter mode, analogous to the JournalView panel.

The `submitFormat` config is introduced to adapt the formatting of
params values for their corresponding api calls.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2023-11-13 15:23:12 +01:00
Christian Ebner
0e3cb037d6 fix #4442: adapt DateTimeField to be more declarative
Reworks the current implementation of the DateTimeField to be more
declarative by using a ViewModel and data bindings as well as formulas,
in order to reduce code and unwanted complexity.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2023-11-13 15:23:12 +01:00
Lukas Wagner
8fc1d232ea panel: sendmail edit: don't translate 'Proxmox VE' in author field
The default value is determined by the backend and is never
translated (which does not make sense any way for a product name).
This was likely just a copy/paste mistake from other from fields.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-06 18:20:10 +01:00
Lukas Wagner
8a8b0428fd schema: endpoint types: don't translate endpoint type names
... that are not really translatable.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-06 18:19:29 +01:00
Thomas Lamprecht
3749f20c5e utils: fix trailing comma
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-31 21:47:12 +01:00
Thomas Lamprecht
5d7d30de0f text field: add trimValue config
Inspired by a recent bug detected in the subscription key field, where
a trailing white space caused verification issues.

We might even enable the trimming by default, after checking call
sites that is – most often one wants to trim the text to be submitted

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-24 13:59:19 +02:00
Thomas Lamprecht
5a3ac9b110 utils: code style cleanup/reduction
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-24 13:57:43 +02:00
Thomas Lamprecht
1326f771b9 bump version to 4.0.9
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-03 10:40:11 +02:00
Thomas Lamprecht
89699c6466 parser: split checking IMG and A tags, make the latter more strict
Split the logic so that each tag is handled explicitly on it's own
if-else branch, which is now safer to do as we default to
allow-only-http-like.

Also address a recently introduced regression from the implementation
of the #4756 where any user that could edit notes could use
javascript: script-urls for XSS purpose to prepare a link that could
leak private user information when another user clicked on it, at
least if they omitted basic sanity checks by looking at the URL
displayed by the browser before.

We have to override a false-positive triggered by a eslint heuristic,
a simple string compression should be always safe.

Fixes: 5cbbb9c ("fix #4756: markdown notes: allow any valid URL for a tags")
Reported-by: Hieu Dang Cong <HieuDC5@fpt.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-03 10:31:18 +02:00
Thomas Lamprecht
9ef8030535 parser: make it clearer that we mark HTTP-like URLs always as safe
we should make this controllable by the user for images, while modern
browser are quite safe w.r.t. not transmitting to much info on cross
origin requests, it still might be nicer if they have some control
over this.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-03 09:15:11 +02:00
Thomas Lamprecht
e21d3a40ad parser: use safer mechanism for allowing URLs
Having a default-remove boolean flag is making it easier to get this
right and decouple the if-branches that check if something is OK
(which may get more complex in the future) from the actual handling of
the result by always removing the href attribute if not explicitly
told otherwise.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-03 08:37:36 +02:00
Thomas Lamprecht
ade0e572d2 parser: factor out getting lower-case canonical tag name
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-03 08:27:22 +02:00
Lukas Wagner
b4b3bcad18 notification config view: add missing parameter for Ext.String.format
Fixes: 7e4b51 ("notification config view: fix using gettext with parameter")
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-09-18 12:06:33 +02:00
Thomas Lamprecht
247304085f sendmail edit panel: fix using gettext with parameter
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-14 08:14:25 +02:00
Thomas Lamprecht
7e4b51778a notification config view: fix using gettext with parameter
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-13 18:17:07 +02:00
Thomas Lamprecht
72a355b8d7 notification config view: fix using gettext with parameter
One must use a parameter {0} replacement string as otherwise this
cannot be translated at all.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-13 18:15:12 +02:00
Thomas Lamprecht
4187bffeeb bump version to 4.0.8
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-13 17:16:06 +02:00
Thomas Lamprecht
6e70fce94f utils: language map: add entry for Croatian translation
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-13 17:05:03 +02:00
Lukas Wagner
0e1fa66f75 auth: ldap: openid: use proxmoxtextfield for comment
The regular `textfield` does not support the `deleteEmpty`
setting. Thus, if no comment was entered the configuration
would still end up with an empty `comment` key:

ldap: foo
    server ....
    bind-dn ...
    comment

Fixed by switching over to `proxmoxtextfield`, which properly
deletes empty keys.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-09-07 17:52:38 +02:00
Dominik Csapak
9133d5b5f9 fix #4951: accept undefined as value for the MultiDiskSelector
otherwise it tries to string split it and throws an exception

This can happen when there was no initial value and the form is reset.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-09-07 17:49:38 +02:00
Filip Schauer
a5736f02e5 fix #4531: acme plugins: correct change detection of dirty form fields
Fix the ACME plugin edit form only detecting dirtychanges once the
value of a textfield was dirtied and then changed back to the
original.

This behaviour is caused as we cannot reuse the field's
resetOriginalValue method, due to that cause breakage here, e.g., if
the value was edited, then another plugin (without a schema) gets
selected, and then one would switch back again to the previous plugin,
it would cause the (actually still dirty) value to get registered as
new original one by mistake.

So the fix here is to keep the manual originalValue tracking, but add
the missing call to checkDirty after setting the originalValue to
refresh the dirty flag.

Fixes: 45708891 ("ui: add ACMEPluginEdit window") from pve-manager
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
 [TL: record reason for originalValue handling & sligthly reword ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-08-30 16:02:41 +02:00
Christian Ebner
575b4f3790 utils: Remove outdated url in comment
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2023-08-23 10:14:15 +02:00
Wolfgang Bumiller
49bb8516ed bump version to 4.0.7
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-16 10:43:13 +02:00
Lukas Wagner
5d1b587fdd notification: add ui for managing notification filters
This commit adds a new dialog window, containing all fields necessary
to configure notification filters.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
2023-08-16 10:37:44 +02:00
Lukas Wagner
90dbb2d359 notification: allow to select filter for notification targets
This commit adds a new selector field for existing endpoint
configuration where one is able to select a notification filter.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
2023-08-16 10:37:40 +02:00
Lukas Wagner
ea5aa12261 notification: add gui for notification groups
The GUI is based on the 'plugin-based' dialog window EndpointEditBase
that was introduced in an earlier commit.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
2023-08-16 10:37:35 +02:00
Lukas Wagner
6669a59fd1 notification: add gui for gotify notification endpoints
The GUI is based on the 'plugin-based' dialog window EndpointEditBase
that was introduced in an earlier commit.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
2023-08-16 10:37:31 +02:00
Lukas Wagner
84f70dfaad notification: add gui for sendmail notification endpoints
This commit adds a new panel 'NotificationConfigView' that is supposed
to be embedded in the datacenter configuration side-bar.
This new view lists all notification endpoints, allowing to
add/modify/delete/test them.

Furthermore, this commits adds the dialog for adding/modifying
sendmail endpoints. The dialog is 'plugin-in' based, meaning that it
consists of a base window (EndpointEditBase) and a panel that holds
the actual fields for the endpoint type configuration. This will show
be beneficial once the GUI for other endpoint types is added.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
2023-08-16 10:37:24 +02:00
Filip Schauer
a60c8dc0c0 certificates: delete: Fix a typo
Fix the UI not refreshing when a successful certificate deletion
requires it.

Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
2023-08-14 12:39:33 +02:00
Thomas Lamprecht
6e5405767c utils: add entry for Georgian translation
which got recently submitted as new translation:
https://git.proxmox.com/?p=extjs.git;a=commitdiff;h=6ee3ee85552152132700448eab148a3bf9a30fcc
https://git.proxmox.com/?p=proxmox-i18n.git;a=commitdiff;h=323c79d6b4fb8c7eb4b5eab7fb0f66d9cd970632

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-08-03 16:31:37 +02:00
Amin Vakil
2d613cac1f fix #4874: improve error message for invalid hostname
Current error message is not correct because having underscores in
domain names are perfectly valid, although it's not acceptable at host
names, so it should be changed to "This is not a valid hostname".

https://www.ietf.org/rfc/rfc1123.txt section 2.1 "Host Names and Numbers"
https://www.rfc-editor.org/rfc/rfc2181#section-11

Signed-off-by: Amin Vakil <info@aminvakil.com>
 [TL: s/Host /host/ once more, reflow msg with 70cc & reword subject]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-08-03 10:49:39 +02:00
Friedrich Weber
e61ffdd6ee ui: add some missing htmlEncodes
Signed-off-by: Friedrich Weber <f.weber@proxmox.com>
2023-07-25 16:58:12 +02:00
Thomas Lamprecht
81562cea8d bump version to 4.0.6
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-26 20:25:01 +02:00
Stefan Sterz
085eed30c2 window: ldap auth edit forbid specifying a bind_dn without a password
this commit enforces passwords when using an non-anonymous bind.
hence, it removes the possibility of configuring unauthenticated binds
and brings the gui in-line with the backend.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-06-26 14:54:53 +02:00
Dominik Csapak
efcb34fa21 try using 'pve-eslint' if it exists
but fallback to 'eslint' otherwise

Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
 [T: move into www/manager Makefile directly]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-24 17:47:18 +02:00
Thomas Lamprecht
589317b094 bump version to 4.0.5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-16 15:58:33 +02:00
Maximiliano Sandoval
c655b2f577 window: addtotp: Increase the size of the quiet zone
It is recommended that the quiet zone has the width of 4 blocks, since
each block is around 4 pixels each, we need a margin of 16 pixels and a
size of 256 + 2 * 16 pixels.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2023-06-16 15:52:37 +02:00
Fiona Ebner
7d5201a32f apt repositories: add production ready warnings for Ceph repositories
Could've been done for the test repository already, but now that there
is a split between no-subscription and enterprise it becomes even more
relevant.

Reported-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-16 15:50:03 +02:00
Thomas Lamprecht
01034bdb5f utils: add Українська - Ukrainian to language map
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-14 14:18:55 +02:00
Thomas Lamprecht
f224688d0c bump version to 4.0.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-09 17:29:54 +02:00
Fiona Ebner
a14bafeca6 apt repositories: avoid potential type error in classifyOrigin helper
with undefined (or otherwise falsy) argument.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-09 17:29:22 +02:00
Fiona Ebner
78be60a079 apt repositories: fix typo for getting the default unknown text
Could lead to a type error with classifyOrigin when there is a
repository that doesn't have an InRelease file and cannot be detected
as Debian/Proxmox origin from its URL. For me, it triggered with the
element.io repository after changing to bookworm (which currently
doesn't exist yet) and running apt update.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-09 17:29:22 +02:00
Thomas Lamprecht
7a0bbba33a bump version to 4.0.3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-09 08:07:45 +02:00
Wolfgang Bumiller
33cfd1f6b4 tfa: show 'Locked' in 'Enabled' column if tfa is locked
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-07 18:05:24 +02:00
Wolfgang Bumiller
50af081a20 tfa: improve UX for recovery keys and when none are left
If we get an empty challenge, tell the user to contact an
administrator as it means no 2nd factors and no recovery
keys are available.

Currently if only 1 key was available and it had a high ID,
we'd show something like: "Recovery keys available: 9,
Warning, less than 4 keys available."
Let's start off with the warning, and then be explicit about
the IDs.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-07 18:05:20 +02:00
Fiona Ebner
e6ed4498cd apt repositories: detect mixed suites before major upgrade
Usually, differing suites already produce warnings/errors, but before
a major upgrade the current and the next suite are both valid. Mixing
them is an issue though.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-07 17:19:38 +02:00
Fiona Ebner
b9b1a51a2e apt repositories: just ignore unknown info rather than throwing an error
This will avoid breaking older UI when extending the backend.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-07 17:19:32 +02:00
Fiona Ebner
3d6b76ee2b apt repositories: add classifyOrigin helper
to be used again to detect mixed repositories before upgrade.

Needed to convert into an actual function for the 'this' usage.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-07 17:19:14 +02:00
Thomas Lamprecht
f6f29f8c1c date time field: fixup syntax error
Fixes: 6883083 ("form: date time field: add alias for backward compat")
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-07 17:19:14 +02:00
Fiona Ebner
fd468868dd apt repositories: actually ignore ignore-pre-upgrade-warning
when upgrading is possible rather than throwing an error by reaching
the else branch.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-07 17:19:14 +02:00
Max Carrara
ecdde39c1c toolkit/utils: fix whitespace
Signed-off-by: Max Carrara <m.carrara@proxmox.com>
2023-06-06 17:11:42 +02:00
Max Carrara
aec7e8d23c toolkit/utils: set SameSite attr of auth cookie to 'strict'
Overrides 'Ext.util.Cookies', optionally allowing the SameSite
attribute of cookies to be defined. Using this override, the SameSite
attribute of the auth cookie is now set to 'strict', prohibiting the
cookie from being sent along in cross-site sub-requests or when the
user navigates to a different site.

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
2023-06-06 17:11:42 +02:00
Christian Ebner
95fa855701 DateTimeField: Extend and refactor to make field value bindable
Extends the date time field so that bindings are updated on value changes.
Also adds a config to disable child components and avoid modification of
current values by cloning the referenced object for min/max value calculation.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2023-06-06 16:45:47 +02:00
Thomas Lamprecht
6883083e8a form: date time field: add alias for backward compat
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-06 16:43:56 +02:00
Christian Ebner
9531241400 DateTimeField: fix typo in xtype
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2023-06-06 16:41:43 +02:00
Thomas Lamprecht
db5d0cc1f4 bump version to 4.0.2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 13:45:34 +02:00
Thomas Lamprecht
2d04f0165d markdown parser: allow setting target tag for links
If one really want's to force a link to open in a new tab (or window,
depending on the browser settings).

Note that we don't set target to _blank by default for links, as
opening in a new tab can already simply be done via a middle-click on
the link without that, but once the target is set opening in the same
tab cannot easily be done, i.e., without a target set the reader has
more freedom and flexibility.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 13:41:23 +02:00
Thomas Lamprecht
5cbbb9c44a fix #4756: markdown notes: allow any valid URL for a tags
As anchor elements cannot load things into the current browsing
context and are not necessarily more dangerous to users compared to
HTTP(S) links, which we allowed since adding markdown rendering in
the first place.

Allows adding short-cuts for virtual guest resources, like RDP or SSH
links.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 13:41:23 +02:00
Thomas Lamprecht
b0c7069a10 markdown parser: normalize tag names and protocol for check
As the tag names are often uppercase, and so this was overly strict
and didn't always match correctly

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 13:41:23 +02:00
Thomas Lamprecht
28f879c09e markdown parser: correctly remove src & href attribute if not valid URL
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 13:41:23 +02:00
Thomas Lamprecht
e09af56554 ship a minified version of the widget-toolkit JS library
so use sites can switch between literal and minified version like we
do already for ExtJS via the debug flag.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 13:41:23 +02:00
Thomas Lamprecht
0676eb3738 buildsys: avoid whitespace in build info heading
this is added to the URL as is by most index templates, and literal
(non-URL-encoded) white space might break things.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 13:41:23 +02:00
Thomas Lamprecht
1683d09017 bump version to 4.0.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-01 16:35:36 +02:00
Noel Ullreich
01e64778ad fix #4551: ui: translate byte unit in format_size
Some languages translate byte units like 'GiB' or write them in their
own script.

By `gettext`ing the units in the `format_size` function, we can
translate the units for (almost) all of the web interface.

Signed-off-by: Noel Ullreich <n.ullreich@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-01 16:10:34 +02:00
Thomas Lamprecht
9dba61e674 parser: adapt to new marked Markdown renderer calling convention
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-01 16:03:57 +02:00
Aaron Lauterer
b515e164b7 tfa: paperkey: cleanup iframes for printing after window close
similar as recently done for the PBS storage add/edit window in
pve-manager

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
[ TL: refer to same change as done in pve-manager ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-28 19:40:02 +02:00
Thomas Lamprecht
9521d1768e buildsys: expand clean target
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-25 10:27:10 +02:00
Thomas Lamprecht
b1f95caa4e bump version to 4.0.0
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-25 10:15:45 +02:00
Thomas Lamprecht
87ea4468db d/control: raise compat level to 13
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-25 10:15:32 +02:00
Thomas Lamprecht
0f19a29995 buildsys: improve DSC target & add sbuild convenience target
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-25 10:15:11 +02:00
Thomas Lamprecht
735be5366b buildsys: use full DEB_VERSION
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-25 10:14:58 +02:00
Thomas Lamprecht
d7fa9ea42b buildsys: improve resolving package meta info
use the full version for the cache invalidation technique and fall
back to the commit ID and current time.

With that we can drop the exports in the top-level makefile, which
would only work for local direct builds anyway, but not for building
from DSC source package.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-25 10:13:39 +02:00
Thomas Lamprecht
89f321671f makefile: convert to use simple parenthesis
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-25 09:26:05 +02:00
Thomas Lamprecht
d4c1b3f515 d/control: rework descriptions
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-25 09:19:18 +02:00
Thomas Lamprecht
6d20cff0b9 bump version to 3.7.0
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-17 14:03:04 +02:00
Thomas Lamprecht
5b0f89fccf d/control: define compat level via build-depends
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-17 11:47:19 +02:00
Thomas Lamprecht
3e13855d21 buildsys: extend clean target
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-17 11:47:00 +02:00
Thomas Lamprecht
382756c3ec buildsys: derive upload dist automatically
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-17 11:45:24 +02:00
Thomas Lamprecht
c7dca629bb makefile: convert to use simple parenthesis
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-05-17 11:43:45 +02:00
Stefan Sterz
da639fb2ae fix #4618: dark-mode: lighten critical/warning charts/gauges colors
by increasing the lightness of these colors to to make them have the
same amount of lightness as the primary color.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-05-17 11:41:49 +02:00
Stefan Sterz
4c435ee1c3 dark-mode: adjust panel header tool icons
by brigthenening the icons on a more individual basis some darker ones
can now feature higher contrasts while others won't be too bright.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-05-17 11:41:49 +02:00
Dominik Csapak
736c3457a2 form: combo grid: use correct method to initialize the picker
'createPicker' does create the picker, but saves no reference to it
in the owning field, Instead, 'getPicker' should be used for that,
since that does all the necessary initialization, and actually
assigns the created picker to the field's me.picker variable so that
the pickers lifetime can be successfully tracked and enforced.

Without this patch, we leak the picker being created every time a
combo grid is created.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-04-12 13:30:41 +02:00
Stefan Sterz
c1a3b7f725 dark-mode: improve apt repo group header contrast ratios
make the group headers darker, so that they have some contrast with
the surrounding rows. also add back the border at the bottom. both
changes improve the legibility of the table.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-04-06 13:09:56 +02:00
Stefan Sterz
3ffcab4167 dark-mode: style the icon for the datastore maintenance mode
.. in Proxmox Backup Server

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-04-06 13:09:30 +02:00
Stefan Sterz
064318188f dark-mode: fix the focused state for background image grid icons
some icons in grids are background images for the whole grid element.
so we need to filter the entire element, which also means that any
background or inner border color would get filtered too. this inverts
the focused border on inner elements and the focused background so
that it looks correct when inverted again.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-04-06 13:05:48 +02:00
Stefan Sterz
2afa090a1a dark-mode: fix focus and focus-over states for tabs
previously the focus and focus-over states weren't styled so the crisp
styling was used, which made them appear too brightly.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-04-06 13:05:48 +02:00
Thomas Lamprecht
38db75a471 bump version to 3.6.5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-28 17:56:16 +02:00
Thomas Lamprecht
4d3a18dfc3 window: ldap auth edit: set view-model form data explicitly on edit
Fix a race that shows up in chrome/chromium by setting the data in
the view-model that is binded to form fields explicitly on edit, this
avoids a race where the default declared in the viewModel data got
applied after the form data was set, thus having the state out of
sync and so marking the field potentially as dirty even if it wasn't.

Reported-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-28 17:49:17 +02:00
Thomas Lamprecht
58bff886fb window: ldap auth edit: avoid relying on the default bind property
When using a string as bind config ExtJS maps this to the property
defined by the components defaultBindProperty, which is a bit to
subtle for my taste.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-28 17:48:12 +02:00
Thomas Lamprecht
e1608311da bump version to 3.6.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-26 17:53:08 +02:00
Thomas Lamprecht
10df1b45b4 fix #4617: dark-mode: increase brightness of tree arrows
Increase brightness of tree expand/collapse arrows to avoid overly
low contrast

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-26 17:51:51 +02:00
Friedrich Weber
4fedb4e28d fix #4612: mobile: avoid crash due to missing getProxy method
A previous commit (51083ee) added an override of the internal ExtJS
method `Ext.data.Store.onProxyLoad` in order to fix a race condition.
This override calls `Operation.getProxy`. However, this method is not
available on Sencha Touch. As a consequence, the mobile UI was
broken.

This commit modifies the override such that it has no effect in the
mobile UI.

Fixes: 51083ee54a
Suggested-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Friedrich Weber <f.weber@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
2023-03-26 17:36:55 +02:00
Thomas Lamprecht
311fb72a27 language selector: increase only picker list view
As that's what matters for the selection, having parts of the already
selected language cut off is not really a big deal as either one
knows the language and the initial native part is enough or the
picker needs to be opened for changing selection anyway.

But, we can give the field itself a bit more space by reducing the
label width to 75, which is enough for all languages as grepping our
PO files for "Language" and their translation, it seems that the
widest one comes from Euskara (Basque) with its "Hizkuntza" having 9
characters.

This way we avoid having to many different

Fixes: 454ff6e ("language selector: increase field width")
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-24 12:24:20 +01:00
Thomas Lamprecht
c43929212d utils: use template strings for language map
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-24 12:24:09 +01:00
Noel Ullreich
454ff6ea91 language selector: increase field width
With the new translations and layout, the width of the dropdown menu
needs to be increased so that the languages are all on the same line.
This width might need to increased in the future if translations were
to create a line break.

Signed-off-by: Noel Ullreich <n.ullreich@proxmox.com>
 [ T: reflow message and reword subject a bit ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-24 12:24:02 +01:00
Noel Ullreich
58518b154e language selector: translate entries to both native and localized
Languages used to be hardcoded to English, change that to both the
native language plus writing system along with their translation in
the currently selected language.

Signed-off-by: Noel Ullreich <n.ullreich@proxmox.com>
 [ T: drop redundant info from subject and reword a bit ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-24 12:24:02 +01:00
Thomas Lamprecht
5835bc4c10 theme edit: ensure that the saved theme is actually valid
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-24 12:24:02 +01:00
Stefan Sterz
745aeaacdd fix #4610: add a small white padding to the totp qr code
some qr code readers need a white "quiet zone" around the main qr
code. otherwise, they won't be able to scan it at all which made it
impossible to scan the totp qr code on certain devices.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-23 17:12:12 +01:00
Stefan Sterz
0bf2b7fb0b dark-mode: improve contrast ratios on the mask message
this removes the transparent background of the masks message box. this
should help improve the contrast between the background and text.
also sets an appropriate border color to make it stand out more.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-23 17:12:12 +01:00
Stefan Sterz
347dd20db8 dark-mode: set boundlist background
this is only visible when no boundlist items are present, which only
occurs when loading elements for the boundlist (e.g., when adding a
nfs storage)

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-23 17:12:12 +01:00
Stefan Sterz
cf2a6407a7 dark-mode: color the custom grid and tree icons
this wasn't noticed before because usually vms would either be running
or stopped/offline/unknown etc. and there the colors are set
separately. however, in e.g., the backup view's missing backups
window these weren't colored properly. so this commit sets a default
color.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-23 17:12:12 +01:00
Stefan Sterz
8020636b24 dark-mode: improve contrast on split buttons
this improves the contrast of the little triangle in split buttons,
making it stand out more especially when focused.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-23 17:12:12 +01:00
Thomas Lamprecht
676bb0e001 bump version to 3.6.3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-22 13:25:10 +01:00
Thomas Lamprecht
7a39409764 dark-mode: tone down border on ceph install card-like window
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-22 12:38:15 +01:00
Stefan Sterz
40fdba341f dark-mode: style locked guest icons properly
Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-22 12:36:51 +01:00
Stefan Sterz
e3cc7af048 dark-mode: style checkboxes that don't use blueish active states
e.g., in the backup job creation window the filter column checkmark

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-22 12:11:48 +01:00
Stefan Sterz
162ff9d978 dark-mode: set the icon color of filtered column headers properly
Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-22 12:11:48 +01:00
Stefan Sterz
79725294d9 dark-mode: fix highlighting of active elements in drop down menus
e.g.,: the filter menu item in the backup job creation window

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-22 12:11:48 +01:00
Thomas Lamprecht
df57fa6c6b bump version to 3.6.2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-21 16:46:31 +01:00
Thomas Lamprecht
5afec12b9e rename "Theme" selector to "Color Scheme"
Having "Color" added makes it easier to translate (i.e. Farbschema,
配色) and at least as understandable as Theme, so change it,

Suggested-by: Markus Frank <m.frank@proxmox.com>
 [ T: while Markus suggested Color Scheme, the hive-mind opted for
 this ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-21 16:37:25 +01:00
Thomas Lamprecht
999a6e5c4c dark theme: allow one to distinguish the ceph install mask from the mask
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-21 15:56:12 +01:00
Stefan Sterz
062520ff6f dark-mode: set the background mask to background-darker again
this removes an issue where the mask would look awkward and
inconsistent (e.g., in the quarantine view, the retention tab of a
zfs storage etc). also  makes the shadow a big bigger to be closer to
crisp and also to improve contrast ratios

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-21 15:39:25 +01:00
Wolfgang Bumiller
883acf6dc9 dark-mode: make shadow black
Better have a barely visible shadow than a backlight.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-03-21 13:53:17 +01:00
Wolfgang Bumiller
a7acac0d9d dark-mode: reduce background mask opacity to 0.5
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-03-21 13:53:17 +01:00
Wolfgang Bumiller
dc90f49b65 Revert "dark-theme: let the background "shine through" mask more"
This looks horrible.

A *much* *much* better way to improve readability is to
simply set the opacity down to 0.5.

This reverts commit 2c837f5766.
2023-03-21 13:53:17 +01:00
Christoph Heiss
d62ef2a856 network edit: add tooltip to bridge ports inputs
.. as the exact format it takes might not be immediately obvious to
users.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2023-03-21 13:34:31 +01:00
Thomas Lamprecht
7996ce875c bump version to 3.6.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-20 14:13:46 +01:00
Stefan Sterz
2c837f5766 dark-theme: let the background "shine through" mask more
this makes the background mask a bit brighter and more see-through to
make it possible to read values from behind the mask, if needed. it
also adds a more visible shadow to windows so that the stand out more
at the same time.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-20 14:05:20 +01:00
Stefan Sterz
9c2ccd1aa4 dark-theme: dim warning and invalid colors more
this brings them more in-line with the appearance of crisp.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-20 14:05:20 +01:00
Stefan Sterz
b20a650bcc dark-theme: make "sorted-by" header highlight more subtle
by making the highlight more subtle, the theme is more consistent with
the look of crisp.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-20 14:05:20 +01:00
Stefan Sterz
437af70685 dark-theme: improve help button contrast ratios in focused state
also improves the hovered/focused state and makes it more consistent
with other buttons by making it brighter than the default state.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-20 14:05:20 +01:00
Lukas Wagner
bfb750a564 auth ui: fix value not defined in enumeration error
...when editing LDAP realm sync settings and only a single property is
empty and thus to be deleted (e.g. values.delete = "filter").

If `delete` is a simple string and not an array,
`Proxmox.Utils.delete_if_default` simply creates a comma-separated list,
(e.g. value.delete = "filter,sync-attributes").

When the properties from the other panel are evaluated and added to the
the `delete` property, comma-separated list format is not considered,
leading to a final value for `delete` that could look like this:
  value.delete = {
    "server2",
    "comment",
    "filter,sync-attributes"
  }

This commit fixes this by splitting `delete` in case it is a string.

Reported-by: Friedrich Weber <f.weber@proxmox.com>
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-03-17 16:41:59 +01:00
Lukas Wagner
78970361b3 auth ui: add firstname and lastname sync-attribute fields
This allows the user to set up a mapping for `firstname` and `lastname`
attributes for LDAP user syncs.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-03-17 16:41:59 +01:00
Lukas Wagner
2ee8cbdac1 auth ui: add onlineHelp for AuthEditLDAP
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-03-17 16:41:59 +01:00
Lukas Wagner
d6b65a89ce auth ui: add LDAP sync UI
Taken and adapted from PVE.
Changes:
  - Removed fields that are irrelevant for PBS for now (PBS has no
    groups yet). If PVE is adapted to use the implementation from the
    widget toolkit, the fields can simply be readded and somehow
    feature-gated so that the fields are only visible/editable on PVE

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-03-17 16:41:59 +01:00
Lukas Wagner
aa5cbdbb32 auth ui: add LDAP realm edit panel
The panel was mostly taken from from PVE, but altered slightly:
  - bind-dn and bind-password are displayed under "General"
    and not under "Sync". For some servers, we need to be bound
    to lookup a user's domain from a given user id attribute.
    In PVE, the bind-dn and bind-password fields are under
    "Sync", which is a bit confusing if a user is not interested
    in automatic user syncing.

  - There is a 'anonymous search' checkbox. The value is not persisted
    in the configuration, it merely enables/disables the
    bind-dn and bind-password fiels to make their intent a bit more
    clear.

  - Instead of a 'secure' checkbox, a combobox for TLS mode is shown.
    This way users can select between LDAP, STARTLS and LDAPS.
    In PVE, the 'secure' config parameter is deprecated anyway, so
    I took the opportunity to replace it with the 'mode' parameter
    as described.

  - Parameters now consistently use kebab-case for naming. If
    PVE is modified to use the same panel, some sort of adapter
    will be needed.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-03-17 16:41:59 +01:00
Lukas Wagner
64f65c027d repo view: replace non-clickable checkbox with icons
From a usability view, having a checkbox that is not clickable is pretty
misleading, especially if the visual style is exactly the same as in
other places in the UI where the checkbox is functional.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-03-17 11:27:38 +01:00
Thomas Lamprecht
dde1f3b094 bump version to 3.6.0
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-14 16:25:41 +01:00
Dominik Csapak
a1177796f9 auth-realm selector: add custom store filters for callers
so that a user can filter the underlying store, e.g. for type

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-14 15:25:27 +01:00
Stefan Sterz
3501ca3fc9 dark-theme: increase contrast on check-boxes
by adding a bit of brightness to the icons they stand out a bit more,
especially when selected but not active (grey check-mark)

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-14 15:13:35 +01:00
Stefan Sterz
a017567bd8 utils: move to using the auto theme per default
make the new default theme the "auto" theme that uses media queries to
detect a users preferred theme.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-14 15:13:35 +01:00
Stefan Sterz
e8b405f770 dark-theme: visually remove the border around the pve resource tree
by setting the color of the border of the resource tree to the panel
background color, it doesn't appear visually anymore while keeping
alignments in place.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-11 17:50:32 +01:00
Stefan Sterz
0cdf266659 dark-theme: remove thicker borders around content
previously the dark theme used thicker borders in certain places to
space out the content a bit more. this removes them again to make the
appearance more consistent with "crisp".

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-11 17:50:32 +01:00
Stefan Sterz
2a5b7cd2ff dark-theme: re-work buttons colors to appear dimmer
this dims buttons further by removing pure white text color and
adjusting backgrounds and border accordingly. it also keeps the help
buttons brighter than other buttons to draw (possibly confused) users
to them.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-11 17:50:32 +01:00
Stefan Sterz
e48bb85042 dark-theme: make windows stand out more against the background mask
makes the background mask darker so windows stand out a bit more

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-11 17:50:32 +01:00
Stefan Sterz
928df9ff32 dark-theme: fix summary row background
previously an "!important" was missing from the `background-color`
property. this meant that the background color wasn't properly
overridden. the "!important" is necessary as it is also used in the
light theme.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-11 17:50:32 +01:00
Dominik Csapak
b2471e89d0 input panel: improve validity change check for advanced fields
instead of only checking the validity of the advanced items when the
form validity changed as a whole, add a validity change listener to
each field in the advanced section.

This improves the behaviour such that every time an advanced field
gets invalid the items are show, not only when the form was valid
before.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-By: Aaron Lauterer <a.lauterer@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-11 17:32:15 +01:00
Alexandre Derumier
0c50257362 fix #4585 : toolkit: configid type: add missing "-" character support
JsonSchema is already ok:
$CONFIGID_RE = qr/[a-z][a-z0-9_-]+/i;

This is blocking creation of snapshot name with "-" in gui.
(works fine command line)

Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
2023-03-11 17:29:16 +01:00
Stefan Sterz
8d32419234 dark-theme: add support for the pmg quarantine theme toggle
allows using the theme toggle in the pmg quarantine properly. adds a
filter over the iframes in the quarantine to make them appear properly
in a dark environment.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-08 17:49:20 +01:00
Daniel Tschlatscher
15fddc20d1 util/window/form: add a theme selector
add a widget that implements a theme selector and sets a cookie to
load the appropriate theme.

Co-authored-by: Daniel Tschlatscher <d.tschlatscher@proxmox.com>
Co-authored-by: Stefan Sterz <s.sterz@proxmox.com>
Signed-off-by: Daniel Tschlatscher <d.tschlatscher@proxmox.com>
Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-08 17:49:20 +01:00
Stefan Sterz
c5559f82ea rrd chart: add support for the dark theme and dynamic theme switching
by integrating the theme switching logic into the chart panel itself,
themes can be switched more responsively based on css variables.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-08 17:49:20 +01:00
Stefan Sterz
f111efa4fd gauge widget: add support for a dark theme and dynamic theme switching
the gauges in the data center overview should use a dark style if the
relevant css variables are set. this also makes it possible to switch
the colors dynamically by adding an event listener

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-08 17:49:20 +01:00
Stefan Sterz
fbe9ee7210 subscription/summary/backup: stop setting the background color
setting the background color in js code adds that property as a style
attribute to the element. that makes it hard to alter later via css
and makes it hard to dynamically change the color e.g., if we want to
add different themes. the background color for these elements are
white already anyway, so just remove them here.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-08 17:49:19 +01:00
Daniel Tschlatscher
6bf5e1256e dark-theme: add initial version of the proxmox-dark theme
adds an initial version of a dark theme for all proxmox products. the
theme works by simply loading an additional css file that adjust the
colors of the original theme "crisp" to be more suitable for a dark
theme.

the theme itself is written in scss, so we need to add sassc as a
build dependency. while sassc is deprecated, it is still maintained in
the debian repositories and, thus, needs no additional packaging on
our end.

this version adds the following on-top of Daniel Tschlatscher's
original draft:

* removes checked-in build artifacts and other stuff that shouldn't be
  tracked
* code clean-up and removal of redundant code
* refactors:
    * icon styling
    * color handling for charts (moved to css variables)
    * color variables, consolidates them and makes the "functional"
    * color values, improves contrast and makes the theme appear more
      consistent
    * using the "theme-" prefix
* adds:
    * markdown note styles
    * combo-box trigger styles
    * even more icon styles (e.g., template icons, check boxes etc.)
    * loading spinners styles
    * number field up and down arrow styles
    * an additional auto theme that switches between light and dark
      theme dynamically
    * widget toolkit hints
    * ceph install mask
    * grid group headers
    * color to toggled buttons
    * date picker styles
    * drag and drop proxy styles
* fixes:
    * contrast on control elements for "scrollable" sidebars
    * make the general appearance closer to the light theme ("crisp")
    * buttons (when hovered, toggled etc)
    * background masking (e.g., when showing the log-in form)
    * grid header separator (adds an outline)
    * separator lines in some menus
    * makes the custom unknown icon more discernible
    * makes headers more readable
    * color adjustments to several components for consistency
    * reduces brightness of dividers in toolbars
    * border color on chart legend elements
    * removes a black border from docked toolbars (e.g., tag edit)
    * dims the "invalid" color to appear less aggressive
    * add hover effects in grids and make them consistent with "crisp"
    * summary rows
    * selected and hovered elements in boundlists
    * row numberers in grids
    * contrast of links in hints
    * ceph overview border colors (e.g., OSD in/out/up/down grid)
    * bottom splitter contrast in certain situations
    * tag visibility
    * pbs compatibility (help buttons stylings, icons, tabs)
    * pmg compatibility:
         * remove border around the spanning element in the header
         * style spam score grid
         * style tracking center rows
         * add appropriate colors to buttons in the quarantine
         * style mail-info element in the quarantine

Co-authored-by: Daniel Tschlatscher <d.tschlatscher@proxmox.com>
Co-authored-by: Stefan Sterz <s.sterz@proxmox.com>
Signed-off-by: Daniel Tschlatscher <d.tschlatscher@proxmox.com>
Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-03-08 17:49:19 +01:00
Matthias Heiserer
7e66b73ce4 ui: SMART: fix eslint error / show correct value
Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
2023-03-08 17:49:19 +01:00
Friedrich Weber
51083ee54a fix #4421: ui: guard setProxy against races of slow vs fast requests
Some UI components use `Ext.data.Store.setProxy` to change their
associated API endpoint URL in reaction to user input. One example is
`BackupView`, which calls `setProxy` when the user switches from
listing backups on storage A to listing backups on storage B. However,
if A is slow, the UI may receive the response for A *after* the
response for B. It will then display the contents of A as if they were
the contents of B, resulting in a UI inconsistency.

The reason is that `Ext.data.Store` still processes the slow response
for A, even though it is obsolete. This patch overrides the
responsible callback of `Ext.data.Store` to only process responses
belonging to the currently active proxy object. This should rule out
similar race conditions in all components that use the `setProxy` API.
In the above example, the patch results in the response for A being
ignored.

Ignored responses are logged to the browser console.

Note that this patch only concerns components that use `setProxy` for
changing API endpoints. Other components (e.g. those using
`proxy.setURL` for the same purpose) may be open to similar race
conditions.

Link: https://lists.proxmox.com/pipermail/pve-devel/2023-March/056062.html
Signed-off-by: Friedrich Weber <f.weber@proxmox.com>
2023-03-08 07:42:11 +01:00
Thomas Lamprecht
c16785f66a apt: use justified flex box for empty text hint
avoid some overly long/nested divs

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-02-14 11:45:41 +01:00
Matthias Heiserer
aacb9ea12a ui: SMART: show SMART data in correct columns
Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
2023-02-14 10:48:40 +01:00
Thomas Lamprecht
f372d3c087 node apt: make changelog window taller for 4:3 ratio
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-02-01 12:15:36 +01:00
Thomas Lamprecht
261f558105 node apt: cleanup and modernize code
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-02-01 12:14:22 +01:00
Thomas Lamprecht
7f14a081af bump version to 3.5.5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-31 17:31:31 +01:00
Thomas Lamprecht
6b70ca845d form: display-edit: add safe default renderer for display field
Due to the value binding on can get interesting effects when the
displayEdit field is in write (input) mode, as then the values still
get relayed to the display field, which itself is wanted as the field
supports live-switching, but even though the display field is
disabled and hidden, the value will be still rendered and a user can
XSS themselves inserting things like:
<img src="a" onerror="alert('cookie:'+document.cookie);"></token

And even though it's harmless (your browser knows your own cookie
already), it is rather odd and simply to cheap to harden against (per
default) to not do so.

Reported-by: Marcel Fromkorth <marcel.fromkorth@8com.de>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-31 17:21:13 +01:00
Thomas Lamprecht
319d450bec api request: add wide spread alert-error logic as smart-on option
The "smartness" is mostly "enable it automatically if the caller did
not specify an explicit override and there's neither a failure nor
callback function define", but that should cover most cases.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-31 15:56:27 +01:00
Thomas Lamprecht
cf93d1da50 utils: always html-encode response message
while this is something that only the user that made the request will
see, and for most people the possibility of "hacking" themselves is
rather redundant, it is still not nice to have this possible in
general; as even if it's highly unlikely that there ever can be an
error triggered to another user via API2 request handling, hardening
against it is simply to cheap to not do it.

Reported-by: Marcel Fromkorth <marcel.fromkorth@8com.de>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-31 15:41:42 +01:00
Thomas Lamprecht
d53046d66b utils: code cleanup for extractRequestError
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-31 15:34:31 +01:00
Dominik Csapak
4a819c891e combobox grid: avoid needing two clicks after re-selecting an item
'picker.hide()' hides the picker, but does not do everything to
properly keep track of the picker state in the combobox class.

This lead to a bug when we reselected an entry, we had to click the
picker again twice to open it again.

Use the 'collapse' method of the combobox instead, which does the
necessary book-keeping.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-01-31 10:34:47 +01:00
Dominik Csapak
c071b0c302 ComboGrid: make height for the error configurable
by introducing a errorHeight config property. This is necessary when
the ComboGrid has e.g. a toolbar and we show the error in the grid body
only, 100 pixels is not enough then. To solve that without hardcoding
different heights, let the subclass/caller configure that

also set this when the store load fails completely (was not done until now)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-01-18 15:13:37 +01:00
Dominik Csapak
aa157deb47 ComboGrid: use the grids view for the error message
for most of the combogrids, this does not make a difference, but we
want to have a node selection in some of their toolbars. There
having the error over the whole grid makes it impossible to select a
different node (which might be necessary to get rid of the error), so
we show the error on the view (which is the grids content body only).

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-01-18 15:13:37 +01:00
Thomas Lamprecht
49dd139a34 bump version to 3.5.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 16:09:56 +01:00
Thomas Lamprecht
f6efb0d487 node network view: move add-menu generation to common helper
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 16:03:45 +01:00
Thomas Lamprecht
bcd21f899e node network view: rework finding free interface ID
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 15:18:10 +01:00
Thomas Lamprecht
13220ad1f5 node network view: code style modernization
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 15:13:33 +01:00
Thomas Lamprecht
8407542364 role selector: make slightly more wide and resizeable
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 15:09:25 +01:00
Thomas Lamprecht
7b8eff314a task viewer: modernize code style
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 14:43:33 +01:00
Thomas Lamprecht
ba4ab7663d code style: use arrow fn for some api request failure paths
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 14:42:33 +01:00
Dominik Csapak
9e12fc0203 privilege role selector: fix renderer for Proxmox VE
In PBS we get an array here, so the renderer is fine, but in pve it's
just a long string, so add a space after commas to achieve the same
effect.

Without this, the second column is not visible in pve because of an
error in the renderer (no 'join' function on a string)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
 [ T: squash in code-reduction to make it a one-liner again ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-09 14:23:38 +01:00
Christoph Heiss
c8bae5b130 node/ServiceView: Show unit-state column in PBS too
The PBS api now reports `unit-state` for services as well, thus enable
the column for it.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2023-01-05 10:16:19 +01:00
Daniel Tschlatscher
45110c1630 add task log download button in TaskViewer
Adds a download button in the TaskViewer. Uses the newly created
downloadAsFile() method in the Utils class.

Signed-off-by: Daniel Tschlatscher <d.tschlatscher@proxmox.com>
Tested-by: Stefan Sterz <s.sterz@proxmox.com>
Reviewed-by: Stefan Sterz <s.sterz@proxmox.com>
2023-01-04 14:33:09 +01:00
Daniel Tschlatscher
8189ce639c Source file download in new Utils function
Adds a function for downloading a file from a remote URL in the Utils
class and uses it to revise one similar usage in FileBrowser.js

Signed-off-by: Daniel Tschlatscher <d.tschlatscher@proxmox.com>
Tested-by: Stefan Sterz <s.sterz@proxmox.com>
Reviewed-by: Stefan Sterz <s.sterz@proxmox.com>
2023-01-04 14:32:59 +01:00
Aaron Lauterer
a95dbf6dea parameter and return grid: allow text selection
Making it easier for people to copy & paste parameter and retrun value
names.

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
2023-01-02 17:44:24 +01:00
Thomas Lamprecht
ddf7a65deb css: whitespace fix
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-11-28 08:34:29 +01:00
Thomas Lamprecht
0bba4fc63f bump version to 3.5.3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-11-21 11:14:39 +01:00
Dominik Csapak
ed6721d0ff log, journal view: fix access to me after destroying
it can happen that the view is destroyed during an api call, so we
should check if it's destroyed as the first thing in the callback

if the view is destroyed, there is nothing we can do here, so simply
return

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-11-21 10:27:50 +01:00
Dominik Csapak
d13f97d6e3 css: don't make full tags inline-block in the tree
when showing full tags in the tree as inline-block, the height is
increased, leading to unwanted 'wobble' when tags are added/removed

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-11-21 10:27:43 +01:00
Thomas Lamprecht
a35c612297 bump version to 3.5.2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-11-17 08:37:29 +01:00
Matthias Heiserer
28a000898f api-viewer: show min/max for values without any other format
Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
2022-11-16 20:35:22 +01:00
Matthias Heiserer
7148e22691 fix #4271: api-viewer: display nested formats instead of [object Object]
I tried to keep the format as close to the HTML docs as possible, but
there are a few discrepancies between HTML docs and how this patch
displays parameters, instead of:
- <enum>,the enum variants are displayed. [1]
- <0|1>, <boolean> is displayed.

[1] The HTML docs explain parameters after the initial format string,
which the GUI doesn't (and there's no space for that). Showing the
variants inline is the easiest way to not loose information here.

Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
2022-11-16 20:35:22 +01:00
Matthias Heiserer
18afb0491c RDD: don't display power-of-two suffix 'i' for values without unit suffix
So instead of showing "0 i" (meaning e.g. 0 Gi), just display "0".

Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
2022-11-16 20:35:01 +01:00
Dominik Csapak
64e7c5371b Toolkit: add override for Ext.dd.DragDropManager
to fix selection behavior for Ext.dd.DragZone.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-11-16 14:48:44 +01:00
Dominik Csapak
2396874180 add tag related helpers
helpers to
* generate a color from a string consistently
* generate a html tag for a tag
* related css classes

contrast is calculated according to SAPC draft:
https://github.com/Myndex/SAPC-APCA

which is likely to become a w3c guideline in the future and seems
to be a better algorithm for this

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-11-16 14:48:44 +01:00
Thomas Lamprecht
121bbf57bf input panel: add onSetValues hook
As counter-part to `onGetValue`, which is for form assembly, add the
`onSetValues` helper that allows to hook into setting the values on
the fields, for example if one needs to transform a `disabled` to
`enable`.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-11-15 15:16:43 +01:00
Wolfgang Bumiller
54fc2533c8 simplify cpuset regex
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-11-10 10:56:03 +01:00
Wolfgang Bumiller
5ef8d10da6 cpuset: test only the regex
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-11-10 10:51:56 +01:00
Daniel Bowder
9109c39202 fix #3593: add CpuSet type to js
Regex parses a cpuset via 2 matches. Find number(s) or range(s) folowed
by a comma, then, find a single number or a single range not followed
by a comma. E.g., 0-1,4-5,6,7,10,11,14-15
CpuSet function first checks regex, then ensures left num <= right num

Signed-off-by: Daniel Bowder <daniel@bowdernet.com>
2022-11-10 09:40:00 +01:00
Aaron Lauterer
d17aa32eb4 TaskProgress: show text instead of percentage
The text needs to be defined in the wait() call as otherwise the
Ext.Progressbar will show a percentage that is not correct anyway but
just reflects where the animated progress bar itself is.

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
2022-11-07 14:57:16 +01:00
Thomas Lamprecht
8df4bd63d7 form: integer field: code cleanup in getSubmitData
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-11-07 14:56:49 +01:00
Alexandre Derumier
60ed17bf96 fix #2703: networkedit: limit custom interface name field to 15 characters.
Linux kernel don't allow interfaces name with more than 15 characters

Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
2022-10-14 15:20:45 +02:00
Matthias Heiserer
34edc91701 CSS: import action column fix from pbs, pmg
So it is in one location and available to pve as well.

Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
2022-10-10 10:01:16 +02:00
Stefan Sterz
eaa8d084e8 fix: toolkit: make email regex pattern match pve-common
`proxmoxMail` used its own regex pattern to validate emails. that
meant certain email addresses were rejected by the front-end that
were accepted by the backend that uses the functionality from
`pve-common`. examples include the following:

- "user@host.test-tld"
- "user-@host.testtld"
- "user@host"

reported on the forum: https://forum.proxmox.com/threads/gui-bugulance-using-the-user-add-gui-interface.114743/

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2022-09-12 17:14:45 +02:00
Daniel Tschlatscher
94d84581c2 fix: gui: up/down arrow keys increment/decrement twice in Number field
When the up or down arrow key on the keyboard was pressed while a
number text field (or any one descending from Ext.form.field.Spinner)
was selected, the up and down callbacks for that text fields KeyNav
were called twice. Therefore, the value in the text field would always
incorrectly increment/decrement by step * 2.

The problem was an overwrite for the onRender() method of the Spinner
class, which caused the callbacks for pressing an arrow key to be
registered for a second time. Simply not doing that in the overwritten
onRender() method fixes the problem.

The redundant declarations for spinUpEl and spinDownEl were removed as
well. Additionally, the 'mousewheel' event handler, registered in the
overwritten (but still executed) parent function, is unregistered now,
as it could lead to unintended side effects in browsers which still
implement this event.

Signed-off-by: Daniel Tschlatscher <d.tschlatscher@proxmox.com>
2022-07-29 09:20:44 +02:00
Hannes Laimer
59551419b5 ui: DiskLisk: handle partition data from PBS backend
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2022-06-15 10:58:39 +02:00
Hannes Laimer
b7e91380ca ui: disks: add 'mounted' column
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2022-06-15 10:27:18 +02:00
Thomas Lamprecht
3e83971bca bump version to 3.5.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-16 18:04:07 +02:00
Thomas Lamprecht
3271d12f24 file browser: disable item # size rendering
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-16 18:03:07 +02:00
Thomas Lamprecht
267c536516 edit window: comment text width/rewording
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-16 16:13:12 +02:00
Thomas Lamprecht
5dc3b0fa00 window: task viewer: code refactoring
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-16 16:13:12 +02:00
Thomas Lamprecht
53dc88d409 pxarFileTypes: fix over-eager s/text/label/
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-16 14:56:03 +02:00
Thomas Lamprecht
141d2ec303 bump version to 3.5.0
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-15 11:59:14 +02:00
Thomas Lamprecht
1fd75548ab d/control: fix build-depends-on-1-revision lintian nit
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-15 11:59:14 +02:00
Thomas Lamprecht
5235dbcc4d d/source: switch over to 3.0 (native) format
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-15 11:59:14 +02:00
Thomas Lamprecht
8812650c8b file browser: only disable button if not downloadable and add hint in tooltip
To avoid to much layout jumping if the whole button disappears
(changes height of footer bar too), rather explain to the user the
why and what they can do instead.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-15 10:37:44 +02:00
Thomas Lamprecht
9aa3d15708 file browser: fix comment layout
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-15 10:18:03 +02:00
Thomas Lamprecht
b33c1d6073 file browser: align size column to end/right
much easier to compare the sizes once lined up

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-15 10:16:49 +02:00
Thomas Lamprecht
427685c6b6 file browser: unify file type schema and avoid switch-case bloat
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-15 10:15:57 +02:00
Stefan Sterz
614b3cd488 fix #4001: FileBrowser: add a configurable prefix to downloaded files
Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2022-05-15 07:47:34 +02:00
Stefan Sterz
f5be46bc99 fix #4001: FileBrowser: show number of items in a directory as size
Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2022-05-15 07:47:34 +02:00
Stefan Sterz
a3faf027a1 fix #4001: FileBrowser: add menu to button and selected entry label
this commit adds a label showing the currently selected entry in the
file browser and merges the "Download .tar.zst" and "Download .zip"
button into one menu button.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2022-05-15 07:47:34 +02:00
Thomas Lamprecht
49275c6726 ui: acl role selector: make wider and wrap priv column
as UX was pretty poort, one could only see one and a half privileges
of the role, the rest overflowed and was hidden. While the column
could be resized, doing so would make the role name column shrink
automatically, and it really shouldn't be required in the first
place.

This is a very important selector and all privs of a role must be
visible when opening without any manual user interaction required.

So increase the width to 500px, make the priv colum take more
relative space and enable cellWrap to avoid hidden overflow.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-14 14:07:08 +02:00
Thomas Lamprecht
66cc6d92b8 safe destroy: indentation fix
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-11 15:40:08 +02:00
Dominik Csapak
a69f23580e window/FileBrowser: try reload again when getting a 503 error
for the file restore, we return a 503 error when we were not finished
mounting a disk in the restore vm, so ignore that error and try again
(up to 10 times) so a file listing now has a "real" timeout of
up to 300 seconds (30s pveproxy timeout * 10) instead of only 30,
which should be enough for most situations.

we also increase the proxy timeout to 60 seconds, since if one has many
disks, all of them will try to load at the same time, but the browser
has a maximum request limit and will stall+queue the remaining ones. so
those will not run into the extjs timeout when we increase it here.

for older backends without the new 503 returning feature, the calls
will still run into a pveproxy timeout anyway.

we also have to reimplement the 'monStoreErrors' functionality to
get a slightly different behaviour:
we disable the default extj loadMask of the treepanel and set it
ourselves. then on 503 we leave it up, and only remove it on success
or error (for non initial loads)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-04-28 15:18:59 +02:00
Thomas Lamprecht
081b26ed59 bump version to 3.4-10
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-27 18:58:26 +02:00
Matthias Heiserer
8832b5909f apt repos: use new AltText button
text is not needed with the AltText button.

Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
2022-04-27 14:29:54 +02:00
Matthias Heiserer
463177281c Buttons: add AltText
The same code is used once in widget toolkit and twice in PVE already,
so it makes sense to add it as a separate button.

Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
2022-04-27 14:29:54 +02:00
Dominik Csapak
8315a64201 StatusView: fix usage calculation for fields without valid values
Sometimes, total can be zero (e.g. for swap when it's not used), which
leads to the usage being NaN. This led to the progressbar not being
updated for InfoWidgets, leaving a spurious '0%' as text.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-04-26 08:28:28 +02:00
Fabian Ebner
5c7c81d39b css: add proxmox-good-row class
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2022-04-23 10:32:25 +02:00
Thomas Lamprecht
ad772f5bc6 bump version to 3.4-9
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-14 07:57:00 +02:00
Thomas Lamprecht
3637defc27 edit window: small code style/nits
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-14 07:53:45 +02:00
Stefan Sterz
11d4f4de44 toolkit: refactor markdown based NotesView and NotesEdit
refactor them to make them more flexible and, thus, usable in pbs.
adds parameters for enabling the TBar, setting the help section in the
editing dialog and cleans up the code in some places

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2022-04-13 11:28:07 +02:00
Stefan Sterz
e914453239 toolkit: add NotesView panel and NotesEdit window
move them here from pve so we can maintain them across several
products

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
[ T: also rename class/xtypes to avoid temporary breakage ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-13 11:23:47 +02:00
Thomas Lamprecht
7d602ae678 css: fix tab icon/text baseline
the baseline for the text was seriously off, the text had
(relatively) much more space below than above, which looks off for
buttons with an actual background

Instead of centering with margin/padding explicitly, do so with the
flex layout model.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-13 11:18:02 +02:00
Dominik Csapak
3b974606a6 window/FileBrowser: add optional 'tar.zst' button
only show it when enabled in config (so that we can hide it where
that is not supported, which is in PVE right now)

also changes the text between 'Download' and 'Download .zip' depending
if the selected entry is a directory or not

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-04-13 10:34:51 +02:00
Thomas Lamprecht
ab6ec02d93 bump version to 3.4-8
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-12 16:46:15 +02:00
Thomas Lamprecht
f77ff713db dns: increase api intervall from 1s to 10s
this is not something that changes very often, so avoid to many api
calls.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-12 16:40:37 +02:00
Thomas Lamprecht
6dce03cf5a dns/time: small refactorings, style
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-12 16:40:12 +02:00
Thomas Lamprecht
38e653f14b object grid: call rendere with our scope
having window as this scope has zero benefits and while one could
already try to get the local scope via some Ext.ComponentQuery query
its just nicer to have it easily available.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-12 16:36:53 +02:00
Thomas Lamprecht
563cf87d59 icons: switch cpu/ram bitmaps to svg
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-01 10:35:50 +02:00
Dominik Csapak
01a79a9781 form: combo grid: fix selection after filtering
firing 'refresh' in 'clearLocalFilter' was wrong, since that triggers
too often, for example when selecting an entry (since the field lost
focus). This lead to the picker refreshing and not registering the
click and thus not selecting the desired entry.

Instead refresh the view when we really need it: when the picker is shown.
The filter is already gone, but the picker grid does not know this yet,
so we fire the event then.

Fixes: 7e47328 ("Combogrid: clear filter on blur")

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-03-29 17:49:15 +02:00
Thomas Lamprecht
04070ce5b1 node: task filter: code style
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-03-29 17:48:13 +02:00
Dominik Csapak
64c1d6191c node/Tasks: don't count 'preFilters' as normal filters
they can not be removed nor are they visible, so don't count them.

When having a prefilter, we now don't show anymore that there is an
active filter, and don't enable the button anymore.
This is the case for vms for example (vmid is a prefilter).

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-03-29 17:42:25 +02:00
Thomas Lamprecht
96fb7eaacd utils: add inline comment about cookie set-for-clear
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-03-18 15:58:53 +01:00
Dominik Csapak
c1a3584103 utils: clear cookies with secure flag set
otherwise firefox complains with a deprecation warning that the secure-flag
is not set but SameSite to 'None'. Since we cannot know how firefox will
behave once that behaviour is no longer supported, add the secure flag
now.

Note: ExtJS also clears by setting the cookie with an empty value,
there's no browser supported clear api.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-03-18 15:56:20 +01:00
Thomas Lamprecht
9716d0def1 add EOL notice component
to avoid copying the same thing to three different product's GUIs
this year..

cherry-picked from stable-6 as we can have this in the master branch
full time

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-03-16 08:23:05 +01:00
Thomas Lamprecht
b33a25589c form: combo grid: fix whitespace/indendation and refactor slightly
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-03-16 07:46:10 +01:00
Matthias Heiserer
7e47328692 Combogrid: clear filter on blur
Previously, deselecting and reselecting the input field
led to the previous filter still being applied, although the
input field was cleared.

Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
2022-03-15 11:03:20 +01:00
Thomas Lamprecht
6971ce9d6b log view: comment length fixup
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-03-04 14:54:59 +01:00
Dominik Csapak
80ea69b416 fix #3919: log view: show first task output line correctly
if a task did not produce output yet, we always get a single line
with "no output". our heuristic in the gui counts the total lines +
the current  position. so to update the first output correctly, we
have to update every time in case we only have one line.

Otherwise, we only update on the second line, which is bad
in case the only line we ever get is the result.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-03-04 14:54:21 +01:00
Thomas Lamprecht
4fecfeef70 toolkit: add comment regarding upstream changes
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-23 13:02:59 +01:00
Thomas Lamprecht
c39864ee29 bump version to 3.4-7
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-23 12:14:13 +01:00
Dominik Csapak
da24e83f64 fix drag&drop for pointerType 'pen'
some devices (e.g. vms via novnc, and some laptops) get the pointerType
'pen' under chromium.

the DragZone handler tries to ignore touch input for that by
checking for "=== 'mouse'" which does not include 'pen'
so override that to handle it when the pointerType !== 'touch'

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-02-23 12:14:13 +01:00
Thomas Lamprecht
4f5e2bd12b edit window: make response handling code path more robust
A 2xx error code doesn't necessarily mean we got data we can
dereference

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-16 07:58:27 +01:00
Thomas Lamprecht
bfc6233d92 tfa view: fix WebAuthn casing
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-16 07:57:43 +01:00
Thomas Lamprecht
67b0280dba bump version to 3.4-6
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-14 11:34:45 +01:00
Thomas Lamprecht
017a637682 tree-wide typo fixes
found with `codespell`, mostly comments but also a fn parameter and
an error in an exception message

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-14 11:28:29 +01:00
Thomas Lamprecht
45a670a4ae d/changelog: fix typos in historic entries
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-14 11:22:38 +01:00
Matthias Heiserer
aca2ab3cb2 edit: fix comment typos
Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
2022-02-14 11:21:41 +01:00
Fabian Ebner
1025802ef1 sorters: use correct property 'direction' and keep default 'ASC'
Ext.util.Sorter does not have an 'order' property, so 'order: DESC'
didn't have an effect. The default is 'ASC' and it is arguably the
preferred direction for realm anyways.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2022-02-10 13:56:33 +01:00
Matthias Heiserer
e06715d2e5 utils: render default value correctly 2022-02-10 13:08:11 +01:00
Thomas Lamprecht
0b73467346 bump version to 3.4-5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-01-13 12:53:42 +01:00
Fabian Ebner
cd7e37fa95 window: safe destroy: make note more visible
by not using a smaller font size and using the pmx-hint class. Also
don't align to the middle, as everything else is left-aligned.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2022-01-13 12:53:42 +01:00
Fabian Ebner
79580cd0b6 zfs detail: hide the pool itself in tree view
and show the overall health in the grid instead.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2022-01-13 12:50:49 +01:00
Fabian Ebner
a18a674d51 zfs detail: increase window height
If there are mirrors and log/special vdevs it just feels too small.
It also doesn't help if there are errors in the upper part taking up
space. Make it 600, which was used in PVE before.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2022-01-13 12:50:49 +01:00
Thomas Lamprecht
c4ac6d1c14 toolkit: fix noisy ext warning of feature we do not want/use
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-12-10 13:23:02 +01:00
Wolfgang Bumiller
834cc8476d improve error handling when adding webauthn entries
There are cases where we directly throw a string error
(particularly, when navigator.credentials.create() fails,
for example when trying to register the same WA device to
the same user twice), which would end up with the WA window
simply not closing before.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-12-02 19:19:31 +01:00
Thomas Lamprecht
d739e44117 tfa login: hide u2f and yubico-otp if not available
Else it may be to confusing for users to see TFA types they cannot
configure anymore (or never could, in the PBS/PMG case).

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-29 17:19:58 +01:00
Thomas Lamprecht
ca867fb10d bump version to 3.4-4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-24 18:29:59 +01:00
Thomas Lamprecht
1eb5c21d91 log viewer: add heuristic for triggering a new limit load early
Less latency for the user

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-24 18:26:24 +01:00
Thomas Lamprecht
4e95d1e906 log viewer: add heuristic for scroll-direction dependent ratio distribution
if the user scrolls down make 2/3 of the buffer load the downward
(newer) buffer and only 1/3 the upward (older), and vice versa, if
the user scrolls up load 2/3 of the older messages vs. 1/3 of newer
ones.

If the user scrolls around frantically we're roughly as good as
previously and in all other cases we're better now.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-24 18:23:37 +01:00
Thomas Lamprecht
a16b036be0 log view: code cleanup and refactoring
save some lines while trying to keep or even improve readability a
bit

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-24 11:42:09 +01:00
Dominik Csapak
2f47411867 ui: logpanel: catch up to very fast task logs with api calls
by updating the start to 'total-limit' if we follow the task log live.
to do that, we decouple the 'scroll' event from updating the 'start'
parameter and call that directly after we scrolled down.

to not trigger the scroll event multiple times, suspend the scroll event
while doing that.

while we're touching those lines, remove the 'setTimeout' workaround
for touchscreens, since it seems to work fine since extjs 7.0

this also fixes the issue that the scroll event is not called sometimes

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-11-23 14:47:20 +01:00
Dominik Csapak
b5ff20a615 ui: logpanel: fix glitching fast task logs
if the total we got was bigger than the last line number, we appended
empty lines at the end of the panel, to which we scrolled

the only time we need to do that is when we do not follow the task log
'live' but when we are elsewhere (to keep the scroll position/size)

so give the lines directly to 'updateView' and let it decide
if we append the empty lines at the end

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-11-23 14:47:20 +01:00
Dominik Csapak
aeecc06ce1 ui: journalview: fix wrong first load with timespan
calling updateParams here lead to making an api call with the default
timespan (last 3 days) on pbs/pmg when going to the 'administration'
panel, even though it would never be shown since on tab activate we would
show the 'live mode' which does not use the timespan

on pve this did not happen since the journalview is on its own page and
the change to livemode triggered too fast..

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-11-23 12:58:03 +01:00
Thomas Lamprecht
3ccdce4055 utils: format_duration_human: calculate years too
Add years and skip showing minute scale once we got over a year,
that's just not relevant anymore at that scale..

Months are just not an ideal thing to show, as they have different
lengths too (leap years have similar issue, but they differ 0.27%
from a normal year, while shortest to longest month is 10.7%
difference!)

Weeks could be done though, they're fixed at 7 days, but for now I
want to avoid unwieldy day numbers like 2634 d as that's just hard to
frame correctly. Also adding years now does not makes adding weeks in
the future impossible anyway..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-23 09:39:50 +01:00
Thomas Lamprecht
6c8df606b1 bump version to 3.4-3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 21:41:41 +01:00
Thomas Lamprecht
6654703de6 bandwidth field: allow to submit auto-scaled size-units as string
opt-in to avoid breaking existing users and a bit of an experiment in
general..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 21:39:25 +01:00
Thomas Lamprecht
851ebc36cd utils: add size unit related helpers to parse/auto-scale/format
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 21:36:01 +01:00
Thomas Lamprecht
1fad0e8822 bandwidth/utils: move out SizeUnits definition to more common module
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 21:35:26 +01:00
Dominik Csapak
52a0eae95a form: copy BandwidthSelector/SizeField from PVE
and replace pve with pmx

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-11-19 16:04:55 +01:00
Thomas Lamprecht
ec12ffb90e ui: OpenID realm: allow to edit acr values
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-19 07:36:46 +01:00
Thomas Lamprecht
5c5cee2dea ui: OpenID realm: allow to edit prompt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-19 07:36:36 +01:00
Thomas Lamprecht
0dce277c5a ui: OpenID realm: allow to edit scopes
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-19 07:36:19 +01:00
Thomas Lamprecht
c860b3490a ui: OpenID edit: make username-claim field editable for arbitrary values
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-19 07:35:40 +01:00
Dominik Csapak
c75098386d data: diffstore: fix autoDestroyRstore option
the change from extjs 6.0.1 to 7.0.0 removed 'onDestroy' but brought
us 'doDestroy' for stores

we did not notice since 'onDestroy' was a private method and thus
the changelog did not mention this (doDestroy is a public method meant
exactly for our use case)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-11-18 11:14:31 +01:00
Thomas Lamprecht
ef91752982 bump version to 3.4-2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-15 10:23:39 +01:00
Thomas Lamprecht
4ec859d9bc proxmox checkbox: add clearOnDisable config
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-15 10:22:02 +01:00
Dominik Csapak
55e47317c0 window/TfaWindow: fix text format
Ext.String.format was missing to show the number correctly

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-11-12 16:14:00 +01:00
Thomas Lamprecht
ceff5d3fc0 bump version to 3.4-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-11 21:11:19 +01:00
Thomas Lamprecht
d6f0eee91c totp: add location hostname into default issuer
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-11 21:11:01 +01:00
Thomas Lamprecht
05da27edef tfa: yubico otp: nudge users in WebAuthn direction
to avoid that they have a yubikey in their hands and think they need
to use Yubico/YubiCloud otp for it to work..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-11 19:55:45 +01:00
Thomas Lamprecht
82a386530e TFA panel: code/style cleanup
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-11 19:53:55 +01:00
Thomas Lamprecht
65c39bc04a tfa view: avoid showing start of unix epoch when no creation date
as that is a bit unrealistically, rather use N/A (not applicable)

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-11 19:26:33 +01:00
Fabian Ebner
7edda053ea disk list: allow wiping individual partitions
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-11-11 14:40:39 +01:00
Wolfgang Bumiller
20b39dd8f6 add yubico otp windows & login support
has to be explicitly enabled since this is only supported in
PVE

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-10 08:58:44 +01:00
Wolfgang Bumiller
ab2538f5e5 add Proxmox.panel.TfaView
copied from pbs with s/pbs/pmx/ and s/PBS/Proxmox/

DELETE call changed from using a body to url parameters,
since pve doesn't support a body there currently, and pbs
doesn't care

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-10 08:58:44 +01:00
Wolfgang Bumiller
641764474d add totp, wa and recovery creation and tfa edit windows
plain copy from pbs with s/pbs/pmx/ and s/PBS/Proxmox/

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-10 08:58:44 +01:00
Wolfgang Bumiller
9e1f1ef616 add Proxmox.window.TfaLoginWindow
copied from pbs and added u2f tab

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-10 08:58:44 +01:00
Wolfgang Bumiller
c7f2b15ac9 add Utils used for u2f and webauthn
base64url parts copied from pbs
render_u2f_error copied from pve

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-10 08:37:03 +01:00
Wolfgang Bumiller
d3812684c2 add pmxUserSelector
copied from pbs

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-10 08:37:03 +01:00
Dominik Csapak
71295836e5 cbind: document cbind by adding a small summary and example
Explain the use-case, the difference to normal binds, and give an
example that contains all features that can be used with explanations.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-10-27 11:22:00 +02:00
Thomas Lamprecht
511c7843d0 disk: smart: code & indentation level cleanup
drop some intermediate variables that are 1:1 the original

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-18 08:43:28 +02:00
Dominik Csapak
53ecc2ad95 fix #3589: show device name in title for SMART values window
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-10-18 07:08:05 +02:00
Thomas Lamprecht
17c580c2a3 code style: text-width, indentation improvements
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-18 07:06:59 +02:00
Fabian Ebner
f340cf64fc (multi) disk selector: allow requesting partitions too
No functional change for existing users is intended.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-09-30 18:14:24 +02:00
Dominik Csapak
c91a73150f panel/RRDCharts: enable scrolling for RRDCharts on touchscreens
quote from extjs docs (AbstractChart.js):

 If you do have a chart inside a scrollable view, even if it has no
 interactions, you have to set its touchAction config to the following:

 touchAction: {
     panX: true,
     panY: true
 }

 Otherwise, if a touch action started on a chart,
 a swipe will not scroll the view.

Since we have RRDCharts always in a scrollable view, and it does not seem to
hurt non-touchscreens, enable it by default so that users on touchscreens
can scroll it.

Reported by users in the forum:
https://forum.proxmox.com/threads/minor-but-annoying-ipad-swipe-bug.93686

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-08-23 18:17:02 +02:00
Thomas Lamprecht
088a3ed9e9 bump version to 3.3-6
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-27 16:41:08 +02:00
Thomas Lamprecht
8daf5b0955 node: network: do not gettext MTU
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-27 16:38:00 +02:00
Oguz Bektas
d1f37e222d node: add MTU column in NetworkView
hidden by default since it's an "advanced" setting

Signed-off-by: Oguz Bektas <o.bektas@proxmox.com>
2021-07-27 16:37:27 +02:00
Fabian Ebner
14247821fb add NodeInfoRepoStatus
adapted from PMG, because it has an additional fix to avoid setting
undefined in the view model, which still affects PBS (see pmg-gui
commit 774418f08b10c651357d11ccb161ac075e1ae905).

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-27 16:34:12 +02:00
Dominik Csapak
0dbcfb8c64 fix #3542: node task logs: query correct node for tasks in clusters
While refactoring, we forgot to replace the 'localhost' URL (which is
valid for PMG/PBS) with the actual nodename we have in Proxmox VE.

Do this by setting the correct URL in the viewModel right at the
start of the initComponent

We now also have the possibility to overwrite the url if we want

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-27 16:32:38 +02:00
Thomas Lamprecht
dacb645550 bump version to 3.3-5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-19 17:52:16 +02:00
Fabian Ebner
5e76f7302a node: repos: add online help
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
Reviewed-by: Stoiko Ivanov <s.ivanov@proxmox.com>
Tested-by: Stoiko Ivanov <s.ivanov@proxmox.com>
2021-07-19 17:39:38 +02:00
Thomas Lamprecht
f0de3268af api-viewer: ensure path starts with slash
should always be the case, but this is now an assumption, so better
to ensure it.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-19 17:38:25 +02:00
Dominik Csapak
6cc360f2b4 api-viewer: drop extra slash in api path
'endpoint' already begins with a slash, so drop it after /api2/json

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-19 17:32:23 +02:00
Fabian Grünbichler
8b06793b3d apt: match "Debian Backports" origin
some users might have that enabled, and we know it is from Debian and
not '?'

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-07-19 17:32:00 +02:00
Thomas Lamprecht
1820e77d5c add package version window
we want that component in all projects, PVE and PBS have the code
duplicated for now, as PMG is about to receive this too I rather want
to use the chance add add it here for actual reuse.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-19 16:58:27 +02:00
Thomas Lamprecht
21823de2c1 bump version to 3.3-4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-13 18:44:16 +02:00
Thomas Lamprecht
d9c6e21492 service view: disable all buttons for masked/not-found/unknown services
at least if we have a unit state (atm only PVE and PMG report that)

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-13 18:39:43 +02:00
Oguz Bektas
0c15c08eee service view: fix stale stop/restart button enabled behavior
fix an issue where the stop or restart button stays enabled for
elements in 'startOnlyServices' after switching selection from a
service that was not restricted to start-only

note: front-end change only as the backend already refused to stop a
start-only service.

Signed-off-by: Oguz Bektas <o.bektas@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-13 18:35:43 +02:00
Thomas Lamprecht
ae6a1cc908 service view: avoid showing not installed services as error
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-13 18:35:36 +02:00
Thomas Lamprecht
7a17156d65 service view: code cleanup
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-13 18:15:55 +02:00
Stoiko Ivanov
66c5ceb848 acme: allow wildcards as domain
allow wildcard dns-names as defined in [0,1] (only the prefix '*.' in
front of a valid dns-name) as domain.

[0] https://tools.ietf.org/html/rfc8555#section-7.1.3
[1] https://community.letsencrypt.org/t/acme-v2-production-environment-wildcards/55578

Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
2021-07-13 10:03:14 +02:00
Thomas Lamprecht
a4fc99f7ab bump version to 3.3-3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-12 09:52:32 +02:00
Dominik Csapak
8d4faa8821 panel/AuthView: handle different baseUrls for configuring realms
we need that for pbs.

also give the option to use the type in the path, like in AuthEditBase

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-12 09:50:24 +02:00
Dominik Csapak
335633d208 window/AuthEditBase: handle differenc config api urls
necessary for pbs. also give an option to use the type as url path
e.g. /config/access/<TYPE>/<ID>

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-12 09:50:24 +02:00
Thomas Lamprecht
ff8ced046a bump version to 3.3-2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-09 17:30:47 +02:00
Thomas Lamprecht
bc2b6fdd21 fixup schema
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-09 17:30:26 +02:00
Thomas Lamprecht
75a2c32d77 bump version to 3.3-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-09 17:01:57 +02:00
Fabian Ebner
7c65b8bfe0 utils: add helper to format node's repository status
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-09 16:58:51 +02:00
Thomas Lamprecht
770b2612d5 auth view: actually use new schema module ...
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-09 16:40:49 +02:00
Thomas Lamprecht
2908d35808 fixup new schema
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-09 16:21:27 +02:00
Thomas Lamprecht
5c085daf0b add schema and move authDomains there
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-09 16:11:19 +02:00
Dominik Csapak
8f30708d44 add generic OpenIDInputPanel
mostly copied from pve, but includes a realm displayeditfield
as well as the comment field

in contrast to the inputpanel in PVE, this is not extended
from a base input panel (where in pve the comment/realm/default/etc.
fields come from)

we do this, since not all products can define a default or tfa

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-09 14:46:27 +02:00
Dominik Csapak
c159449543 add panel/AuthView from PVE
added the following (necessary) changes:
* use Proxmox.Utils.authSchema
* omit the sync button/handler, but add a possibilty to add extra buttons
* check for an 'edit' property in the authSchema for enabling editing
* removed the onlineHelp property
* removed 'TFA' column (can be added by the caller)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-09 14:46:27 +02:00
Dominik Csapak
402964713a Utils: add authSchema from PVE and make it overrideable
like we do for the task_descriptions.
This way we can have a basic config that is true for all products
and override where necessary

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-09 14:46:27 +02:00
Dominik Csapak
6a504e1995 add window/AuthEditBase from PVE
basically a straight copy, with the exception that it references
Proxmox.Utils.authSchema instead of PVE.Utils.authSchema

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-09 14:46:27 +02:00
Fabian Ebner
e455399aef node: repos: only show suites warning at the top if enabled repository is affected
The warning still shows up for the disabled repository itself, but
having a disabled repository with a bad suite is not problematic for
the configuration status as a whole.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-09 14:42:55 +02:00
Thomas Lamprecht
89b0dd2e4f bump version to 3.2-5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-08 14:32:40 +02:00
Dominik Csapak
68152254af window/SafeDestroy: add taskDone and apiCallDone callbacks
like in edit window, so we can reacto to a successful task/api call

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-08 14:07:19 +02:00
Fabian Ebner
bfab8fc134 node: tasks: use helper to format status again
which avoids the all-caps "WARNINGS:" and uses the localized version.
The call was thrown out by the big overhaul in
9e059d560c.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-08 11:50:27 +02:00
Thomas Lamprecht
bb5511d569 network: use std remove button for confirm
alows to make the code simpler too, but we need to instantiate the
selection model explicitly, as we use a bit of a weird layout here to
be able to show the pending changes at the bottom, if any, so the
main gridpanel is not the parent of the toolbar buttons, so the
std-remove button does not automatically finds the selection model
when searching in the parent

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-06 20:55:56 +02:00
Thomas Lamprecht
99f3e147e4 code cleanups, inline more, use arrow-fns
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-06 20:54:59 +02:00
Thomas Lamprecht
b51cbf5a00 d/control: bump eslint requirement
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-06 14:14:09 +02:00
Thomas Lamprecht
4e77e86d47 bump version to 3.2-4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-05 16:50:58 +02:00
Dominik Csapak
abde5f2bad node/Tasks: add 'clear filter' button
that does two things,

show the user that some filters are active (even if filters are collapsed),
and let the user to reset the filters all at once

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-05 16:44:37 +02:00
Dominik Csapak
a86b1f26da node/Task: show errors on store load
since we use a buffered store on a grid, we cannot use our
normal monStoreErrors from Utils (The store on the grid is a memorystore
without our 'proxmox' proxy, and the store in the viewmodel is not
initialized yet in the init)

simply set the mask in the already existing 'updateLayout' function
which is exactly the right place for the buffered store load

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-05 16:44:37 +02:00
Dominik Csapak
12a6f6824f node/Tasks: deselect entries when filter changes
since we use a remotefilter on a bufferedstore, the only event where
we can check if the selected element is still there is the 'refresh'
event. Simply deselect if the seleceted element is not in the store

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-05 16:44:37 +02:00
Dominik Csapak
f00e321173 node/Task: remove leftover 'datastore' reference
this does nothing here, since the 'datastore' would be an
'extraFilter' defined in pbs itself

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-05 16:44:37 +02:00
Thomas Lamprecht
6bac17e394 node: repos: always skip "you get updates" on parsing error
As normally that means that the host does not gets updates, as most
apt use will fail too.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-05 16:39:24 +02:00
Thomas Lamprecht
4227a5578a node: repos: improve repo-parsing error message
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-05 16:38:46 +02:00
Thomas Lamprecht
6c9af17882 node: repos: factor out addCritical into closure
now that there's more use for it

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-05 16:38:16 +02:00
Dominik Csapak
25d17082b0 node/APTRepositories: make 'no pve repositories' an error
since we also show it as error in the node summary

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-05 16:25:35 +02:00
Thomas Lamprecht
e1fc474412 bump version to 3.2-3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-05 10:11:46 +02:00
Thomas Lamprecht
fb4bb95e9d utils: fix typo in variable name
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-05 10:11:46 +02:00
Thomas Lamprecht
5b2485ace3 utils: updateColumnWidth: allow overriding tresholdWidth and make that more general
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-05 09:46:58 +02:00
Thomas Lamprecht
8b7e349a25 utils: column width handler: drop duplicate
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-05 09:34:32 +02:00
Thomas Lamprecht
76899da698 info widget: early return from update if text & value stayed the same
No need then and if we'd call into update we'd still generate new
elements which is annoying for debugging and extra DOM work we can
avoid.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-04 22:01:40 +02:00
Thomas Lamprecht
1d3d61ead9 markdown: extend blocked tags in sanitizer
not all of those are really problematic, but it's always easier to
start out stricter than required and see if any user even would use
those.

It seems that we should probably switch to a white-list approach...

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-04 20:52:56 +02:00
Thomas Lamprecht
f2c4f9bdc2 markdown: make sanitizer more strict
The href, and in some browser also the src attrs on img, or a tags
can be made to execute JS rather easily, catch thoseand just remove
the attr if, after creating an URL object from it, it does not looks
like it's a http(s) request.

Further, filter out the style tag completely, as that can be misused
too, even if only to break cosmetics.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-04 20:06:28 +02:00
Thomas Lamprecht
71bc0913bd markdown: encode bad nodes HTML instead of pruning it
As pruning means content an user wrote into the box, even if with
malicious intend, gets hidden and that can be quite confusing..

So rather get the outerHTML, transform it with ExtJS's htmlEncode and
set it again.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-04 19:22:40 +02:00
Thomas Lamprecht
65f4704b62 node: repos: handle that components can be undefined
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-04 19:09:42 +02:00
Thomas Lamprecht
38c1f53f83 bump version to 3.2-2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-03 16:34:47 +02:00
Thomas Lamprecht
eb1fff8294 node: repo: use more stati-schema like code style to configure store
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-03 16:34:46 +02:00
Thomas Lamprecht
7705801f48 node: repo: use more stati-schema like code style to set grouping feature
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-03 16:21:17 +02:00
Thomas Lamprecht
faacb77fb4 node: repo: avoid using IDs on elements, as that breaks easily on clusters
Anything where more instances can exist, even if only for a very
short time, must NOT use `id` as that needs to be unique in all
current instances of components/elements of the whole gui.

Here it broke most of the gui when switching between node, as the new
nodes repo gui was instantiated before the old ones was completely
destroyed, so there where (at least) two elements with the same id ->
💥 boom.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-03 16:06:17 +02:00
Thomas Lamprecht
99829a9e78 bump version to 3.2-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-03 00:12:38 +02:00
Thomas Lamprecht
c7ca21b4b8 node: repo: health: drop br line-breaks, replaced by flex display
Firefox ignores them anyway but chrome adds them additionally, making
it look rather weird..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-03 00:09:09 +02:00
Thomas Lamprecht
96ecd62ac1 ui: repo status: adapt some warning/error gettexts
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 23:59:56 +02:00
Thomas Lamprecht
46231d0da2 ui: repo status: do not duplicate warning to health icon
It feels quite weird to have the last warning text duplicated, the
left health icon should be a very concise summary only.

allow one to force it by setting important, but that's not yet used
anywhere.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 23:58:27 +02:00
Thomas Lamprecht
fe787c8c12 fixup! ui: repo status: code cleanup/refactoring 2021-07-02 23:58:02 +02:00
Thomas Lamprecht
f411afb40c ui: repo status: adapt flex-ratio and center health vertically
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 23:57:20 +02:00
Thomas Lamprecht
5267494a87 css: add centered-flex-column class
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 23:56:19 +02:00
Thomas Lamprecht
df7def01a1 ui: repo status: code cleanup/refactoring
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 23:12:53 +02:00
Thomas Lamprecht
de73e0b3d8 add OpenID icon
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 21:58:09 +02:00
Dominik Csapak
5e0cecb7d5 node/APTRepositories: improve error/warning display
always show grid besides status, with the accumulated
stati/warnings/errors

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-02 16:28:53 +02:00
Dominik Csapak
f59d1076d7 node/APTRepositories: rework top status and error grid
instead of having a title bar and a seperate error grid,
add an always visible panel that displays the status (ok, warning, errors)
which also contains the error grid (if necessary, ala ceph summary)

this makes the panel more consistent to use and it is immediatly
visible if something is wrong

this also adds a test for the 'test' repositories, as well as a test
for not correctly configured suites

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-02 15:31:25 +02:00
Dominik Csapak
fbbe68fd69 Utils: add get_health_icon from PVE
we'll need it here too

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-02 15:31:25 +02:00
Thomas Lamprecht
cd20320b7f utils: followup: fix base class
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 15:17:47 +02:00
Thomas Lamprecht
e722f108aa ui: Utils: refactor userid parsing to Utils
Originally-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 15:06:26 +02:00
Thomas Lamprecht
e8c60b3215 utils: add getOpenIDRedirectionAuthorization: to avoid code-duplication
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 13:53:11 +02:00
Thomas Lamprecht
665b206b0f css: fix debian openlogo background-size for chrom* based browsers
The debian one higher than wide and it seems, contrary to my belief,
the background-size is not taken for both, height and width if only
one param is set, but rather the second paramet height then defaults
to `auto` which Firefox and Chromium handle different in this case.

Set both to make this fixed.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 09:18:45 +02:00
Thomas Lamprecht
4fc57df439 fixup comment
forgot to save in vim when editing the comment before committing..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 08:09:39 +02:00
Thomas Lamprecht
0668981954 node: repo: highlight proxmox repos that are not producition-ready
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-02 08:04:39 +02:00
Thomas Lamprecht
58b7186065 node: repos: fix reversed validation on configured
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-01 10:49:25 +02:00
Fabian Ebner
d7aeb02f3e apt repos: use correct URL
so there is a 'result' property, which the window expects.

Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-01 10:46:22 +02:00
Thomas Lamprecht
824f997749 node: repos: fix add repo validator
isValid is a boolean not a callback, so won't really work and just
set the state once.

Use the `validator` and first call into the parents helper, then do
our logic and return a string  with an explanation in the expected
invalid case.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-01 10:43:12 +02:00
Thomas Lamprecht
f0966f2930 node: repos: fallback to "Other" for unknown origin
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:58:29 +02:00
Thomas Lamprecht
5a1fddb622 node: repos: use warning, not critical for suite-errors if disabled
As in that case it's actually safe, so rather show as warning, albeit
even info/notice like would probably be enough, but still, it is not
a configuration that is wanted permanently.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:47:57 +02:00
Thomas Lamprecht
205d2751f4 node: repos: fade text of disabled rows
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:47:20 +02:00
Thomas Lamprecht
5e9eb2456c node: repos: increase width of add-repo window + style cleanup
We have some longer descriptions so the default width looks crammed.

Also, avoid a intermediate variable which is only used once without
any change.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:46:12 +02:00
Dominik Csapak
7f671fee58 APTRepositories: remove unecessary selection model
this is the default anyway, no reason to explicitely create it

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-30 21:27:04 +02:00
Dominik Csapak
e71fc6e438 APTRepositories: make the Suites warnings inline errors
by iterating only once over the info array and adding them directly
to the records this way we can avoid iterating over the records
again

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-30 21:27:04 +02:00
Dominik Csapak
003c49829d APTRepositories: change updating button text/state to viewcontroller
we already have a viewmodel we can use, and so the handler does
not have to navigate the component tree up/down

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-30 21:27:04 +02:00
Dominik Csapak
82071150f7 APTRepositories: make panel scrollable
so that it is still usable with many errors and/or repositories

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-30 21:27:04 +02:00
Dominik Csapak
d8b5cd80c8 APTRepositories: change separation style between panels
having double borders does not look so nice, separate them with a bit
of padding

also change the warning panel to a simple header

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-30 21:27:04 +02:00
Thomas Lamprecht
036f48c14d node: repos: render Origin logo for Debian & Proxmox
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:26:45 +02:00
Fabian Ebner
21860ea45c node: apt: spawn a window for adding repository
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-30 21:26:45 +02:00
Thomas Lamprecht
3dfb0b9a2b css: increase padding for Debian and Proxmox symbol logos
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:26:45 +02:00
Thomas Lamprecht
77d6d9929e images: use svgcleaner to reduce logo SVG sizes
Used svgcleaner[0] with the following safe[1] command:

```
svgcleaner INPUT.svg OUTPUT.svg --indent 1 --resolve-use no \
 --convert-shapes no --group-by-style no --join-arcto-flags no \
 --join-style-attributes no --remove-comments no --remove-declarations no \
 --remove-invisible-elements no --remove-metadata no \
 --remove-nonsvg-attributes no --remove-nonsvg-elements no \
 --remove-text-attributes no --remove-title no \
 --remove-unreferenced-ids no --trim-ids no --ungroup-groups no \
 --list-separator comma
```

Resulting size reductions:

Debian   22.51% smaller
Proxmox  51.00% smaller

Note that the Debian one had a small issue with a unused variable set
which I reported[2] and cleaned up manually for now.

[0]: https://github.com/RazrFalcon/svgcleaner
[1]: https://commons.wikimedia.org/wiki/User:JoKalliauer/Optimization#svgcleaner
[2]: https://github.com/RazrFalcon/svgcleaner/issues/240

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:26:45 +02:00
Thomas Lamprecht
259c29df9f add Debian and Proxmox symbol logos and css
also add a new 'pmx-itype-icon' helper class for the classic bg
repeat/position option to allow more easier reuse

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:26:45 +02:00
Thomas Lamprecht
a8ab1d9820 node: services: fix logic for displaying unit state
still hacky but at least works now ;-)

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 21:26:45 +02:00
Thomas Lamprecht
03325cedcf buildsys: do not install css or images with executable flag
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 15:20:20 +02:00
Thomas Lamprecht
87c64c495e css: some markdown heading and paragraph font-size & padding tuning
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 14:21:50 +02:00
Fabian Ebner
d91987a558 apt repositories: replace OfficialHost with Origin
to adapt to the new backend behavior.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-30 13:41:21 +02:00
Thomas Lamprecht
e972890e69 add basic .gitignore
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-29 12:21:06 +02:00
Thomas Lamprecht
0970ef7fb0 bump version to 3.1-4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-28 19:14:50 +02:00
Thomas Lamprecht
6fc91fc698 node tasks: fixup overly-short comment lengths
The minimum "max length" should be 80cc, and 100cc is also fine.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-28 16:19:40 +02:00
Dominik Csapak
9e059d560c node/Tasks: merge improvements from PBS and make it more generic
this copies most of the task grid from pbs, but adds handling so that
users can add aribtrary filter fields

the filter fields always present are:
* since
* until
* task type
* task status

other filters fields can be added by giving an 'extraFilter' array
which must contain widget definitions that emit a 'change' event.
this is then used to update the filters for the api call

also you can add a 'preFilter' object, that sets the filter parameter
only once at the beginning

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-28 16:06:49 +02:00
Thomas Lamprecht
5995eddcc4 cbind mixin: also descend in elements with an cbind property
Not only into those with an xtype one, as we can either have a
implicit default xtype (e.g., in tbars for buttons, or set explicitly
via the `defaults` mechanism) or want to apply cbinds to stores or
other objects.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-25 08:42:44 +02:00
Thomas Lamprecht
f5853d4605 bump version to 3.1-3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 23:11:46 +02:00
Thomas Lamprecht
03c4c65bab apt repos: move Official to the right & use checkbox to render enabled
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 19:57:33 +02:00
Thomas Lamprecht
3fc020f417 apt repos: add product config-option and rework warning renderer
product defaults to PVE. It is added in the warnings and should be
set by any instance correctly.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 19:42:14 +02:00
Thomas Lamprecht
0e79ce21c1 apt repos: make add-repo a checked command
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 19:40:54 +02:00
Thomas Lamprecht
994fe897b7 apt repos: style grouping headers a bit different
else the repo/header difference is not that big, and it gets a bit
hard to read as it feels a bit monotonic

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 19:39:48 +02:00
Thomas Lamprecht
bb64cd0341 apt repos: make enable/disable text selection dependent
the size hack is copied over from pve-manager's qemu/HardwareView

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 19:38:34 +02:00
Thomas Lamprecht
af48de6bf8 apt repos: code/format cleanups
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 19:01:24 +02:00
Fabian Ebner
d76eedb46f add buttons for add/enable/disable
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-23 16:01:06 +02:00
Fabian Ebner
24313a9d80 add UI for APT repositories
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-23 16:01:06 +02:00
Thomas Lamprecht
89de7ec504 node/services: show masked and some unknown units as disabled (greyed out)
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 15:55:00 +02:00
Thomas Lamprecht
b6eedbba04 node/services: disable start/stop for masked and unknown units-states
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 15:54:17 +02:00
Thomas Lamprecht
6607de47e8 node/services: optionally show unit/active states
show unit state by default for PVE, with a bit of an hack

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-23 15:53:32 +02:00
Thomas Lamprecht
ab34f5b215 node/service view: code cleanups
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-22 14:40:34 +02:00
Dominik Csapak
17c320c831 panel/JournalView: fix flickering in journal livemode
it seems that something changed in extjs 7 which does not quite
restore the correct scroll position when the identical content is set
on a component. this means that sometimes, we update the text
with the identical one, but the scroll position is now off, only
to scroll back to the bottom

this causes a flickering everytime we do the api call.

instead, only update the component when the content really changed.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-22 12:46:26 +02:00
Thomas Lamprecht
f869d73e50 css: markdown: add some nicer blockquote styling
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-21 18:08:06 +02:00
Thomas Lamprecht
a610dd9e32 parser: sanitize HTML: allow 'disabled', 'start', 'type' and 'checked' attributes
This makes check box lists like the following snipped work:

- [X] Done
- [ ] not done

Further allow the start attr, sometimes generated for ordered lists.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-21 17:37:38 +02:00
Thomas Lamprecht
d1c4a7389f css: markdown: add some nicer table styling
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-21 16:24:27 +02:00
Thomas Lamprecht
59754b7bf8 bump version to 3.1-2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-18 15:32:31 +02:00
Thomas Lamprecht
51a2f11c6b integrate marked as markdown parser
Define our own, rather minimal interface so that we change the parser
under the hood if ever needed, I already did so once during
evaluating this, as first I checked out Snarkdown[0], which is really
nice for the few lines of code it needs, but is a bit to limited for
the use case.

Currently marked[1] is used, provided by the libjs-marked Debian
package.

For now statically link the marked parser in on built time to avoid
the need to add new directories to serve in our pve/pmg/pbs proxies.
This is a bit ugly but can be cleaned up afterwards transparently
too.

We sanitize the produced HTML ourselves (most MD JS parser/renderer
don't do that) by creating a real, but not active, DOM tree and
recursively prune bad nodes/attrs from it and let it spit out HTML
again at the end. While a tad inefficient it really won't matter for
our use case, as the notes/comments we render are only a few KiB of
text and it's done on the client side anyway.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-18 15:19:29 +02:00
Thomas Lamprecht
00cbb47e09 d/copyright: mark license explicitly and indent like machine readable format
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-18 15:18:11 +02:00
Aaron Lauterer
3d886f9422 ui: network: add columns for vlan-id and vlan-raw-device
Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
2021-06-15 14:43:58 +02:00
Thomas Lamprecht
580ccacf1a buildsys: change upload dist to bullseye
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-08 09:46:54 +02:00
Thomas Lamprecht
142d5d2cdf combo grid: load: rework auto-selection and validity logic
We do not want to trigger an autoSelect if there's a value set, even
if it isn't found in the store, as that hides the fact that an (now)
invalid valid is configured from the user, which can be confusing if
something is not working, as when editing an object it seems like a
valid value is selected.

Further, if a value is set we mark the field as invalid from the
start, at least if it's neither disabled nor allowed to have a
value which is does not exists in the backing store.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-07 18:10:15 +02:00
Thomas Lamprecht
674895d354 combo grid: reformat/place comment
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-07 18:10:15 +02:00
Thomas Lamprecht
e8c1bffbcb bump version to 3.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 16:26:54 +02:00
Thomas Lamprecht
4957686b0d panel: disks: more static task window creation
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 16:26:54 +02:00
Thomas Lamprecht
94791efabb buildsys: cleanup variable names/grouping
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 16:10:58 +02:00
Thomas Lamprecht
b0e33a3e2a buildsys: enforce eslint for api-viewer
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 16:10:31 +02:00
Thomas Lamprecht
ac4fa7fee8 api-viewer: eslint fixes, code cleanups
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 16:08:32 +02:00
Thomas Lamprecht
5eb59676c3 api-viewer: drop unused clicmdhash
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 16:08:06 +02:00
Thomas Lamprecht
8556628c93 api viewer: rename gloabl schema variable and tell eslint about it
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 14:46:32 +02:00
Thomas Lamprecht
52428d6055 api viewer: eslint (auto) fixes + slight cleanup
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 14:43:51 +02:00
Thomas Lamprecht
2f80505306 don't shout: s/APIVIEWER/APIViewer/
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-02 14:43:13 +02:00
Dominik Csapak
ed9ae47edd add proxmox-widget-toolkit-dev package
used for various other packages to dev-depend on (e.g. pve-docs)
which only contains the Toolkit.js for now

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-02 13:44:37 +02:00
Dominik Csapak
4ae75df35d add api-viewer source
so that we can reuse it across products
source was 'pbs' since that had the most features (http upgrade check)

a few changes to combine pve/pbs/pmg:
* use an optional 'cliusage' function it it exists to determine CLI usage
* check allowtoken for undefined to see if it is allowed or not
* use 'pmxapi' instead of pbs/pmg/pveapi
* rename all occurrences of 'pve' to 'pmx'

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-02 13:44:37 +02:00
Dominik Csapak
53758e164a Toolkit: move defaultDownloadServerUrl override to panel/RRDChart
not all users of Toolkit.js have the charts lib loaded, which means
the class does not exist then.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-06-02 13:44:37 +02:00
Fabian Ebner
d453438aa0 disk list: add wipe disk button
which shows a confirm dialog with the most relevant information before actually
issuing the API call.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-02 13:19:42 +02:00
Fabian Ebner
e3a5040c55 disk list: move title bar initialization to initComponent
to conditionally add more buttons later on.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-02 13:19:42 +02:00
Thomas Lamprecht
557c45056c buildsys: touch incremental-lint in check target to avoid triggering twice
Without this the check and the, through the 'install' target
triggered, incremental lint target triggered a full eslint run.

Makes it similar to what PBS did from the beginning of eslint
inclusion..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-05-28 10:40:21 +02:00
Dominik Csapak
fe3a919417 panel/RRDChart: fix animation settings for highlight/marker
in extjs 7.0, the marker does not have the 'fx' property anymore,
but the now documented 'animation' property does not work as it seems.

instead set the animation settings directly on the chart. The only
downside is that this is now for *all* animations, not only the
fade in of the highlight. (the default duration is 500ms)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-28 07:56:23 +02:00
Dominik Csapak
a78ec00494 Toolkit: remove Datepicker fix
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
65ed0bf8b8 Toolkit: remove textarea fix
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
97b71d24a5 Toolkit: remove pie chart fix
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
10a6a6d5ef Toolkit: remove firefox touchscreen override
seems to be fixed, at least i could not reproduce here.
If users report this again, we can still revert it if necessary.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
9c1296e503 Toolkit: update focusJump override
upstream code changed a bit, update to current version

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
d78eb5ec99 Toolkit: update overrides for scroll fixes
while some scrolling issues where fixed since 6.0.1, some where introduced,
namely:
* for firefox, the correct event to listen to is 'wheel' not 'mousewheel'
* the spinner scroll direction was incorrect
* the boxOverflow scroll direction was incorrect
* the boxOverflow scroll amount was too high

functions were copied from extjs source, eslintified, and adapted

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
fef7d02477 panel/RRDChart: fix legend/undoZoom
the legend is by default of type 'sprite', rever to 'dom'
but we now have to unset the '.legend', else on destruction
extjs tries to destroy it twice

also change the onAfterAnimation listener to 'redraw', since
the original event does not exist anymore, add a buffer to it
so that it is not that heavy

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
ee4801472d css: add css changes for treelist
add a new class 'x-treelist-pve-nav' so that we can use
ui: 'pve-nav'
instead of
ui: 'nav'
which has some default styling we do not want

also overwrite the font to FontAwesome
(extjs 7.0 uses 'FontAwesome 5 Free' which we do not ship)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
322de5629f Toolkit: set clearPropertiesOnDestroy to false by default
instead of the upstream default of 'async'

we do this since it creates some problems with our callbacks which can
happen during component destruction. The upstream reasoning does not
really makes sense for us normally, since we do not keep any references
around for most things, and thus the garbage collector can claim it.

note that this is only for components, Ext.Base sets it to 'true' by
default and we do not change this since we normally do not extend
from non-components

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
518dd0b6d9 Toolkit: set download url for draw containers to '-'
so that there can be no privacy leak, since the default points
to senchas server

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Dominik Csapak
fba354621f data/ProxmoxProxy: set responseType to undefined for XMLHTTPRequest
extjs 7.0 gives the responseType to the XMLHTTPRequest (which
is 'json' for a json reader), but that means that the response is
automatically decoded by the browser, with no means to get the original
return back

in our case, for successful api calls it would work, but some of our
errors are plain text, not json, so the decoded json object is 'null'
and we lose the error information

revert the type to 'undefined' which tells the browser not do do any
decoding

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-05-27 13:37:36 +02:00
Thomas Lamprecht
d00e8b940a object grid: line-wrap cleanup
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-05-23 16:24:51 +02:00
Thomas Lamprecht
52d3f3b019 object grid: improve/add to documentation
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-05-21 17:15:21 +02:00
Thomas Lamprecht
bc9ae6029f object grid: allow one to declaratively specify rows
So that users of this component do not necesacrrily need to add an
initComponent override and make the `me.add_XYZ_row()` there, but
instead can use something like:

  gridRows: [
    {
      xtype: 'text',
      name: 'http-proxy',
      text: gettext('HTTP proxy'),
      defaultValue: Proxmox.Utils.noneText,
      vtype: 'HttpProxy',
      deleteEmpty: true,
    },
  ],

I avoid using `rows` as config key as that is internally used for
quite a few things, and potentially some existing users (did not
checked all). We can still switch to that easily if it is deemed to
be better...

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-05-21 17:15:03 +02:00
Thomas Lamprecht
f0f898d2dd object grid: code cleanup
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-05-21 16:38:37 +02:00
Thomas Lamprecht
a2f0234d48 bump version to 3.0-2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-05-14 10:35:03 +02:00
Lorenz Stechauner
32bb96f4c7 disk smart: fix non working smart value window
fix regression in refactor from commit 7eb1fb18ad

Reported in forum:
https://forum.proxmox.com/threads/gui-disks-tab-cant-showup-smart-values.89180/
https://forum.proxmox.com/threads/smart-values-bug-in-6-4-6.89179/

Signed-off-by: Lorenz Stechauner <l.stechauner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-05-14 10:34:28 +02:00
Thomas Lamprecht
c60c729250 bump version to 3.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-05-13 19:46:57 +02:00
146 changed files with 13900 additions and 1489 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
*.buildinfo
*.changes
*.deb
*.dsc
*.tar.xz
/proxmox-widget-toolkit-[0-9]*/
src/.lint-incremental
src/proxmox-dark/theme-proxmox-dark.css
src/proxmoxlib.js
src/proxmoxlib.min.js

View File

@ -1,46 +1,56 @@
include /usr/share/dpkg/pkg-info.mk
export DEB_VERSION_UPSTREAM_REVISION
export PACKAGE=proxmox-widget-toolkit
BUILDDIR ?= ${PACKAGE}-${DEB_VERSION_UPSTREAM}
DEB=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}_all.deb
DSC=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}.dsc
PACKAGE=proxmox-widget-toolkit
GITVERSION:=$(shell git rev-parse HEAD)
DEB=$(PACKAGE)_$(DEB_VERSION)_all.deb
DEV_DEB=$(PACKAGE)-dev_$(DEB_VERSION)_all.deb
${BUILDDIR}:
rm -rf ${BUILDDIR} ${BUILDDIR}.tmp
cp -a src/ ${BUILDDIR}.tmp
cp -a debian ${BUILDDIR}.tmp/
echo "git clone git://git.proxmox.com/git/proxmox-widget-toolkit.git\\ngit checkout ${GITVERSION}" > ${BUILDDIR}.tmp/debian/SOURCE
mv ${BUILDDIR}.tmp/ ${BUILDDIR}
DEBS=$(DEB) $(DEV_DEB)
DSC=$(PACKAGE)_$(DEB_VERSION).dsc
BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM)
$(BUILDDIR): GITVERSION:=$(shell git rev-parse HEAD)
$(BUILDDIR):
rm -rf $(BUILDDIR) $(BUILDDIR).tmp
cp -a src/ $(BUILDDIR).tmp
cp -a debian $(BUILDDIR).tmp/
echo "git clone git://git.proxmox.com/git/proxmox-widget-toolkit.git\\ngit checkout $(GITVERSION)" > $(BUILDDIR).tmp/debian/SOURCE
mv $(BUILDDIR).tmp/ $(BUILDDIR)
.PHONY: deb
deb: ${DEB}
${DEB}: ${BUILDDIR}
cd ${BUILDDIR}; dpkg-buildpackage -b -us -uc
lintian ${DEB}
deb: $(DEBS)
$(DEBS): $(BUILDDIR)
cd $(BUILDDIR); dpkg-buildpackage -b -us -uc
lintian $(DEBS)
.PHONY: dsc
dsc: ${DSC}
${DSC}: ${BUILDDIR}
cd ${BUILDDIR}; dpkg-buildpackage -S -us -uc -d
lintian ${DSC}
dsc: $(DSC)
$(MAKE) clean
$(MAKE) $(DSC)
lintian $(DSC)
$(DSC): $(BUILDDIR)
cd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d
sbuild: $(DSC)
sbuild $(DSC)
.PHONY: lint
lint: ${JSSRC}
${MAKE} -C src lint
lint: $(JSSRC)
$(MAKE) -C src lint
.PHONY: upload
upload: ${DEB}
tar cf - ${DEB} | ssh -X repoman@repo.proxmox.com -- upload --product pve,pmg,pbs --dist buster
upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION)
upload: $(DEBS)
tar cf - $(DEB) | ssh -X repoman@repo.proxmox.com -- upload --product pve,pmg,pbs --dist $(UPLOAD_DIST)
tar cf - $(DEV_DEB) | ssh -X repoman@repo.proxmox.com -- upload --product devel --dist $(UPLOAD_DIST)
distclean: clean
clean:
$(MAKE) -C src clean
rm -rf ${BUILDDIR} ${BUILDDIR}.tmp *.tar.gz *.dsc *.deb *.changes *.buildinfo
find . -name '*~' -exec rm {} ';'
rm -rf $(PACKAGE)-[0-9]*/ *.tar.* *.dsc *.deb *.changes *.buildinfo *.build
.PHONY: dinstall
dinstall: ${DEB}
dpkg -i ${DEB}
dinstall: $(DEBS)
dpkg -i $(DEBS)

945
debian/changelog vendored
View File

@ -1,29 +1,946 @@
proxmox-widget-toolkit (2.6-2) buster; urgency=medium
proxmox-widget-toolkit (4.3.7) bookworm; urgency=medium
* add EOL notice component
* authentication realm edit: use correct property to derive the realm type.
-- Proxmox Support Team <support@proxmox.com> Tue, 15 Mar 2022 17:29:26 +0100
* authentication view: allow downstream users to override the API path to
query available authentication realms from.
proxmox-widget-toolkit (2.6-1) buster; urgency=medium
* from: realm combobox: allow downstream users to override the API path to
query available authentication realms from.
* combo grid: load: rework auto-selection and validity logic to ensure it's
always shown if a (now) invalid value is configured
-- 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: avoid potential type error in classifyOrigin helper
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jun 2023 17:29:44 +0200
proxmox-widget-toolkit (4.0.3) bookworm; urgency=medium
* date time field: fix typo in xtype name but add alias for backward compat
* set 'SameSite' attr of auth cookie to 'strict', which modern browsers
already ensured in practice
* apt repositories: actually ignore ignore-pre-upgrade-warning
* apt repositories: just ignore unknown info rather than throwing an error
* apt repositories: detect mixed suites before major upgrade
* tfa: improve UX for recovery keys and when none are left
* tfa: show 'Locked' in 'Enabled' column if tfa is locked
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jun 2023 08:07:01 +0200
proxmox-widget-toolkit (4.0.2) bookworm; urgency=medium
* markdown parser: allow setting "target" attribute for links
* fix #4756: markdown parser: allow any valid URL for link (a) tags,
allowing one to add short-cuts like a RDP url to quickly open the remote
viewer.
* ship a minified version of the widget-toolkit JS library
-- Proxmox Support Team <support@proxmox.com> Sat, 03 Jun 2023 13:45:27 +0200
proxmox-widget-toolkit (4.0.1) bookworm; urgency=medium
* tfa: paperkey: cleanup iframes for printing after window close
* parser: adapt to calling convention of updated Markdown renderer library
"marked" has since its v4.0.0
* fix #4551: ui: translate byte unit in `format_size`
-- Proxmox Support Team <support@proxmox.com> Thu, 01 Jun 2023 16:35:32 +0200
proxmox-widget-toolkit (4.0.0) bookworm; urgency=medium
* re-build for Debian 12 Bookworm based releases
-- Proxmox Support Team <support@proxmox.com> Thu, 25 May 2023 09:13:29 +0200
proxmox-widget-toolkit (3.7.0) bullseye; urgency=medium
* dark-mode:
- fix focus and focus-over states for tabs
- fix the focused state for background image grid icons
- style the icon for the datastore maintenance mode
- improve apt repo group header contrast ratios
- adjust panel header tool icons
- fix #4618: lighten critical/warning charts/gauges colors
* form: combo grid: use correct method to initialize the picker to ensure
it's cleaned up again after closed.
-- Proxmox Support Team <support@proxmox.com> Wed, 17 May 2023 14:02:50 +0200
proxmox-widget-toolkit (3.6.5) bullseye; urgency=medium
* window: ldap auth edit: avoid relying on the default bind property
* window: ldap auth edit: set view-model form data explicitly on edit to
avoid a data race in chromium based browser that could result in a
mismatch of the configured value and the initially shown one.
-- Proxmox Support Team <support@proxmox.com> Tue, 28 Mar 2023 17:56:10 +0200
proxmox-widget-toolkit (3.6.4) bullseye; urgency=medium
* dark-mode:
- improve contrast on split buttons
- color the custom grid and tree icons
- set boundlist (combo box picker) background so that loading progress or
errors are styled correctly too
- add a small white padding to the TOTP QR-code, as some apps are confused
otherwise
- fix #4617: increase brightness of tree expand/collapse arrows to avoid
overly low contrast
* fix #4612: mobile: avoid crash due to missing getProxy method
* theme edit window: ensure that the saved theme is actually valid
* language selector: translate entries to both native and localized variants
* language selector: increase picker list view width
-- Proxmox Support Team <support@proxmox.com> Sun, 26 Mar 2023 17:52:48 +0200
proxmox-widget-toolkit (3.6.3) bullseye; urgency=medium
* dark-mode fine-tuning:
- fix highlighting of active elements in drop down menus
- set the icon color of filtered column headers properly
- style checkboxes that don't use blueish active states
- style locked guest icons properly
- tone down border on ceph install card-like window
-- Proxmox Support Team <support@proxmox.com> Wed, 22 Mar 2023 13:25:05 +0100
proxmox-widget-toolkit (3.6.2) bullseye; urgency=medium
* network edit: add tooltip to bridge ports inputs
* dark-mode: reduce background mask opacity to 0.5
* dark-mode: make window shadow black again to avoid a backlight that some
have very strong opionions against
* rename "Theme" selector to "Color Theme" to add some context for
translation
-- Proxmox Support Team <support@proxmox.com> Tue, 21 Mar 2023 16:46:27 +0100
proxmox-widget-toolkit (3.6.1) bullseye; urgency=medium
* repo view: replace non-clickable checkbox with icons
* auth ui: add LDAP realm-edit panel and sync UI, refactored & adapted from
the pve-manager implementation for future reuse
* dark-theme: improve help button contrast ratios in focused state
* dark-theme: make "sorted-by" header highlight more subtle
* dark-theme: dim warning and invalid colors more
* dark-theme: let the background "shine through" mask more to avoid that
information on it becomes unreadable
-- Proxmox Support Team <support@proxmox.com> Mon, 20 Mar 2023 14:13:42 +0100
proxmox-widget-toolkit (3.6.0) bullseye; urgency=medium
* node apt: make changelog window taller for 4:3 ratio and cleanup/modernize
code
* ui: SMART: show SMART data in correct columns with correct values
* fix #4421: ui: guard setProxy against races of slow vs fast requests
* dark-theme: add initial version of the proxmox-dark theme
* subscription/summary/backup: stop setting the background color
* gauge widget: add support for a dark theme and dynamic theme switching
* rrd chart: add support for the dark theme and dynamic theme switching
* form: add a theme selector window
* dark-theme:
- fix summary row background
- increase contrast on check-boxes
- visually remove the border around the pve resource tree
- remove thicker borders around content
- re-work buttons colors to appear dimmer
- make windows stand out more against the background mask
* fix #4585 : toolkit: configid type: add missing "-" character support
* input panel: improve validity change check for advanced fields
* auth-realm selector: add custom store filters for callers
-- Proxmox Support Team <support@proxmox.com> Tue, 14 Mar 2023 16:25:37 +0100
proxmox-widget-toolkit (3.5.5) bullseye; urgency=medium
* combobox grid: use the grids view for the error message
* combobox grid: make height for the error configurable
* combobox grid: instead of hiding the picker collapse it, keeping the
internal state consitent which avoids, among other things, the need
fo two clicks after re-selecting an item
* utils: always html-encode response message from errors to avoid rendering
glitches, that while not known to be problematic from a safety POV, are
possibly odd for people to find.
* form: display-edit: add safe default renderer for display field to avoid
unproblematic, but possible odd and glitchy side-effects from the
value-bind if the display-edit field is in iput mode.
* api request: add wide spread alert-error logic as smart-on option
-- Proxmox Support Team <support@proxmox.com> Tue, 31 Jan 2023 17:27:41 +0100
proxmox-widget-toolkit (3.5.4) bullseye; urgency=medium
* api-viewer: allow text selection in the parameter and the return grids
* task viewer: add optional button to download full task-log
* permission role selector: fix renderer for column of included privileges
for Proxmox VE
* permission role selector: make slightly more wide and resizeable
* node network view: rework finding free interface ID and move add-menu
generation to common helper (no semantic change intended)
-- Proxmox Support Team <support@proxmox.com> Wed, 11 Jan 2023 16:09:53 +0100
proxmox-widget-toolkit (3.5.3) bullseye; urgency=medium
* css: do not make full-style tags display as inline-block in the tree
to avoid height jumps
* log, journal view: fix access to `me` after destroying
-- Proxmox Support Team <support@proxmox.com> Mon, 21 Nov 2022 11:14:27 +0100
proxmox-widget-toolkit (3.5.2) bullseye; urgency=medium
* host disks: add 'mounted' column
* host disk: handle partition data from Proxmox Backup Server backend
* number field: avoid that a single up/down arrow key press
increment or decrements twice
* toolkit: make email regex pattern match pve-common
* css: import action column fix from pbs, pmg
* fix #2703: networkedit: limit custom interface name field to 15
characters.
* task progress: show text instead of bogus percentage
* fix #3593: add CPU affinity task set type
* input panel: add onSetValues hook
* add tag related helpers
* toolkit: add override for ExtJS DragDropManager to fix selection behavior
in the drag zone
* rdd charts: don't display power-of-two suffix 'i' for values without unit
suffix
* fix #4271: api-viewer: display nested formats instead of `[object Object]`
* api-viewer: show min/max for values without any other format
-- Proxmox Support Team <support@proxmox.com> Thu, 17 Nov 2022 08:37:26 +0100
proxmox-widget-toolkit (3.5.1) bullseye; urgency=medium
* pxar file types: fix over-eager s/text/label/ so that text file icon is
shown again
* file browser: disable item # size rendering
-- Proxmox Support Team <support@proxmox.com> Mon, 16 May 2022 18:03:35 +0200
proxmox-widget-toolkit (3.5.0) bullseye; urgency=medium
* file browser: try reload again when getting a 503 error
* ui: acl role selector: make the picker grid wider and ensure that the
text wraps in the privilege column
* fix #4001: file browser: add a configurable prefix to downloaded files
* fix #4001: file browser: show number of items in a directory as size, if
available
* file browser: align size column to end/right
* file browser: only disable, not hide button if not downloadable and add
hint for why and what to do in tooltip
* switch to native version format for native package
-- Proxmox Support Team <support@proxmox.com> Sun, 15 May 2022 11:46:54 +0200
proxmox-widget-toolkit (3.4-10) bullseye; urgency=medium
* css: add proxmox-good-row class
* status view: fix usage calculation for fields without valid values, like
for example SWAP can often be.
* buttons: add AltText to unify the hack to detect the max size of a button
that switches its text dynamically
-- Proxmox Support Team <support@proxmox.com> Wed, 27 Apr 2022 18:58:21 +0200
proxmox-widget-toolkit (3.4-9) bullseye; urgency=medium
* file browser: optionally allow showing a "download as tar.zst"
button, if supported
* tab buttons: fix vertial centering of text and reduce padding
slightly
* move over the markdown based notes view panel and edit window from
pve-manager for reuse
-- Proxmox Support Team <support@proxmox.com> Thu, 14 Apr 2022 07:56:54 +0200
proxmox-widget-toolkit (3.4-8) bullseye; urgency=medium
* fix #3919: log view: show first task output line correctly
* combo grid: clear filter on blur
* utils: clear cookies with secure flag set to avoid bogus browser
warning
* node tasks: do not count preset filters as normal filters to avoid
"clear filter" button glitch
* icons: switch cpu and ram bitmaps to svg
* object grid: call rendere with our scope
* dns view: increase api polling intervall from 1s to 10s
-- Proxmox Support Team <support@proxmox.com> Tue, 12 Apr 2022 16:45:50 +0200
proxmox-widget-toolkit (3.4-7) bullseye; urgency=medium
* extjs: fix check for 'touch' input event in drag and drop handler, making
the 'pen' pointer event source type, that chrome/chromium emits in some
setups, work again.
-- Proxmox Support Team <support@proxmox.com> Wed, 23 Feb 2022 12:12:13 +0100
proxmox-widget-toolkit (3.4-6) bullseye; urgency=medium
* utils: render language: fix rendering special default value
* sorters: use correct property 'direction' and keep default 'ASC'
-- Proxmox Support Team <support@proxmox.com> Mon, 14 Feb 2022 11:34:42 +0100
proxmox-widget-toolkit (3.4-5) bullseye; urgency=medium
* login: tfa: hide u2f and yubico-otp if not available
* improve error handling when adding webauthn entries
* toolkit: fix noisy ext warning of feature we do not want/use
* zfs detail: increase default window height
* zfs detail: hide the pool itself in tree view
-- Proxmox Support Team <support@proxmox.com> Thu, 13 Jan 2022 12:52:18 +0100
proxmox-widget-toolkit (3.4-4) bullseye; urgency=medium
* utils: format duration: render years when we can avoid huge day numbers
* journalview: fix wrong initial load with default timespan on widget
creation
* logpanel: fix glitching on fast task logs
* logpanel: actually catch up when following the log for tasks with an
almost artificially high log output traffic
* log viewer: add heuristic for scroll-direction dependent ratio
distribution
* log viewer: add heuristic for triggering a new limit load earlier to
reduce latency on casual scrolling
-- Proxmox Support Team <support@proxmox.com> Wed, 24 Nov 2021 18:29:56 +0100
proxmox-widget-toolkit (3.4-3) bullseye; urgency=medium
* data: diffstore: fix autoDestroyRstore option (regression from ExtJS 7)
* ui: OpenID edit: make username-claim field editable for arbitrary values
* ui: OpenID realm: allow to edit scopes
* ui: OpenID realm: allow to edit prompt
* ui: OpenID realm: allow to edit acr values
* form: copy BandwidthSelector/SizeField from Proxmox VE's manager
* bandwidth/utils: move out SizeUnits definition to more common module
* utils: add size unit related helpers to parse/auto-scale/format
* bandwidth field: allow to submit auto-scaled size-units as string
-- Proxmox Support Team <support@proxmox.com> Sat, 20 Nov 2021 21:41:37 +0100
proxmox-widget-toolkit (3.4-2) bullseye; urgency=medium
* TFA login window: fix a formatted label showed when being low on unused
recovery-keys
* proxmox checkbox: add clearOnDisable config
-- Proxmox Support Team <support@proxmox.com> Mon, 15 Nov 2021 10:23:34 +0100
proxmox-widget-toolkit (3.4-1) bullseye; urgency=medium
* panel/RRDCharts: enable scrolling for RRDCharts on touchscreens
* disk selector: allow requesting partitions too
* fix #3589: show device name in title for SMART values window
* cbind: document cbind by adding a small summary and example
* add common utils used for u2f and webauthn, adapted from PVE and PBS,
respectively
* add TFA-login, TOTP, WebAuthn and recover-key edit windows for better reuse
* disk list: allow wiping individual partitions
-- Proxmox Support Team <support@proxmox.com> Thu, 11 Nov 2021 21:11:16 +0100
proxmox-widget-toolkit (3.3-6) bullseye; urgency=medium
* fix #3542: node: task logs: query correct node for tasks in clusters
* node: add a, by default hidden, MTU column in network view
-- Proxmox Support Team <support@proxmox.com> Tue, 27 Jul 2021 16:41:01 +0200
proxmox-widget-toolkit (3.3-5) bullseye; urgency=medium
* node: repos: add possibility to link online help
* api-viewer: drop extra slash in api path
* apt: match "Debian Backports" origin as Debian one
* add new shared component for the package version window
-- Proxmox Support Team <support@proxmox.com> Mon, 19 Jul 2021 17:52:08 +0200
proxmox-widget-toolkit (3.3-4) bullseye; urgency=medium
* acme: allow wildcards as domain
* service view: avoid showing not installed services as error
* service view: fix stale stop/restart button enabled behavior
* service view: disable all buttons for masked/not-found/unknown services
-- Proxmox Support Team <support@proxmox.com> Tue, 13 Jul 2021 18:42:51 +0200
proxmox-widget-toolkit (3.3-3) bullseye; urgency=medium
* realm view/edit: make more generic for better reuse
-- Proxmox Support Team <support@proxmox.com> Mon, 12 Jul 2021 09:52:27 +0200
proxmox-widget-toolkit (3.3-2) bullseye; urgency=medium
* node: repos: only show suites warning at the top if enabled repository is
affected
* move over authentication-real edit window widget from Proxmox VE
* utils: add helper to format node's repository status
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jul 2021 17:30:43 +0200
proxmox-widget-toolkit (3.2-5) bullseye; urgency=medium
* network: always ask for confirmation before removing a network
interface from the configuration. While it is not that dangerous as we
have a pending config that needs to get applied, it still is nicer to do
for remove actions.
* node: tasks: use helper to format status again for a localized warnings
text
* window: safe-destroy: add taskDone and apiCallDone callbacks
-- Proxmox Support Team <support@proxmox.com> Thu, 08 Jul 2021 14:30:44 +0200
proxmox-widget-toolkit (3.2-4) bullseye; urgency=medium
* node: APT repositories: upgrade "no Proxmox product repo configured" from
warning to error
* node: task history: deselect entries when filter changes
* node: task history: show errors on store load
* node: task history: add 'clear filter' button
-- Proxmox Support Team <support@proxmox.com> Mon, 05 Jul 2021 16:50:23 +0200
proxmox-widget-toolkit (3.2-3) bullseye; urgency=medium
* node: repos: handle that components can be undefined
* markdown: encode bad nodes HTML instead of pruning it
* markdown: make sanitizer more strict in filtering tags and ensure that the
src and the href attributes point to a HTTP url, or is a data-url on a
image.
* info widget: early return from update if text & value stayed the same
* utils: updateColumnWidth: drop duplicate implementation and allow
overriding tresholdWidth
* utils: updateColumnWidth: directly calculate column count by threshold,
automatically using more columns on wide containers.
-- Proxmox Support Team <support@proxmox.com> Mon, 05 Jul 2021 10:10:47 +0200
proxmox-widget-toolkit (3.2-2) bullseye; urgency=medium
* avoid using unique ids for components that may have more than one instance
at the same time. Fixes and issue with switching between nodes on the new
APT Repository panel.
-- Proxmox Support Team <support@proxmox.com> Mon, 05 Jul 2021 09:47:46 +0200
proxmox-widget-toolkit (3.2-1) bullseye; urgency=medium
* css: some markdown heading and paragraph font-size & padding tuning
* node: services: fix logic for displaying unit state
* add Debian and Proxmox symbol logos and css
* node: apt: spawn a window for adding repository
* utils: add getOpenIDRedirectionAuthorization helper
* factor out userid parsing and username, realm renderer to Utils
* add OpenID icon + css class
* node: APT Repositories: rework top status and error grid
-- Proxmox Support Team <support@proxmox.com> Sat, 03 Jul 2021 00:12:34 +0200
proxmox-widget-toolkit (3.1-4) bullseye; urgency=medium
* cbind mixin: also descend in elements with an cbind property
-- Proxmox Support Team <support@proxmox.com> Fri, 25 Jun 2021 09:32:40 +0200
* node: tasks: merge improved Filters over from Proxmox Backup Server
proxmox-widget-toolkit (2.5-6) buster; urgency=medium
-- Proxmox Support Team <support@proxmox.com> Mon, 28 Jun 2021 19:14:10 +0200
proxmox-widget-toolkit (3.1-3) bullseye; urgency=medium
* css: markdown: add some nicer table, blockquote and task-list checkbox
styling
* Journal View: fix flickering in journal livemode
* node/services: optionally show unit-, and active-states
* add initial building blocks for an APT repositories UI
-- Proxmox Support Team <support@proxmox.com> Wed, 23 Jun 2021 23:11:37 +0200
proxmox-widget-toolkit (3.1-2) bullseye; urgency=medium
* ui: network: add, by default hidden, columns for the `vlan-id` and the
`vlan-raw-device`
* add interface for markdown parser and wire-up marked to it
-- Proxmox Support Team <support@proxmox.com> Fri, 18 Jun 2021 15:32:27 +0200
proxmox-widget-toolkit (3.1-1) bullseye; urgency=medium
* support ExtJS 7
* object grid: allow one to declaratively specify rows
-- Proxmox Support Team <support@proxmox.com> Fri, 21 May 2021 17:39:59 +0200
* disk list: add wipe disk button which users of this widget can opt-in
proxmox-widget-toolkit (2.5-5) pve pmg; urgency=medium
* data/ProxmoxProxy: set responseType to undefined for XMLHTTPRequest
* ship api-viewer in new proxmox-widget-toolkit-dev package to improve
possibility for code reuse
-- Proxmox Support Team <support@proxmox.com> Wed, 02 Jun 2021 16:16:02 +0200
proxmox-widget-toolkit (3.0-2) bullseye; urgency=medium
* disks: fix regression in S.M.A.R.T. window
-- Proxmox Support Team <support@proxmox.com> Fri, 14 May 2021 10:30:39 +0200
-- Proxmox Support Team <support@proxmox.com> Fri, 14 May 2021 10:34:57 +0200
proxmox-widget-toolkit (3.0-1) bullseye; urgency=medium
* re-build for Debian 11 Bullseye based releases
-- Proxmox Support Team <support@proxmox.com> Thu, 13 May 2021 19:46:29 +0200
proxmox-widget-toolkit (2.5-4) pve pmg; urgency=medium
@ -32,7 +949,7 @@ proxmox-widget-toolkit (2.5-4) pve pmg; urgency=medium
* node disk: S.M.A.R.T.: improve the simple layout and enable autoscroll for
long output
* format/render size: allow one to specifiy if base 2 or 10 (SI unit) is
* format/render size: allow one to specify if base 2 or 10 (SI unit) is
desired
-- Proxmox Support Team <support@proxmox.com> Fri, 07 May 2021 18:00:29 +0200
@ -48,7 +965,7 @@ proxmox-widget-toolkit (2.5-2) pve pmg; urgency=medium
* rrd chart: add option to render values and Y-axis with a power-of-two base
* safe destroy: allow specifing additional items
* safe destroy: allow specifying additional items
* utils: add several render and helper functions from Proxmox VE's manager
@ -729,7 +1646,7 @@ proxmox-widget-toolkit (1.0-6) unstable; urgency=medium
* change 'create' parameter to 'isCreate'
* make network devices types configureable
* make network devices types configurable
* use Proxmox.window.TaskProgress instead of PVE.window.TaskProgress

1
debian/compat vendored
View File

@ -1 +0,0 @@
12

21
debian/control vendored
View File

@ -2,13 +2,24 @@ Source: proxmox-widget-toolkit
Section: web
Priority: optional
Maintainer: Proxmox Support Team <support@proxmox.com>
Build-Depends: debhelper (>= 12~),
pve-eslint (>= 7.12.1-1),
Standards-Version: 4.5.1
Build-Depends: debhelper-compat (= 13),
libjs-marked,
pve-eslint (>= 7.28.0),
sassc,
uglifyjs,
Standards-Version: 4.6.2
Homepage: https://www.proxmox.com
Package: proxmox-widget-toolkit
Architecture: all
Depends: ${misc:Depends}
Description: ExtJS Helper Classes for Proxmox
ExtJS Helper Classes to easy access to Proxmox APIs.
Description: Core Widgets and ExtJS Helper Classes for Proxmox Web UIs
The base framework providing widgets, models, and general utilities for the
ExtJS based Web UIs of various Proxmox projects
Package: proxmox-widget-toolkit-dev
Architecture: all
Depends: ${misc:Depends}
Description: ExtJS based widgets and utilities for development
Contains some common JavaScript code that some Proxmox projects might used to
build common interfaces, like the API viewer in each documnetation repo.

30
debian/copyright vendored
View File

@ -2,15 +2,25 @@ Copyright (C) 2010-2021 Proxmox Server Solutions GmbH
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
License: AGPLv3
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
Marked:
The Marked JavaScript library is shipped through linkage from the Debian
package unmodified alongside proxmox-widget-toolkit.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/) Copyright (c)
2011-2018, Christopher Jeffrey (https://github.com/chjj/)
For the license and copyright details see `/usr/share/doc/libjs-marked/copyright`

View File

@ -0,0 +1,2 @@
Toolkit.js /usr/share/javascript/proxmox-widget-toolkit-dev/
api-viewer/APIViewer.js /usr/share/javascript/proxmox-widget-toolkit-dev/

1
debian/proxmox-widget-toolkit.docs vendored Normal file
View File

@ -0,0 +1 @@
debian/SOURCE

1
debian/proxmox-widget-toolkit.install vendored Normal file
View File

@ -0,0 +1 @@
/usr/share/javascript/proxmox-widget-toolkit

4
debian/rules vendored
View File

@ -3,6 +3,10 @@
# output every command that modifies files on the build system.
#DH_VERBOSE = 1
include /usr/share/dpkg/pkg-info.mk
export DEB_SOURCE
export DEB_VERSION
%:
dh $@

View File

@ -1 +1 @@
1.0
3.0 (native)

View File

@ -1,11 +1,19 @@
include defines.mk
SUBDIRS= css images
ESLINT ?= $(if $(shell command -v pve-eslint), pve-eslint, eslint)
SUBDIRS= css images proxmox-dark
# bundle it for now from the libjs-marked debian package to avoid touching our proxies file mapper,
# we could also just ship a link to the packages file and load from same path as the widget-toolkit
MARKEDJS=/usr/share/javascript/marked/marked.js
JSSRC= \
Utils.js \
Schema.js \
Toolkit.js \
Logo.js \
Parser.js \
mixin/CBind.js \
data/reader/JsonObject.js \
data/ProxmoxProxy.js \
@ -14,13 +22,17 @@ JSSRC= \
data/ObjectStore.js \
data/RRDStore.js \
data/TimezoneStore.js \
data/model/NotificationConfig.js \
data/model/Realm.js \
data/model/Certificates.js \
data/model/ACME.js \
form/BandwidthSelector.js \
form/DisplayEdit.js \
form/ExpireDate.js \
form/IntegerField.js \
form/TextField.js \
form/TextAreaField.js \
form/VlanField.js \
form/DateTimeField.js \
form/Checkbox.js \
form/KVComboBox.js \
@ -36,38 +48,71 @@ JSSRC= \
form/MultiDiskSelector.js \
form/TaskTypeSelector.js \
form/ACME.js \
form/UserSelector.js \
form/ThemeSelector.js \
form/FingerprintField.js \
button/Button.js \
button/AltText.js \
button/HelpButton.js \
grid/ObjectGrid.js \
grid/PendingObjectGrid.js \
panel/EOLNotice.js \
panel/AuthView.js \
panel/DiskList.js \
panel/EOLNotice.js \
panel/InputPanel.js \
panel/InfoWidget.js \
panel/LogView.js \
panel/NodeInfoRepoStatus.js \
panel/NotificationConfigView.js \
panel/JournalView.js \
panel/PermissionView.js \
panel/PruneKeepPanel.js \
panel/RRDChart.js \
panel/GaugeWidget.js \
panel/GotifyEditPanel.js \
panel/Certificates.js \
panel/ACMEAccount.js \
panel/ACMEPlugin.js \
panel/ACMEDomains.js \
panel/EmailRecipientPanel.js \
panel/SendmailEditPanel.js \
panel/SmtpEditPanel.js \
panel/StatusView.js \
panel/TfaView.js \
panel/NotesView.js \
panel/WebhookEditPanel.js \
window/Edit.js \
window/PasswordEdit.js \
window/SafeDestroy.js \
window/PackageVersions.js \
window/TaskViewer.js \
window/LanguageEdit.js \
window/DiskSmart.js \
window/ZFSDetail.js \
window/Certificates.js \
window/ConsentModal.js \
window/ACMEAccount.js \
window/ACMEPluginEdit.js \
window/ACMEDomains.js \
window/EndpointEditBase.js \
window/NotificationMatcherEdit.js \
window/FileBrowser.js \
window/AuthEditBase.js \
window/AuthEditOpenId.js \
window/AuthEditLDAP.js \
window/AuthEditAD.js \
window/AuthEditSimple.js \
window/TfaWindow.js \
window/AddTfaRecovery.js \
window/AddTotp.js \
window/AddWebauthn.js \
window/AddYubico.js \
window/TfaEdit.js \
window/NotesEdit.js \
window/ThemeEdit.js \
window/SyncWindow.js \
node/APT.js \
node/APTRepositories.js \
node/NetworkEdit.js \
node/NetworkView.js \
node/DNSEdit.js \
@ -78,30 +123,38 @@ JSSRC= \
node/TimeEdit.js \
node/TimeView.js
all: ${SUBDIRS}
set -e && for i in ${SUBDIRS}; do ${MAKE} -C $$i; done
all: $(SUBDIRS)
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i; done
.lint-incremental: ${JSSRC}
eslint $?
.lint-incremental: $(JSSRC)
$(ESLINT) $?
touch "$@"
.PHONY: lint
check: lint
lint: ${JSSRC}
eslint --strict ${JSSRC}
$(ESLINT) --strict api-viewer/APIViewer.js
lint: $(JSSRC)
$(ESLINT) --strict $(JSSRC)
touch ".lint-incremental"
proxmoxlib.js: .lint-incremental ${JSSRC}
BUILD_TIME=$(or $(SOURCE_DATE_EPOCH),$(shell date '+%s.%N'))
BUILD_VERSION=$(or $(DEB_VERSION),$(shell git rev-parse HEAD),unknown version)
proxmoxlib.js: .lint-incremental $(JSSRC)
# add the version as comment in the file
echo "// ${DEB_VERSION_UPSTREAM_REVISION}" > $@.tmp
cat ${JSSRC} >> $@.tmp
echo "// v$(BUILD_VERSION)-t$(BUILD_TIME)" > $@.tmp
cat $(JSSRC) $(MARKEDJS) >> $@.tmp
mv $@.tmp $@
install: proxmoxlib.js
install -d -m 755 ${WWWBASEDIR}
install -m 0644 proxmoxlib.js ${WWWBASEDIR}
set -e && for i in ${SUBDIRS}; do ${MAKE} -C $$i $@; done
proxmoxlib.min.js: proxmoxlib.js
uglifyjs $< -c -m -o $@.tmp
mv $@.tmp $@
install: proxmoxlib.js proxmoxlib.min.js
install -d -m 755 $(WWWBASEDIR)
install -m 0644 proxmoxlib.js proxmoxlib.min.js $(WWWBASEDIR)
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
.PHONY: clean
clean:
rm -f proxmoxlib.js
$(MAKE) -C proxmox-dark $@
rm -f proxmoxlib.js* proxmoxlib.min.js* .lint-incremental

74
src/Parser.js Normal file
View File

@ -0,0 +1,74 @@
// NOTE: just relays parsing to markedjs parser
Ext.define('Proxmox.Markdown', {
alternateClassName: 'Px.Markdown', // just trying out something, do NOT copy this line
singleton: true,
// transforms HTML to a DOM tree and recursively descends and HTML-encodes every branch with a
// "bad" node.type and drops "bad" attributes from the remaining nodes.
// "bad" means anything which can do XSS or break the layout of the outer page
sanitizeHTML: function(input) {
if (!input) {
return input;
}
let _isHTTPLike = value => value.match(/^\s*https?:/i); // URL's protocol ends with :
let _sanitize;
_sanitize = (node) => {
if (node.nodeType === 3) return;
if (node.nodeType !== 1 ||
/^(script|style|form|select|option|optgroup|map|area|canvas|textarea|applet|font|iframe|audio|video|object|embed|svg)$/i.test(node.tagName)
) {
// could do node.remove() instead, but it's nicer UX if we keep the (encoded!) html
node.outerHTML = Ext.String.htmlEncode(node.outerHTML);
return;
}
for (let i=node.attributes.length; i--;) {
const name = node.attributes[i].name;
const value = node.attributes[i].value;
const canonicalTagName = node.tagName.toLowerCase();
// TODO: we may want to also disallow class and id attrs
if (
!/^(class|id|name|href|src|alt|align|valign|disabled|checked|start|type|target)$/i.test(name)
) {
node.attributes.removeNamedItem(name);
} else if ((name === 'href' || name === 'src') && !_isHTTPLike(value)) {
let safeURL = false;
try {
let url = new URL(value, window.location.origin);
safeURL = _isHTTPLike(url.protocol);
if (canonicalTagName === 'img' && url.protocol.toLowerCase() === 'data:') {
safeURL = true;
} else if (canonicalTagName === 'a') {
// allow most link protocols so admins can use short-cuts to, e.g., RDP
safeURL = url.protocol.toLowerCase() !== 'javascript:'; // eslint-disable-line no-script-url
}
if (safeURL) {
node.attributes[i].value = url.href;
} else {
node.attributes.removeNamedItem(name);
}
} catch (e) {
node.attributes.removeNamedItem(name);
}
} else if (name === 'target' && canonicalTagName !== 'a') {
node.attributes.removeNamedItem(name);
}
}
for (let i=node.childNodes.length; i--;) _sanitize(node.childNodes[i]);
};
const doc = new DOMParser().parseFromString(`<!DOCTYPE html><html><body>${input}`, 'text/html');
doc.normalize();
_sanitize(doc.body);
return doc.body.innerHTML;
},
parse: function(markdown) {
/*global marked*/
let unsafeHTML = marked.parse(markdown);
return `<div class="pmx-md">${this.sanitizeHTML(unsafeHTML)}</div>`;
},
});

99
src/Schema.js Normal file
View File

@ -0,0 +1,99 @@
Ext.define('Proxmox.Schema', { // a singleton
singleton: true,
authDomains: {
pam: {
name: 'Linux PAM',
ipanel: 'pmxAuthSimplePanel',
onlineHelp: 'user-realms-pam',
add: false,
edit: true,
pwchange: true,
sync: false,
useTypeInUrl: false,
},
openid: {
name: gettext('OpenID Connect Server'),
ipanel: 'pmxAuthOpenIDPanel',
add: true,
edit: true,
tfa: false,
pwchange: false,
sync: false,
iconCls: 'pmx-itype-icon-openid-logo',
useTypeInUrl: true,
},
ldap: {
name: gettext('LDAP Server'),
ipanel: 'pmxAuthLDAPPanel',
syncipanel: 'pmxAuthLDAPSyncPanel',
add: true,
edit: true,
tfa: true,
pwchange: false,
sync: true,
useTypeInUrl: true,
},
ad: {
name: gettext('Active Directory Server'),
ipanel: 'pmxAuthADPanel',
syncipanel: 'pmxAuthADSyncPanel',
add: true,
edit: true,
tfa: true,
pwchange: false,
sync: true,
useTypeInUrl: true,
},
},
// to add or change existing for product specific ones
overrideAuthDomains: function(extra) {
for (const [key, value] of Object.entries(extra)) {
Proxmox.Schema.authDomains[key] = value;
}
},
notificationEndpointTypes: {
sendmail: {
name: 'Sendmail',
ipanel: 'pmxSendmailEditPanel',
iconCls: 'fa-envelope-o',
defaultMailAuthor: 'Proxmox VE',
},
smtp: {
name: 'SMTP',
ipanel: 'pmxSmtpEditPanel',
iconCls: 'fa-envelope-o',
defaultMailAuthor: 'Proxmox VE',
},
gotify: {
name: 'Gotify',
ipanel: 'pmxGotifyEditPanel',
iconCls: 'fa-bell-o',
},
webhook: {
name: 'Webhook',
ipanel: 'pmxWebhookEditPanel',
iconCls: 'fa-bell-o',
},
},
// to add or change existing for product specific ones
overrideEndpointTypes: function(extra) {
for (const [key, value] of Object.entries(extra)) {
Proxmox.Schema.notificationEndpointTypes[key] = value;
}
},
pxarFileTypes: {
b: { icon: 'cube', label: gettext('Block Device') },
c: { icon: 'tty', label: gettext('Character Device') },
d: { icon: 'folder-o', label: gettext('Directory') },
f: { icon: 'file-text-o', label: gettext('File') },
h: { icon: 'file-o', label: gettext('Hardlink') },
l: { icon: 'link', label: gettext('Softlink') },
p: { icon: 'exchange', label: gettext('Pipe/Fifo') },
s: { icon: 'plug', label: gettext('Socket') },
v: { icon: 'cube', label: gettext('Virtual') },
},
});

File diff suppressed because it is too large Load Diff

View File

@ -62,37 +62,44 @@ utilities: {
stateText: gettext('State'),
groupText: gettext('Group'),
language_map: {
ar: 'Arabic',
ca: 'Catalan',
zh_CN: 'Chinese (Simplified)',
zh_TW: 'Chinese (Traditional)',
da: 'Danish',
nl: 'Dutch',
en: 'English',
eu: 'Euskera (Basque)',
fr: 'French',
de: 'German',
he: 'Hebrew',
it: 'Italian',
ja: 'Japanese',
kr: 'Korean',
nb: 'Norwegian (Bokmal)',
nn: 'Norwegian (Nynorsk)',
fa: 'Persian (Farsi)',
pl: 'Polish',
pt_BR: 'Portuguese (Brazil)',
ru: 'Russian',
sl: 'Slovenian',
es: 'Spanish',
sv: 'Swedish',
tr: 'Turkish',
language_map: { //language map is sorted alphabetically by iso 639-1
ar: `العربية - ${gettext("Arabic")}`,
bg: `Български - ${gettext("Bulgarian")}`,
ca: `Català - ${gettext("Catalan")}`,
da: `Dansk - ${gettext("Danish")}`,
de: `Deutsch - ${gettext("German")}`,
en: `English - ${gettext("English")}`,
es: `Español - ${gettext("Spanish")}`,
eu: `Euskera (Basque) - ${gettext("Euskera (Basque)")}`,
fa: `فارسی - ${gettext("Persian (Farsi)")}`,
fr: `Français - ${gettext("French")}`,
hr: `Hrvatski - ${gettext("Croatian")}`,
he: `עברית - ${gettext("Hebrew")}`,
it: `Italiano - ${gettext("Italian")}`,
ja: `日本語 - ${gettext("Japanese")}`,
ka: `ქართული - ${gettext("Georgian")}`,
ko: `한국어 - ${gettext("Korean")}`,
nb: `Bokmål - ${gettext("Norwegian (Bokmal)")}`,
nl: `Nederlands - ${gettext("Dutch")}`,
nn: `Nynorsk - ${gettext("Norwegian (Nynorsk)")}`,
pl: `Polski - ${gettext("Polish")}`,
pt_BR: `Português Brasileiro - ${gettext("Portuguese (Brazil)")}`,
ru: `Русский - ${gettext("Russian")}`,
sl: `Slovenščina - ${gettext("Slovenian")}`,
sv: `Svenska - ${gettext("Swedish")}`,
tr: `Türkçe - ${gettext("Turkish")}`,
ukr: `Українська - ${gettext("Ukrainian")}`,
zh_CN: `中文(简体)- ${gettext("Chinese (Simplified)")}`,
zh_TW: `中文(繁體)- ${gettext("Chinese (Traditional)")}`,
},
render_language: function(value) {
if (!value) {
if (!value || value === '__default__') {
return Proxmox.Utils.defaultText + ' (English)';
}
if (value === 'kr') {
value = 'ko'; // fix-up wrongly used Korean code. FIXME: remove with trixie releases
}
let text = Proxmox.Utils.language_map[value];
if (text) {
return text + ' (' + value + ')';
@ -100,6 +107,8 @@ utilities: {
return value;
},
renderEnabledIcon: enabled => `<i class="fa fa-${enabled ? 'check' : 'minus'}"></i>`,
language_array: function() {
let data = [['__default__', Proxmox.Utils.render_language('')]];
Ext.Object.each(Proxmox.Utils.language_map, function(key, value) {
@ -109,6 +118,31 @@ utilities: {
return data;
},
theme_map: {
crisp: 'Light theme',
"proxmox-dark": 'Proxmox Dark',
},
render_theme: function(value) {
if (!value || value === '__default__') {
return Proxmox.Utils.defaultText + ' (auto)';
}
let text = Proxmox.Utils.theme_map[value];
if (text) {
return text;
}
return value;
},
theme_array: function() {
let data = [['__default__', Proxmox.Utils.render_theme('')]];
Ext.Object.each(Proxmox.Utils.theme_map, function(key, value) {
data.push([key, Proxmox.Utils.render_theme(value)]);
});
return data;
},
bond_mode_gettext_map: {
'802.3ad': 'LACP (802.3ad)',
'lacp-balance-slb': 'LACP (balance-slb)',
@ -122,8 +156,11 @@ utilities: {
},
getNoSubKeyHtml: function(url) {
// url http://www.proxmox.com/products/proxmox-ve/subscription-service-plans
return Ext.String.format('You do not have a valid subscription for this server. Please visit <a target="_blank" href="{0}">www.proxmox.com</a> to get a list of available options.', url || 'https://www.proxmox.com');
let html_url = Ext.String.format('<a target="_blank" href="{0}">www.proxmox.com</a>', url || 'https://www.proxmox.com');
return Ext.String.format(
gettext('You do not have a valid subscription for this server. Please visit {0} to get a list of available options.'),
html_url,
);
},
format_boolean_with_default: function(value) {
@ -155,7 +192,7 @@ utilities: {
// somewhat like a human would tell durations, omit zero values and do not
// give seconds precision if we talk days already
format_duration_human: function(ut) {
let seconds = 0, minutes = 0, hours = 0, days = 0;
let seconds = 0, minutes = 0, hours = 0, days = 0, years = 0;
if (ut <= 0.1) {
return '<0.1s';
@ -171,7 +208,11 @@ utilities: {
hours = remaining % 24;
remaining = Math.trunc(remaining / 24);
if (remaining > 0) {
days = remaining;
days = remaining % 365;
remaining = Math.trunc(remaining / 365); // yea, just lets ignore leap years...
if (remaining > 0) {
years = remaining;
}
}
}
}
@ -182,11 +223,14 @@ utilities: {
return t > 0;
};
let addMinutes = !add(years, 'y');
let addSeconds = !add(days, 'd');
add(hours, 'h');
add(minutes, 'm');
if (addSeconds) {
add(seconds, 's');
if (addMinutes) {
add(minutes, 'm');
if (addSeconds) {
add(seconds, 's');
}
}
return res.join(' ');
},
@ -238,6 +282,30 @@ utilities: {
return min < width ? width : min;
},
// returns username + realm
parse_userid: function(userid) {
if (!Ext.isString(userid)) {
return [undefined, undefined];
}
let match = userid.match(/^(.+)@([^@]+)$/);
if (match !== null) {
return [match[1], match[2]];
}
return [undefined, undefined];
},
render_username: function(userid) {
let username = Proxmox.Utils.parse_userid(userid)[0] || "";
return Ext.htmlEncode(username);
},
render_realm: function(userid) {
let username = Proxmox.Utils.parse_userid(userid)[1] || "";
return Ext.htmlEncode(username);
},
getStoredAuth: function() {
let storedAuth = JSON.parse(window.localStorage.getItem('ProxmoxUser'));
return storedAuth || {};
@ -250,7 +318,7 @@ utilities: {
// that way the cookie gets deleted after the browser window is closed
if (data.ticket) {
Proxmox.CSRFPreventionToken = data.CSRFPreventionToken;
Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, data.ticket, null, '/', null, true);
Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, data.ticket, null, '/', null, true, "lax");
}
if (data.token) {
@ -275,10 +343,21 @@ utilities: {
if (Proxmox.LoggedOut) {
return;
}
Ext.util.Cookies.clear(Proxmox.Setup.auth_cookie_name);
// 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");
window.localStorage.removeItem("ProxmoxUser");
},
// The End-User gets redirected back here after login on the OpenID auth. portal, and in the
// redirection URL the state and auth.code are passed as URL GET params, this helper parses those
getOpenIDRedirectionAuthorization: function() {
const auth = Ext.Object.fromQueryString(window.location.search);
if (auth.state !== undefined && auth.code !== undefined) {
return auth;
}
return undefined;
},
// comp.setLoading() is buggy in ExtJS 4.0.7, so we
// use el.mask() instead
setErrorMask: function(comp, msg) {
@ -354,16 +433,15 @@ utilities: {
if (!result.success) {
msg = gettext("Unknown error");
if (result.message) {
msg = result.message;
msg = Ext.htmlEncode(result.message);
if (result.status) {
msg += ' (' + result.status + ')';
msg += ` (${result.status})`;
}
}
if (verbose && Ext.isObject(result.errors)) {
msg += "<br>";
Ext.Object.each(result.errors, function(prop, desc) {
msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
Ext.htmlEncode(desc);
Ext.Object.each(result.errors, (prop, desc) => {
msg += `<br><b>${Ext.htmlEncode(prop)}</b>: ${Ext.htmlEncode(desc)}`;
});
}
}
@ -377,10 +455,20 @@ utilities: {
waitMsg: gettext('Please wait...'),
}, reqOpts);
// default to enable if user isn't handling the failure already explicitly
let autoErrorAlert = reqOpts.autoErrorAlert ??
(typeof reqOpts.failure !== 'function' && typeof reqOpts.callback !== 'function');
if (!newopts.url.match(/^\/api2/)) {
newopts.url = '/api2/extjs' + newopts.url;
}
delete newopts.callback;
let unmask = (target) => {
if (target.waitMsgTargetCount === undefined || --target.waitMsgTargetCount <= 0) {
target.setLoading(false);
delete target.waitMsgTargetCount;
}
};
let createWrapper = function(successFn, callbackFn, failureFn) {
Ext.apply(newopts, {
@ -389,7 +477,7 @@ utilities: {
if (Proxmox.Utils.toolkit === 'touch') {
options.waitMsgTarget.setMasked(false);
} else {
options.waitMsgTarget.setLoading(false);
unmask(options.waitMsgTarget);
}
}
let result = Ext.decode(response.responseText);
@ -398,6 +486,9 @@ utilities: {
response.htmlStatus = Proxmox.Utils.extractRequestError(result, true);
Ext.callback(callbackFn, options.scope, [options, false, response]);
Ext.callback(failureFn, options.scope, [response, options]);
if (autoErrorAlert) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
return;
}
Ext.callback(callbackFn, options.scope, [options, true, response]);
@ -408,7 +499,7 @@ utilities: {
if (Proxmox.Utils.toolkit === 'touch') {
options.waitMsgTarget.setMasked(false);
} else {
options.waitMsgTarget.setLoading(false);
unmask(options.waitMsgTarget);
}
}
response.result = {};
@ -438,9 +529,16 @@ utilities: {
if (target) {
if (Proxmox.Utils.toolkit === 'touch') {
target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg });
} else {
// Note: ExtJS bug - this does not work when component is not rendered
} else if (target.rendered) {
target.waitMsgTargetCount = (target.waitMsgTargetCount ?? 0) + 1;
target.setLoading(newopts.waitMsg);
} else {
target.waitMsgTargetCount = (target.waitMsgTargetCount ?? 0) + 1;
target.on('afterlayout', function() {
if ((target.waitMsgTargetCount ?? 0) > 0) {
target.setLoading(newopts.waitMsg);
}
}, target, { single: true });
}
}
Ext.Ajax.request(newopts);
@ -450,12 +548,7 @@ utilities: {
// Proxmox.Async.api2({
// ...
// }).catch(Proxmox.Utils.alertResponseFailure);
alertResponseFailure: (response) => {
Ext.Msg.alert(
gettext('Error'),
response.htmlStatus || response.result.message,
);
},
alertResponseFailure: res => Ext.Msg.alert(gettext('Error'), res.htmlStatus || res.result.message),
checked_command: function(orig_cmd) {
Proxmox.Utils.API2Request(
@ -490,7 +583,7 @@ utilities: {
},
assemble_field_data: function(values, data) {
if (!Ext.isObject(data)) {
if (!Ext.isObject(data)) {
return;
}
Ext.Object.each(data, function(name, val) {
@ -510,7 +603,7 @@ utilities: {
});
},
updateColumnWidth: function(container) {
updateColumnWidth: function(container, thresholdWidth) {
let mode = Ext.state.Manager.get('summarycolumns') || 'auto';
let factor;
if (mode !== 'auto') {
@ -519,14 +612,15 @@ utilities: {
factor = 1;
}
} else {
factor = container.getSize().width < 1600 ? 1 : 2;
thresholdWidth = (thresholdWidth || 1400) + 1;
factor = Math.ceil(container.getSize().width / thresholdWidth);
}
if (container.oldFactor === factor) {
return;
}
let items = container.query('>'); // direct childs
let items = container.query('>'); // direct children
factor = Math.min(factor, items.length);
container.oldFactor = factor;
@ -540,6 +634,9 @@ utilities: {
container.updateLayout();
},
// NOTE: depreacated, use updateColumnWidth
updateColumns: container => Proxmox.Utils.updateColumnWidth(container),
dialog_title: function(subject, create, isAdd) {
if (create) {
if (isAdd) {
@ -568,6 +665,37 @@ utilities: {
Proxmox.Utils.unknownText;
},
// Only add product-agnostic fields here!
notificationFieldName: {
'type': gettext('Notification type'),
'hostname': gettext('Hostname'),
},
formatNotificationFieldName: (value) =>
Proxmox.Utils.notificationFieldName[value] || value,
// to add or change existing for product specific ones
overrideNotificationFieldName: function(extra) {
for (const [key, value] of Object.entries(extra)) {
Proxmox.Utils.notificationFieldName[key] = value;
}
},
// Only add product-agnostic fields here!
notificationFieldValue: {
'system-mail': gettext('Forwarded mails to the local root user'),
},
formatNotificationFieldValue: (value) =>
Proxmox.Utils.notificationFieldValue[value] || value,
// to add or change existing for product specific ones
overrideNotificationFieldValue: function(extra) {
for (const [key, value] of Object.entries(extra)) {
Proxmox.Utils.notificationFieldValue[key] = value;
}
},
// NOTE: only add general, product agnostic, ones here! Else use override helper in product repos
task_desc_table: {
aptupdate: ['', gettext('Update package database')],
@ -609,21 +737,70 @@ utilities: {
},
format_size: function(size, useSI) {
let units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
let unitsSI = [gettext('B'), gettext('KB'), gettext('MB'), gettext('GB'),
gettext('TB'), gettext('PB'), gettext('EB'), gettext('ZB'), gettext('YB')];
let unitsIEC = [gettext('B'), gettext('KiB'), gettext('MiB'), gettext('GiB'),
gettext('TiB'), gettext('PiB'), gettext('EiB'), gettext('ZiB'), gettext('YiB')];
let order = 0;
let commaDigits = 2;
const baseValue = useSI ? 1000 : 1024;
while (size >= baseValue && order < units.length) {
while (size >= baseValue && order < unitsSI.length) {
size = size / baseValue;
order++;
}
let unit = units[order], commaDigits = 2;
let unit = useSI ? unitsSI[order] : unitsIEC[order];
if (order === 0) {
commaDigits = 0;
} else if (!useSI) {
unit += 'i';
}
return `${size.toFixed(commaDigits)} ${unit}B`;
return `${size.toFixed(commaDigits)} ${unit}`;
},
SizeUnits: {
'B': 1,
'KiB': 1024,
'MiB': 1024*1024,
'GiB': 1024*1024*1024,
'TiB': 1024*1024*1024*1024,
'PiB': 1024*1024*1024*1024*1024,
'KB': 1000,
'MB': 1000*1000,
'GB': 1000*1000*1000,
'TB': 1000*1000*1000*1000,
'PB': 1000*1000*1000*1000*1000,
},
parse_size_unit: function(val) {
//let m = val.match(/([.\d])+\s?([KMGTP]?)(i?)B?\s*$/i);
let m = val.match(/(\d+(?:\.\d+)?)\s?([KMGTP]?)(i?)B?\s*$/i);
let size = parseFloat(m[1]);
let scale = m[2].toUpperCase();
let binary = m[3].toLowerCase();
let unit = `${scale}${binary}B`;
let factor = Proxmox.Utils.SizeUnits[unit];
return { size, factor, unit, binary }; // for convenience return all we got
},
size_unit_to_bytes: function(val) {
let { size, factor } = Proxmox.Utils.parse_size_unit(val);
return size * factor;
},
autoscale_size_unit: function(val) {
let { size, factor, binary } = Proxmox.Utils.parse_size_unit(val);
return Proxmox.Utils.format_size(size * factor, binary !== "i");
},
size_unit_ratios: function(a, b) {
a = typeof a !== "undefined" ? a : 0;
b = typeof b !== "undefined" ? b : Infinity;
let aBytes = typeof a === "number" ? a : Proxmox.Utils.size_unit_to_bytes(a);
let bBytes = typeof b === "number" ? b : Proxmox.Utils.size_unit_to_bytes(b);
return aBytes / (bBytes || Infinity); // avoid division by zero
},
render_upid: function(value, metaData, record) {
@ -751,7 +928,7 @@ utilities: {
let parsed = Proxmox.Utils.parse_task_status(status);
switch (parsed) {
case 'unknown': return Proxmox.Utils.unknownText;
case 'error': return Proxmox.Utils.errorText + ': ' + status;
case 'error': return Proxmox.Utils.errorText + ': ' + Ext.htmlEncode(status);
case 'warning': return status.replace('WARNINGS', Proxmox.Utils.warningsText);
case 'ok': // fall-through
default: return status;
@ -1077,34 +1254,226 @@ utilities: {
return acme;
},
updateColumns: function(container) {
let mode = Ext.state.Manager.get('summarycolumns') || 'auto';
let factor;
if (mode !== 'auto') {
factor = parseInt(mode, 10);
if (Number.isNaN(factor)) {
factor = 1;
get_health_icon: function(state, circle) {
if (circle === undefined) {
circle = false;
}
if (state === undefined) {
state = 'uknown';
}
var icon = 'faded fa-question';
switch (state) {
case 'good':
icon = 'good fa-check';
break;
case 'upgrade':
icon = 'warning fa-upload';
break;
case 'old':
icon = 'warning fa-refresh';
break;
case 'warning':
icon = 'warning fa-exclamation';
break;
case 'critical':
icon = 'critical fa-times';
break;
default: break;
}
if (circle) {
icon += '-circle';
}
return icon;
},
formatNodeRepoStatus: function(status, product) {
let fmt = (txt, cls) => `<i class="fa fa-fw fa-lg fa-${cls}"></i>${txt}`;
let getUpdates = Ext.String.format(gettext('{0} updates'), product);
let noRepo = Ext.String.format(gettext('No {0} repository enabled!'), product);
if (status === 'ok') {
return fmt(getUpdates, 'check-circle good') + ' ' +
fmt(gettext('Production-ready Enterprise repository enabled'), 'check-circle good');
} else if (status === 'no-sub') {
return fmt(gettext('Production-ready Enterprise repository enabled'), 'check-circle good') + ' ' +
fmt(gettext('Enterprise repository needs valid subscription'), 'exclamation-circle warning');
} else if (status === 'non-production') {
return fmt(getUpdates, 'check-circle good') + ' ' +
fmt(gettext('Non production-ready repository enabled!'), 'exclamation-circle warning');
} else if (status === 'no-repo') {
return fmt(noRepo, 'exclamation-circle critical');
}
return Proxmox.Utils.unknownText;
},
render_u2f_error: function(error) {
var ErrorNames = {
'1': gettext('Other Error'),
'2': gettext('Bad Request'),
'3': gettext('Configuration Unsupported'),
'4': gettext('Device Ineligible'),
'5': gettext('Timeout'),
};
return "U2F Error: " + ErrorNames[error] || Proxmox.Utils.unknownText;
},
// Convert an ArrayBuffer to a base64url encoded string.
// A `null` value will be preserved for convenience.
bytes_to_base64url: function(bytes) {
if (bytes === null) {
return null;
}
return btoa(Array
.from(new Uint8Array(bytes))
.map(val => String.fromCharCode(val))
.join(''),
)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/[=]/g, '');
},
// Convert an a base64url string to an ArrayBuffer.
// A `null` value will be preserved for convenience.
base64url_to_bytes: function(b64u) {
if (b64u === null) {
return null;
}
return new Uint8Array(
atob(b64u
.replace(/-/g, '+')
.replace(/_/g, '/'),
)
.split('')
.map(val => val.charCodeAt(0)),
);
},
// Convert utf-8 string to base64.
// This also escapes unicode characters such as emojis.
utf8ToBase64: function(string) {
let bytes = new TextEncoder().encode(string);
const escapedString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte),
).join("");
return btoa(escapedString);
},
// Converts a base64 string into a utf8 string.
// Decodes escaped unicode characters correctly.
base64ToUtf8: function(b64_string) {
let string = atob(b64_string);
let bytes = Uint8Array.from(string, (m) => m.codePointAt(0));
return new TextDecoder().decode(bytes);
},
stringToRGB: function(string) {
let hash = 0;
if (!string) {
return hash;
}
string += 'prox'; // give short strings more variance
for (let i = 0; i < string.length; i++) {
hash = string.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash; // to int
}
let alpha = 0.7; // make the color a bit brighter
let bg = 255; // assume white background
return [
(hash & 255) * alpha + bg * (1 - alpha),
((hash >> 8) & 255) * alpha + bg * (1 - alpha),
((hash >> 16) & 255) * alpha + bg * (1 - alpha),
];
},
rgbToCss: function(rgb) {
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
},
rgbToHex: function(rgb) {
let r = Math.round(rgb[0]).toString(16);
let g = Math.round(rgb[1]).toString(16);
let b = Math.round(rgb[2]).toString(16);
return `${r}${g}${b}`;
},
hexToRGB: function(hex) {
if (!hex) {
return undefined;
}
if (hex.length === 7) {
hex = hex.slice(1);
}
let r = parseInt(hex.slice(0, 2), 16);
let g = parseInt(hex.slice(2, 4), 16);
let b = parseInt(hex.slice(4, 6), 16);
return [r, g, b];
},
// optimized & simplified SAPC function
// https://github.com/Myndex/SAPC-APCA
getTextContrastClass: function(rgb) {
const blkThrs = 0.022;
const blkClmp = 1.414;
// linearize & gamma correction
let r = (rgb[0] / 255) ** 2.4;
let g = (rgb[1] / 255) ** 2.4;
let b = (rgb[2] / 255) ** 2.4;
// relative luminance sRGB
let bg = r * 0.2126729 + g * 0.7151522 + b * 0.0721750;
// black clamp
bg = bg > blkThrs ? bg : bg + (blkThrs - bg) ** blkClmp;
// SAPC with white text
let contrastLight = bg ** 0.65 - 1;
// SAPC with black text
let contrastDark = bg ** 0.56 - 0.046134502;
if (Math.abs(contrastLight) >= Math.abs(contrastDark)) {
return 'light';
} else {
return 'dark';
}
},
getTagElement: function(string, color_overrides) {
let rgb = color_overrides?.[string] || Proxmox.Utils.stringToRGB(string);
let style = `background-color: ${Proxmox.Utils.rgbToCss(rgb)};`;
let cls;
if (rgb.length > 3) {
style += `color: ${Proxmox.Utils.rgbToCss([rgb[3], rgb[4], rgb[5]])}`;
cls = "proxmox-tag-dark";
} else {
factor = container.getSize().width < 1400 ? 1 : 2;
let txtCls = Proxmox.Utils.getTextContrastClass(rgb);
cls = `proxmox-tag-${txtCls}`;
}
return `<span class="${cls}" style="${style}">${string}</span>`;
},
if (container.oldFactor === factor) {
return;
// Setting filename here when downloading from a remote url sometimes fails in chromium browsers
// because of a bug when using attribute download in conjunction with a self signed certificate.
// For more info see https://bugs.chromium.org/p/chromium/issues/detail?id=993362
downloadAsFile: function(source, fileName) {
let hiddenElement = document.createElement('a');
hiddenElement.href = source;
hiddenElement.target = '_blank';
if (fileName) {
hiddenElement.download = fileName;
}
let items = container.query('>'); // direct childs
factor = Math.min(factor, items.length);
container.oldFactor = factor;
items.forEach((item) => {
item.columnWidth = 1 / factor;
});
// we have to update the layout twice, since the first layout change
// can trigger the scrollbar which reduces the amount of space left
container.updateLayout();
container.updateLayout();
hiddenElement.click();
},
},
@ -1147,12 +1516,35 @@ utilities: {
let DnsName_REGEXP = "(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-]*[a-zA-Z0-9])?)\\.)*(?:[A-Za-z0-9](?:[A-Za-z0-9\\-]*[A-Za-z0-9])?))";
me.DnsName_match = new RegExp("^" + DnsName_REGEXP + "$");
me.DnsName_or_Wildcard_match = new RegExp("^(?:\\*\\.)?" + DnsName_REGEXP + "$");
me.CpuSet_match = /^[0-9]+(?:-[0-9]+)?(?:,[0-9]+(?:-[0-9]+)?)*$/;
me.HostPort_match = new RegExp("^(" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")(?::(\\d+))?$");
me.HostPortBrackets_match = new RegExp("^\\[(" + IPV6_REGEXP + "|" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")\\](?::(\\d+))?$");
me.IP6_dotnotation_match = new RegExp("^(" + IPV6_REGEXP + ")(?:\\.(\\d+))?$");
me.Vlan_match = /^vlan(\d+)/;
me.VlanInterface_match = /(\w+)\.(\d+)/;
// Taken from proxmox-schema and ported to JS
let PORT_REGEX_STR = "(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])";
let IPRE_BRACKET_STR = "(?:" + IPV4_REGEXP + "|\\[(?:" + IPV6_REGEXP + ")\\])";
let DNS_NAME_STR = "(?:(?:" + DnsName_REGEXP + "\\.)*" + DnsName_REGEXP + ")";
let HTTP_URL_REGEX = "^https?://(?:(?:(?:"
+ DNS_NAME_STR
+ "|"
+ IPRE_BRACKET_STR
+ ")(?::"
+ PORT_REGEX_STR
+ ")?)|"
+ IPV6_REGEXP
+ ")(?:/[^\x00-\x1F\x7F]*)?$";
me.httpUrlRegex = new RegExp(HTTP_URL_REGEX);
// Same as SAFE_ID_REGEX in proxmox-schema
me.safeIdRegex = /^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/;
},
});
@ -1160,7 +1552,7 @@ Ext.define('Proxmox.Async', {
singleton: true,
// Returns a Promise resolving to the result of an `API2Request` or rejecting to the error
// repsonse on failure
// response on failure
api2: function(reqOpts) {
return new Promise((resolve, reject) => {
delete reqOpts.callback; // not allowed in this api
@ -1175,3 +1567,19 @@ Ext.define('Proxmox.Async', {
return new Promise((resolve, _reject) => setTimeout(resolve, millis));
},
});
Ext.override(Ext.data.Store, {
// If the store's proxy is changed while it is waiting for an AJAX
// response, `onProxyLoad` will still be called for the outdated response.
// To avoid displaying inconsistent information, only process responses
// belonging to the current proxy. However, do not apply this workaround
// to the mobile UI, as Sencha Touch has an incompatible internal API.
onProxyLoad: function(operation) {
let me = this;
if (Proxmox.Utils.toolkit === 'touch' || operation.getProxy() === me.getProxy()) {
me.callParent(arguments);
} else {
console.log(`ignored outdated response: ${operation.getRequest().getUrl()}`);
}
},
});

552
src/api-viewer/APIViewer.js Normal file
View File

@ -0,0 +1,552 @@
/*global apiSchema*/
Ext.onReady(function() {
Ext.define('pmx-param-schema', {
extend: 'Ext.data.Model',
fields: [
'name', 'type', 'typetext', 'description', 'verbose_description',
'enum', 'minimum', 'maximum', 'minLength', 'maxLength',
'pattern', 'title', 'requires', 'format', 'default',
'disallow', 'extends', 'links', 'instance-types',
{
name: 'optional',
type: 'boolean',
},
],
});
let store = Ext.define('pmx-updated-treestore', {
extend: 'Ext.data.TreeStore',
model: Ext.define('pmx-api-doc', {
extend: 'Ext.data.Model',
fields: [
'path', 'info', 'text',
],
}),
proxy: {
type: 'memory',
data: apiSchema,
},
sorters: [{
property: 'leaf',
direction: 'ASC',
}, {
property: 'text',
direction: 'ASC',
}],
filterer: 'bottomup',
doFilter: function(node) {
this.filterNodes(node, this.getFilters().getFilterFn(), true);
},
filterNodes: function(node, filterFn, parentVisible) {
let me = this;
let match = filterFn(node) && (parentVisible || (node.isRoot() && !me.getRootVisible()));
if (node.childNodes && node.childNodes.length) {
let bottomUpFiltering = me.filterer === 'bottomup';
let childMatch;
for (const child of node.childNodes) {
childMatch = me.filterNodes(child, filterFn, match || bottomUpFiltering) || childMatch;
}
if (bottomUpFiltering) {
match = childMatch || match;
}
}
node.set("visible", match, me._silentOptions);
return match;
},
}).create();
let render_description = function(value, metaData, record) {
let pdef = record.data;
value = pdef.verbose_description || value;
// TODO: try to render asciidoc correctly
metaData.style = 'white-space:pre-wrap;';
return Ext.htmlEncode(value);
};
let render_type = function(value, metaData, record) {
let pdef = record.data;
return pdef.enum ? 'enum' : pdef.type || 'string';
};
const renderFormatString = function(obj) {
if (!Ext.isObject(obj)) {
return obj;
}
const mandatory = [];
const optional = [];
Object.entries(obj).forEach(function([name, param]) {
let list = param.optional ? optional : mandatory;
let str = param.default_key ? `[${name}=]` : `${name}=`;
if (param.alias) {
return;
} else if (param.enum) {
str += `(${param.enum?.join(' | ')})`;
} else {
str += `<${param.format_description || param.pattern || param.type}>`;
}
list.push(str);
});
return mandatory.join(", ") + ' ' + optional.map(each => `[,${each}]`).join(' ');
};
let render_simple_format = function(pdef, type_fallback) {
if (pdef.typetext) {
return pdef.typetext;
}
if (pdef.enum) {
return pdef.enum.join(' | ');
}
if (pdef.format) {
return renderFormatString(pdef.format);
}
if (pdef.pattern) {
return pdef.pattern;
}
if (pdef.type === 'boolean') {
return `<true|false>`;
}
if (type_fallback && pdef.type) {
return `<${pdef.type}>`;
}
if (pdef.minimum || pdef.maximum) {
return `${pdef.minimum || 'N'} - ${pdef.maximum || 'N'}`;
}
return '';
};
let render_format = function(value, metaData, record) {
let pdef = record.data;
metaData.style = 'white-space:normal;';
if (pdef.type === 'array' && pdef.items) {
let format = render_simple_format(pdef.items, true);
return `[${Ext.htmlEncode(format)}, ...]`;
}
return Ext.htmlEncode(render_simple_format(pdef));
};
let real_path = function(path) {
if (!path.match(/^[/]/)) {
path = `/${path}`;
}
return path.replace(/^.*\/_upgrade_(\/)?/, "/");
};
let permission_text = function(permission) {
let permhtml = "";
if (permission.user) {
if (!permission.description) {
if (permission.user === 'world') {
permhtml += "Accessible without any authentication.";
} else if (permission.user === 'all') {
permhtml += "Accessible by all authenticated users.";
} else {
permhtml += `Only accessible by user "${permission.user}"`;
}
}
} else if (permission.check) {
permhtml += `<pre>Check: ${Ext.htmlEncode(JSON.stringify(permission.check))}</pre>`;
} else if (permission.userParam) {
permhtml += `<div>Check if user matches parameter '${permission.userParam}'`;
} else if (permission.or) {
permhtml += "<div>Or<div style='padding-left: 10px;'>";
permhtml += permission.or.map(v => permission_text(v)).join('');
permhtml += "</div></div>";
} else if (permission.and) {
permhtml += "<div>And<div style='padding-left: 10px;'>";
permhtml += permission.and.map(v => permission_text(v)).join('');
permhtml += "</div></div>";
} else {
permhtml += "Unknown syntax!";
}
return permhtml;
};
let render_docu = function(data) {
let md = data.info;
let items = [];
Ext.Array.each(['GET', 'POST', 'PUT', 'DELETE'], function(method) {
let info = md[method];
if (info) {
let endpoint = real_path(data.path);
let usage = `<table><tr><td>HTTP:&nbsp;&nbsp;&nbsp;</td><td>`;
usage += `${method} /api2/json${endpoint}</td></tr>`;
if (typeof cliUsageRenderer === 'function') {
usage += cliUsageRenderer(method, endpoint); // eslint-disable-line no-undef
}
let sections = [
{
title: 'Description',
html: Ext.htmlEncode(info.description),
bodyPadding: 10,
},
{
title: 'Usage',
html: usage,
bodyPadding: 10,
},
];
if (info.parameters && info.parameters.properties) {
let pstore = Ext.create('Ext.data.Store', {
model: 'pmx-param-schema',
proxy: {
type: 'memory',
},
groupField: 'optional',
sorters: [
{
property: 'instance-types',
direction: 'ASC',
},
{
property: 'name',
direction: 'ASC',
},
],
});
let has_type_properties = false;
Ext.Object.each(info.parameters.properties, function(name, pdef) {
if (pdef.oneOf) {
pdef.oneOf.forEach((alternative) => {
alternative.name = name;
pstore.add(alternative);
has_type_properties = true;
});
} else if (pdef['instance-types']) {
pdef['instance-types'].forEach((type) => {
let typePdef = Ext.apply({}, pdef);
typePdef.name = name;
typePdef['instance-types'] = [type];
pstore.add(typePdef);
has_type_properties = true;
});
} else {
pdef.name = name;
pstore.add(pdef);
}
});
pstore.sort();
let groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
enableGroupingMenu: false,
groupHeaderTpl: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Required</tpl>',
});
sections.push({
xtype: 'gridpanel',
title: 'Parameters',
features: [groupingFeature],
store: pstore,
viewConfig: {
trackOver: false,
stripeRows: true,
enableTextSelection: true,
},
columns: [
{
header: 'Name',
dataIndex: 'name',
flex: 1,
},
{
header: 'Type',
dataIndex: 'type',
renderer: render_type,
flex: 1,
},
{
header: 'For Types',
dataIndex: 'instance-types',
hidden: !has_type_properties,
flex: 1,
},
{
header: 'Default',
dataIndex: 'default',
flex: 1,
},
{
header: 'Format',
dataIndex: 'type',
renderer: render_format,
flex: 2,
},
{
header: 'Description',
dataIndex: 'description',
renderer: render_description,
flex: 6,
},
],
});
}
if (info.returns) {
let retinf = info.returns;
let rtype = retinf.type;
if (!rtype && retinf.items) {rtype = 'array';}
if (!rtype) {rtype = 'object';}
let rpstore = Ext.create('Ext.data.Store', {
model: 'pmx-param-schema',
proxy: {
type: 'memory',
},
groupField: 'optional',
sorters: [
{
property: 'name',
direction: 'ASC',
},
],
});
let properties;
if (rtype === 'array' && retinf.items.properties) {
properties = retinf.items.properties;
}
if (rtype === 'object' && retinf.properties) {
properties = retinf.properties;
}
Ext.Object.each(properties, function(name, pdef) {
pdef.name = name;
rpstore.add(pdef);
});
rpstore.sort();
let groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
enableGroupingMenu: false,
groupHeaderTpl: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Obligatory</tpl>',
});
let returnhtml;
if (retinf.items) {
returnhtml = '<pre>items: ' + Ext.htmlEncode(JSON.stringify(retinf.items, null, 4)) + '</pre>';
}
if (retinf.properties) {
returnhtml = returnhtml || '';
returnhtml += '<pre>properties:' + Ext.htmlEncode(JSON.stringify(retinf.properties, null, 4)) + '</pre>';
}
let rawSection = Ext.create('Ext.panel.Panel', {
bodyPadding: '0px 10px 10px 10px',
html: returnhtml,
hidden: true,
});
sections.push({
xtype: 'gridpanel',
title: 'Returns: ' + rtype,
features: [groupingFeature],
store: rpstore,
viewConfig: {
trackOver: false,
stripeRows: true,
enableTextSelection: true,
},
columns: [
{
header: 'Name',
dataIndex: 'name',
flex: 1,
},
{
header: 'Type',
dataIndex: 'type',
renderer: render_type,
flex: 1,
},
{
header: 'Default',
dataIndex: 'default',
flex: 1,
},
{
header: 'Format',
dataIndex: 'type',
renderer: render_format,
flex: 2,
},
{
header: 'Description',
dataIndex: 'description',
renderer: render_description,
flex: 6,
},
],
bbar: [
{
xtype: 'button',
text: 'Show RAW',
handler: function(btn) {
rawSection.setVisible(!rawSection.isVisible());
btn.setText(rawSection.isVisible() ? 'Hide RAW' : 'Show RAW');
},
},
],
});
sections.push(rawSection);
}
if (!data.path.match(/\/_upgrade_/)) {
let permhtml = '';
if (!info.permissions) {
permhtml = "Root only.";
} else {
if (info.permissions.description) {
permhtml += "<div style='white-space:pre-wrap;padding-bottom:10px;'>" +
Ext.htmlEncode(info.permissions.description) + "</div>";
}
permhtml += permission_text(info.permissions);
}
if (info.allowtoken !== undefined && !info.allowtoken) {
permhtml += "<br />This API endpoint is not available for API tokens.";
}
sections.push({
title: 'Required permissions',
bodyPadding: 10,
html: permhtml,
});
}
items.push({
title: method,
autoScroll: true,
defaults: {
border: false,
},
items: sections,
});
}
});
let ct = Ext.getCmp('docview');
ct.setTitle("Path: " + real_path(data.path));
ct.removeAll(true);
ct.add(items);
ct.setActiveTab(0);
};
Ext.define('Ext.form.SearchField', {
extend: 'Ext.form.field.Text',
alias: 'widget.searchfield',
emptyText: 'Search...',
flex: 1,
inputType: 'search',
listeners: {
'change': function() {
let value = this.getValue();
if (!Ext.isEmpty(value)) {
store.filter({
property: 'path',
value: value,
anyMatch: true,
});
} else {
store.clearFilter();
}
},
},
});
let treePanel = Ext.create('Ext.tree.Panel', {
title: 'Resource Tree',
tbar: [
{
xtype: 'searchfield',
},
],
tools: [
{
type: 'expand',
tooltip: 'Expand all',
tooltipType: 'title',
callback: tree => tree.expandAll(),
},
{
type: 'collapse',
tooltip: 'Collapse all',
tooltipType: 'title',
callback: tree => tree.collapseAll(),
},
],
store: store,
width: 200,
region: 'west',
split: true,
margins: '5 0 5 5',
rootVisible: false,
listeners: {
selectionchange: function(v, selections) {
if (!selections[0]) {return;}
let rec = selections[0];
render_docu(rec.data);
location.hash = '#' + rec.data.path;
},
},
});
Ext.create('Ext.container.Viewport', {
layout: 'border',
renderTo: Ext.getBody(),
items: [
treePanel,
{
xtype: 'tabpanel',
title: 'Documentation',
id: 'docview',
region: 'center',
margins: '5 5 5 0',
layout: 'fit',
items: [],
},
],
});
let deepLink = function() {
let path = window.location.hash.substring(1).replace(/\/\s*$/, '');
let endpoint = store.findNode('path', path);
if (endpoint) {
treePanel.getSelectionModel().select(endpoint);
treePanel.expandPath(endpoint.getPath());
render_docu(endpoint.data);
}
};
window.onhashchange = deepLink;
deepLink();
});

22
src/button/AltText.js Normal file
View File

@ -0,0 +1,22 @@
Ext.define('Proxmox.button.AltText', {
extend: 'Proxmox.button.Button',
xtype: 'proxmoxAltTextButton',
defaultText: "",
altText: "",
listeners: {
// HACK: calculate the max button width on first render to avoid toolbar glitches
render: function(button) {
let me = this;
button.setText(me.altText);
let altWidth = button.getSize().width;
button.setText(me.defaultText);
let defaultWidth = button.getSize().width;
button.setWidth(defaultWidth > altWidth ? defaultWidth : altWidth);
},
},
});

View File

@ -110,6 +110,7 @@ Ext.define('Proxmox.button.StdRemoveButton', {
config: {
baseurl: undefined,
customConfirmationMessage: undefined,
},
getUrl: function(rec) {
@ -133,7 +134,14 @@ Ext.define('Proxmox.button.StdRemoveButton', {
let me = this;
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) {
@ -152,9 +160,7 @@ Ext.define('Proxmox.button.StdRemoveButton', {
callback: function(options, success, response) {
Ext.callback(me.callback, me.scope, [options, success, response], 0, me);
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
});
},
initComponent: function() {

View File

@ -5,9 +5,9 @@ CSS=ext6-pmx.css
all:
.PHONY: install
install: ${CSS}
install -d ${WWWCSSDIR}
for i in ${CSS}; do install -m 0755 $$i ${WWWCSSDIR}/$$i; done
install: $(CSS)
install -d $(WWWCSSDIR)
for i in $(CSS); do install -m 0644 $$i $(WWWCSSDIR)/$$i; done
.PHONY: clean
clean:

View File

@ -6,9 +6,82 @@
background-color: LightYellow;
}
.proxmox-tags-full .proxmox-tag-light,
.proxmox-tags-full .proxmox-tag-dark {
border-radius: 3px;
padding: 1px 6px;
margin: 0px 1px;
display: inline-block;
}
.proxmox-tags-full .x-grid-cell-inner-treecolumn .proxmox-tag-light,
.proxmox-tags-full .x-grid-cell-inner-treecolumn .proxmox-tag-dark,
.x-grid-cell-inner-treecolumn .proxmox-tags-full .proxmox-tag-light,
.x-grid-cell-inner-treecolumn .proxmox-tags-full .proxmox-tag-dark {
display: inherit;
}
.x-boundlist-item > .proxmox-tag-light,
.x-boundlist-item > .proxmox-tag-dark {
line-height: 15px;
}
.proxmox-tags-circle :not(span.proxmox-tags-full) > .proxmox-tag-light,
.proxmox-tags-circle :not(span.proxmox-tags-full) > .proxmox-tag-dark,
.proxmox-tags-circle > .proxmox-tag-light,
.proxmox-tags-circle > .proxmox-tag-dark {
margin: 0px 1px;
position: relative;
top: 2px;
border-radius: 6px;
height: 12px;
width: 12px;
display: inline-block;
color: transparent !important;
overflow: hidden;
}
.proxmox-tags-none :not(span.proxmox-tags-full) > .proxmox-tag-light,
.proxmox-tags-none :not(span.proxmox-tags-full) > .proxmox-tag-dark,
.proxmox-tags-none > .proxmox-tag-light,
.proxmox-tags-none > .proxmox-tag-dark {
display: none;
}
.proxmox-tags-dense :not(span.proxmox-tags-full) > .proxmox-tag-light,
.proxmox-tags-dense :not(span.proxmox-tags-full) > .proxmox-tag-dark,
.proxmox-tags-dense > .proxmox-tag-light,
.proxmox-tags-dense > .proxmox-tag-dark {
width: 6px;
margin-right: 1px;
display: inline-block;
color: transparent !important;
overflow: hidden;
vertical-align: bottom;
}
.proxmox-tags-full .proxmox-tag-light {
color: #fff;
background-color: #383838;
}
.proxmox-tags-full .proxmox-tag-dark {
color: #000;
background-color: #f0f0f0;
}
.x-mask-msg-text {
text-align: center;
}
.x-window-header-default-top .x-mask {
background-color: #f5f5f5B4; /* ~ 0.7 opacity */
}
.proxmox-disabled-row, .proxmox-disabled-row td {
/*color: #a0a0a0;*/
color: #666665;
}
.proxmox-invalid-row {
background-color: #f3d6d7;
@ -18,6 +91,10 @@
background-color: #f5e5d8;
}
.proxmox-good-row {
background-color: #21BF4B;
}
/* some icons have to be color manually */
.black {
color: #000;
@ -43,12 +120,18 @@
color: #FF6C59;
}
.pwt-eol-icon {
.info-blue {
color: #3892d4;
}
.eol-notice a:visited {
color: inherit;
}
.eol-notice > i.fa {
position: relative;
float: left;
margin-right: 5px;
font-size: 1.3em;
color: #FF6C59;
}
/* reduce chart legend space usage to something more sane */
@ -104,19 +187,177 @@ div.right-aligned {
}
.pmx-itype-icon-memory,
.pmx-itype-icon-processor
.pmx-itype-icon-processor,
.pmx-itype-icon /* NOTE: use this one instead of adding new specific ones! */
{
background-repeat: no-repeat;
background-position:3px center;
padding-left: 20px;
background-size: 16px 16px; /* Chrom* needs both as else it gets cut-off due do non 1:1 ratio */
}
.pmx-itype-icon-memory
{
background-image:url(../images/icon-ram.png);
.pmx-itype-icon-memory {
background-image:url(../images/icon-ram.svg);
}
.pmx-itype-icon-processor
{
background-image:url(../images/icon-cpu.png);
.pmx-itype-icon-processor {
background-image:url(../images/icon-cpu.svg);
}
.pmx-itype-icon-debian-swirl {
padding-left: 22px;
background-size: 16px 16px; /* Chrom* needs both as else it gets cut-off due do non 1:1 ratio */
background-image:url(../images/debian-swirl-openlogo.svg);
}
.pmx-itype-icon-proxmox-x {
padding-left: 22px;
background-size: 16px 16px; /* Not really required here, as here WxH is 1:1 but cannot hurt */
background-image:url(../images/proxmox-symbol-x.svg);
}
.pmx-itype-icon-openid-logo {
padding-left: 22px;
background-size: 16px 16px;
background-image:url(../images/openid-icon-100x100.png);
}
/* change font for config panel back to fontawesome */
.x-treelist-item-expanded > * > * > .x-treelist-item-expander::after,
.x-treelist-item-expander::after {
font: 16px/1 FontAwesome;
}
.x-treelist-pve-nav {
background-color: #f5f5f5;
}
/* fix padding for legend in header */
.x-legend-inner {
padding: 0;
}
.proxmox-apt-repos .x-grid-group-hd {
color: #000000;
background-color: #f5f5f5;
}
.proxmox-apt-repos .x-grid-group-title {
color: #333;
}
/* some general helper classes */
.centered-flex-column {
display: flex;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100%;
}
/* Fix icon/text baseline */
.x-tab-default {
display: inline-flex;
align-items: center;
justify-content: center;
}
.x-tab-default > span {
text-align: center;
vertical-align: middle;
}
.x-tab-button {
line-height: unset;
}
.x-tab-inner {
display: unset;
vertical-align: text-top;
}
.x-tab-wrap {
display: unset;
}
.x-tab-default-top {
padding: 2px 6px 2px 6px;
}
/* rules for the markdown content, prefix with the .pmx-md class */
.pmx-md {
font-size: 1.0em;
line-height: 1.25em;
}
.pmx-md p {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
.pmx-md :is(h1, h2, h3, h4, h5, h6) {
margin-top: 0.9em;
margin-bottom: 0.75em;
}
.pmx-md h1 { font-size: 175%; }
.pmx-md h2 { font-size: 150%; }
.pmx-md h3 { font-size: 125%; }
.pmx-md h4 { font-size: 110%; }
.pmx-md h5 { font-size: 100%; }
.pmx-md h6 { font-size: 100%; }
.pmx-md code {
white-space: pre;
background-color: #f5f5f5;
padding: 1px;
}
.pmx-md pre code {
display: inline-block;
padding: 5px;
border-left: 3px solid #e0e0e0;
}
.pmx-md strong {
font-weight: bold;
}
.pmx-md blockquote {
border-left: 1px solid #666666;
padding-left: 4px;
margin: 10px 2ch;
}
/* markdown tables */
.pmx-md table {
border-spacing: 0;
border-collapse: collapse;
}
.pmx-md td, .pmx-md th {
padding: 5px;
}
.pmx-md td[align="center"] {
text-align: center;
}
.pmx-md td[align="right"] {
text-align: right;
}
.pmx-md tbody td {
border-bottom: 1px solid #e0e0e0;
}
.pmx-md tbody tr:nth-of-type(even) {
background-color: #f5f5f5;
}
.pmx-md tbody tr:last-of-type td {
border-bottom: 1px solid #666666;
}
.pmx-md tbody tr:hover td {
background-color: #e0e0e0;
}
/* markdown tables end */
/* markdown content end */
/* action column fix start */
.x-action-col-icon {
margin: 0 1px;
font-size: 14px;
}
.x-action-col-icon:before, .x-action-col-icon:after {
font-size: 14px;
}
.x-action-col-icon:hover:before, .x-action-col-icon:hover:after {
text-shadow: 1px 1px 1px #AAA;
font-weight: 800;
}
.x-action-col-icon:before {
color: #555;
}
/* action column fix end */

View File

@ -28,7 +28,7 @@ Ext.define('Proxmox.data.DiffStore', {
// config is passed instead of an existing rstore instance
autoDestroyRstore: false,
onDestroy: function() {
doDestroy: function() {
let me = this;
if (me.autoDestroyRstore) {
if (Ext.isFunction(me.rstore.destroy)) {

View File

@ -17,6 +17,7 @@ Ext.define('Proxmox.RestProxy', {
constructor: function(config) {
Ext.applyIf(config, {
reader: {
responseType: undefined,
type: 'json',
rootProperty: config.root || 'data',
},

View File

@ -0,0 +1,29 @@
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',
});

View File

@ -9,7 +9,7 @@
* example2: [ {data1: "xyz", data2: "abc"} ]
* returns [{key: "data1", value: "xyz"}, {key: "data2", value: "abc"}]
*
* If you set 'readArray', the reader expexts the object as array:
* If you set 'readArray', the reader expects the object as array:
*
* example3: [ { key: "data1", value: "xyz", p2: "cde" }, { key: "data2", value: "abc", p2: "efg" }]
* returns [{key: "data1", value: "xyz", p2: "cde}, {key: "data2", value: "abc", p2: "efg"}]
@ -32,6 +32,7 @@ Ext.define('Proxmox.data.reader.JsonObject', {
alias: 'reader.jsonobject',
readArray: false,
responseType: undefined,
rows: undefined,

View File

@ -1,5 +1,8 @@
PACKAGE ?= $(or $(DEB_SOURCE), proxmox-widget-toolkit)
DESTDIR=
DOCDIR=${DESTDIR}/usr/share/doc/${PACKAGE}
WWWBASEDIR=${DESTDIR}/usr/share/javascript/${PACKAGE}
WWWCSSDIR=${WWWBASEDIR}/css
WWWIMAGESDIR=${WWWBASEDIR}/images
DOCDIR=$(DESTDIR)/usr/share/doc/$(PACKAGE)
WWWBASEDIR=$(DESTDIR)/usr/share/javascript/$(PACKAGE)
WWWCSSDIR=$(WWWBASEDIR)/css
WWWIMAGESDIR=$(WWWBASEDIR)/images
WWWTHEMEDIR=$(WWWBASEDIR)/themes

View File

@ -0,0 +1,151 @@
Ext.define('Proxmox.form.SizeField', {
extend: 'Ext.form.FieldContainer',
alias: 'widget.pmxSizeField',
mixins: ['Proxmox.Mixin.CBind'],
viewModel: {
data: {
unit: 'MiB',
unitPostfix: '',
},
formulas: {
unitlabel: (get) => get('unit') + get('unitPostfix'),
},
},
emptyText: '',
layout: 'hbox',
defaults: {
hideLabel: true,
},
// display unit (TODO: make (optionally) selectable)
unit: 'MiB',
unitPostfix: '',
// use this if the backend saves values in another unit than bytes, e.g.,
// for KiB set it to 'KiB'
backendUnit: undefined,
// submit a canonical size unit, e.g., 20.5 MiB
submitAutoScaledSizeUnit: false,
// allow setting 0 and using it as a submit value
allowZero: false,
emptyValue: null,
items: [
{
xtype: 'numberfield',
cbind: {
name: '{name}',
emptyText: '{emptyText}',
allowZero: '{allowZero}',
emptyValue: '{emptyValue}',
},
minValue: 0,
step: 1,
submitLocaleSeparator: false,
fieldStyle: 'text-align: right',
flex: 1,
enableKeyEvents: true,
setValue: function(v) {
if (!this._transformed) {
let fieldContainer = this.up('fieldcontainer');
let vm = fieldContainer.getViewModel();
let unit = vm.get('unit');
if (typeof v === "string") {
v = Proxmox.Utils.size_unit_to_bytes(v);
}
v /= Proxmox.Utils.SizeUnits[unit];
v *= fieldContainer.backendFactor;
this._transformed = true;
}
if (Number(v) === 0 && !this.allowZero) {
v = undefined;
}
return Ext.form.field.Text.prototype.setValue.call(this, v);
},
getSubmitValue: function() {
let v = this.processRawValue(this.getRawValue());
v = v.replace(this.decimalSeparator, '.');
if (v === undefined || v === '') {
return this.emptyValue;
}
if (Number(v) === 0) {
return this.allowZero ? 0 : null;
}
let fieldContainer = this.up('fieldcontainer');
let vm = fieldContainer.getViewModel();
let unit = vm.get('unit');
v = parseFloat(v) * Proxmox.Utils.SizeUnits[unit];
if (fieldContainer.submitAutoScaledSizeUnit) {
return Proxmox.Utils.format_size(v, !unit.endsWith('iB'));
} else {
return String(Math.floor(v / fieldContainer.backendFactor));
}
},
listeners: {
// our setValue gets only called if we have a value, avoid
// transformation of the first user-entered value
keydown: function() { this._transformed = true; },
},
},
{
xtype: 'displayfield',
name: 'unit',
submitValue: false,
padding: '0 0 0 10',
bind: {
value: '{unitlabel}',
},
listeners: {
change: (f, v) => {
f.originalValue = v;
},
},
width: 40,
},
],
initComponent: function() {
let me = this;
me.unit = me.unit || 'MiB';
if (!(me.unit in Proxmox.Utils.SizeUnits)) {
throw "unknown unit: " + me.unit;
}
me.backendFactor = 1;
if (me.backendUnit !== undefined) {
if (!(me.unit in Proxmox.Utils.SizeUnits)) {
throw "unknown backend unit: " + me.backendUnit;
}
me.backendFactor = Proxmox.Utils.SizeUnits[me.backendUnit];
}
me.callParent(arguments);
me.getViewModel().set('unit', me.unit);
me.getViewModel().set('unitPostfix', me.unitPostfix);
},
});
Ext.define('Proxmox.form.BandwidthField', {
extend: 'Proxmox.form.SizeField',
alias: 'widget.pmxBandwidthField',
unitPostfix: '/s',
});

View File

@ -6,6 +6,7 @@ Ext.define('Proxmox.form.Checkbox', {
defaultValue: undefined,
deleteDefaultValue: false,
deleteEmpty: false,
clearOnDisable: false,
},
inputValue: '1',
@ -31,6 +32,19 @@ Ext.define('Proxmox.form.Checkbox', {
return data;
},
setDisabled: function(disabled) {
let me = this;
// only clear on actual transition
let toClearValue = me.clearOnDisable && !me.disabled && disabled;
me.callParent(arguments);
if (toClearValue) {
me.setValue(false); // TODO: could support other "reset value" or use originalValue?
}
},
// also accept integer 1 as true
setRawValue: function(value) {
let me = this;

View File

@ -31,6 +31,10 @@ Ext.define('Proxmox.form.ComboGrid', {
skipEmptyText: false,
notFoundIsValid: false,
deleteEmpty: false,
errorHeight: 100,
// NOTE: the trigger will always be shown if allowBlank is true, setting showClearTrigger
// to false cannot change that
showClearTrigger: false,
},
// needed to trigger onKeyUp etc.
@ -53,7 +57,7 @@ Ext.define('Proxmox.form.ComboGrid', {
setValue: function(value) {
let me = this;
let empty = Ext.isArray(value) ? !value.length : !value;
me.triggers.clear.setVisible(!empty && me.allowBlank);
me.triggers.clear.setVisible(!empty && (me.allowBlank || me.showClearTrigger));
return me.callParent([value]);
},
@ -289,7 +293,7 @@ Ext.define('Proxmox.form.ComboGrid', {
if (!me.multiSelect) {
picker.on('itemclick', function(sm, record) {
if (picker.getSelection()[0] === record) {
picker.hide();
me.collapse();
}
});
}
@ -300,12 +304,13 @@ Ext.define('Proxmox.form.ComboGrid', {
//
// we save the minheight to reset it after the load
picker.on('show', function() {
me.store.fireEvent('refresh');
if (me.enableLoadMask) {
me.savedMinHeight = picker.getMinHeight();
picker.setMinHeight(100);
me.savedMinHeight = me.savedMinHeight ?? picker.getMinHeight();
picker.setMinHeight(me.errorHeight);
}
if (me.loadError) {
Proxmox.Utils.setErrorMask(picker, me.loadError);
Proxmox.Utils.setErrorMask(picker.getView(), me.loadError);
delete me.loadError;
picker.updateLayout();
}
@ -317,15 +322,14 @@ Ext.define('Proxmox.form.ComboGrid', {
},
clearLocalFilter: function() {
let me = this,
filter = me.queryFilter;
let me = this;
if (filter) {
me.queryFilter = null;
me.changingFilters = true;
me.store.removeFilter(filter, true);
me.changingFilters = false;
}
if (me.queryFilter) {
me.changingFilters = true; // FIXME: unused?
me.store.removeFilter(me.queryFilter, true);
me.queryFilter = null;
me.changingFilters = false;
}
},
isValueInStore: function(value) {
@ -399,7 +403,7 @@ Ext.define('Proxmox.form.ComboGrid', {
matchFieldWidth: false,
});
Ext.applyIf(me, { value: '' }); // hack: avoid ExtJS validate() bug
Ext.applyIf(me, { value: [] }); // hack: avoid ExtJS validate() bug
Ext.applyIf(me.listConfig, { width: 400 });
@ -407,7 +411,7 @@ Ext.define('Proxmox.form.ComboGrid', {
// Create the picker at an early stage, so it is available to store the previous selection
if (!me.picker) {
me.createPicker();
me.getPicker();
}
me.mon(me.store, 'beforeload', function() {
@ -428,7 +432,7 @@ Ext.define('Proxmox.form.ComboGrid', {
// if the picker exists, we reset its minHeight to the previous saved one or 0
if (me.picker) {
me.picker.setMinHeight(me.savedMinHeight || 0);
Proxmox.Utils.setErrorMask(me.picker);
Proxmox.Utils.setErrorMask(me.picker.getView());
delete me.savedMinHeight;
// we have to update the layout, otherwise the height gets not recalculated
me.picker.updateLayout();
@ -463,7 +467,10 @@ Ext.define('Proxmox.form.ComboGrid', {
} else {
let msg = Proxmox.Utils.getResponseErrorMessage(o.getError());
if (me.picker) {
Proxmox.Utils.setErrorMask(me.picker, msg);
me.savedMinHeight = me.savedMinHeight ?? me.picker.getMinHeight();
me.picker.setMinHeight(me.errorHeight);
Proxmox.Utils.setErrorMask(me.picker.getView(), msg);
me.picker.updateLayout();
}
me.loadError = msg;
}

View File

@ -1,151 +1,169 @@
Ext.define('Proxmox.DateTimeField', {
extend: 'Ext.form.FieldContainer',
xtype: 'promxoxDateTimeField',
// FIXME: remove once all use sites upgraded (with versioned depends on new WTK!)
alias: ['widget.promxoxDateTimeField'],
xtype: 'proxmoxDateTimeField',
layout: 'hbox',
referenceHolder: true,
viewModel: {
data: {
datetime: null,
minDatetime: null,
maxDatetime: null,
},
submitFormat: 'U',
formulas: {
date: {
get: function(get) {
return get('datetime');
},
set: function(date) {
if (!date) {
this.set('datetime', null);
return;
}
let datetime = new Date(this.get('datetime'));
datetime.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
this.set('datetime', datetime);
},
},
time: {
get: function(get) {
return get('datetime');
},
set: function(time) {
if (!time) {
this.set('datetime', null);
return;
}
let datetime = new Date(this.get('datetime'));
datetime.setHours(time.getHours());
datetime.setMinutes(time.getMinutes());
datetime.setSeconds(time.getSeconds());
datetime.setMilliseconds(time.getMilliseconds());
this.set('datetime', datetime);
},
},
minDate: {
get: function(get) {
let datetime = get('minDatetime');
return datetime ? new Date(datetime) : null;
},
},
maxDate: {
get: function(get) {
let datetime = get('maxDatetime');
return datetime ? new Date(datetime) : null;
},
},
minTime: {
get: function(get) {
let current = get('datetime');
let min = get('minDatetime');
if (min && current && !this.isSameDay(current, min)) {
return new Date(min).setHours('00', '00', '00', '000');
}
return min;
},
},
maxTime: {
get: function(get) {
let current = get('datetime');
let max = get('maxDatetime');
if (max && current && !this.isSameDay(current, max)) {
return new Date(max).setHours('23', '59', '59', '999');
}
return max;
},
},
},
// Helper function to check if dates are the same day of the year
isSameDay: function(date1, date2) {
return date1.getDate() === date2.getDate() &&
date1.getMonth() === date2.getMonth() &&
date1.getFullYear() === date2.getFullYear();
},
},
config: {
value: null,
submitFormat: 'U',
disabled: false,
},
setValue: function(value) {
this.getViewModel().set('datetime', value);
},
getValue: function() {
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);
return this.getViewModel().get('datetime');
},
getSubmitValue: function() {
let me = this;
let format = me.submitFormat;
let value = me.getValue();
let me = this;
let value = me.getValue();
return value ? Ext.Date.format(value, me.submitFormat) : null;
},
return value ? Ext.Date.format(value, format) : null;
setMinValue: function(value) {
this.getViewModel().set('minDatetime', value);
},
getMinValue: function() {
return this.getViewModel().get('minDatetime');
},
setMaxValue: function(value) {
this.getViewModel().set('maxDatetime', value);
},
getMaxValue: function() {
return this.getViewModel().get('maxDatetime');
},
initComponent: function() {
let me = this;
me.callParent();
let vm = me.getViewModel();
vm.set('datetime', me.config.value);
// Propagate state change to binding
vm.bind('{datetime}', function(value) {
me.publishState('value', value);
me.fireEvent('change', value);
});
},
items: [
{
xtype: 'datefield',
editable: false,
reference: 'dateentry',
flex: 1,
format: 'Y-m-d',
bind: {
value: '{date}',
minValue: '{minDate}',
maxValue: '{maxDate}',
},
},
{
xtype: 'timefield',
reference: 'timeentry',
format: 'H:i',
width: 80,
value: '00:00',
increment: 60,
bind: {
value: '{time}',
minValue: '{minTime}',
maxValue: '{maxTime}',
},
},
],
setMinValue: function(value) {
let me = this;
let current = me.getValue();
if (!value || !current) {
return;
}
let minhours = value.getHours();
let minminutes = value.getMinutes();
let hours = current.getHours();
let minutes = current.getMinutes();
value.setHours(0);
value.setMinutes(0);
value.setSeconds(0);
current.setHours(0);
current.setMinutes(0);
current.setSeconds(0);
let time = new Date();
if (current-value > 0) {
time.setHours(0);
time.setMinutes(0);
time.setSeconds(0);
time.setMilliseconds(0);
} else {
time.setHours(minhours);
time.setMinutes(minminutes);
}
me.lookup('timeentry').setMinValue(time);
// current time is smaller than the time part of the new minimum
// so we have to add 1 to the day
if (minhours*60+minminutes > hours*60+minutes) {
value.setDate(value.getDate()+1);
}
me.lookup('dateentry').setMinValue(value);
},
setMaxValue: function(value) {
let me = this;
let current = me.getValue();
if (!value || !current) {
return;
}
let maxhours = value.getHours();
let maxminutes = value.getMinutes();
let hours = current.getHours();
let minutes = current.getMinutes();
value.setHours(0);
value.setMinutes(0);
current.setHours(0);
current.setMinutes(0);
let time = new Date();
if (value-current > 0) {
time.setHours(23);
time.setMinutes(59);
time.setSeconds(59);
} else {
time.setHours(maxhours);
time.setMinutes(maxminutes);
}
me.lookup('timeentry').setMaxValue(time);
// current time is biger than the time part of the new maximum
// so we have to subtract 1 to the day
if (maxhours*60+maxminutes < hours*60+minutes) {
value.setDate(value.getDate()-1);
}
me.lookup('dateentry').setMaxValue(value);
},
initComponent: function() {
let me = this;
me.callParent();
let value = me.value || new Date();
me.lookupReference('dateentry').setValue(value);
me.lookupReference('timeentry').setValue(value);
if (me.minValue) {
me.setMinValue(me.minValue);
}
if (me.maxValue) {
me.setMaxValue(me.maxValue);
}
me.relayEvents(me.lookupReference('dateentry'), ['change']);
me.relayEvents(me.lookupReference('timeentry'), ['change']);
},
});

View File

@ -8,7 +8,10 @@ Ext.define('Proxmox.form.DiskSelector', {
// journal_disk: all disks with gpt
diskType: undefined,
// the property the backend wnats for the type ('type' by default)
// use include-partitions=1 as a parameter
includePartitions: false,
// the property the backend wants for the type ('type' by default)
typeProperty: 'type',
valueField: 'devpath',
@ -53,6 +56,10 @@ Ext.define('Proxmox.form.DiskSelector', {
extraParams[me.typeProperty] = me.diskType;
}
if (me.includePartitions) {
extraParams['include-partitions'] = 1;
}
var store = Ext.create('Ext.data.Store', {
filterOnLoad: true,
model: 'pmx-disk-list',

View File

@ -24,7 +24,7 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
getEditable: function() {
let me = this;
let vm = me.getViewModel();
vm.get('editable');
return vm.get('editable');
},
setValue: function(value) {
@ -37,9 +37,19 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
getValue: function() {
let me = this;
let vm = me.getViewModel();
// FIXME: add return, but check all use-sites for regressions then
vm.get('value');
},
setEmptyText: function(emptyText) {
let me = this;
me.editField.setEmptyText(emptyText);
},
getEmptyText: function() {
let me = this;
return me.editField.getEmptyText();
},
layout: 'fit',
defaults: {
hideLabel: true,
@ -68,6 +78,10 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
delete displayConfig.displayConfig;
}
Ext.applyIf(displayConfig, {
renderer: v => Ext.htmlEncode(v),
});
Ext.applyIf(displayConfig.bind, {
hidden: '{editable}',
disabled: '{editable}',
@ -94,6 +108,11 @@ Ext.define('Proxmox.form.field.DisplayEdit', {
me.callParent();
// save a reference to make it easier when one needs to operate on the underlying fields,
// like when creating a passthrough getter/setter to allow easy data-binding.
me.editField = me.down(editConfig.xtype);
me.displayField = me.down(displayConfig.xtype);
me.getViewModel().set('editable', me.editable);
},

View File

@ -0,0 +1,14 @@
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,
},
});

View File

@ -11,20 +11,19 @@ Ext.define('Proxmox.form.field.Integer', {
step: 1,
getSubmitData: function() {
let me = this,
data = null,
val;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
val = me.getSubmitValue();
if (val !== undefined && val !== null && val !== '') {
data = {};
data[me.getName()] = val;
} else if (me.getDeleteEmpty()) {
let me = this;
let data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
let val = me.getSubmitValue();
if (val !== undefined && val !== null && val !== '') {
data = {};
data.delete = me.getName();
data[me.getName()] = val;
} else if (me.getDeleteEmpty()) {
data = {};
data.delete = me.getName();
}
}
return data;
}
return data;
},
});

View File

@ -18,7 +18,7 @@ Ext.define('Proxmox.form.KVComboBox', {
valueField: 'key',
queryMode: 'local',
// overide framework function to implement deleteEmpty behaviour
// override framework function to implement deleteEmpty behaviour
getSubmitData: function() {
let me = this,
data = null,

View File

@ -3,4 +3,9 @@ Ext.define('Proxmox.form.LanguageSelector', {
xtype: 'proxmoxLanguageSelector',
comboItems: Proxmox.Utils.language_array(),
matchFieldWidth: false,
listConfig: {
width: 300,
},
});

View File

@ -24,6 +24,9 @@ Ext.define('Proxmox.form.MultiDiskSelector', {
// the type of disks to show
diskType: 'unused',
// add include-partitions=1 as a request parameter
includePartitions: false,
disks: [],
allowBlank: false,
@ -36,6 +39,8 @@ Ext.define('Proxmox.form.MultiDiskSelector', {
setValue: function(value) {
let me = this;
value ??= [];
if (!Ext.isArray(value)) {
value = value.split(/;, /);
}
@ -141,22 +146,31 @@ Ext.define('Proxmox.form.MultiDiskSelector', {
initComponent: function() {
let me = this;
let extraParams = {};
if (!me.url) {
if (!me.nodename) {
throw "no url or nodename given";
}
let node = me.nodename;
let param = me.typeParameter;
let type = me.diskType;
me.url = `/api2/json/nodes/${node}/disks/list?${param}=${type}`;
me.url = `/api2/json/nodes/${me.nodename}/disks/list`;
extraParams[me.typeParameter] = me.diskType;
if (me.includePartitions) {
extraParams['include-partitions'] = 1;
}
}
me.disks = [];
me.callParent();
let store = me.getStore();
store.getProxy().setUrl(me.url);
store.setProxy({
type: 'proxmox',
url: me.url,
extraParams,
});
store.load();
store.sort({ property: me.valueField });
},

View File

@ -45,10 +45,6 @@ Ext.define('Proxmox.form.NetworkSelector', {
networkSelectorStore.load();
}
},
// set default value to empty array, else it inits it with
// null and after the store load it is an empty array,
// triggering dirtychange
value: [],
valueField: 'cidr',
displayField: 'cidr',
store: {
@ -69,8 +65,8 @@ Ext.define('Proxmox.form.NetworkSelector', {
},
],
listeners: {
load: function(store, records, successfull) {
if (successfull) {
load: function(store, records, successful) {
if (successful) {
records.forEach(function(record) {
if (record.data.cidr6) {
let dest = record.data.cidr ? record.copy(null) : record;
@ -123,6 +119,7 @@ Ext.define('Proxmox.form.NetworkSelector', {
header: gettext('Comment'),
flex: 2,
dataIndex: 'comments',
renderer: Ext.String.htmlEncode,
},
],
},

View File

@ -6,7 +6,13 @@ Ext.define('Proxmox.form.RealmComboBox', {
xclass: 'Ext.app.ViewController',
init: function(view) {
view.store.on('load', this.onLoad, view);
let store = view.getStore();
store.proxy.url = `/api2/json${view.baseUrl}`;
if (view.storeFilter) {
store.setFilters(view.storeFilter);
}
store.on('load', this.onLoad, view);
store.load();
},
onLoad: function(store, records, success) {
@ -27,6 +33,9 @@ Ext.define('Proxmox.form.RealmComboBox', {
},
},
// define custom filters for the underlying store
storeFilter: undefined,
fieldLabel: gettext('Realm'),
name: 'realm',
queryMode: 'local',
@ -37,6 +46,7 @@ Ext.define('Proxmox.form.RealmComboBox', {
triggerAction: 'all',
valueField: 'realm',
displayField: 'descr',
baseUrl: '/access/domains',
getState: function() {
return { value: this.getValue() };
},
@ -52,6 +62,6 @@ Ext.define('Proxmox.form.RealmComboBox', {
store: {
model: 'pmx-domains',
autoLoad: true,
autoLoad: false,
},
});

View File

@ -18,17 +18,22 @@ Ext.define('Proxmox.form.RoleSelector', {
displayField: 'roleid',
listConfig: {
width: 560,
resizable: true,
columns: [
{
header: gettext('Role'),
sortable: true,
dataIndex: 'roleid',
flex: 1,
flex: 2,
},
{
header: gettext('Privileges'),
dataIndex: 'privs',
flex: 1,
cellWrap: true,
// join manually here, as ExtJS joins without whitespace which breaks cellWrap
renderer: v => Ext.isArray(v) ? v.join(', ') : v.replaceAll(',', ', '),
flex: 5,
},
],
},

61
src/form/TextAreaField.js Normal file
View File

@ -0,0 +1,61 @@
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();
},
});

View File

@ -6,6 +6,8 @@ Ext.define('Proxmox.form.field.Textfield', {
skipEmptyText: true,
deleteEmpty: false,
trimValue: false,
},
getSubmitData: function() {
@ -29,6 +31,9 @@ Ext.define('Proxmox.form.field.Textfield', {
let me = this;
let value = this.processRawValue(this.getRawValue());
if (me.getTrimValue() && typeof value === 'string') {
value = value.trim();
}
if (value !== '') {
return value;
}

View File

@ -0,0 +1,6 @@
Ext.define('Proxmox.form.ThemeSelector', {
extend: 'Proxmox.form.KVComboBox',
xtype: 'proxmoxThemeSelector',
comboItems: Proxmox.Utils.theme_array(),
});

50
src/form/UserSelector.js Normal file
View File

@ -0,0 +1,50 @@
Ext.define('Proxmox.form.UserSelector', {
extend: 'Proxmox.form.ComboGrid',
alias: 'widget.pmxUserSelector',
allowBlank: false,
autoSelect: false,
valueField: 'userid',
displayField: 'userid',
editable: true,
anyMatch: true,
forceSelection: true,
store: {
model: 'pmx-users',
autoLoad: true,
params: {
enabled: 1,
},
sorters: 'userid',
},
listConfig: {
columns: [
{
header: gettext('User'),
sortable: true,
dataIndex: 'userid',
renderer: Ext.String.htmlEncode,
flex: 1,
},
{
header: gettext('Name'),
sortable: true,
renderer: (first, mD, rec) => Ext.String.htmlEncode(
`${first || ''} ${rec.data.lastname || ''}`,
),
dataIndex: 'firstname',
flex: 1,
},
{
header: gettext('Comment'),
sortable: false,
dataIndex: 'comment',
renderer: Ext.String.htmlEncode,
flex: 1,
},
],
},
});

40
src/form/VlanField.js Normal file
View File

@ -0,0 +1,40 @@
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();
},
});

View File

@ -1,8 +1,8 @@
/** Renders a list of key values objets
/** Renders a list of key values objects
Mandatory Config Parameters:
rows: an object container where each propery is a key-value object we want to render
rows: an object container where each property is a key-value object we want to render
rows: {
keyboard: {
@ -67,7 +67,6 @@ Ext.define('Proxmox.grid.ObjectGrid', {
editor: {
xtype: 'proxmoxWindowEdit',
subject: text,
onlineHelp: opts.onlineHelp,
fieldDefaults: {
labelWidth: opts.labelWidth || 100,
},
@ -84,6 +83,9 @@ Ext.define('Proxmox.grid.ObjectGrid', {
},
},
};
if (opts.onlineHelp) {
me.rows[name].editor.onlineHelp = opts.onlineHelp;
}
},
add_text_row: function(name, text, opts) {
@ -100,7 +102,6 @@ Ext.define('Proxmox.grid.ObjectGrid', {
editor: {
xtype: 'proxmoxWindowEdit',
subject: text,
onlineHelp: opts.onlineHelp,
fieldDefaults: {
labelWidth: opts.labelWidth || 100,
},
@ -115,6 +116,9 @@ Ext.define('Proxmox.grid.ObjectGrid', {
},
},
};
if (opts.onlineHelp) {
me.rows[name].editor.onlineHelp = opts.onlineHelp;
}
},
add_boolean_row: function(name, text, opts) {
@ -131,7 +135,6 @@ Ext.define('Proxmox.grid.ObjectGrid', {
editor: {
xtype: 'proxmoxWindowEdit',
subject: text,
onlineHelp: opts.onlineHelp,
fieldDefaults: {
labelWidth: opts.labelWidth || 100,
},
@ -147,6 +150,9 @@ Ext.define('Proxmox.grid.ObjectGrid', {
},
},
};
if (opts.onlineHelp) {
me.rows[name].editor.onlineHelp = opts.onlineHelp;
}
},
add_integer_row: function(name, text, opts) {
@ -163,7 +169,6 @@ Ext.define('Proxmox.grid.ObjectGrid', {
editor: {
xtype: 'proxmoxWindowEdit',
subject: text,
onlineHelp: opts.onlineHelp,
fieldDefaults: {
labelWidth: opts.labelWidth || 100,
},
@ -180,6 +185,40 @@ 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
@ -248,7 +287,7 @@ Ext.define('Proxmox.grid.ObjectGrid', {
let renderer = rowdef.renderer;
if (renderer) {
return renderer(value, metaData, record, rowIndex, colIndex, store);
return renderer.call(me, value, metaData, record, rowIndex, colIndex, store);
}
return value;

View File

@ -1,19 +1,18 @@
# icon-cpu, icon-ram
# are self made (sources as .xcf)
include ../defines.mk
IMAGES=pmx-clear-trigger.png \
icon-cpu.png \
icon-ram.png \
IMAGES=pmx-clear-trigger.png \
openid-icon-100x100.png \
icon-cpu.svg \
icon-ram.svg \
debian-swirl-openlogo.svg \
proxmox-symbol-x.svg \
all:
.PHONY: install
install: ${IMAGES}
install -d ${WWWIMAGESDIR}
for i in ${IMAGES}; do install -m 0755 $$i ${WWWIMAGESDIR}/$$i; done
install: $(IMAGES)
install -d $(WWWIMAGESDIR)
for i in $(IMAGES); do install -m 0644 $$i $(WWWIMAGESDIR)/$$i; done
.PHONY: clean
clean:

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 10.0, SVG Export Plug-In . SVG Version: 3.0.0 Build 77) -->
<svg enable-background="new 0 0 87.041 108.445" height="108.445" viewBox="0,0,87.041,108.445" width="87.041" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" i:viewOrigin="262 450" i:rulerOrigin="0 0" i:pageBounds="0 792 612 0" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/">
<metadata>
<sfw>
<slices/>
<sliceSourceBounds bottomLeftOrigin="true"/>
</sfw>
</metadata>
<g id="Layer_1" i:layer="yes" i:dimmedPercent="50" i:rgbTrio="#4F008000FFFF">
<g>
<path d="m51.986 57.297c-1.797.025.34.926 2.686 1.287.648-.506 1.236-1.018 1.76-1.516-1.461.358-2.948.366-4.446.229" fill="#a80030" i:knockout="Off"/>
<path d="m61.631 54.893c1.07-1.477 1.85-3.094 2.125-4.766-.24 1.192-.887 2.221-1.496 3.307-3.359 2.115-.316-1.256-.002-2.537-3.612 4.546-.496 2.726-.627 3.996" fill="#a80030" i:knockout="Off"/>
<path d="m65.191 45.629c.217-3.236-.637-2.213-.924-.978.335.174.6 2.281.924.978" fill="#a80030" i:knockout="Off"/>
<path d="m45.172 1.399c.959.172 2.072.304 1.916.533 1.049-.23 1.287-.442-1.916-.533" fill="#a80030" i:knockout="Off"/>
<path d="m47.088 1.932-.678.14.631-.056z" fill="#a80030" i:knockout="Off"/>
<path d="m76.992 46.856c.107 2.906-.85 4.316-1.713 6.812l-1.553.776c-1.271 2.468.123 1.567-.787 3.53-1.984 1.764-6.021 5.52-7.313 5.863-.943-.021.639-1.113.846-1.541-2.656 1.824-2.131 2.738-6.193 3.846l-.119-.264c-10.018 4.713-23.934-4.627-23.751-17.371-.107.809-.304.607-.526.934-.517-6.557 3.028-13.143 9.007-15.832 5.848-2.895 12.704-1.707 16.893 2.197-2.301-3.014-6.881-6.209-12.309-5.91-5.317.084-10.291 3.463-11.951 7.131-2.724 1.715-3.04 6.611-4.227 7.507-1.597 11.737 3.004 16.808 10.787 22.773 1.225.826.345.951.511 1.58-2.586-1.211-4.954-3.039-6.901-5.277 1.033 1.512 2.148 2.982 3.589 4.137-2.438-.826-5.695-5.908-6.646-6.115 4.203 7.525 17.052 13.197 23.78 10.383-3.113.115-7.068.064-10.566-1.229-1.469-.756-3.467-2.322-3.11-2.615 9.182 3.43 18.667 2.598 26.612-3.771 2.021-1.574 4.229-4.252 4.867-4.289-.961 1.445.164.695-.574 1.971 2.014-3.248-.875-1.322 2.082-5.609l1.092 1.504c-.406-2.696 3.348-5.97 2.967-10.234.861-1.304.961 1.403.047 4.403 1.268-3.328.334-3.863.66-6.609.352.923.814 1.904 1.051 2.878-.826-3.216.848-5.416 1.262-7.285-.408-.181-1.275 1.422-1.473-2.377.029-1.65.459-.865.625-1.271-.324-.186-1.174-1.451-1.691-3.877.375-.57 1.002 1.478 1.512 1.562-.328-1.929-.893-3.4-.916-4.88-1.49-3.114-.527.415-1.736-1.337-1.586-4.947 1.316-1.148 1.512-3.396 2.404 3.483 3.775 8.881 4.404 11.117-.48-2.726-1.256-5.367-2.203-7.922.73.307-1.176-5.609.949-1.691-2.27-8.352-9.715-16.156-16.564-19.818.838.767 1.896 1.73 1.516 1.881-3.406-2.028-2.807-2.186-3.295-3.043-2.775-1.129-2.957.091-4.795.002-5.23-2.774-6.238-2.479-11.051-4.217l.219 1.023c-3.465-1.154-4.037.438-7.782.004-.228-.178 1.2-.644 2.375-.815-3.35.442-3.193-.66-6.471.122.808-.567 1.662-.942 2.524-1.424-2.732.166-6.522 1.59-5.352.295-4.456 1.988-12.37 4.779-16.811 8.943l-.14-.933c-2.035 2.443-8.874 7.296-9.419 10.46l-.544.127c-1.059 1.793-1.744 3.825-2.584 5.67-1.385 2.36-2.03.908-1.833 1.278-2.724 5.523-4.077 10.164-5.246 13.97.833 1.245.02 7.495.335 12.497-1.368 24.704 17.338 48.69 37.785 54.228 2.997 1.072 7.454 1.031 11.245 1.141-4.473-1.279-5.051-.678-9.408-2.197-3.143-1.48-3.832-3.17-6.058-5.102l.881 1.557c-4.366-1.545-2.539-1.912-6.091-3.037l.941-1.229c-1.415-.107-3.748-2.385-4.386-3.646l-1.548.061c-1.86-2.295-2.851-3.949-2.779-5.23l-.5.891c-.567-.973-6.843-8.607-3.587-6.83-.605-.553-1.409-.9-2.281-2.484l.663-.758c-1.567-2.016-2.884-4.6-2.784-5.461.836 1.129 1.416 1.34 1.99 1.533-3.957-9.818-4.179-.541-7.176-9.994l.634-.051c-.486-.732-.781-1.527-1.172-2.307l.276-2.75c-2.849-3.294-.797-14.006-.386-19.881.285-2.389 2.378-4.932 3.97-8.92l-.97-.167c1.854-3.234 10.586-12.988 14.63-12.486 1.959-2.461-.389-.009-.772-.629 4.303-4.453 5.656-3.146 8.56-3.947 3.132-1.859-2.688.725-1.203-.709 5.414-1.383 3.837-3.144 10.9-3.846.745.424-1.729.655-2.35 1.205 4.511-2.207 14.275-1.705 20.617 1.225 7.359 3.439 15.627 13.605 15.953 23.17l.371.1c-.188 3.802.582 8.199-.752 12.238z" fill="#a80030" i:knockout="Off"/>
<path d="m32.372 59.764-.252 1.26c1.181 1.604 2.118 3.342 3.626 4.596-1.085-2.118-1.891-2.993-3.374-5.856" fill="#a80030" i:knockout="Off"/>
<path d="m35.164 59.654c-.625-.691-.995-1.523-1.409-2.352.396 1.457 1.207 2.709 1.962 3.982z" fill="#a80030" i:knockout="Off"/>
<path d="m84.568 48.916-.264.662c-.484 3.438-1.529 6.84-3.131 9.994 1.77-3.328 2.915-6.968 3.395-10.656" fill="#a80030" i:knockout="Off"/>
<path d="m45.527.537c1.215-.445 2.987-.244 4.276-.537-1.68.141-3.352.225-5.003.438z" fill="#a80030" i:knockout="Off"/>
<path d="m2.872 23.219c.28 2.592-1.95 3.598.494 1.889 1.31-2.951-.512-.815-.494-1.889" fill="#a80030" i:knockout="Off"/>
<path d="m0 35.215c.563-1.728.665-2.766.88-3.766-1.556 1.989-.716 2.413-.88 3.766" fill="#a80030" i:knockout="Off"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

38
src/images/icon-cpu.svg Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="100"
height="100"
version="1.1"
>
<g fill="none" stroke="#000" stroke-width="5" shape-rendering="crispEdges">
<!-- base CPU -->
<rect x="15" y="15" rx="0" ry="0" width="70" height="70" />
<rect x="30" y="30" rx="0" ry="0" width="40" height="40" fill="black"/>
<g stroke-width="8"> <!-- pins -->
<!-- left -->
<path d="m14,24.5 h-12.5"/>
<path d="m14,41.5 h-12.5"/>
<path d="m14,58.5 h-12.5"/>
<path d="m14,75.5 h-12.5"/>
<!-- right -->
<path d="m86,24.5 h+12.5"/>
<path d="m86,41.5 h+12.5"/>
<path d="m86,58.5 h+12.5"/>
<path d="m86,75.5 h+12.5"/>
<!-- top -->
<path d="m24.5,14 v-12.5"/>
<path d="m41.5,14 v-12.5"/>
<path d="m58.5,14 v-12.5"/>
<path d="m75.5,14 v-12.5"/>
<!-- bottom -->
<path d="m24.5,86 v+12.5"/>
<path d="m41.5,86 v+12.5"/>
<path d="m58.5,86 v+12.5"/>
<path d="m75.5,86 v+12.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

29
src/images/icon-ram.svg Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="100"
height="100"
version="1.1"
>
<g fill="none" stroke="#000" stroke-width="7" shape-rendering="crispEdges">
<rect x="3.0" y="25" rx="0" ry="0" width="94" height="40"/> <!-- outer dimm PCB border -->
<g stroke-width="0"> <!-- dimm chips -->
<rect x="12.5" y="35" rx="0" ry="0" width="20" height="20" fill="black"/>
<rect x="40.0" y="35" rx="0" ry="0" width="20" height="20" fill="black"/>
<rect x="67.5" y="35" rx="0" ry="0" width="20" height="20" fill="black"/>
</g>
<g stroke-width="8"> <!-- pins -->
<path d="m10,67 v+10"/>
<path d="m26,67 v+10"/>
<path d="m42,67 v+10"/>
<path d="m58,67 v+10"/>
<path d="m74,67 v+10"/>
<path d="m90,67 v+10"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="svg5876" height="260" viewBox="0,0,259.99998,259.99998" width="260" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" sodipodi:docname="Proxmox_symbol_favicon.svg" inkscape:export-filename="S:\Proxmox\Marketing\Logo\Favicon\Proxmox_symbol_favicon_260.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96">
<clipPath id="clipPath3102-4-5">
<rect id="rect3104-1-3" height="326.40991" transform="matrix(.73449161 .67861776 -.78497193 .61953133 0 0)" width="436.40189" x="-82.999916" y="-347.71387"/>
</clipPath>
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.4" inkscape:cx="-45.895771" inkscape:cy="6.3650844" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="2560" inkscape:window-height="1377" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" units="px" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0"/>
<metadata id="metadata5881">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g id="layer1" transform="translate(-2.603202 -774.18762)" inkscape:label="Layer 1" inkscape:groupmode="layer">
<g id="g3021" transform="matrix(9.45675 0 0 9.45675 -18.704755 -8896.4297)" inkscape:export-xdpi="300" inkscape:export-ydpi="300">
<g id="g7607-5" clip-path="url(#clipPath3102-4-5)" font-family="Helion" font-size="144" letter-spacing="0" line-height="125%" transform="matrix(-.04778455 0 0 .04778455 29.203266 1024.7667)" word-spacing="0">
<path id="path7609-8" d="m276.30443 226.6231 190.58784-209.602495c-7.39324-7.3925179-16.00465-13.2011729-25.83431-17.42598195-9.8304-4.22414015-20.39156-6.37699515-31.68359-6.45857095-11.99338.0917448-22.98109 2.4680153-32.96314 7.12881886-9.98263 4.66147074-18.655 11.05910904-26.0171 19.19293404l-74.09086 80.915015-73.60342-80.915015c-7.6265-8.133835-16.50198-14.5314802-26.62641-19.1929557-10.12463-4.6608083-21.07169-7.0370717-32.84124-7.1287972-11.29242.0815863-21.8536 2.2344429-31.6836 6.45857839-9.83005 4.22480401-18.441478 10.03345661-25.834313 17.42597451l190.593403 209.596915" inkscape:connector-curvature="0" sodipodi:nodetypes="ccccccccccccc"/>
</g>
<g id="g7611-6" clip-path="url(#clipPath3102-4-5)" font-family="Helion" font-size="144" letter-spacing="0" line-height="125%" transform="matrix(.04778455 0 0 -.04778455 2.796945 1047.9575)" word-spacing="0">
<path id="path7613-7" d="m276.30443 226.6231 190.58784-209.602495c-7.39324-7.3925179-16.00465-13.2011729-25.83431-17.42598195-9.8304-4.22414015-20.39156-6.37699515-31.68359-6.45857095-11.99338.0917448-22.98109 2.4680153-32.96314 7.12881886-9.98263 4.66147074-18.655 11.05910904-26.0171 19.19293404l-74.09086 80.915015-73.60342-80.915015c-7.6265-8.133835-16.50198-14.5314802-26.62641-19.1929557-10.12463-4.6608083-21.07169-7.0370717-32.84124-7.1287972-11.29242.0815863-21.8536 2.2344429-31.6836 6.45857839-9.83005 4.22480401-18.441478 10.03345661-25.834313 17.42597451l190.593403 209.596915" sodipodi:nodetypes="ccccccccccccc" inkscape:connector-curvature="0"/>
</g>
<path id="path7615-7" d="m15.23405 1036.3622-6.8601967-7.5231c-.3990192-.4256-.8633807-.7604-1.3930839-1.0042-.5297195-.2439-1.1024686-.3682-1.7182481-.3731-.590815 0-1.1433739.1168-1.6576769.338-.5143056.221-.9648526.5249-1.3516436.9118l6.9622457 7.6508-6.9622457 7.6508c.38679.3989.837338.7103 1.3516436.934.514303.2237 1.0668619.3373 1.6576769.3411.6173744 0 1.1933086-.129 1.7278076-.373.5344943-.2439.9956679-.5787 1.3835244-1.0041l6.8601817-7.549" fill="#e57000" font-family="Helion" font-size="1059.612793" letter-spacing="0" line-height="125%" word-spacing="0" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccccscscc"/>
<path id="path7617-2" d="m16.76594 1036.3622 6.860196-7.5231c.399019-.4256.86338-.7604 1.393084-1.0042.529719-.2439 1.10247-.3682 1.718247-.3731.590818 0 1.143377.1168 1.657677.338.51431.221.964857.5249 1.351646.9118l-6.962247 7.6508 6.962247 7.6508c-.386789.3989-.837336.7103-1.351646.934-.5143.2237-1.066859.3373-1.657673.3411-.617375 0-1.193311-.129-1.727809-.373-.534496-.2439-.99567-.5787-1.383526-1.0041l-6.860182-7.549" fill="#e57000" font-family="Helion" font-size="1059.612793" letter-spacing="0" line-height="125%" word-spacing="0" sodipodi:nodetypes="cccccccccscscc" inkscape:connector-curvature="0"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,3 +1,99 @@
/*
* The Proxmox CBind mixin is intended to supplement the 'bind' mechanism
* of ExtJS. In contrast to the 'bind', 'cbind' only acts during the creation
* of the component, not during its lifetime. It's only applied once before
* the 'initComponent' method is executed, and thus you have only access
* to the basic initial configuration of it.
*
* You can use it to get a 'declarative' approach to component declaration,
* even when you need to set some properties of sub-components dynamically
* (e.g., the 'nodename'). It overwrites the given properties of the 'cbind'
* object in the component with their computed values of the computed
* cbind configuration object of the 'cbindData' function (or object).
*
* The cbind syntax is inspired by ExtJS' bind syntax ('{property}'), where
* it is possible to negate values ('{!negated}'), access sub-properties of
* objects ('{object.property}') and even use a getter function,
* akin to viewModel formulas ('(get) => get("prop")') to execute more
* complicated dependencies (e.g., urls).
*
* The 'cbind' will be recursively applied to all properties (objects/arrays)
* that contain an 'xtype' or 'cbind' property, but stops for a subtree if the
* object in question does not have either (if you have one or more levels that
* have no cbind/xtype property, you can insert empty cbind objects there to
* reach deeper nested objects).
*
* This reduces the code in the 'initComponent' and instead we can statically
* declare items, buttons, tbars, etc. while the dynamic parts are contained
* in the 'cbind'.
*
* It is used like in the following example:
*
* Ext.define('Some.Component', {
* extend: 'Some.other.Component',
*
* // first it has to be enabled
* mixins: ['Proxmox.Mixin.CBind'],
*
* // then a base config has to be defined. this can be a function,
* // which has access to the initial config and can store persistent
* // properties, as well as return temporary ones (which only exist during
* // the cbind process)
* // this function will be called before 'initComponent'
* cbindData: function(initialconfig) {
* // 'this' here is the same as in 'initComponent'
* let me = this;
* me.persistentProperty = false;
* return {
* temporaryProperty: true,
* };
* },
*
* // if there is no need for persistent properties, it can also simply be an object
* cbindData: {
* temporaryProperty: true,
* // properties itself can also be functions that will be evaluated before
* // replacing the values
* dynamicProperty: (cfg) => !cfg.temporaryProperty,
* numericProp: 0,
* objectProp: {
* foo: 'bar',
* bar: 'baz',
* }
* },
*
* // you can 'cbind' the component itself, here the 'target' property
* // will be replaced with the content of 'temporaryProperty' (true)
* // before the components initComponent
* cbind: {
* target: '{temporaryProperty}',
* },
*
* items: [
* {
* xtype: 'checkbox',
* cbind: {
* value: '{!persistentProperty}',
* object: '{objectProp.foo}'
* dynamic: (get) => get('numericProp') + 1,
* },
* },
* {
* // empty cbind so that subitems are reached
* cbind: {},
* items: [
* {
* xtype: 'textfield',
* cbind: {
* value: '{objectProp.bar}',
* },
* },
* ],
* },
* ],
* });
*/
Ext.define('Proxmox.Mixin.CBind', {
extend: 'Ext.Mixin',

View File

@ -1,7 +1,9 @@
Ext.define('apt-pkglist', {
extend: 'Ext.data.Model',
fields: ['Package', 'Title', 'Description', 'Section', 'Arch',
'Priority', 'Version', 'OldVersion', 'ChangeLogUrl', 'Origin'],
fields: [
'Package', 'Title', 'Description', 'Section', 'Arch', 'Priority', 'Version', 'OldVersion',
'Origin',
],
idProperty: 'Package',
});
@ -56,7 +58,7 @@ Ext.define('Proxmox.node.APT', {
groupField: 'Origin',
proxy: {
type: 'proxmox',
url: "/api2/json/nodes/" + me.nodename + "/apt/update",
url: `/api2/json/nodes/${me.nodename}/apt/update`,
},
sorters: [
{
@ -65,6 +67,7 @@ Ext.define('Proxmox.node.APT', {
},
],
});
Proxmox.Utils.monStoreErrors(me, store, true);
let groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
groupHeaderTpl: '{[ "Origin: " + values.name ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})',
@ -76,37 +79,24 @@ Ext.define('Proxmox.node.APT', {
let headerCt = this.view.headerCt;
let colspan = headerCt.getColumnCount();
return {
rowBody: '<div style="padding: 1em">' +
Ext.String.htmlEncode(data.Description) +
'</div>',
rowBody: `<div style="padding: 1em">${Ext.htmlEncode(data.Description)}</div>`,
rowBodyCls: me.full_description ? '' : Ext.baseCSSPrefix + 'grid-row-body-hidden',
rowBodyColspan: colspan,
};
},
});
let reload = function() {
store.load();
};
Proxmox.Utils.monStoreErrors(me, store, true);
let apt_command = function(cmd) {
Proxmox.Utils.API2Request({
url: "/nodes/" + me.nodename + "/apt/" + cmd,
url: `/nodes/${me.nodename}/apt/${cmd}`,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, opts) {
let upid = response.result.data;
let win = Ext.create('Proxmox.window.TaskViewer', {
upid: upid,
});
win.show();
me.mon(win, 'close', reload);
},
success: ({ result }) => Ext.create('Proxmox.window.TaskViewer', {
autoShow: true,
upid: result.data,
listeners: {
close: () => store.load(),
},
}),
});
};
@ -114,20 +104,18 @@ Ext.define('Proxmox.node.APT', {
let update_btn = new Ext.Button({
text: gettext('Refresh'),
handler: function() {
Proxmox.Utils.checked_command(function() { apt_command('update'); });
},
handler: () => Proxmox.Utils.checked_command(function() { apt_command('update'); }),
});
let show_changelog = function(rec) {
if (!rec || !rec.data || !(rec.data.ChangeLogUrl && rec.data.Package)) {
if (!rec?.data?.Package) {
console.debug('cannot show changelog, missing Package', rec);
return;
}
let view = Ext.createWidget('component', {
autoScroll: true,
style: {
'background-color': 'white',
'white-space': 'pre',
'font-family': 'monospace',
padding: '5px',
@ -137,7 +125,7 @@ Ext.define('Proxmox.node.APT', {
let win = Ext.create('Ext.window.Window', {
title: gettext('Changelog') + ": " + rec.data.Package,
width: 800,
height: 400,
height: 600,
layout: 'fit',
modal: true,
items: [view],
@ -166,15 +154,8 @@ Ext.define('Proxmox.node.APT', {
text: gettext('Changelog'),
selModel: sm,
disabled: true,
enableFn: function(rec) {
if (!rec || !rec.data || !(rec.data.ChangeLogUrl && rec.data.Package)) {
return false;
}
return true;
},
handler: function(b, e, rec) {
show_changelog(rec);
},
enableFn: rec => !!rec?.data?.Package,
handler: (b, e, rec) => show_changelog(rec),
});
let verbose_desc_checkbox = new Ext.form.field.Checkbox({
@ -201,14 +182,12 @@ Ext.define('Proxmox.node.APT', {
selModel: sm,
viewConfig: {
stripeRows: false,
emptyText: '<div style="display:table; width:100%; height:100%;"><div style="display:table-cell; vertical-align: middle; text-align:center;"><b>' + gettext('No updates available.') + '</div></div>',
emptyText: `<div style="display:flex;justify-content:center;"><p>${gettext('No updates available.')}</p></div>`,
},
features: [groupingFeature, rowBodyFeature],
listeners: {
activate: reload,
itemdblclick: function(v, rec) {
show_changelog(rec);
},
activate: () => store.load(),
itemdblclick: (v, rec) => show_changelog(rec),
},
});

801
src/node/APTRepositories.js Normal file
View File

@ -0,0 +1,801 @@
Ext.define('apt-repolist', {
extend: 'Ext.data.Model',
fields: [
'Path',
'Index',
'Origin',
'FileType',
'Enabled',
'Comment',
'Types',
'URIs',
'Suites',
'Components',
'Options',
],
});
Ext.define('Proxmox.window.APTRepositoryAdd', {
extend: 'Proxmox.window.Edit',
alias: 'widget.pmxAPTRepositoryAdd',
isCreate: true,
isAdd: true,
subject: gettext('Repository'),
width: 600,
initComponent: function() {
let me = this;
if (!me.repoInfo || me.repoInfo.length === 0) {
throw "repository information not initialized";
}
let description = Ext.create('Ext.form.field.Display', {
fieldLabel: gettext('Description'),
name: 'description',
});
let status = Ext.create('Ext.form.field.Display', {
fieldLabel: gettext('Status'),
name: 'status',
renderer: function(value) {
let statusText = gettext('Not yet configured');
if (value !== '') {
statusText = Ext.String.format(
'{0}: {1}',
gettext('Configured'),
value ? gettext('enabled') : gettext('disabled'),
);
}
return statusText;
},
});
let repoSelector = Ext.create('Proxmox.form.KVComboBox', {
fieldLabel: gettext('Repository'),
xtype: 'proxmoxKVComboBox',
name: 'handle',
allowBlank: false,
comboItems: me.repoInfo.map(info => [info.handle, info.name]),
validator: function(renderedValue) {
let handle = this.value;
// we cannot use this.callParent in instantiations
let valid = Proxmox.form.KVComboBox.prototype.validator.call(this, renderedValue);
if (!valid || !handle) {
return false;
}
const info = me.repoInfo.find(elem => elem.handle === handle);
if (!info) {
return false;
}
if (info.status) {
return Ext.String.format(gettext('{0} is already configured'), renderedValue);
}
return valid;
},
listeners: {
change: function(f, value) {
const info = me.repoInfo.find(elem => elem.handle === value);
description.setValue(info.description);
status.setValue(info.status);
},
},
});
repoSelector.setValue(me.repoInfo[0].handle);
Ext.apply(me, {
items: [
repoSelector,
description,
status,
],
repoSelector: repoSelector,
});
me.callParent();
},
});
Ext.define('Proxmox.node.APTRepositoriesErrors', {
extend: 'Ext.grid.GridPanel',
xtype: 'proxmoxNodeAPTRepositoriesErrors',
store: {},
scrollable: true,
viewConfig: {
stripeRows: false,
getRowClass: (record) => {
switch (record.data.status) {
case 'warning': return 'proxmox-warning-row';
case 'critical': return 'proxmox-invalid-row';
default: return '';
}
},
},
hideHeaders: true,
columns: [
{
dataIndex: 'status',
renderer: (value) => `<i class="fa fa-fw ${Proxmox.Utils.get_health_icon(value, true)}"></i>`,
width: 50,
},
{
dataIndex: 'message',
flex: 1,
},
],
});
Ext.define('Proxmox.node.APTRepositoriesGrid', {
extend: 'Ext.grid.GridPanel',
xtype: 'proxmoxNodeAPTRepositoriesGrid',
mixins: ['Proxmox.Mixin.CBind'],
title: gettext('APT Repositories'),
cls: 'proxmox-apt-repos', // to allow applying styling to general components with local effect
border: false,
tbar: [
{
text: gettext('Reload'),
iconCls: 'fa fa-refresh',
handler: function() {
let me = this;
me.up('proxmoxNodeAPTRepositories').reload();
},
},
{
text: gettext('Add'),
name: 'addRepo',
disabled: true,
repoInfo: undefined,
cbind: {
onlineHelp: '{onlineHelp}',
},
handler: function(button, event, record) {
Proxmox.Utils.checked_command(() => {
let me = this;
let panel = me.up('proxmoxNodeAPTRepositories');
let extraParams = {};
if (panel.digest !== undefined) {
extraParams.digest = panel.digest;
}
Ext.create('Proxmox.window.APTRepositoryAdd', {
repoInfo: me.repoInfo,
url: `/api2/extjs/nodes/${panel.nodename}/apt/repositories`,
method: 'PUT',
extraRequestParams: extraParams,
onlineHelp: me.onlineHelp,
listeners: {
destroy: function() {
panel.reload();
},
},
}).show();
});
},
},
'-',
{
xtype: 'proxmoxAltTextButton',
defaultText: gettext('Enable'),
altText: gettext('Disable'),
name: 'repoEnable',
disabled: true,
bind: {
text: '{enableButtonText}',
},
handler: function(button, event, record) {
let me = this;
let panel = me.up('proxmoxNodeAPTRepositories');
let params = {
path: record.data.Path,
index: record.data.Index,
enabled: record.data.Enabled ? 0 : 1, // invert
};
if (panel.digest !== undefined) {
params.digest = panel.digest;
}
Proxmox.Utils.API2Request({
url: `/nodes/${panel.nodename}/apt/repositories`,
method: 'POST',
params: params,
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
panel.reload();
},
success: function(response, opts) {
panel.reload();
},
});
},
},
],
sortableColumns: false,
viewConfig: {
stripeRows: false,
getRowClass: (record, index) => record.get('Enabled') ? '' : 'proxmox-disabled-row',
},
columns: [
{
header: gettext('Enabled'),
dataIndex: 'Enabled',
align: 'center',
renderer: Proxmox.Utils.renderEnabledIcon,
width: 90,
},
{
header: gettext('Types'),
dataIndex: 'Types',
renderer: function(types, cell, record) {
return types.join(' ');
},
width: 100,
},
{
header: gettext('URIs'),
dataIndex: 'URIs',
renderer: function(uris, cell, record) {
return uris.join(' ');
},
width: 350,
},
{
header: gettext('Suites'),
dataIndex: 'Suites',
renderer: function(suites, metaData, record) {
let err = '';
if (record.data.warnings && record.data.warnings.length > 0) {
let txt = [gettext('Warning')];
record.data.warnings.forEach((warning) => {
if (warning.property === 'Suites') {
txt.push(Ext.htmlEncode(warning.message));
}
});
metaData.tdAttr = `data-qtip="${Ext.htmlEncode(txt.join('<br>'))}"`;
if (record.data.Enabled) {
metaData.tdCls = 'proxmox-invalid-row';
err = '<i class="fa fa-fw critical fa-exclamation-circle"></i> ';
} else {
metaData.tdCls = 'proxmox-warning-row';
err = '<i class="fa fa-fw warning fa-exclamation-circle"></i> ';
}
}
return suites.join(' ') + err;
},
width: 130,
},
{
header: gettext('Components'),
dataIndex: 'Components',
renderer: function(components, metaData, record) {
if (components === undefined) {
return '';
}
let err = '';
if (components.length === 1) {
// FIXME: this should be a flag set to the actual repsotiories, i.e., a tristate
// like production-ready = <yes|no|other> (Option<bool>)
if (components[0].match(/\w+(-no-subscription|test)\s*$/i)) {
metaData.tdCls = 'proxmox-warning-row';
err = '<i class="fa fa-fw warning fa-exclamation-circle"></i> ';
let qtip = components[0].match(/no-subscription/)
? gettext('The no-subscription repository is NOT production-ready')
: gettext('The test repository may contain unstable updates')
;
metaData.tdAttr = `data-qtip="${Ext.htmlEncode(Ext.htmlEncode(qtip))}"`;
}
}
return components.join(' ') + err;
},
width: 170,
},
{
header: gettext('Options'),
dataIndex: 'Options',
renderer: function(options, cell, record) {
if (!options) {
return '';
}
let filetype = record.data.FileType;
let text = '';
options.forEach(function(option) {
let key = option.Key;
if (filetype === 'list') {
let values = option.Values.join(',');
text += `${key}=${values} `;
} else if (filetype === 'sources') {
let values = option.Values.join(' ');
text += `${key}: ${values}<br>`;
} else {
throw "unknown file type";
}
});
return text;
},
flex: 1,
},
{
header: gettext('Origin'),
dataIndex: 'Origin',
width: 120,
renderer: function(value, meta, rec) {
if (typeof value !== 'string' || value.length === 0) {
value = gettext('Other');
}
let cls = 'fa fa-fw fa-question-circle-o';
let originType = this.up('proxmoxNodeAPTRepositories').classifyOrigin(value);
if (originType === 'Proxmox') {
cls = 'pmx-itype-icon pmx-itype-icon-proxmox-x';
} else if (originType === 'Debian') {
cls = 'pmx-itype-icon pmx-itype-icon-debian-swirl';
}
return `<i class='${cls}'></i> ${value}`;
},
},
{
header: gettext('Comment'),
dataIndex: 'Comment',
flex: 2,
renderer: Ext.String.htmlEncode,
},
],
features: [
{
ftype: 'grouping',
groupHeaderTpl: '{[ "File: " + values.name ]} ({rows.length} repositor{[values.rows.length > 1 ? "ies" : "y"]})',
enableGroupingMenu: false,
},
],
store: {
model: 'apt-repolist',
groupField: 'Path',
sorters: [
{
property: 'Index',
direction: 'ASC',
},
],
},
initComponent: function() {
let me = this;
if (!me.nodename) {
throw "no node name specified";
}
me.callParent();
},
});
Ext.define('Proxmox.node.APTRepositories', {
extend: 'Ext.panel.Panel',
xtype: 'proxmoxNodeAPTRepositories',
mixins: ['Proxmox.Mixin.CBind'],
digest: undefined,
onlineHelp: undefined,
product: 'Proxmox VE', // default
classifyOrigin: function(origin) {
origin ||= '';
if (origin.match(/^\s*Proxmox\s*$/i)) {
return 'Proxmox';
} else if (origin.match(/^\s*Debian\s*(:?Backports)?$/i)) {
return 'Debian';
}
return 'Other';
},
controller: {
xclass: 'Ext.app.ViewController',
selectionChange: function(grid, selection) {
let me = this;
if (!selection || selection.length < 1) {
return;
}
let rec = selection[0];
let vm = me.getViewModel();
vm.set('selectionenabled', rec.get('Enabled'));
vm.notify();
},
updateState: function() {
let me = this;
let vm = me.getViewModel();
let store = vm.get('errorstore');
store.removeAll();
let status = 'good'; // start with best, the helper below will downgrade if needed
let text = gettext('All OK, you have production-ready repositories configured!');
let addGood = message => store.add({ status: 'good', message });
let addWarn = (message, important) => {
if (status !== 'critical') {
status = 'warning';
text = important ? message : gettext('Warning');
}
store.add({ status: 'warning', message });
};
let addCritical = (message, important) => {
status = 'critical';
text = important ? message : gettext('Error');
store.add({ status: 'critical', message });
};
let errors = vm.get('errors');
errors.forEach(error => addCritical(`${error.path} - ${error.error}`));
let activeSubscription = vm.get('subscriptionActive');
let enterprise = vm.get('enterpriseRepo');
let nosubscription = vm.get('noSubscriptionRepo');
let test = vm.get('testRepo');
let cephRepos = {
enterprise: vm.get('cephEnterpriseRepo'),
nosubscription: vm.get('cephNoSubscriptionRepo'),
test: vm.get('cephTestRepo'),
};
let wrongSuites = vm.get('suitesWarning');
let mixedSuites = vm.get('mixedSuites');
if (!enterprise && !nosubscription && !test) {
addCritical(
Ext.String.format(gettext('No {0} repository is enabled, you do not get any updates!'), vm.get('product')),
);
} else if (errors.length > 0) {
// nothing extra, just avoid that we show "get updates"
} else if (enterprise && !nosubscription && !test && activeSubscription) {
addGood(Ext.String.format(gettext('You get supported updates for {0}'), vm.get('product')));
} else if (nosubscription || test) {
addGood(Ext.String.format(gettext('You get updates for {0}'), vm.get('product')));
}
if (wrongSuites) {
addWarn(gettext('Some suites are misconfigured'));
}
if (mixedSuites) {
addWarn(gettext('Detected mixed suites before upgrade'));
}
let productionReadyCheck = (repos, type, noSubAlternateName) => {
if (!activeSubscription && repos.enterprise) {
addWarn(Ext.String.format(
gettext('The {0}enterprise repository is enabled, but there is no active subscription!'),
type,
));
}
if (repos.nosubscription) {
addWarn(Ext.String.format(
gettext('The {0}no-subscription{1} repository is not recommended for production use!'),
type,
noSubAlternateName,
));
}
if (repos.test) {
addWarn(Ext.String.format(
gettext('The {0}test repository may pull in unstable updates and is not recommended for production use!'),
type,
));
}
};
productionReadyCheck({ enterprise, nosubscription, test }, '', '');
// TODO drop alternate 'main' name when no longer relevant
productionReadyCheck(cephRepos, 'Ceph ', '/main');
if (errors.length > 0) {
text = gettext('Fatal parsing error for at least one repository');
}
let iconCls = Proxmox.Utils.get_health_icon(status, true);
vm.set('state', {
iconCls,
text,
});
},
},
viewModel: {
data: {
product: 'Proxmox VE', // default
errors: [],
suitesWarning: false,
mixedSuites: false, // used before major upgrade
subscriptionActive: '',
noSubscriptionRepo: '',
enterpriseRepo: '',
testRepo: '',
cephEnterpriseRepo: '',
cephNoSubscriptionRepo: '',
cephTestRepo: '',
selectionenabled: false,
state: {},
},
formulas: {
enableButtonText: (get) => get('selectionenabled')
? gettext('Disable') : gettext('Enable'),
},
stores: {
errorstore: {
fields: ['status', 'message'],
},
},
},
scrollable: true,
layout: {
type: 'vbox',
align: 'stretch',
},
items: [
{
xtype: 'panel',
border: false,
layout: {
type: 'hbox',
align: 'stretch',
},
height: 200,
title: gettext('Status'),
items: [
{
xtype: 'box',
flex: 2,
margin: 10,
data: {
iconCls: Proxmox.Utils.get_health_icon(undefined, true),
text: '',
},
bind: {
data: '{state}',
},
tpl: [
'<center class="centered-flex-column" style="font-size:15px;line-height: 25px;">',
'<i class="fa fa-4x {iconCls}"></i>',
'{text}',
'</center>',
],
},
{
xtype: 'proxmoxNodeAPTRepositoriesErrors',
name: 'repositoriesErrors',
flex: 7,
margin: 10,
bind: {
store: '{errorstore}',
},
},
],
},
{
xtype: 'proxmoxNodeAPTRepositoriesGrid',
name: 'repositoriesGrid',
flex: 1,
cbind: {
nodename: '{nodename}',
onlineHelp: '{onlineHelp}',
},
majorUpgradeAllowed: false, // TODO get release information from an API call?
listeners: {
selectionchange: 'selectionChange',
},
},
],
check_subscription: function() {
let me = this;
let vm = me.getViewModel();
Proxmox.Utils.API2Request({
url: `/nodes/${me.nodename}/subscription`,
method: 'GET',
failure: (response, opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
success: function(response, opts) {
const res = response.result;
const subscription = !(!res || !res.data || res.data.status.toLowerCase() !== 'active');
vm.set('subscriptionActive', subscription);
me.getController().updateState();
},
});
},
updateStandardRepos: function(standardRepos) {
let me = this;
let vm = me.getViewModel();
let addButton = me.down('button[name=addRepo]');
addButton.repoInfo = [];
for (const standardRepo of standardRepos) {
const handle = standardRepo.handle;
const status = standardRepo.status;
if (handle === "enterprise") {
vm.set('enterpriseRepo', status);
} else if (handle === "no-subscription") {
vm.set('noSubscriptionRepo', status);
} else if (handle === 'test') {
vm.set('testRepo', status);
} else if (handle.match(/^ceph-[a-zA-Z]+-enterprise$/)) {
vm.set('cephEnterpriseRepo', status);
} else if (handle.match(/^ceph-[a-zA-Z]+-no-subscription$/)) {
vm.set('cephNoSubscriptionRepo', status);
} else if (handle.match(/^ceph-[a-zA-Z]+-test$/)) {
vm.set('cephTestRepo', status);
}
me.getController().updateState();
addButton.repoInfo.push(standardRepo);
addButton.digest = me.digest;
}
addButton.setDisabled(false);
},
reload: function() {
let me = this;
let vm = me.getViewModel();
let repoGrid = me.down('proxmoxNodeAPTRepositoriesGrid');
me.store.load(function(records, operation, success) {
let gridData = [];
let errors = [];
let digest;
let suitesWarning = false;
// Usually different suites will give errors anyways, but before a major upgrade the
// current and the next suite are allowed, so it makes sense to check for mixed suites.
let checkMixedSuites = false;
let mixedSuites = false;
if (success && records.length > 0) {
let data = records[0].data;
let files = data.files;
errors = data.errors;
digest = data.digest;
let infos = {};
for (const info of data.infos) {
let path = info.path;
let idx = info.index;
if (!infos[path]) {
infos[path] = {};
}
if (!infos[path][idx]) {
infos[path][idx] = {
origin: '',
warnings: [],
// Used as a heuristic to detect mixed repositories pre-upgrade. The
// warning is set on all repositories that do configure the next suite.
gotIgnorePreUpgradeWarning: false,
};
}
if (info.kind === 'origin') {
infos[path][idx].origin = info.message;
} else if (info.kind === 'warning') {
infos[path][idx].warnings.push(info);
} else if (info.kind === 'ignore-pre-upgrade-warning') {
infos[path][idx].gotIgnorePreUpgradeWarning = true;
if (!repoGrid.majorUpgradeAllowed) {
infos[path][idx].warnings.push(info);
} else {
checkMixedSuites = true;
}
}
}
files.forEach(function(file) {
for (let n = 0; n < file.repositories.length; n++) {
let repo = file.repositories[n];
repo.Path = file.path;
repo.Index = n;
if (infos[file.path] && infos[file.path][n]) {
repo.Origin = infos[file.path][n].origin || Proxmox.Utils.unknownText;
repo.warnings = infos[file.path][n].warnings || [];
if (repo.Enabled) {
if (repo.warnings.some(w => w.property === 'Suites')) {
suitesWarning = true;
}
let originType = me.classifyOrigin(repo.Origin);
// Only Proxmox and Debian repositories checked here, because the
// warning can be missing for others for a different reason (e.g.
// using 'stable' or non-Debian code names).
if (checkMixedSuites && repo.Types.includes('deb') &&
(originType === 'Proxmox' || originType === 'Debian') &&
!infos[file.path][n].gotIgnorePreUpgradeWarning
) {
mixedSuites = true;
}
}
}
gridData.push(repo);
}
});
repoGrid.store.loadData(gridData);
me.updateStandardRepos(data['standard-repos']);
}
me.digest = digest;
vm.set('errors', errors);
vm.set('suitesWarning', suitesWarning);
vm.set('mixedSuites', mixedSuites);
me.getController().updateState();
});
me.check_subscription();
},
listeners: {
activate: function() {
let me = this;
me.reload();
},
},
initComponent: function() {
let me = this;
if (!me.nodename) {
throw "no node name specified";
}
let store = Ext.create('Ext.data.Store', {
proxy: {
type: 'proxmox',
url: `/api2/json/nodes/${me.nodename}/apt/repositories`,
},
});
Ext.apply(me, { store: store });
Proxmox.Utils.monStoreErrors(me, me.store, true);
me.callParent();
me.getViewModel().set('product', me.product);
},
});

View File

@ -2,6 +2,10 @@ Ext.define('Proxmox.node.DNSEdit', {
extend: 'Proxmox.window.Edit',
alias: ['widget.proxmoxNodeDNSEdit'],
// Some longer existing APIs use a brittle "replace whole config" style, you can set this option
// if the DNSEdit component is used in an API that has more modern, granular update semantics.
deleteEmpty: false,
initComponent: function() {
let me = this;
@ -21,6 +25,7 @@ Ext.define('Proxmox.node.DNSEdit', {
fieldLabel: gettext('DNS server') + " 1",
vtype: 'IP64Address',
skipEmptyText: true,
deleteEmpty: me.deleteEmpty,
name: 'dns1',
},
{
@ -28,6 +33,7 @@ Ext.define('Proxmox.node.DNSEdit', {
fieldLabel: gettext('DNS server') + " 2",
vtype: 'IP64Address',
skipEmptyText: true,
deleteEmpty: me.deleteEmpty,
name: 'dns2',
},
{
@ -35,6 +41,7 @@ Ext.define('Proxmox.node.DNSEdit', {
fieldLabel: gettext('DNS server') + " 3",
vtype: 'IP64Address',
skipEmptyText: true,
deleteEmpty: me.deleteEmpty,
name: 'dns3',
},
];

View File

@ -2,6 +2,10 @@ Ext.define('Proxmox.node.DNSView', {
extend: 'Proxmox.grid.ObjectGrid',
alias: ['widget.proxmoxNodeDNSView'],
// Some longer existing APIs use a brittle "replace whole config" style, you can set this option
// if the DNSView component is used in an API that has more modern, granular update semantics.
deleteEmpty: false,
initComponent: function() {
let me = this;
@ -9,21 +13,20 @@ Ext.define('Proxmox.node.DNSView', {
throw "no node name specified";
}
let run_editor = function() {
let win = Ext.create('Proxmox.node.DNSEdit', {
nodename: me.nodename,
});
win.show();
};
let run_editor = () => Ext.create('Proxmox.node.DNSEdit', {
autoShow: true,
nodename: me.nodename,
deleteEmpty: me.deleteEmpty,
});
Ext.apply(me, {
url: "/api2/json/nodes/" + me.nodename + "/dns",
url: `/api2/json/nodes/${me.nodename}/dns`,
cwidth1: 130,
interval: 1000,
interval: 10 * 1000,
run_editor: run_editor,
rows: {
search: {
header: 'Search domain',
header: gettext('Search domain'),
required: true,
renderer: Ext.htmlEncode,
},

View File

@ -2,6 +2,9 @@ Ext.define('Proxmox.node.NetworkEdit', {
extend: 'Proxmox.window.Edit',
alias: ['widget.proxmoxNodeNetworkEdit'],
// Enable to show the VLAN ID field
enableBridgeVlanIds: false,
initComponent: function() {
let me = this;
@ -57,22 +60,75 @@ Ext.define('Proxmox.node.NetworkEdit', {
}
if (me.iftype === 'bridge') {
let vlanIdsField = !me.enableBridgeVlanIds ? undefined : Ext.create('Ext.form.field.Text', {
fieldLabel: gettext('VLAN IDs'),
name: 'bridge_vids',
emptyText: '2-4094',
disabled: true,
autoEl: {
tag: 'div',
'data-qtip': gettext("List of VLAN IDs and ranges, useful for NICs with restricted VLAN offloading support. For example: '2 4 100-200'"),
},
validator: function(value) {
if (!value) { // empty
return true;
}
for (const vid of value.split(/\s+[,;]?/)) {
if (!vid) {
continue;
}
let res = vid.match(/^(\d+)(?:-(\d+))?$/);
if (!res) {
return Ext.String.format(gettext("not a valid bridge VLAN ID entry: {0}"), vid);
}
let start = Number(res[1]), end = Number(res[2] ?? res[1]); // end=start for single IDs
if (Number.isNaN(start) || Number.isNaN(end)) {
return Ext.String.format(gettext('VID range includes not-a-number: {0}'), vid);
} else if (start > end) {
return Ext.String.format(gettext('VID range must go from lower to higher tag: {0}'), vid);
} else if (start < 2 || end > 4094) { // check just one each, we already ensured start < end
return Ext.String.format(gettext('VID range outside of allowed 2 and 4094 limit: {0}'), vid);
}
}
return true;
},
});
column2.push({
xtype: 'proxmoxcheckbox',
fieldLabel: gettext('VLAN aware'),
name: 'bridge_vlan_aware',
deleteEmpty: !me.isCreate,
listeners: {
change: function(f, newVal) {
if (vlanIdsField) {
vlanIdsField.setDisabled(!newVal);
}
},
},
});
column2.push({
xtype: 'textfield',
fieldLabel: gettext('Bridge ports'),
name: 'bridge_ports',
autoEl: {
tag: 'div',
'data-qtip': gettext('Space-separated list of interfaces, for example: enp0s0 enp1s0'),
},
});
if (vlanIdsField) {
advancedColumn2.push(vlanIdsField);
}
} else if (me.iftype === 'OVSBridge') {
column2.push({
xtype: 'textfield',
fieldLabel: gettext('Bridge ports'),
name: 'ovs_ports',
autoEl: {
tag: 'div',
'data-qtip': gettext('Space-separated list of interfaces, for example: enp0s0 enp1s0'),
},
});
column2.push({
xtype: 'textfield',
@ -89,7 +145,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
name: 'ovs_bridge',
});
column2.push({
xtype: 'pveVlanField',
xtype: 'proxmoxvlanfield',
deleteEmpty: !me.isCreate,
name: 'ovs_tag',
value: '',
@ -132,7 +188,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
});
column2.push({
xtype: 'pveVlanField',
xtype: 'proxmoxvlanfield',
name: 'vlan-id',
value: me.vlanidvalue,
disabled: me.disablevlanid,
@ -203,7 +259,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
name: 'ovs_bridge',
});
column2.push({
xtype: 'pveVlanField',
xtype: 'proxmoxvlanfield',
deleteEmpty: !me.isCreate,
name: 'ovs_tag',
value: '',
@ -246,6 +302,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
value: me.iface,
vtype: iface_vtype,
allowBlank: false,
maxLength: iface_vtype === 'BridgeName' ? 10 : 15,
autoEl: {
tag: 'div',
'data-qtip': gettext('For example, vmbr0.100, vmbr0, vlan0.100, vlan0'),

View File

@ -16,6 +16,8 @@ Ext.define('proxmox-networks', {
'netmask6',
'slaves',
'type',
'vlan-id',
'vlan-raw-device',
],
idProperty: 'iface',
});
@ -31,6 +33,9 @@ Ext.define('Proxmox.node.NetworkView', {
showApplyBtn: false,
// for options passed down to the network edit window
editOptions: {},
initComponent: function() {
let me = this;
@ -38,7 +43,7 @@ Ext.define('Proxmox.node.NetworkView', {
throw "no node name specified";
}
let baseUrl = '/nodes/' + me.nodename + '/network';
let baseUrl = `/nodes/${me.nodename}/network`;
let store = Ext.create('Ext.data.Store', {
model: 'proxmox-networks',
@ -93,13 +98,16 @@ Ext.define('Proxmox.node.NetworkView', {
return;
}
let win = Ext.create('Proxmox.node.NetworkEdit', {
Ext.create('Proxmox.node.NetworkEdit', {
autoShow: true,
nodename: me.nodename,
iface: rec.data.iface,
iftype: rec.data.type,
...me.editOptions,
listeners: {
destroy: () => reload(),
},
});
win.show();
win.on('destroy', reload);
};
let edit_btn = new Ext.Button({
@ -108,31 +116,12 @@ Ext.define('Proxmox.node.NetworkView', {
handler: run_editor,
});
let del_btn = new Ext.Button({
text: gettext('Remove'),
disabled: true,
handler: function() {
let grid = me.down('gridpanel');
let sm = grid.getSelectionModel();
let rec = sm.getSelection()[0];
if (!rec) {
return;
}
let sm = Ext.create('Ext.selection.RowModel', {});
let iface = rec.data.iface;
Proxmox.Utils.API2Request({
url: baseUrl + '/' + iface,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
});
},
let del_btn = new Proxmox.button.StdRemoveButton({
selModel: sm,
getUrl: ({ data }) => `${baseUrl}/${data.iface}`,
callback: () => reload(),
});
let apply_btn = Ext.create('Proxmox.button.Button', {
@ -146,102 +135,64 @@ Ext.define('Proxmox.node.NetworkView', {
url: baseUrl,
method: 'PUT',
waitMsgTarget: me,
success: function(response, opts) {
let upid = response.result.data;
let win = Ext.create('Proxmox.window.TaskProgress', {
success: function({ result }, opts) {
Ext.create('Proxmox.window.TaskProgress', {
autoShow: true,
taskDone: reload,
upid: upid,
upid: result.data,
});
win.show();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
});
},
});
let set_button_status = function() {
let grid = me.down('gridpanel');
let sm = grid.getSelectionModel();
let rec = sm.getSelection()[0];
edit_btn.setDisabled(!rec);
del_btn.setDisabled(!rec);
};
let render_ports = function(value, metaData, record) {
if (value === 'bridge') {
return record.data.bridge_ports;
} else if (value === 'bond') {
return record.data.slaves;
} else if (value === 'OVSBridge') {
return record.data.ovs_ports;
} else if (value === 'OVSBond') {
return record.data.ovs_bonds;
let findNextFreeInterfaceId = function(prefix) {
for (let next = 0; next <= 9999; next++) {
let id = `${prefix}${next.toString()}`;
if (!store.getById(id)) {
return id;
}
}
Ext.Msg.alert('Error', `No free ID for ${prefix} found!`);
return '';
};
let find_next_iface_id = function(prefix) {
let next;
for (next = 0; next <= 9999; next++) {
if (!store.getById(prefix + next.toString())) {
break;
}
}
return prefix + next.toString();
let menu_items = [];
let addEditWindowToMenu = (iType, iDefault) => {
menu_items.push({
text: Proxmox.Utils.render_network_iface_type(iType),
handler: () => Ext.create('Proxmox.node.NetworkEdit', {
autoShow: true,
nodename: me.nodename,
iftype: iType,
iface_default: findNextFreeInterfaceId(iDefault ?? iType),
...me.editOptions,
onlineHelp: 'sysadmin_network_configuration',
listeners: {
destroy: () => reload(),
},
}),
});
};
let menu_items = [];
if (me.types.indexOf('bridge') !== -1) {
menu_items.push({
text: Proxmox.Utils.render_network_iface_type('bridge'),
handler: function() {
let win = Ext.create('Proxmox.node.NetworkEdit', {
nodename: me.nodename,
iftype: 'bridge',
iface_default: find_next_iface_id('vmbr'),
onlineHelp: 'sysadmin_network_configuration',
});
win.on('destroy', reload);
win.show();
},
});
addEditWindowToMenu('bridge', 'vmbr');
}
if (me.types.indexOf('bond') !== -1) {
menu_items.push({
text: Proxmox.Utils.render_network_iface_type('bond'),
handler: function() {
let win = Ext.create('Proxmox.node.NetworkEdit', {
nodename: me.nodename,
iftype: 'bond',
iface_default: find_next_iface_id('bond'),
onlineHelp: 'sysadmin_network_configuration',
});
win.on('destroy', reload);
win.show();
},
});
addEditWindowToMenu('bond');
}
if (me.types.indexOf('vlan') !== -1) {
menu_items.push({
text: Proxmox.Utils.render_network_iface_type('vlan'),
handler: function() {
let win = Ext.create('Proxmox.node.NetworkEdit', {
nodename: me.nodename,
iftype: 'vlan',
iface_default: find_next_iface_id('vlan'),
onlineHelp: 'sysadmin_network_configuration',
});
win.on('destroy', reload);
win.show();
},
});
addEditWindowToMenu('vlan');
}
if (me.types.indexOf('ovs') !== -1) {
@ -249,43 +200,20 @@ Ext.define('Proxmox.node.NetworkView', {
menu_items.push({ xtype: 'menuseparator' });
}
menu_items.push(
{
text: Proxmox.Utils.render_network_iface_type('OVSBridge'),
handler: function() {
let win = Ext.create('Proxmox.node.NetworkEdit', {
nodename: me.nodename,
iftype: 'OVSBridge',
iface_default: find_next_iface_id('vmbr'),
});
win.on('destroy', reload);
win.show();
addEditWindowToMenu('OVSBridge', 'vmbr');
addEditWindowToMenu('OVSBond', 'bond');
menu_items.push({
text: Proxmox.Utils.render_network_iface_type('OVSIntPort'),
handler: () => Ext.create('Proxmox.node.NetworkEdit', {
autoShow: true,
nodename: me.nodename,
iftype: 'OVSIntPort',
listeners: {
destroy: () => reload(),
},
},
{
text: Proxmox.Utils.render_network_iface_type('OVSBond'),
handler: function() {
let win = Ext.create('Proxmox.node.NetworkEdit', {
nodename: me.nodename,
iftype: 'OVSBond',
iface_default: find_next_iface_id('bond'),
});
win.on('destroy', reload);
win.show();
},
},
{
text: Proxmox.Utils.render_network_iface_type('OVSIntPort'),
handler: function() {
let win = Ext.create('Proxmox.node.NetworkEdit', {
nodename: me.nodename,
iftype: 'OVSIntPort',
});
win.on('destroy', reload);
win.show();
},
},
);
}),
});
}
let renderer_generator = function(fieldname) {
@ -322,9 +250,7 @@ Ext.define('Proxmox.node.NetworkView', {
callback: function() {
reload();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
});
},
},
@ -339,6 +265,7 @@ Ext.define('Proxmox.node.NetworkView', {
stateful: true,
stateId: 'grid-node-network',
store: store,
selModel: sm,
region: 'center',
border: false,
columns: [
@ -387,7 +314,18 @@ Ext.define('Proxmox.node.NetworkView', {
{
header: gettext('Ports/Slaves'),
dataIndex: 'type',
renderer: render_ports,
renderer: (value, metaData, { data }) => {
if (value === 'bridge') {
return data.bridge_ports;
} else if (value === 'bond') {
return data.slaves;
} else if (value === 'OVSBridge') {
return data.ovs_ports;
} else if (value === 'OVSBond') {
return data.ovs_bonds;
}
return '';
},
},
{
header: gettext('Bond Mode'),
@ -429,6 +367,24 @@ Ext.define('Proxmox.node.NetworkView', {
dataIndex: 'gateway',
renderer: renderer_generator('gateway'),
},
{
header: gettext('VLAN ID'),
hidden: true,
sortable: true,
dataIndex: 'vlan-id',
},
{
header: gettext('VLAN raw device'),
hidden: true,
sortable: true,
dataIndex: 'vlan-raw-device',
},
{
header: 'MTU',
hidden: true,
sortable: true,
dataIndex: 'mtu',
},
{
header: gettext('Comment'),
dataIndex: 'comments',

View File

@ -1,6 +1,6 @@
Ext.define('proxmox-services', {
extend: 'Ext.data.Model',
fields: ['service', 'name', 'desc', 'state'],
fields: ['service', 'name', 'desc', 'state', 'unit-state', 'active-state'],
idProperty: 'service',
});
@ -24,11 +24,13 @@ Ext.define('Proxmox.node.ServiceView', {
interval: 1000,
model: 'proxmox-services',
proxy: {
type: 'proxmox',
url: "/api2/json/nodes/" + me.nodename + "/services",
type: 'proxmox',
url: `/api2/json/nodes/${me.nodename}/services`,
},
});
let filterInstalledOnly = record => record.get('unit-state') !== 'not-found';
let store = Ext.create('Proxmox.data.DiffStore', {
rstore: rstore,
sortAfterUpdate: true,
@ -38,30 +40,45 @@ Ext.define('Proxmox.node.ServiceView', {
direction: 'ASC',
},
],
filters: [
filterInstalledOnly,
],
});
let unHideCB = Ext.create('Ext.form.field.Checkbox', {
boxLabel: gettext('Show only installed services'),
value: true,
boxLabelAlign: 'before',
listeners: {
change: function(_cb, value) {
if (value) {
store.addFilter([filterInstalledOnly]);
} else {
store.clearFilter();
}
},
},
});
let view_service_log = function() {
let sm = me.getSelectionModel();
let rec = sm.getSelection()[0];
let win = Ext.create('Ext.window.Window', {
title: gettext('Syslog') + ': ' + rec.data.service,
let { data: { service } } = me.getSelectionModel().getSelection()[0];
Ext.create('Ext.window.Window', {
title: gettext('Syslog') + ': ' + service,
modal: true,
width: 800,
height: 400,
layout: 'fit',
items: {
xtype: 'proxmoxLogView',
url: "/api2/extjs/nodes/" + me.nodename + "/syslog?service=" +
rec.data.service,
url: `/api2/extjs/nodes/${me.nodename}/syslog?service=${service}`,
log_select_timespan: 1,
},
autoShow: true,
});
win.show();
};
let service_cmd = function(cmd) {
let rec = me.getSelectionModel().getSelection()[0];
let service = rec.data.service;
let { data: { service } } = me.getSelectionModel().getSelection()[0];
Proxmox.Utils.API2Request({
url: `/nodes/${me.nodename}/services/${service}/${cmd}`,
method: 'POST',
@ -71,12 +88,10 @@ Ext.define('Proxmox.node.ServiceView', {
},
success: function(response, opts) {
rstore.startUpdate();
let upid = response.result.data;
let win = Ext.create('Proxmox.window.TaskProgress', {
upid: upid,
Ext.create('Proxmox.window.TaskProgress', {
upid: response.result.data,
autoShow: true,
});
win.show();
},
});
};
@ -84,27 +99,18 @@ Ext.define('Proxmox.node.ServiceView', {
let start_btn = new Ext.Button({
text: gettext('Start'),
disabled: true,
handler: function() {
service_cmd("start");
},
handler: () => service_cmd("start"),
});
let stop_btn = new Ext.Button({
text: gettext('Stop'),
disabled: true,
handler: function() {
service_cmd("stop");
},
handler: () => service_cmd("stop"),
});
let restart_btn = new Ext.Button({
text: gettext('Restart'),
disabled: true,
handler: function() {
service_cmd(me.restartCommand || "restart");
},
handler: () => service_cmd(me.restartCommand || "restart"),
});
let syslog_btn = new Ext.Button({
text: gettext('Syslog'),
disabled: true,
@ -124,23 +130,28 @@ Ext.define('Proxmox.node.ServiceView', {
}
let service = rec.data.service;
let state = rec.data.state;
let unit = rec.data['unit-state'];
syslog_btn.enable();
if (state === 'running') {
if (me.startOnlyServices[service]) {
stop_btn.disable();
restart_btn.enable();
} else {
stop_btn.enable();
restart_btn.enable();
start_btn.disable();
}
} else if (unit !== undefined && (unit === 'masked' || unit === 'unknown' || unit === 'not-found')) {
start_btn.disable();
restart_btn.enable();
restart_btn.disable();
stop_btn.disable();
} else {
start_btn.enable();
stop_btn.disable();
restart_btn.disable();
}
if (!me.startOnlyServices[service]) {
if (state === 'running') {
stop_btn.enable();
} else {
stop_btn.disable();
}
}
};
me.mon(store, 'refresh', set_button_status);
@ -148,9 +159,36 @@ Ext.define('Proxmox.node.ServiceView', {
Proxmox.Utils.monStoreErrors(me, rstore);
Ext.apply(me, {
viewConfig: {
trackOver: false,
stripeRows: false, // does not work with getRowClass()
getRowClass: function(record, index) {
let unitState = record.get('unit-state');
if (!unitState) {
return '';
}
if (unitState === 'masked' || unitState === 'not-found') {
return "proxmox-disabled-row";
} else if (unitState === 'unknown') {
if (record.get('name') === 'syslog') {
return "proxmox-disabled-row"; // replaced by journal on most hosts
}
return "proxmox-warning-row";
}
return '';
},
},
store: store,
stateful: false,
tbar: [start_btn, stop_btn, restart_btn, syslog_btn],
tbar: [
start_btn,
stop_btn,
restart_btn,
'-',
syslog_btn,
'->',
unHideCB,
],
columns: [
{
header: gettext('Name'),
@ -163,6 +201,30 @@ Ext.define('Proxmox.node.ServiceView', {
width: 100,
sortable: true,
dataIndex: 'state',
renderer: (value, meta, rec) => {
const unitState = rec.get('unit-state');
if (unitState === 'masked') {
return gettext('disabled');
} else if (unitState === 'not-found') {
return gettext('not installed');
} else {
return value;
}
},
},
{
header: gettext('Active'),
width: 100,
sortable: true,
hidden: true,
dataIndex: 'active-state',
},
{
header: gettext('Unit'),
width: 120,
sortable: true,
hidden: !Ext.Array.contains(['PVEAuthCookie', 'PBSAuthCookie'], Proxmox?.Setup?.auth_cookie_name),
dataIndex: 'unit-state',
},
{
header: gettext('Description'),

View File

@ -1,217 +1,515 @@
Ext.define('Proxmox.node.Tasks', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.proxmoxNodeTasks'],
alias: 'widget.proxmoxNodeTasks',
stateful: true,
stateId: 'grid-node-tasks',
stateId: 'pve-grid-node-tasks',
loadMask: true,
sortableColumns: false,
vmidFilter: 0,
initComponent: function() {
let me = this;
// set extra filter components, must have a 'name' property for the parameter, and must
// trigger a 'change' event if the value is 'undefined', it will not be sent to the api
extraFilter: [],
if (!me.nodename) {
throw "no node name specified";
}
let store = Ext.create('Ext.data.BufferedStore', {
pageSize: 500,
autoLoad: true,
remoteFilter: true,
model: 'proxmox-tasks',
proxy: {
type: 'proxmox',
startParam: 'start',
limitParam: 'limit',
url: "/api2/json/nodes/" + me.nodename + "/tasks",
},
});
// fixed filters which cannot be changed after instantiation, for example:
// { vmid: 100 }
preFilter: {},
store.on('prefetch', function() {
// we want to update the scrollbar on every store load
// since the total count might be different
// the buffered grid plugin does this only on scrolling itself
// and even reduces the scrollheight again when scrolling up
me.updateLayout();
});
controller: {
xclass: 'Ext.app.ViewController',
let userfilter = '';
let filter_errors = 0;
let updateProxyParams = function() {
let params = {
errors: filter_errors,
};
if (userfilter) {
params.userfilter = userfilter;
}
if (me.vmidFilter) {
params.vmid = me.vmidFilter;
}
store.proxy.extraParams = params;
};
updateProxyParams();
let reload_task = Ext.create('Ext.util.DelayedTask', function() {
updateProxyParams();
store.reload();
});
let run_task_viewer = function() {
let sm = me.getSelectionModel();
let rec = sm.getSelection()[0];
if (!rec) {
showTaskLog: function() {
let me = this;
let selection = me.getView().getSelection();
if (selection.length < 1) {
return;
}
let win = Ext.create('Proxmox.window.TaskViewer', {
let rec = selection[0];
Ext.create('Proxmox.window.TaskViewer', {
upid: rec.data.upid,
endtime: rec.data.endtime,
}).show();
},
updateLayout: function(store, records, success, operation) {
let me = this;
let view = me.getView().getView(); // the table view, not the whole grid
Proxmox.Utils.setErrorMask(view, false);
// update the scrollbar on every store load since the total count might be different.
// the buffered grid plugin does this only on (user) scrolling itself and even reduces
// the scrollheight again when scrolling up
me.getView().updateLayout();
if (!success) {
Proxmox.Utils.setErrorMask(view, Proxmox.Utils.getResponseErrorMessage(operation.getError()));
}
},
refresh: function() {
let me = this;
let view = me.getView();
let selection = view.getSelection();
let store = me.getViewModel().get('bufferedstore');
if (selection && selection.length > 0) {
// deselect if selection is not there anymore
if (!store.contains(selection[0])) {
view.setSelection(undefined);
}
}
},
sinceChange: function(field, newval) {
let me = this;
let vm = me.getViewModel();
vm.set('since', newval);
},
untilChange: function(field, newval, oldval) {
let me = this;
let vm = me.getViewModel();
vm.set('until', newval);
},
reload: function() {
let me = this;
let view = me.getView();
view.getStore().load();
},
showFilter: function(btn, pressed) {
let me = this;
let vm = me.getViewModel();
vm.set('showFilter', pressed);
},
clearFilter: function() {
let me = this;
me.lookup('filtertoolbar').query('field').forEach((field) => {
field.setValue(undefined);
});
win.show();
};
},
},
let view_btn = new Ext.Button({
text: gettext('View'),
disabled: true,
handler: run_task_viewer,
});
listeners: {
itemdblclick: 'showTaskLog',
},
Proxmox.Utils.monStoreErrors(me, store, true);
viewModel: {
data: {
typefilter: '',
statusfilter: '',
showFilter: false,
extraFilter: {},
since: null,
until: null,
},
Ext.apply(me, {
store: store,
viewConfig: {
trackOver: false,
stripeRows: false, // does not work with getRowClass()
formulas: {
filterIcon: (get) => 'fa fa-filter' + (get('showFilter') ? ' info-blue' : ''),
extraParams: function(get) {
let me = this;
let params = {};
if (get('typefilter')) {
params.typefilter = get('typefilter');
}
if (get('statusfilter')) {
params.statusfilter = get('statusfilter');
}
getRowClass: function(record, index) {
let status = record.get('status');
if (status) {
let parsed = Proxmox.Utils.parse_task_status(status);
if (parsed === 'error') {
return "proxmox-invalid-row";
} else if (parsed === 'warning') {
return "proxmox-warning-row";
if (get('extraFilter')) {
let extraFilter = get('extraFilter');
for (const [name, value] of Object.entries(extraFilter)) {
if (value !== undefined && value !== null && value !== "") {
params[name] = value;
}
}
return '';
}
if (get('since')) {
params.since = get('since').valueOf()/1000;
}
if (get('until')) {
let until = new Date(get('until').getTime()); // copy object
until.setDate(until.getDate() + 1); // end of the day
params.until = until.valueOf()/1000;
}
me.getView().getStore().load();
return params;
},
filterCount: function(get) {
let count = 0;
if (get('typefilter')) {
count++;
}
let status = get('statusfilter');
if ((Ext.isArray(status) && status.length > 0) ||
(!Ext.isArray(status) && status)) {
count++;
}
if (get('since')) {
count++;
}
if (get('until')) {
count++;
}
if (get('extraFilter')) {
let preFilter = get('preFilter') || {};
let extraFilter = get('extraFilter');
for (const [name, value] of Object.entries(extraFilter)) {
if (value !== undefined && value !== null && value !== "" &&
preFilter[name] === undefined
) {
count++;
}
}
}
return count;
},
clearFilterText: function(get) {
let count = get('filterCount');
let fieldMsg = '';
if (count > 1) {
fieldMsg = ` (${count} ${gettext('Fields')})`;
} else if (count > 0) {
fieldMsg = ` (1 ${gettext('Field')})`;
}
return gettext('Clear Filter') + fieldMsg;
},
},
stores: {
bufferedstore: {
type: 'buffered',
pageSize: 500,
autoLoad: true,
remoteFilter: true,
model: 'proxmox-tasks',
proxy: {
type: 'proxmox',
startParam: 'start',
limitParam: 'limit',
extraParams: '{extraParams}',
url: '{url}',
},
listeners: {
prefetch: 'updateLayout',
refresh: 'refresh',
},
},
tbar: [
view_btn,
},
},
bind: {
store: '{bufferedstore}',
},
dockedItems: [
{
xtype: 'toolbar',
items: [
{
text: gettext('Refresh'), // FIXME: smart-auto-refresh store
handler: () => store.reload(),
xtype: 'proxmoxButton',
text: gettext('View'),
iconCls: 'fa fa-window-restore',
disabled: true,
handler: 'showTaskLog',
},
{
xtype: 'button',
text: gettext('Reload'),
iconCls: 'fa fa-refresh',
handler: 'reload',
},
'->',
gettext('User name') +':',
' ',
{
xtype: 'textfield',
width: 200,
value: userfilter,
enableKeyEvents: true,
xtype: 'button',
bind: {
text: '{clearFilterText}',
disabled: '{!filterCount}',
},
text: gettext('Clear Filter'),
enabled: false,
handler: 'clearFilter',
},
{
xtype: 'button',
enableToggle: true,
bind: {
iconCls: '{filterIcon}',
},
text: gettext('Filter'),
stateful: true,
stateId: 'task-showfilter',
stateEvents: ['toggle'],
applyState: function(state) {
if (state.pressed !== undefined) {
this.setPressed(state.pressed);
}
},
getState: function() {
return {
pressed: this.pressed,
};
},
listeners: {
keyup: function(field, e) {
userfilter = field.getValue();
reload_task.delay(500);
},
},
}, ' ', gettext('Only Errors') + ':', ' ',
{
xtype: 'checkbox',
hideLabel: true,
checked: filter_errors,
listeners: {
change: function(field, checked) {
filter_errors = checked ? 1 : 0;
reload_task.delay(10);
},
},
}, ' ',
],
columns: [
{
header: gettext("Start Time"),
dataIndex: 'starttime',
width: 130,
renderer: function(value) {
return Ext.Date.format(value, "M d H:i:s");
},
},
{
header: gettext("End Time"),
dataIndex: 'endtime',
width: 130,
renderer: function(value, metaData, record) {
if (!value) {
metaData.tdCls = "x-grid-row-loading";
return '';
}
return Ext.Date.format(value, "M d H:i:s");
},
},
{
header: gettext("Duration"),
hidden: true,
width: 80,
renderer: function(value, metaData, record) {
let start = record.data.starttime;
if (start) {
let end = record.data.endtime || Date.now();
let duration = end - start;
if (duration > 0) {
duration /= 1000;
}
return Proxmox.Utils.format_duration_human(duration);
}
return Proxmox.Utils.unknownText;
},
},
{
header: gettext("Node"),
dataIndex: 'node',
width: 120,
},
{
header: gettext("User name"),
dataIndex: 'user',
width: 150,
},
{
header: gettext("Description"),
dataIndex: 'upid',
flex: 1,
renderer: Proxmox.Utils.render_upid,
},
{
header: gettext("Status"),
dataIndex: 'status',
width: 200,
renderer: function(value, metaData, record) {
if (value === undefined && !record.data.endtime) {
metaData.tdCls = "x-grid-row-loading";
return '';
}
return Proxmox.Utils.format_task_status(value);
toggle: 'showFilter',
},
},
],
listeners: {
itemdblclick: run_task_viewer,
selectionchange: function(v, selections) {
view_btn.setDisabled(!(selections && selections[0]));
},
show: function() { reload_task.delay(10); },
destroy: function() { reload_task.cancel(); },
},
{
xtype: 'toolbar',
dock: 'top',
reference: 'filtertoolbar',
layout: {
type: 'hbox',
align: 'top',
},
});
bind: {
hidden: '{!showFilter}',
},
items: [
{
xtype: 'container',
padding: 10,
layout: {
type: 'vbox',
align: 'stretch',
},
defaults: {
labelWidth: 80,
},
// cannot bind the values directly, as it then changes also
// on blur, causing wrong reloads of the store
items: [
{
xtype: 'datefield',
fieldLabel: gettext('Since'),
format: 'Y-m-d',
bind: {
maxValue: '{until}',
},
listeners: {
change: 'sinceChange',
},
},
{
xtype: 'datefield',
fieldLabel: gettext('Until'),
format: 'Y-m-d',
bind: {
minValue: '{since}',
},
listeners: {
change: 'untilChange',
},
},
],
},
{
xtype: 'container',
padding: 10,
layout: {
type: 'vbox',
align: 'stretch',
},
defaults: {
labelWidth: 80,
},
items: [
{
xtype: 'pmxTaskTypeSelector',
fieldLabel: gettext('Task Type'),
emptyText: gettext('All'),
bind: {
value: '{typefilter}',
},
},
{
xtype: 'combobox',
fieldLabel: gettext('Task Result'),
emptyText: gettext('All'),
multiSelect: true,
store: [
['ok', gettext('OK')],
['unknown', Proxmox.Utils.unknownText],
['warning', gettext('Warnings')],
['error', gettext('Errors')],
],
bind: {
value: '{statusfilter}',
},
},
],
},
],
},
],
viewConfig: {
trackOver: false,
stripeRows: false, // does not work with getRowClass()
emptyText: gettext('No Tasks found'),
getRowClass: function(record, index) {
let status = record.get('status');
if (status) {
let parsed = Proxmox.Utils.parse_task_status(status);
if (parsed === 'error') {
return "proxmox-invalid-row";
} else if (parsed === 'warning') {
return "proxmox-warning-row";
}
}
return '';
},
},
columns: [
{
header: gettext("Start Time"),
dataIndex: 'starttime',
width: 130,
renderer: function(value) {
return Ext.Date.format(value, "M d H:i:s");
},
},
{
header: gettext("End Time"),
dataIndex: 'endtime',
width: 130,
renderer: function(value, metaData, record) {
if (!value) {
metaData.tdCls = "x-grid-row-loading";
return '';
}
return Ext.Date.format(value, "M d H:i:s");
},
},
{
header: gettext("Duration"),
hidden: true,
width: 80,
renderer: function(value, metaData, record) {
let start = record.data.starttime;
if (start) {
let end = record.data.endtime || Date.now();
let duration = end - start;
if (duration > 0) {
duration /= 1000;
}
return Proxmox.Utils.format_duration_human(duration);
}
return Proxmox.Utils.unknownText;
},
},
{
header: gettext("User name"),
dataIndex: 'user',
width: 150,
},
{
header: gettext("Description"),
dataIndex: 'upid',
flex: 1,
renderer: Proxmox.Utils.render_upid,
},
{
header: gettext("Status"),
dataIndex: 'status',
width: 200,
renderer: function(value, metaData, record) {
if (value === undefined && !record.data.endtime) {
metaData.tdCls = "x-grid-row-loading";
return '';
}
return Proxmox.Utils.format_task_status(value);
},
},
],
initComponent: function() {
const me = this;
let nodename = me.nodename || 'localhost';
let url = me.url || `/api2/json/nodes/${nodename}/tasks`;
me.getViewModel().set('url', url);
let updateExtraFilters = function(name, value) {
let vm = me.getViewModel();
let extraFilter = Ext.clone(vm.get('extraFilter'));
extraFilter[name] = value;
vm.set('extraFilter', extraFilter);
};
for (const [name, value] of Object.entries(me.preFilter)) {
updateExtraFilters(name, value);
}
me.getViewModel().set('preFilter', me.preFilter);
me.callParent();
let addFields = function(items) {
me.lookup('filtertoolbar').add({
xtype: 'container',
padding: 10,
layout: {
type: 'vbox',
align: 'stretch',
},
defaults: {
labelWidth: 80,
},
items,
});
};
// start with a userfilter
me.extraFilter = [
{
xtype: 'textfield',
fieldLabel: gettext('User name'),
changeOptions: {
buffer: 500,
},
name: 'userfilter',
},
...me.extraFilter,
];
let items = [];
for (const filterTemplate of me.extraFilter) {
let filter = Ext.clone(filterTemplate);
filter.listeners = filter.listeners || {};
filter.listeners.change = Ext.apply(filter.changeOptions || {}, {
fn: function(field, value) {
updateExtraFilters(filter.name, value);
},
});
items.push(filter);
if (items.length === 2) {
addFields(items);
items = [];
}
}
addFields(items);
},
});

View File

@ -9,21 +9,19 @@ Ext.define('Proxmox.node.TimeView', {
throw "no node name specified";
}
let tzoffset = new Date().getTimezoneOffset()*60000;
let renderlocaltime = function(value) {
let servertime = new Date((value * 1000) + tzoffset);
let tzOffset = new Date().getTimezoneOffset() * 60000;
let renderLocaltime = function(value) {
let servertime = new Date((value * 1000) + tzOffset);
return Ext.Date.format(servertime, 'Y-m-d H:i:s');
};
let run_editor = function() {
let win = Ext.create('Proxmox.node.TimeEdit', {
nodename: me.nodename,
});
win.show();
};
let run_editor = () => Ext.create('Proxmox.node.TimeEdit', {
autoShow: true,
nodename: me.nodename,
});
Ext.apply(me, {
url: "/api2/json/nodes/" + me.nodename + "/time",
url: `/api2/json/nodes/${me.nodename}/time`,
cwidth1: 150,
interval: 1000,
run_editor: run_editor,
@ -35,7 +33,7 @@ Ext.define('Proxmox.node.TimeView', {
localtime: {
header: gettext('Server time'),
required: true,
renderer: renderlocaltime,
renderer: renderLocaltime,
},
},
tbar: [

182
src/panel/AuthView.js Normal file
View File

@ -0,0 +1,182 @@
Ext.define('Proxmox.panel.AuthView', {
extend: 'Ext.grid.GridPanel',
alias: 'widget.pmxAuthView',
mixins: ['Proxmox.Mixin.CBind'],
showDefaultRealm: false,
stateful: true,
stateId: 'grid-authrealms',
viewConfig: {
trackOver: false,
},
baseUrl: '/access/domains',
storeBaseUrl: '/access/domains',
columns: [
{
header: gettext('Realm'),
width: 100,
sortable: true,
dataIndex: 'realm',
},
{
header: gettext('Type'),
width: 100,
sortable: true,
dataIndex: 'type',
},
{
header: gettext('Default'),
width: 80,
sortable: true,
dataIndex: 'default',
renderer: isDefault => isDefault ? Proxmox.Utils.renderEnabledIcon(true) : '',
align: 'center',
cbind: {
hidden: '{!showDefaultRealm}',
},
},
{
header: gettext('Comment'),
sortable: false,
dataIndex: 'comment',
renderer: Ext.String.htmlEncode,
flex: 1,
},
],
openEditWindow: function(authType, realm) {
let me = this;
const { useTypeInUrl, onlineHelp } = Proxmox.Schema.authDomains[authType];
Ext.create('Proxmox.window.AuthEditBase', {
baseUrl: me.baseUrl,
useTypeInUrl,
onlineHelp,
authType,
realm,
showDefaultRealm: me.showDefaultRealm,
listeners: {
destroy: () => me.reload(),
},
}).show();
},
reload: function() {
let me = this;
me.getStore().load();
},
run_editor: function() {
let me = this;
let rec = me.getSelection()[0];
if (!rec) {
return;
}
if (!Proxmox.Schema.authDomains[rec.data.type].edit) {
return;
}
me.openEditWindow(rec.data.type, rec.data.realm);
},
open_sync_window: function() {
let rec = this.getSelection()[0];
if (!rec) {
return;
}
if (!Proxmox.Schema.authDomains[rec.data.type].sync) {
return;
}
Ext.create('Proxmox.window.SyncWindow', {
type: rec.data.type,
realm: rec.data.realm,
listeners: {
destroy: () => this.reload(),
},
}).show();
},
initComponent: function() {
var me = this;
me.store = {
model: 'pmx-domains',
sorters: {
property: 'realm',
direction: 'ASC',
},
proxy: {
type: 'proxmox',
url: `/api2/json${me.storeBaseUrl}`,
},
};
let menuitems = [];
for (const [authType, config] of Object.entries(Proxmox.Schema.authDomains).sort()) {
if (!config.add) { continue; }
menuitems.push({
text: config.name,
iconCls: 'fa fa-fw ' + (config.iconCls || 'fa-address-book-o'),
handler: () => me.openEditWindow(authType),
});
}
let tbar = [
{
text: gettext('Add'),
menu: {
items: menuitems,
},
},
{
xtype: 'proxmoxButton',
text: gettext('Edit'),
disabled: true,
enableFn: (rec) => Proxmox.Schema.authDomains[rec.data.type].edit,
handler: () => me.run_editor(),
},
{
xtype: 'proxmoxStdRemoveButton',
getUrl: (rec) => {
let url = me.baseUrl;
if (Proxmox.Schema.authDomains[rec.data.type].useTypeInUrl) {
url += `/${rec.get('type')}`;
}
url += `/${rec.getId()}`;
return url;
},
enableFn: (rec) => Proxmox.Schema.authDomains[rec.data.type].add,
callback: () => me.reload(),
},
{
xtype: 'proxmoxButton',
text: gettext('Sync'),
disabled: true,
enableFn: (rec) => Proxmox.Schema.authDomains[rec.data.type].sync,
handler: () => me.open_sync_window(),
},
];
if (me.extraButtons) {
tbar.push('-');
for (const button of me.extraButtons) {
tbar.push(button);
}
}
Ext.apply(me, {
tbar,
listeners: {
activate: () => me.reload(),
itemdblclick: () => me.run_editor(),
},
});
me.callParent();
},
});

View File

@ -85,7 +85,7 @@ Ext.define('Proxmox.panel.Certificates', {
url: `/api2/extjs/${url}?restart=1`,
method: 'DELETE',
success: function(response, opt) {
if (cert.reloadUid) {
if (cert.reloadUi) {
Ext.getBody().mask(
gettext('API server will be restarted to use new certificates, please reload web-interface!'),
['pve-static-mask'],
@ -237,10 +237,16 @@ Ext.define('Proxmox.panel.Certificates', {
{
xtype: 'proxmoxButton',
text: gettext('Delete Custom Certificate'),
confirmMsg: rec => Ext.String.format(
gettext('Are you sure you want to remove the certificate used for {0}'),
me.certById[rec.id].name,
),
confirmMsg: rec => {
let cert = me.certById[rec.id];
if (cert.name) {
return Ext.String.format(
gettext('Are you sure you want to remove the certificate used for {0}'),
cert.name,
);
}
return gettext('Are you sure you want to remove the certificate');
},
callback: () => me.reload(),
selModel: me.selModel,
disabled: true,

View File

@ -35,7 +35,7 @@ Ext.define('pmx-disk-list', {
return undefined;
},
},
'vendor', 'model', 'serial', 'rpm', 'type', 'wearout', 'health',
'vendor', 'model', 'serial', 'rpm', 'type', 'wearout', 'health', 'mounted',
],
idProperty: 'devpath',
});
@ -44,6 +44,8 @@ Ext.define('Proxmox.DiskList', {
extend: 'Ext.tree.Panel',
alias: 'widget.pmxDiskList',
supportsWipeDisk: false,
rootVisible: false,
emptyText: gettext('No Disks found'),
@ -97,18 +99,40 @@ Ext.define('Proxmox.DiskList', {
waitMsgTarget: view,
method: 'POST',
params: { disk: rec.data.name },
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('Proxmox.window.TaskProgress', {
upid: upid,
Ext.create('Proxmox.window.TaskProgress', {
upid: response.result.data,
taskDone: function() {
me.reload();
},
autoShow: true,
});
},
});
},
wipeDisk: function() {
let me = this;
let view = me.getView();
let selection = view.getSelection();
if (!selection || selection.length < 1) return;
let rec = selection[0];
Proxmox.Utils.API2Request({
url: `${view.exturl}/wipedisk`,
waitMsgTarget: view,
method: 'PUT',
params: { disk: rec.data.name },
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
success: function(response, options) {
Ext.create('Proxmox.window.TaskProgress', {
upid: response.result.data,
taskDone: function() {
me.reload();
},
autoShow: true,
});
win.show();
},
});
},
@ -143,10 +167,19 @@ Ext.define('Proxmox.DiskList', {
for (const item of records) {
let data = item.data;
data.leaf = true;
data.expanded = true;
data.children = [];
data.children = data.partitions ?? [];
for (let p of data.children) {
p['disk-type'] = 'partition';
p.iconCls = 'fa fa-fw fa-hdd-o x-fa-tree';
p.used = p.used === 'filesystem' ? p.filesystem : p.used;
p.parent = data.devpath;
p.children = [];
p.leaf = true;
}
data.iconCls = 'fa fa-fw fa-hdd-o x-fa-tree';
data.leaf = data.children.length === 0;
if (!data.parent) {
disks[data.devpath] = data;
}
@ -187,7 +220,11 @@ Ext.define('Proxmox.DiskList', {
let extendedInfo = '';
if (rec) {
let types = [];
if (rec.data.osdid !== undefined && rec.data.osdid >= 0) {
if (rec.data['osdid-list'] && rec.data['osdid-list'].length > 0) {
for (const id of rec.data['osdid-list'].sort()) {
types.push(`OSD.${id.toString()}`);
}
} else if (rec.data.osdid !== undefined && rec.data.osdid >= 0) {
types.push(`OSD.${rec.data.osdid.toString()}`);
}
if (rec.data.journals > 0) {
@ -203,45 +240,18 @@ Ext.define('Proxmox.DiskList', {
extendedInfo = `, Ceph (${types.join(', ')})`;
}
}
const formatMap = {
'bios': 'BIOS boot',
'zfsreserved': 'ZFS reserved',
'efi': 'EFI',
'lvm': 'LVM',
'zfs': 'ZFS',
};
v = formatMap[v] || v;
return v ? `${v}${extendedInfo}` : Proxmox.Utils.noText;
},
tbar: [
{
text: gettext('Reload'),
handler: 'reload',
},
{
xtype: 'proxmoxButton',
text: gettext('Show S.M.A.R.T. values'),
parentXType: 'treepanel',
disabled: true,
enableFn: function(rec) {
if (!rec || rec.data.parent) {
return false;
} else {
return true;
}
},
handler: 'openSmartWindow',
},
{
xtype: 'proxmoxButton',
text: gettext('Initialize Disk with GPT'),
parentXType: 'treepanel',
disabled: true,
enableFn: function(rec) {
if (!rec || rec.data.parent ||
(rec.data.used && rec.data.used !== 'unused')) {
return false;
} else {
return true;
}
},
handler: 'initGPT',
},
],
columns: [
{
xtype: 'treecolumn',
@ -315,7 +325,14 @@ Ext.define('Proxmox.DiskList', {
dataIndex: 'status',
},
{
header: 'Wearout',
header: gettext('Mounted'),
width: 60,
align: 'right',
renderer: Proxmox.Utils.format_boolean,
dataIndex: 'mounted',
},
{
header: gettext('Wearout'),
width: 90,
sortable: true,
align: 'right',
@ -324,7 +341,7 @@ Ext.define('Proxmox.DiskList', {
if (Ext.isNumeric(value)) {
return (100 - value).toString() + '%';
}
return 'N/A';
return gettext('N/A');
},
},
],
@ -332,4 +349,91 @@ Ext.define('Proxmox.DiskList', {
listeners: {
itemdblclick: 'openSmartWindow',
},
initComponent: function() {
let me = this;
let tbar = [
{
text: gettext('Reload'),
handler: 'reload',
},
{
xtype: 'proxmoxButton',
text: gettext('Show S.M.A.R.T. values'),
parentXType: 'treepanel',
disabled: true,
enableFn: function(rec) {
if (!rec || rec.data.parent) {
return false;
} else {
return true;
}
},
handler: 'openSmartWindow',
},
{
xtype: 'proxmoxButton',
text: gettext('Initialize Disk with GPT'),
parentXType: 'treepanel',
disabled: true,
enableFn: function(rec) {
if (!rec || rec.data.parent ||
(rec.data.used && rec.data.used !== 'unused')) {
return false;
} else {
return true;
}
},
handler: 'initGPT',
},
];
if (me.supportsWipeDisk) {
tbar.push('-');
tbar.push({
xtype: 'proxmoxButton',
text: gettext('Wipe Disk'),
parentXType: 'treepanel',
dangerous: true,
confirmMsg: function(rec) {
const data = rec.data;
let mainMessage = Ext.String.format(
gettext('Are you sure you want to wipe {0}?'),
data.devpath,
);
mainMessage += `<br> ${gettext('All data on the device will be lost!')}`;
const type = me.renderDiskType(data["disk-type"]);
let usage;
if (data.children.length > 0) {
const partitionUsage = data.children.map(
partition => me.renderDiskUsage(partition.used),
).join(', ');
usage = `${gettext('Partitions')} (${partitionUsage})`;
} else {
usage = me.renderDiskUsage(data.used, undefined, rec);
}
const size = Proxmox.Utils.format_size(data.size);
const serial = Ext.String.htmlEncode(data.serial);
let additionalInfo = `${gettext('Type')}: ${type}<br>`;
additionalInfo += `${gettext('Usage')}: ${usage}<br>`;
additionalInfo += `${gettext('Size')}: ${size}<br>`;
additionalInfo += `${gettext('Serial')}: ${serial}`;
return `${mainMessage}<br><br>${additionalInfo}`;
},
disabled: true,
handler: 'wipeDisk',
});
}
me.tbar = tbar;
me.callParent();
},
});

View File

@ -3,6 +3,7 @@ Ext.define('Proxmox.EOLNotice', {
extend: 'Ext.Component',
alias: 'widget.proxmoxEOLNotice',
userCls: 'eol-notice',
padding: '0 5',
config: {
@ -17,14 +18,25 @@ Ext.define('Proxmox.EOLNotice', {
'data-qtip': gettext("You won't get any security fixes after the End-Of-Life date. Please consider upgrading."),
},
getIconCls: function() {
let me = this;
const now = new Date();
const eolDate = new Date(me.eolDate);
const warningCutoff = new Date(eolDate.getTime() - (21 * 24 * 60 * 60 * 1000)); // 3 weeks
return now > warningCutoff ? 'critical fa-exclamation-triangle' : 'info-blue fa-info-circle';
},
initComponent: function() {
let me = this;
let iconCls = me.getIconCls();
let href = me.href.startsWith('http') ? me.href : `https://${me.href}`;
let message = Ext.String.format(
gettext('Support for {0} {1} ends on {2}'), me.product, me.version, me.eolDate);
me.html = `<i class="fa pwt-eol-icon fa-exclamation-triangle"></i>
me.html = `<i class="fa ${iconCls}"></i>
<a href="${href}" target="_blank">${message} <i class="fa fa-external-link"></i></a>
`;

View File

@ -0,0 +1,91 @@
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();
},
},
],
},
],
});

View File

@ -20,6 +20,8 @@ Ext.define('Proxmox.panel.GaugeWidget', {
xtype: 'polar',
height: 120,
border: false,
// set to '-' to suppress warning in debug mode
downloadServerUrl: '-',
itemId: 'chart',
series: [{
type: 'gauge',
@ -59,6 +61,36 @@ Ext.define('Proxmox.panel.GaugeWidget', {
initialValue: 0,
checkThemeColors: function() {
let me = this;
let rootStyle = getComputedStyle(document.documentElement);
// get colors
let panelBg = rootStyle.getPropertyValue("--pwt-panel-background").trim() || "#ffffff";
let textColor = rootStyle.getPropertyValue("--pwt-text-color").trim() || "#000000";
me.defaultColor = rootStyle.getPropertyValue("--pwt-gauge-default").trim() || '#c2ddf2';
me.criticalColor = rootStyle.getPropertyValue("--pwt-gauge-crit").trim() || '#ff6c59';
me.warningColor = rootStyle.getPropertyValue("--pwt-gauge-warn").trim() || '#fc0';
me.backgroundColor = rootStyle.getPropertyValue("--pwt-gauge-back").trim() || '#f5f5f5';
// set gauge colors
let value = me.chart.series[0].getValue() / 100;
let color = me.defaultColor;
if (value >= me.criticalThreshold) {
color = me.criticalColor;
} else if (value >= me.warningThreshold) {
color = me.warningColor;
}
me.chart.series[0].setColors([color, me.backgroundColor]);
// set text and background colors
me.chart.setBackground(panelBg);
me.valueSprite.setAttributes({ fillStyle: textColor }, true);
me.chart.redraw();
},
updateValue: function(value, text) {
let me = this;
@ -98,5 +130,20 @@ Ext.define('Proxmox.panel.GaugeWidget', {
me.text = me.getComponent('text');
me.chart = me.getComponent('chart');
me.valueSprite = me.chart.getSurface('chart').get('valueSprite');
me.checkThemeColors();
// switch colors on media query changes
me.mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
me.themeListener = (e) => { me.checkThemeColors(); };
me.mediaQueryList.addEventListener("change", me.themeListener);
},
doDestroy: function() {
let me = this;
me.mediaQueryList.removeEventListener("change", me.themeListener);
me.callParent();
},
});

View File

@ -0,0 +1,75 @@
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;
},
});

View File

@ -59,7 +59,14 @@ Ext.define('Proxmox.widget.Info', {
},
updateValue: function(text, usage) {
var me = this;
let me = this;
if (me.lastText === text && me.lastUsage === usage) {
return;
}
me.lastText = text;
me.lastUsage = usage;
var label = me.getComponent('label');
label.update(Ext.apply(label.data, { title: me.title, usage: text }));

View File

@ -23,8 +23,7 @@ Ext.define('Proxmox.panel.InputPanel', {
// will be set if the inputpanel has advanced items
hasAdvanced: false,
// if the panel has advanced items,
// this will determine if they are shown by default
// if the panel has advanced items, this will determine if they are shown by default
showAdvanced: false,
// overwrite this to modify submit data
@ -58,12 +57,18 @@ Ext.define('Proxmox.panel.InputPanel', {
}
},
onSetValues: function(values) {
return values;
},
setValues: function(values) {
let me = this;
let form = me.up('form');
Ext.iterate(values, function(fieldId, val) {
values = me.onSetValues(values);
Ext.iterate(values, function(fieldId, val) {
let fields = me.query('[isFormField][name=' + fieldId + ']');
for (const field of fields) {
if (field) {

View File

@ -1,7 +1,7 @@
/*
* Display log entries in a panel with scrollbar
* The log entries are automatically refreshed via a background task,
* with newest entries comming at the bottom
* with newest entries coming at the bottom
*/
Ext.define('Proxmox.panel.JournalView', {
extend: 'Ext.panel.Panel',
@ -77,6 +77,8 @@ Ext.define('Proxmox.panel.JournalView', {
let num = lines.length;
let text = lines.map(Ext.htmlEncode).join('<br>');
let contentChanged = true;
if (!livemode) {
if (num) {
view.content = text;
@ -89,6 +91,8 @@ Ext.define('Proxmox.panel.JournalView', {
view.content = view.content ? text + '<br>' + view.content : text;
} else if (!top && num) {
view.content = view.content ? view.content + '<br>' + text : text;
} else {
contentChanged = false;
}
// update cursors
@ -101,7 +105,9 @@ Ext.define('Proxmox.panel.JournalView', {
}
}
contentEl.update(view.content);
if (contentChanged) {
contentEl.update(view.content);
}
me.updateScroll(livemode, num, scrollPos, scrollPosTop);
},
@ -137,6 +143,9 @@ Ext.define('Proxmox.panel.JournalView', {
waitMsgTarget: !livemode ? view : undefined,
method: 'GET',
success: function(response) {
if (me.isDestroyed) {
return;
}
Proxmox.Utils.setErrorMask(me, false);
let lines = response.result.data;
me.updateView(lines, livemode, top);
@ -192,7 +201,6 @@ Ext.define('Proxmox.panel.JournalView', {
view.loadTask = new Ext.util.DelayedTask(me.doLoad, me, [true, false]);
me.updateParams();
view.task = Ext.TaskManager.start({
run: function() {
if (!view.isVisible() || !view.scrollToEnd || !viewmodel.get('livemode')) {

View File

@ -1,13 +1,13 @@
/*
* Display log entries in a panel with scrollbar
* The log entries are automatically refreshed via a background task,
* with newest entries comming at the bottom
* with newest entries coming at the bottom
*/
Ext.define('Proxmox.panel.LogView', {
extend: 'Ext.panel.Panel',
xtype: 'proxmoxLogView',
pageSize: 500,
pageSize: 510,
viewBuffer: 50,
lineHeight: 16,
@ -22,19 +22,28 @@ Ext.define('Proxmox.panel.LogView', {
updateParams: function() {
let me = this;
let viewModel = me.getViewModel();
let since = viewModel.get('since');
let until = viewModel.get('until');
if (viewModel.get('hide_timespan')) {
if (viewModel.get('hide_timespan') || viewModel.get('livemode')) {
return;
}
let since = viewModel.get('since');
let until = viewModel.get('until');
if (since > until) {
Ext.Msg.alert('Error', 'Since date must be less equal than Until date.');
return;
}
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');
let submitFormat = viewModel.get('submitFormat');
viewModel.set('params.since', Ext.Date.format(since, submitFormat));
if (submitFormat === 'Y-m-d') {
viewModel.set('params.until', Ext.Date.format(until, submitFormat) + ' 23:59:59');
} else {
viewModel.set('params.until', Ext.Date.format(until, submitFormat));
}
me.getView().loadTask.delay(200);
},
@ -45,29 +54,42 @@ Ext.define('Proxmox.panel.LogView', {
return maxPos - pos;
},
updateView: function(text, first, total) {
updateView: function(lines, first, total) {
let me = this;
let view = me.getView();
let viewModel = me.getViewModel();
let content = me.lookup('content');
let data = viewModel.get('data');
if (first === data.first && total === data.total && text.length === data.textlen) {
return; // same content, skip setting and scrolling
if (first === data.first && total === data.total && lines.length === data.lines) {
// before there is any real output, we get 'no output' as a single line, so always
// update if we only have one to be sure to catch the first real line of output
if (total !== 1) {
return; // same content, skip setting and scrolling
}
}
viewModel.set('data', {
first: first,
total: total,
textlen: text.length,
lines: lines.length,
});
let scrollPos = me.scrollPosBottom();
let scrollToBottom = view.scrollToEnd && scrollPos <= 5;
content.update(text);
if (!scrollToBottom) {
// so that we have the 'correct' height for the text
lines.length = total;
}
if (view.scrollToEnd && scrollPos <= 5) {
// we use setTimeout to work around scroll handling on touchscreens
setTimeout(function() { view.scrollTo(0, Infinity); }, 10);
content.update(lines.join('<br>'));
if (scrollToBottom) {
let scroller = view.getScrollable();
scroller.suspendEvent('scroll');
view.scrollTo(0, Infinity);
me.updateStart(true);
scroller.resumeEvent('scroll');
}
},
@ -85,6 +107,9 @@ Ext.define('Proxmox.panel.LogView', {
params: viewModel.get('params'),
method: 'GET',
success: function(response) {
if (me.isDestroyed) {
return;
}
Proxmox.Utils.setErrorMask(me, false);
let total = response.result.total;
let lines = [];
@ -97,8 +122,7 @@ Ext.define('Proxmox.panel.LogView', {
lines[line.n - 1] = Ext.htmlEncode(line.t);
});
lines.length = total;
me.updateView(lines.join('<br>'), first - 1, total);
me.updateView(lines, first - 1, total);
me.running = false;
if (me.requested) {
me.requested = false;
@ -121,27 +145,66 @@ Ext.define('Proxmox.panel.LogView', {
});
},
updateStart: function(scrolledToBottom, targetLine) {
let me = this;
let view = me.getView(), viewModel = me.getViewModel();
let limit = viewModel.get('params.limit');
let total = viewModel.get('data.total');
// heuristic: scroll up? -> load more in front; scroll down? -> load more at end
let startRatio = view.lastTargetLine && view.lastTargetLine > targetLine ? 2/3 : 1/3;
view.lastTargetLine = targetLine;
let newStart = scrolledToBottom
? Math.trunc(total - limit, 10)
: Math.trunc(targetLine - (startRatio * limit) + 10);
viewModel.set('params.start', Math.max(newStart, 0));
view.loadTask.delay(200);
},
onScroll: function(x, y) {
let me = this;
let view = me.getView();
let viewModel = me.getViewModel();
let view = me.getView(), viewModel = me.getViewModel();
let lineHeight = view.lineHeight;
let line = view.getScrollY()/lineHeight;
let start = viewModel.get('params.start');
let limit = viewModel.get('params.limit');
let viewLines = view.getHeight()/lineHeight;
let line = view.getScrollY() / view.lineHeight;
let viewLines = view.getHeight() / view.lineHeight;
let viewStart = Math.max(parseInt(line - 1 - view.viewBuffer, 10), 0);
let viewEnd = parseInt(line + viewLines + 1 + view.viewBuffer, 10);
let viewStart = Math.max(Math.trunc(line - 1 - view.viewBuffer), 0);
let viewEnd = Math.trunc(line + viewLines + 1 + view.viewBuffer);
if (viewStart < start || viewEnd > start+limit) {
viewModel.set('params.start',
Math.max(parseInt(line - (limit / 2) + 10, 10), 0));
view.loadTask.delay(200);
let { start, limit } = viewModel.get('params');
let margin = start < 20 ? 0 : 20;
if (viewStart < start + margin || viewEnd > start + limit - margin) {
me.updateStart(false, line);
}
},
onLiveMode: function() {
let me = this;
let viewModel = me.getViewModel();
viewModel.set('livemode', true);
viewModel.set('params', { start: 0, limit: 510 });
let view = me.getView();
delete view.content;
view.scrollToEnd = true;
me.updateView([], true, false);
},
onTimespan: function() {
let me = this;
me.getViewModel().set('livemode', false);
me.updateView([], false);
// Directly apply currently selected values without update
// button click.
me.updateParams();
},
init: function(view) {
let me = this;
@ -156,17 +219,17 @@ Ext.define('Proxmox.panel.LogView', {
viewModel.set('since', since);
viewModel.set('params.limit', view.pageSize);
viewModel.set('hide_timespan', !view.log_select_timespan);
me.lookup('content').setStyle('line-height', view.lineHeight + 'px');
viewModel.set('submitFormat', view.submitFormat);
me.lookup('content').setStyle('line-height', `${view.lineHeight}px`);
view.loadTask = new Ext.util.DelayedTask(me.doLoad, me);
me.updateParams();
view.task = Ext.TaskManager.start({
run: function() {
run: () => {
if (!view.isVisible() || !view.scrollToEnd) {
return;
}
if (me.scrollPosBottom() <= 5) {
view.loadTask.delay(200);
}
@ -192,6 +255,8 @@ Ext.define('Proxmox.panel.LogView', {
data: {
until: null,
since: null,
submitFormat: 'Y-m-d',
livemode: true,
hide_timespan: false,
data: {
start: 0,
@ -200,7 +265,7 @@ Ext.define('Proxmox.panel.LogView', {
},
params: {
start: 0,
limit: 500,
limit: 510,
},
},
},
@ -211,9 +276,8 @@ Ext.define('Proxmox.panel.LogView', {
x: 'auto',
y: 'auto',
listeners: {
// we have to have this here, since we cannot listen to events
// of the scroller in the viewcontroller (extjs bug?), nor does
// the panel have a 'scroll' event'
// we have to have this here, since we cannot listen to events of the scroller in
// the viewcontroller (extjs bug?), nor does the panel have a 'scroll' event'
scroll: {
fn: function(scroller, x, y) {
let controller = this.component.getController();
@ -232,32 +296,70 @@ Ext.define('Proxmox.panel.LogView', {
},
items: [
'->',
'Since: ',
{
xtype: 'datefield',
xtype: 'segmentedbutton',
items: [
{
text: gettext('Live Mode'),
bind: {
pressed: '{livemode}',
},
handler: 'onLiveMode',
},
{
text: gettext('Select Timespan'),
bind: {
pressed: '{!livemode}',
},
handler: 'onTimespan',
},
],
},
{
xtype: 'box',
autoEl: { cn: gettext('Since') + ':' },
bind: {
disabled: '{livemode}',
},
},
{
xtype: 'proxmoxDateTimeField',
name: 'since_date',
reference: 'since',
format: 'Y-m-d',
bind: {
disabled: '{livemode}',
value: '{since}',
maxValue: '{until}',
submitFormat: '{submitFormat}',
},
},
'Until: ',
{
xtype: 'datefield',
xtype: 'box',
autoEl: { cn: gettext('Until') + ':' },
bind: {
disabled: '{livemode}',
},
},
{
xtype: 'proxmoxDateTimeField',
name: 'until_date',
reference: 'until',
format: 'Y-m-d',
bind: {
disabled: '{livemode}',
value: '{until}',
minValue: '{since}',
submitFormat: '{submitFormat}',
},
},
{
xtype: 'button',
text: 'Update',
handler: 'updateParams',
bind: {
disabled: '{livemode}',
},
},
],
},

View File

@ -0,0 +1,102 @@
Ext.define('Proxmox.widget.NodeInfoRepoStatus', {
extend: 'Proxmox.widget.Info',
alias: 'widget.pmxNodeInfoRepoStatus',
title: gettext('Repository Status'),
colspan: 2,
printBar: false,
product: undefined,
repoLink: undefined,
viewModel: {
data: {
subscriptionActive: '',
noSubscriptionRepo: '',
enterpriseRepo: '',
testRepo: '',
},
formulas: {
repoStatus: function(get) {
if (get('subscriptionActive') === '' || get('enterpriseRepo') === '') {
return '';
}
if (get('noSubscriptionRepo') || get('testRepo')) {
return 'non-production';
} else if (get('subscriptionActive') && get('enterpriseRepo')) {
return 'ok';
} else if (!get('subscriptionActive') && get('enterpriseRepo')) {
return 'no-sub';
} else if (!get('enterpriseRepo') || !get('noSubscriptionRepo') || !get('testRepo')) {
return 'no-repo';
}
return 'unknown';
},
repoStatusMessage: function(get) {
let me = this;
let view = me.getView();
const status = get('repoStatus');
let repoLink = ` <a data-qtip="${gettext("Open Repositories Panel")}"
href="${view.repoLink}">
<i class="fa black fa-chevron-right txt-shadow-hover"></i>
</a>`;
return Proxmox.Utils.formatNodeRepoStatus(status, view.product) + repoLink;
},
},
},
setValue: function(value) { // for binding below
this.updateValue(value);
},
bind: {
value: '{repoStatusMessage}',
},
setRepositoryInfo: function(standardRepos) {
let me = this;
let vm = me.getViewModel();
for (const standardRepo of standardRepos) {
const handle = standardRepo.handle;
const status = standardRepo.status || 0;
if (handle === "enterprise") {
vm.set('enterpriseRepo', status);
} else if (handle === "no-subscription") {
vm.set('noSubscriptionRepo', status);
} else if (handle === "test") {
vm.set('testRepo', status);
}
}
},
setSubscriptionStatus: function(status) {
let me = this;
let vm = me.getViewModel();
vm.set('subscriptionActive', status);
},
initComponent: function() {
let me = this;
if (me.product === undefined) {
throw "no product name provided";
}
if (me.repoLink === undefined) {
throw "no repo link href provided";
}
me.callParent();
},
});

172
src/panel/NotesView.js Normal file
View File

@ -0,0 +1,172 @@
Ext.define('Proxmox.panel.NotesView', {
extend: 'Ext.panel.Panel',
xtype: 'pmxNotesView',
mixins: ['Proxmox.Mixin.CBind'],
title: gettext("Notes"),
bodyPadding: 10,
scrollable: true,
animCollapse: false,
collapseFirst: false,
maxLength: 64 * 1024,
enableTBar: false,
onlineHelp: 'markdown_basics',
tbar: {
itemId: 'tbar',
hidden: true,
items: [
{
text: gettext('Edit'),
iconCls: 'fa fa-pencil-square-o',
handler: function() {
let view = this.up('panel');
view.run_editor();
},
},
],
},
cbindData: function(initalConfig) {
let me = this;
let type = '';
if (me.node) {
me.url = `/api2/extjs/nodes/${me.node}/config`;
} else if (me.pveSelNode?.data?.id === 'root') {
me.url = '/api2/extjs/cluster/options';
type = me.pveSelNode?.data?.type;
} else {
const nodename = me.pveSelNode?.data?.node;
type = me.pveSelNode?.data?.type;
if (!nodename) {
throw "no node name specified";
}
if (!Ext.Array.contains(['node', 'qemu', 'lxc'], type)) {
throw 'invalid type specified';
}
const vmid = me.pveSelNode?.data?.vmid;
if (!vmid && type !== 'node') {
throw "no VM ID specified";
}
me.url = `/api2/extjs/nodes/${nodename}/`;
// add the type specific path if qemu/lxc and set the backend's maxLen
if (type === 'qemu' || type === 'lxc') {
me.url += `${type}/${vmid}/`;
me.maxLength = 8 * 1024;
}
me.url += 'config';
}
me.pveType = type;
me.load();
return {};
},
run_editor: function() {
let me = this;
Ext.create('Proxmox.window.NotesEdit', {
url: me.url,
onlineHelp: me.onlineHelp,
listeners: {
destroy: () => me.load(),
},
autoShow: true,
}).setMaxLength(me.maxLength);
},
setNotes: function(value = '') {
let me = this;
let mdHtml = Proxmox.Markdown.parse(value);
me.update(mdHtml);
if (me.collapsible && me.collapseMode === 'auto') {
me.setCollapsed(!value);
}
},
load: function() {
let me = this;
Proxmox.Utils.API2Request({
url: me.url,
waitMsgTarget: me,
failure: (response, opts) => {
me.update(gettext('Error') + " " + response.htmlStatus);
me.setCollapsed(false);
},
success: ({ result }) => me.setNotes(result.data.description),
});
},
listeners: {
render: function(c) {
let me = this;
let sp = Ext.state.Manager.getProvider();
// to cover live changes to the browser setting
me.mon(sp, 'statechange', function(provider, key, value) {
if (value === null || key !== 'edit-notes-on-double-click') {
return;
}
if (value) {
me.getEl().on('dblclick', me.run_editor, me);
} else {
// there's only the me.run_editor listener, and removing just that did not work
me.getEl().clearListeners();
}
});
// to cover initial setting value
if (sp.get('edit-notes-on-double-click', false)) {
me.getEl().on('dblclick', me.run_editor, me);
}
},
afterlayout: function() {
let me = this;
if (me.collapsible && !me.getCollapsed() && me.collapseMode === 'always') {
me.setCollapsed(true);
me.collapseMode = ''; // only once, on initial load!
}
},
},
tools: [
{
glyph: 'xf044@FontAwesome', // fa-pencil-square-o
tooltip: gettext('Edit notes'),
callback: view => view.run_editor(),
style: {
paddingRight: '5px',
},
},
],
initComponent: function() {
let me = this;
me.callParent();
// '' is for datacenter
if (me.enableTBar === true || me.pveType === 'node' || me.pveType === '') {
me.down('#tbar').setVisible(true);
} else if (me.pveSelNode?.data?.template !== 1) {
me.setCollapsible(true);
me.collapseDirection = 'right';
let sp = Ext.state.Manager.getProvider();
me.collapseMode = sp.get('guest-notes-collapse', 'never');
if (me.collapseMode === 'auto') {
me.setCollapsed(true);
}
}
},
});

View File

@ -0,0 +1,410 @@
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`);
},
});

View File

@ -1,3 +1,6 @@
// override the download server url globally, for privacy reasons
Ext.draw.Container.prototype.defaultDownloadServerUrl = "-";
Ext.define('Proxmox.chart.axis.segmenter.NumericBase2', {
extend: 'Ext.chart.axis.segmenter.Numeric',
alias: 'segmenter.numericBase2',
@ -61,6 +64,9 @@ Ext.define('Proxmox.widget.RRDChart', {
powerOfTwo: false,
// set to empty string to suppress warning in debug mode
downloadServerUrl: '-',
controller: {
xclass: 'Ext.app.ViewController',
@ -86,7 +92,7 @@ Ext.define('Proxmox.widget.RRDChart', {
value = Ext.util.Format.number(value, format);
let unit = units[si];
if (this.powerOfTwo) unit += 'i';
if (unit && this.powerOfTwo) unit += 'i';
return `${value.toString()} ${unit}`;
},
@ -118,6 +124,9 @@ Ext.define('Proxmox.widget.RRDChart', {
},
onAfterAnimation: function(chart, eopts) {
if (!chart.header || !chart.header.tools) {
return;
}
// if the undo button is disabled, disable our tool
let ourUndoZoomButton = chart.header.tools[0];
let undoButton = chart.interactions[0].getUndoButton();
@ -134,10 +143,21 @@ Ext.define('Proxmox.widget.RRDChart', {
},
],
legend: {
type: 'dom',
padding: 0,
},
listeners: {
animationend: 'onAfterAnimation',
redraw: {
fn: 'onAfterAnimation',
options: {
buffer: 500,
},
},
},
touchAction: {
panX: true,
panY: true,
},
constructor: function(config) {
@ -163,6 +183,27 @@ Ext.define('Proxmox.widget.RRDChart', {
me.callParent([config]);
},
checkThemeColors: function() {
let me = this;
let rootStyle = getComputedStyle(document.documentElement);
// get colors
let background = rootStyle.getPropertyValue("--pwt-panel-background").trim() || "#ffffff";
let text = rootStyle.getPropertyValue("--pwt-text-color").trim() || "#000000";
let primary = rootStyle.getPropertyValue("--pwt-chart-primary").trim() || "#000000";
let gridStroke = rootStyle.getPropertyValue("--pwt-chart-grid-stroke").trim() || "#dddddd";
// set the colors
me.setBackground(background);
me.axes.forEach((axis) => {
axis.setLabel({ color: text });
axis.setTitle({ color: text });
axis.setStyle({ strokeStyle: primary });
axis.setGrid({ stroke: gridStroke });
});
me.redraw();
},
initComponent: function() {
let me = this;
@ -197,6 +238,7 @@ Ext.define('Proxmox.widget.RRDChart', {
if (me.header && me.legend) {
me.header.padding = '4 9 4';
me.header.add(me.legend);
me.legend = undefined;
}
if (!me.noTool) {
@ -233,10 +275,6 @@ Ext.define('Proxmox.widget.RRDChart', {
marker: {
opacity: 0,
scaling: 0.01,
fx: {
duration: 200,
easing: 'easeOut',
},
},
highlightCfg: {
opacity: 1,
@ -253,7 +291,26 @@ Ext.define('Proxmox.widget.RRDChart', {
// enable animation after the store is loaded
me.store.onAfter('load', function() {
me.setAnimation(true);
me.setAnimation({
duration: 200,
easing: 'easeIn',
});
}, this, { single: true });
me.checkThemeColors();
// switch colors on media query changes
me.mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
me.themeListener = (e) => { me.checkThemeColors(); };
me.mediaQueryList.addEventListener("change", me.themeListener);
},
doDestroy: function() {
let me = this;
me.mediaQueryList.removeEventListener("change", me.themeListener);
me.callParent();
},
});

View File

@ -0,0 +1,103 @@
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;
},
});

205
src/panel/SmtpEditPanel.js Normal file
View File

@ -0,0 +1,205 @@
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;
},
});

View File

@ -47,7 +47,7 @@ Ext.define('Proxmox.panel.StatusView', {
*/
if (used.used !== undefined &&
used.total !== undefined) {
return used.used/used.total;
return used.total > 0 ? used.used/used.total : 0;
}
}

304
src/panel/TfaView.js Normal file
View File

@ -0,0 +1,304 @@
Ext.define('pmx-tfa-users', {
extend: 'Ext.data.Model',
fields: ['userid'],
idProperty: 'userid',
proxy: {
type: 'proxmox',
url: '/api2/json/access/tfa',
},
});
Ext.define('pmx-tfa-entry', {
extend: 'Ext.data.Model',
fields: ['fullid', 'userid', 'type', 'description', 'created', 'enable'],
idProperty: 'fullid',
});
Ext.define('Proxmox.panel.TfaView', {
extend: 'Ext.grid.GridPanel',
alias: 'widget.pmxTfaView',
mixins: ['Proxmox.Mixin.CBind'],
title: gettext('Second Factors'),
reference: 'tfaview',
issuerName: 'Proxmox',
yubicoEnabled: false,
cbindData: function(initialConfig) {
let me = this;
return {
yubicoEnabled: me.yubicoEnabled,
};
},
store: {
type: 'diff',
autoDestroy: true,
autoDestroyRstore: true,
model: 'pmx-tfa-entry',
rstore: {
type: 'store',
proxy: 'memory',
storeid: 'pmx-tfa-entry',
model: 'pmx-tfa-entry',
},
},
controller: {
xclass: 'Ext.app.ViewController',
init: function(view) {
let me = this;
view.tfaStore = Ext.create('Proxmox.data.UpdateStore', {
autoStart: true,
interval: 5 * 1000,
storeid: 'pmx-tfa-users',
model: 'pmx-tfa-users',
});
view.tfaStore.on('load', this.onLoad, this);
view.on('destroy', view.tfaStore.stopUpdate);
Proxmox.Utils.monStoreErrors(view, view.tfaStore);
},
reload: function() { this.getView().tfaStore.load(); },
onLoad: function(store, data, success) {
if (!success) return;
let now = new Date().getTime() / 1000;
let records = [];
Ext.Array.each(data, user => {
let tfa_locked = (user.data['tfa-locked-until'] || 0) > now;
let totp_locked = user.data['totp-locked'];
Ext.Array.each(user.data.entries, entry => {
records.push({
fullid: `${user.id}/${entry.id}`,
userid: user.id,
type: entry.type,
description: entry.description,
created: entry.created,
enable: entry.enable,
locked: tfa_locked || (entry.type === 'totp' && totp_locked),
});
});
});
let rstore = this.getView().store.rstore;
rstore.loadData(records);
rstore.fireEvent('load', rstore, records, true);
},
addTotp: function() {
let me = this;
Ext.create('Proxmox.window.AddTotp', {
isCreate: true,
issuerName: me.getView().issuerName,
listeners: {
destroy: function() {
me.reload();
},
},
}).show();
},
addWebauthn: function() {
let me = this;
Ext.create('Proxmox.window.AddWebauthn', {
isCreate: true,
autoShow: true,
listeners: {
destroy: () => me.reload(),
},
});
},
addRecovery: async function() {
let me = this;
Ext.create('Proxmox.window.AddTfaRecovery', {
autoShow: true,
listeners: {
destroy: () => me.reload(),
},
});
},
addYubico: function() {
let me = this;
Ext.create('Proxmox.window.AddYubico', {
isCreate: true,
autoShow: true,
listeners: {
destroy: () => me.reload(),
},
});
},
editItem: function() {
let me = this;
let view = me.getView();
let selection = view.getSelection();
if (selection.length !== 1 || selection[0].id.endsWith("/recovery")) {
return;
}
Ext.create('Proxmox.window.TfaEdit', {
'tfa-id': selection[0].data.fullid,
autoShow: true,
listeners: {
destroy: () => me.reload(),
},
});
},
renderUser: fullid => fullid.split('/')[0],
renderEnabled: function(enabled, metaData, record) {
if (record.data.locked) {
return gettext("Locked");
} else if (enabled === undefined) {
return Proxmox.Utils.yesText;
} else {
return Proxmox.Utils.format_boolean(enabled);
}
},
onRemoveButton: function(btn, event, record) {
let me = this;
Ext.create('Proxmox.tfa.confirmRemove', {
...record.data,
callback: password => me.removeItem(password, record),
autoShow: true,
});
},
removeItem: async function(password, record) {
let me = this;
if (password !== null) {
password = '?password=' + encodeURIComponent(password);
} else {
password = '';
}
try {
me.getView().mask(gettext('Please wait...'), 'x-mask-loading');
await Proxmox.Async.api2({
url: `/api2/extjs/access/tfa/${record.id}${password}`,
method: 'DELETE',
});
me.reload();
} catch (response) {
Ext.Msg.alert(gettext('Error'), response.result.message);
} finally {
me.getView().unmask();
}
},
},
viewConfig: {
trackOver: false,
},
listeners: {
itemdblclick: 'editItem',
},
columns: [
{
header: gettext('User'),
width: 200,
sortable: true,
dataIndex: 'fullid',
renderer: 'renderUser',
},
{
header: gettext('Enabled'),
width: 80,
sortable: true,
dataIndex: 'enable',
renderer: 'renderEnabled',
},
{
header: gettext('TFA Type'),
width: 80,
sortable: true,
dataIndex: 'type',
},
{
header: gettext('Created'),
width: 150,
sortable: true,
dataIndex: 'created',
renderer: t => !t ? 'N/A' : Proxmox.Utils.render_timestamp(t),
},
{
header: gettext('Description'),
width: 300,
sortable: true,
dataIndex: 'description',
renderer: Ext.String.htmlEncode,
flex: 1,
},
],
tbar: [
{
text: gettext('Add'),
cbind: {},
menu: {
xtype: 'menu',
items: [
{
text: gettext('TOTP'),
itemId: 'totp',
iconCls: 'fa fa-fw fa-clock-o',
handler: 'addTotp',
},
{
text: gettext('WebAuthn'),
itemId: 'webauthn',
iconCls: 'fa fa-fw fa-shield',
handler: 'addWebauthn',
},
{
text: gettext('Recovery Keys'),
itemId: 'recovery',
iconCls: 'fa fa-fw fa-file-text-o',
handler: 'addRecovery',
},
{
text: gettext('Yubico OTP'),
itemId: 'yubico',
iconCls: 'fa fa-fw fa-yahoo', // close enough
handler: 'addYubico',
cbind: {
hidden: '{!yubicoEnabled}',
},
},
],
},
},
'-',
{
xtype: 'proxmoxButton',
text: gettext('Edit'),
handler: 'editItem',
enableFn: rec => !rec.id.endsWith("/recovery"),
disabled: true,
},
{
xtype: 'proxmoxButton',
disabled: true,
text: gettext('Remove'),
getRecordName: rec => rec.data.description,
handler: 'onRemoveButton',
},
],
});

View File

@ -0,0 +1,423 @@
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();
},
});

47
src/proxmox-dark/Makefile Normal file
View File

@ -0,0 +1,47 @@
include ../defines.mk
SCSSSRC=scss/ProxmoxDark.scss \
scss/abstracts/_mixins.scss \
scss/abstracts/_variables.scss \
scss/extjs/_body.scss \
scss/extjs/form/_button.scss \
scss/extjs/form/_combobox.scss \
scss/extjs/form/_formfield.scss \
scss/extjs/_grid.scss \
scss/extjs/_menu.scss \
scss/extjs/_panel.scss \
scss/extjs/_presentation.scss \
scss/extjs/_progress.scss \
scss/extjs/_splitter.scss \
scss/extjs/_tabbar.scss \
scss/extjs/_tip.scss \
scss/extjs/_toolbar.scss \
scss/extjs/_treepanel.scss \
scss/extjs/_window.scss \
scss/other/_charts.scss \
scss/other/_icons.scss \
scss/proxmox/_general.scss \
scss/proxmox/_helpbutton.scss \
scss/proxmox/_loadingindicator.scss \
scss/proxmox/_markdown.scss \
scss/proxmox/_nodes.scss \
scss/proxmox/_quarantine.scss \
scss/proxmox/_storages.scss \
scss/proxmox/_tags.scss \
scss/proxmox/_datepicker.scss
.PHONY: all
all: theme-proxmox-dark.css
.PHONY: install
install: theme-proxmox-dark.css
install -d $(WWWTHEMEDIR)/
install -m 0664 theme-proxmox-dark.css $(WWWTHEMEDIR)/
theme-proxmox-dark.css: $(SCSSSRC)
sassc -t compressed $< $@.tmp
mv $@.tmp $@
.PHONY: clean
clean:
rm -rf theme-proxmox-dark.css

View File

@ -0,0 +1,37 @@
@charset "utf-8";
// Abstracts
@import "abstracts/mixins";
@import "abstracts/variables";
// Chart, Icon, Keyboar-mode fixups
@import "other/charts";
@import "other/icons";
// ExtJS re-stylings
@import "extjs/form/button";
@import "extjs/form/combobox";
@import "extjs/form/formfield";
@import "extjs/grid";
@import "extjs/menu";
@import "extjs/panel";
@import "extjs/progress";
@import "extjs/splitter";
@import "extjs/tabbar";
@import "extjs/tip";
@import "extjs/toolbar";
@import "extjs/treepanel";
@import "extjs/window";
@import "extjs/body";
@import "extjs/presentation";
// Proxmox re-stylings
@import "proxmox/general";
@import "proxmox/helpbutton";
@import "proxmox/loadingindicator";
@import "proxmox/markdown";
@import "proxmox/nodes";
@import "proxmox/quarantine";
@import "proxmox/storages";
@import "proxmox/tags";
@import "proxmox/datepicker";

View File

@ -0,0 +1,5 @@
// selected items in dropdown etc
@mixin selection {
background-color: $selection-background-color;
color: $selection-background-text-color;
}

View File

@ -0,0 +1,67 @@
// Primary colors
$primary-color: hsl(205deg, 100%, 32.25%);
$primary-light: hsl(205deg, 100%, 40.5%);
$primary-dark: hsl(205deg, 100%, 25%);
// Hightlighted Text (Links, Headers, etc.)
$highlighted-text: hsl(205deg, 100%, 65%);
$highlighted-text-alt: hsl(205deg, 100%, 80%);
$highlighted-text-crit: hsl(360deg, 100%, 65%);
// Icon and Text colors
$text-color: hsl(0deg, 0%, 95%);
$text-color-inactive: hsl(0deg, 0%, 60%);
$icon-color: hsl(0deg, 0%, 90%);
$icon-color-alt: hsl(0deg, 0%, 55%);
// Borders
$border-color: hsl(0deg, 0%, 40%);
$border-color-alt: hsl(0deg, 0%, 25%);
// Backgrounds
$content-background-color: hsl(0deg, 0%, 20%);
$content-background-selected: hsl(0deg, 0%, 30%);
$background-dark: hsl(0deg, 0%, 20%);
$background-darker: hsl(0deg, 0%, 15%);
$background-darkest: hsl(0deg, 0%, 10%);
$background-invalid: hsl(360deg, 60%, 20%);
$background-warning: hsl(40deg, 100%, 20%);
// Buttons
$neutral-button-color: hsl(0deg, 0%, 25%);
$neutral-button-color-alt: hsl(0deg, 0%, 35%);
$neutral-button-text-color: hsl(0deg, 0%, 95%);
$neutral-button-icon-color: $neutral-button-text-color;
// Help Buttons
$help-button-color: hsl(0deg, 0%, 65%);
$help-button-color-alt: hsl(0deg, 0%, 75%);
$help-button-text-color: hsl(0deg, 0%, 10%);
$help-button-icon-color: $help-button-text-color;
// Selection Colors
$selection-background-color: hsl(0deg, 0%, 35%);
$selection-background-text-color: hsl(0deg, 0%, 100%);
// Other
$form-field-body-color: $background-dark;
$bottom-splitter-color: hsl(0deg, 0%, 5%);
// Some icons are black and do not respect the 'color' style property.
// For the dark mode these can be turned grey or white with the
// 'filter: invert(value)' attribute
$icon-brightness: lightness($icon-color);
// Spam score colors
// for spam scores with an absolute score >= 3
$spam-high-neg: hsl(205deg, 65%, 20%);
$spam-high-pos: hsl(360deg, 55%, 20%);
// for spam scores with an absolute score between 0.1 and 3
$spam-mid-neg: hsl(205deg, 65%, 30%);
$spam-mid-pos: hsl(360deg, 55%, 30%);
// for spam scores with an absolute score <= 0.1
$spam-low-neg: hsl(205deg, 65%, 40%);
$spam-low-pos: hsl(360deg, 55%, 40%);

View File

@ -0,0 +1,23 @@
// Chrome 81, Firefox 96 and Safari 13 support a dark version of the
// scrollbar and form controls source
// https://stackoverflow.com/q/65940522
:root {
color-scheme: dark;
}
.x-body {
color: $text-color;
background-color: $background-darkest;
}
// Should be the absolute background of the document
.x-viewport > .x-body {
background-color: $background-darkest;
}
// necessary for some masks to work properly (e.g. when hidding the
// attachment grid in pmg)
body.x-border-layout-ct,
div.x-border-layout-ct {
background-color: $background-darkest;
}

View File

@ -0,0 +1,148 @@
.x-column-header {
border-color: $border-color-alt;
}
.x-grid-item,
.x-column-header-default,
// the row number field (e.g. in the ipsets in pve)
.x-grid-cell-row-numberer {
color: $text-color;
background-color: $background-darker;
}
// Trigger in grid/table header cells
.x-column-header-trigger {
border-color: $border-color;
}
.x-grid-cell-special {
border-color: $border-color-alt;
}
.x-grid-group-hd {
background-color: $background-darker;
border-color: $border-color-alt;
}
.x-grid-group-title {
color: $text-color;
}
// Border-top in tables
.x-grid-header-ct {
border: solid 1px $background-dark;
background-color: $background-dark;
}
// alternating row colors
.x-grid-item-alt {
background-color: $background-darkest;
}
.x-grid-with-row-lines {
.x-grid-item {
border-color: $border-color-alt;
border-right: 0;
// A border at the bottom of tables
&:last-child {
border-color: $border-color-alt;
}
// A border at the top of tables
&:first-child {
border-color: $border-color-alt;
}
// hovered row in a grid
&.x-grid-item-over,
&.x-grid-item-selected {
background-color: $selection-background-color;
border-color: $border-color-alt;
}
}
// borders on selected elements
.x-grid-item-selected + .x-grid-item,
.x-grid-item-over + .x-grid-item {
border-color: $border-color-alt;
}
}
// Sometimes a selected node in the ResourceTree loses the
// selection-background-color
.x-grid-item-over,
.x-grid-item-selected {
// Otherwise .x-grid-item overrides the background color
background-color: $selection-background-color;
}
// Hovering over a grid/table header cell
.x-column-header-over,
// When opening the sort/settings header of a table/grid header cell
.x-column-header-open,
.x-column-header-last .x-column-header-over .x-column-header-trigger, {
background-color: $content-background-selected;
}
// header element that the grid is currently sorted by
.x-column-header-sort-ASC,
.x-column-header-sort-DESC {
background-color: mix($background-darker, $primary-color, 70%);
}
// summary rows (e.g. ceph pools last row)
.x-grid-row-summary {
.x-grid-cell,
.x-grid-rowwrap,
.x-grid-cell-rowbody {
// the "!important" is needed here, because crisp also sets this
// as important
background-color: $background-darker !important;
border-color: $border-color-alt;
}
}
.x-grid-with-col-lines {
.x-grid-cell,
.x-grid-item-over .x-grid-cell,
.x-grid-item-selected .x-grid-cell {
border-color: $border-color-alt;
}
}
// drag and drop proxy
.x-dd-drag-proxy {
background-color: $background-darkest;
border-color: $border-color-alt;
color: $text-color;
}
.x-keyboard-mode .x-grid-item-focused {
@include selection;
.x-grid-cell-inner::before {
border-color: $primary-color;
}
}
// Grid/table headers that are selected and active
.x-keyboard-mode .x-column-header.x-column-header-focus {
color: $text-color;
// Elements in table
.x-column-header-inner::after {
border-color: $primary-color;
}
}
.x-keyboard-mode .proxmox-invalid-row .x-grid-item-focused {
background-color: $background-invalid;
}
// As far as I can tell only used under Node > "System" >
// "Certificates"
.x-grid-empty {
background-color: $background-darker;
color: $text-color;
}

View File

@ -0,0 +1,40 @@
.x-menu-default {
border-color: $form-field-body-color;
}
.x-menu-body-default {
background-color: $form-field-body-color;
}
// E.g. the content menu in the resource tree displays a header
.x-menu-header {
background-color: $primary-color;
}
.x-menu-item-default {
// Horizontal divider in menu (e.g. in UserInfo above "Logout")
&.x-menu-item-separator {
background-color: $background-dark;
border-color: $border-color;
}
// When hovering over a menu item
&.x-menu-item-focus,
&.x-menu-item-active {
@include selection;
}
}
.x-menu-item-text-default {
color: $text-color;
}
.x-menu-item-icon-default {
color: $icon-color;
}
// Vertical divider (e.g. in UserInfo between icons and text)
.x-menu-icon-separator-default {
background-color: $background-dark;
border-color: $border-color;
}

View File

@ -0,0 +1,58 @@
.x-panel-header-default {
background-color: $content-background-color;
border: none;
.x-tool-tool-el {
background-color: transparent;
}
}
// The small navigation elements in the panel header bar e.g. to
// collapse a panel
.x-tool-img {
filter: brightness(175%);
// these are brighter per default, so they don't need to be
// brigthened as much
&.x-tool-expand,
&.x-tool-collapse,
&.x-tool-refresh {
filter: brightness(125%);
}
// this icon uses multiple tones, to have them behave appropriatelly
// invert them before brightening them
&.x-tool-print {
filter: invert(100%) hue-rotate(180deg) brightness(125%);
}
.x-tool-over & {
filter: brightness(200%);
}
.x-tool-over &.x-tool-expand,
.x-tool-over &.x-tool-collapse,
.x-tool-over &.x-tool-refresh {
filter: brightness(150%);
}
.x-tool-over &.x-tool-print {
filter: invert(100%) hue-rotate(180deg) brightness(150%);
}
}
.x-panel-header-title-default {
color: $highlighted-text;
}
.x-panel-body-default {
background-color: $background-darker;
border-color: $border-color-alt;
color: $text-color;
}
// override the border around the pve-resource-tree specifically to be
// more consistent with crisp, while keep allignments "correct"
div[id^="pveResourceTree-"][id$="-body"] {
border-color: $background-darker;
}

View File

@ -0,0 +1,13 @@
// The mask that is applied when the window is unaccessible (Login
// screen, Loading, ...)
.x-mask {
background-color: rgba($background-darker, 0.5);
}
// Shadows of floating windows like window modals, form selectors and
// message boxes
.x-css-shadow {
// the additional styling from the pve css overwrites the setting on
// the element with "!important", that's why we need it here.
box-shadow: black 0 -1px 15px 5px !important;
}

View File

@ -0,0 +1,19 @@
.x-progress-default {
background-color: $form-field-body-color;
.x-progress-bar-default {
background-color: $primary-color; // Taken from the chart
}
.x-progress-text {
color: $text-color;
}
}
.x-progress.warning .x-progress-bar {
background-color: var(--pwt-gauge-warn);
}
.x-progress.critical .x-progress-bar {
background-color: var(--pwt-gauge-crit);
}

View File

@ -0,0 +1,18 @@
// Splitters separating two views (e.g. Firewall > "Security Group",
// "IPSet", ...)
.x-splitter {
background-color: $background-darkest;
}
.x-splitter-horizontal {
background-color: $bottom-splitter-color;
}
// Splitters that separate content and resize parts of the window
.x-keyboard-mode .x-splitter-focus::after {
border-color: $primary-color;
}
.x-layout-split-bottom {
opacity: 0.7;
}

View File

@ -0,0 +1,45 @@
// The header of the tabbar
.x-tab-bar-default {
background-color: $background-darker;
}
.x-tab-default {
// Hovering over a tab button
&.x-tab-over {
background-color: $primary-dark;
border-color: $primary-dark;
}
// Selected tab buttons
&.x-tab.x-tab-active {
background-color: $primary-light;
border-color: $primary-light;
}
// Disabled tab buttons
&.x-tab.x-tab-disabled {
background-color: $background-darker;
// make the border invisible so it matches the light theme, setting
// it to none messes with the allignment of the elements.
border-color: transparent;
color: $text-color;
}
.x-keyboard-mode &.x-tab-focus,
.x-keyboard-mode &.x-tab-focus.x-tab-over,
.x-keyboard-mode &.x-tab-focus.x-tab-active {
background-color: $primary-color;
border-color: $primary-color;
}
}
// Not selected tab buttons
.x-tab-default-top {
background-color: $background-darker;
border-color: $background-darker;
}
.x-tab-inner-default {
color: $text-color;
}

View File

@ -0,0 +1,18 @@
.x-tip-default {
background-color: $background-darkest;
border-color: $border-color-alt;
}
.x-tip-body-default {
color: $text-color;
}
// Form error tip
.x-tip-form-invalid {
background-color: $background-dark;
border-color: $border-color-alt;
}
.x-tip-body-form-invalid {
color: $text-color;
}

Some files were not shown because too many files have changed in this diff Show More