mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-19 09:53:34 -06:00
commit
8f5005efd5
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.5.7
|
placeholder: v3.5.8
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.5.7
|
placeholder: v3.5.8
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -14,12 +14,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<h3></h3>
|
<h3></h3>
|
||||||
|
|
||||||
Some general tips for engaging here on GitHub:
|
## :information_source: Welcome to the Stadium!
|
||||||
|
|
||||||
|
In her book [Working in Public](https://www.amazon.com/Working-Public-Making-Maintenance-Software/dp/0578675862), Nadia Eghbal defines four production models for open source projects, categorized by contributor and user growth: federations, clubs, toys, and stadiums. The NetBox project fits her definition of a stadium very well:
|
||||||
|
|
||||||
|
> Stadiums are projects with low contributor growth and high user growth. While they may receive casual contributions, their regular contributor base does not grow proportionately to their users. As a result, they tend to be powered by one or a few developers.
|
||||||
|
|
||||||
|
The bulk of NetBox's development is carried out by a handful of core maintainers, with occasional contributions from collaborators in the community. We find the stadium analogy very useful in conveying the roles and obligations of both contributors and users.
|
||||||
|
|
||||||
|
If you're a contributor, actively working on the center stage, you have an obligation to produce quality content that will benefit the project as a whole. Conversely, if you're in the audience consuming the work being produced, you have the option of making requests and suggestions, but must also recognize that contributors are under no obligation to act on them.
|
||||||
|
|
||||||
|
NetBox users are welcome to participate in either role, on stage or in the crowd. We ask only that you acknowledge the role you've chosen and respect the roles of others.
|
||||||
|
|
||||||
|
### General Tips for Working on GitHub
|
||||||
|
|
||||||
* Register for a free [GitHub account](https://github.com/signup) if you haven't already.
|
* Register for a free [GitHub account](https://github.com/signup) if you haven't already.
|
||||||
* You can use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting text and adding images.
|
* You can use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting text and adding images.
|
||||||
* To help mitigate notification spam, please avoid "bumping" issues with no activity. (To vote an issue up or down, use a :thumbsup: or :thumbsdown: reaction.)
|
* To help mitigate notification spam, please avoid "bumping" issues with no activity. (To vote an issue up or down, use a :thumbsup: or :thumbsdown: reaction.)
|
||||||
* Please avoid pinging members with `@` unless they've previously expressed interest or involvement with that particular issue.
|
* Please avoid pinging members with `@` unless they've previously expressed interest or involvement with that particular issue.
|
||||||
|
* Familiarize yourself with [this list of discussion anti-patterns](https://github.com/bradfitz/issue-tracker-behaviors) and make every effort to avoid them.
|
||||||
|
|
||||||
## :bug: Reporting Bugs
|
## :bug: Reporting Bugs
|
||||||
|
|
||||||
|
561
contrib/generated_schema.json
Normal file
561
contrib/generated_schema.json
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"definitions": {
|
||||||
|
"airflow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"front-to-rear",
|
||||||
|
"rear-to-front",
|
||||||
|
"left-to-right",
|
||||||
|
"right-to-left",
|
||||||
|
"side-to-rear",
|
||||||
|
"passive",
|
||||||
|
"mixed"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"weight-unit": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"kg",
|
||||||
|
"g",
|
||||||
|
"lb",
|
||||||
|
"oz"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"subdevice-role": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"parent",
|
||||||
|
"child"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"console-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"de-9",
|
||||||
|
"db-25",
|
||||||
|
"rj-11",
|
||||||
|
"rj-12",
|
||||||
|
"rj-45",
|
||||||
|
"mini-din-8",
|
||||||
|
"usb-a",
|
||||||
|
"usb-b",
|
||||||
|
"usb-c",
|
||||||
|
"usb-mini-a",
|
||||||
|
"usb-mini-b",
|
||||||
|
"usb-micro-a",
|
||||||
|
"usb-micro-b",
|
||||||
|
"usb-micro-ab",
|
||||||
|
"other"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"console-server-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"de-9",
|
||||||
|
"db-25",
|
||||||
|
"rj-11",
|
||||||
|
"rj-12",
|
||||||
|
"rj-45",
|
||||||
|
"mini-din-8",
|
||||||
|
"usb-a",
|
||||||
|
"usb-b",
|
||||||
|
"usb-c",
|
||||||
|
"usb-mini-a",
|
||||||
|
"usb-mini-b",
|
||||||
|
"usb-micro-a",
|
||||||
|
"usb-micro-b",
|
||||||
|
"usb-micro-ab",
|
||||||
|
"other"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"power-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"iec-60320-c6",
|
||||||
|
"iec-60320-c8",
|
||||||
|
"iec-60320-c14",
|
||||||
|
"iec-60320-c16",
|
||||||
|
"iec-60320-c20",
|
||||||
|
"iec-60320-c22",
|
||||||
|
"iec-60309-p-n-e-4h",
|
||||||
|
"iec-60309-p-n-e-6h",
|
||||||
|
"iec-60309-p-n-e-9h",
|
||||||
|
"iec-60309-2p-e-4h",
|
||||||
|
"iec-60309-2p-e-6h",
|
||||||
|
"iec-60309-2p-e-9h",
|
||||||
|
"iec-60309-3p-e-4h",
|
||||||
|
"iec-60309-3p-e-6h",
|
||||||
|
"iec-60309-3p-e-9h",
|
||||||
|
"iec-60309-3p-n-e-4h",
|
||||||
|
"iec-60309-3p-n-e-6h",
|
||||||
|
"iec-60309-3p-n-e-9h",
|
||||||
|
"iec-60906-1",
|
||||||
|
"nbr-14136-10a",
|
||||||
|
"nbr-14136-20a",
|
||||||
|
"nema-1-15p",
|
||||||
|
"nema-5-15p",
|
||||||
|
"nema-5-20p",
|
||||||
|
"nema-5-30p",
|
||||||
|
"nema-5-50p",
|
||||||
|
"nema-6-15p",
|
||||||
|
"nema-6-20p",
|
||||||
|
"nema-6-30p",
|
||||||
|
"nema-6-50p",
|
||||||
|
"nema-10-30p",
|
||||||
|
"nema-10-50p",
|
||||||
|
"nema-14-20p",
|
||||||
|
"nema-14-30p",
|
||||||
|
"nema-14-50p",
|
||||||
|
"nema-14-60p",
|
||||||
|
"nema-15-15p",
|
||||||
|
"nema-15-20p",
|
||||||
|
"nema-15-30p",
|
||||||
|
"nema-15-50p",
|
||||||
|
"nema-15-60p",
|
||||||
|
"nema-l1-15p",
|
||||||
|
"nema-l5-15p",
|
||||||
|
"nema-l5-20p",
|
||||||
|
"nema-l5-30p",
|
||||||
|
"nema-l5-50p",
|
||||||
|
"nema-l6-15p",
|
||||||
|
"nema-l6-20p",
|
||||||
|
"nema-l6-30p",
|
||||||
|
"nema-l6-50p",
|
||||||
|
"nema-l10-30p",
|
||||||
|
"nema-l14-20p",
|
||||||
|
"nema-l14-30p",
|
||||||
|
"nema-l14-50p",
|
||||||
|
"nema-l14-60p",
|
||||||
|
"nema-l15-20p",
|
||||||
|
"nema-l15-30p",
|
||||||
|
"nema-l15-50p",
|
||||||
|
"nema-l15-60p",
|
||||||
|
"nema-l21-20p",
|
||||||
|
"nema-l21-30p",
|
||||||
|
"nema-l22-30p",
|
||||||
|
"cs6361c",
|
||||||
|
"cs6365c",
|
||||||
|
"cs8165c",
|
||||||
|
"cs8265c",
|
||||||
|
"cs8365c",
|
||||||
|
"cs8465c",
|
||||||
|
"ita-c",
|
||||||
|
"ita-e",
|
||||||
|
"ita-f",
|
||||||
|
"ita-ef",
|
||||||
|
"ita-g",
|
||||||
|
"ita-h",
|
||||||
|
"ita-i",
|
||||||
|
"ita-j",
|
||||||
|
"ita-k",
|
||||||
|
"ita-l",
|
||||||
|
"ita-m",
|
||||||
|
"ita-n",
|
||||||
|
"ita-o",
|
||||||
|
"usb-a",
|
||||||
|
"usb-b",
|
||||||
|
"usb-c",
|
||||||
|
"usb-mini-a",
|
||||||
|
"usb-mini-b",
|
||||||
|
"usb-micro-a",
|
||||||
|
"usb-micro-b",
|
||||||
|
"usb-micro-ab",
|
||||||
|
"usb-3-b",
|
||||||
|
"usb-3-micro-b",
|
||||||
|
"dc-terminal",
|
||||||
|
"saf-d-grid",
|
||||||
|
"neutrik-powercon-20",
|
||||||
|
"neutrik-powercon-32",
|
||||||
|
"neutrik-powercon-true1",
|
||||||
|
"neutrik-powercon-true1-top",
|
||||||
|
"ubiquiti-smartpower",
|
||||||
|
"hardwired",
|
||||||
|
"other"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"power-outlet": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"iec-60320-c5",
|
||||||
|
"iec-60320-c7",
|
||||||
|
"iec-60320-c13",
|
||||||
|
"iec-60320-c15",
|
||||||
|
"iec-60320-c19",
|
||||||
|
"iec-60320-c21",
|
||||||
|
"iec-60309-p-n-e-4h",
|
||||||
|
"iec-60309-p-n-e-6h",
|
||||||
|
"iec-60309-p-n-e-9h",
|
||||||
|
"iec-60309-2p-e-4h",
|
||||||
|
"iec-60309-2p-e-6h",
|
||||||
|
"iec-60309-2p-e-9h",
|
||||||
|
"iec-60309-3p-e-4h",
|
||||||
|
"iec-60309-3p-e-6h",
|
||||||
|
"iec-60309-3p-e-9h",
|
||||||
|
"iec-60309-3p-n-e-4h",
|
||||||
|
"iec-60309-3p-n-e-6h",
|
||||||
|
"iec-60309-3p-n-e-9h",
|
||||||
|
"iec-60906-1",
|
||||||
|
"nbr-14136-10a",
|
||||||
|
"nbr-14136-20a",
|
||||||
|
"nema-1-15r",
|
||||||
|
"nema-5-15r",
|
||||||
|
"nema-5-20r",
|
||||||
|
"nema-5-30r",
|
||||||
|
"nema-5-50r",
|
||||||
|
"nema-6-15r",
|
||||||
|
"nema-6-20r",
|
||||||
|
"nema-6-30r",
|
||||||
|
"nema-6-50r",
|
||||||
|
"nema-10-30r",
|
||||||
|
"nema-10-50r",
|
||||||
|
"nema-14-20r",
|
||||||
|
"nema-14-30r",
|
||||||
|
"nema-14-50r",
|
||||||
|
"nema-14-60r",
|
||||||
|
"nema-15-15r",
|
||||||
|
"nema-15-20r",
|
||||||
|
"nema-15-30r",
|
||||||
|
"nema-15-50r",
|
||||||
|
"nema-15-60r",
|
||||||
|
"nema-l1-15r",
|
||||||
|
"nema-l5-15r",
|
||||||
|
"nema-l5-20r",
|
||||||
|
"nema-l5-30r",
|
||||||
|
"nema-l5-50r",
|
||||||
|
"nema-l6-15r",
|
||||||
|
"nema-l6-20r",
|
||||||
|
"nema-l6-30r",
|
||||||
|
"nema-l6-50r",
|
||||||
|
"nema-l10-30r",
|
||||||
|
"nema-l14-20r",
|
||||||
|
"nema-l14-30r",
|
||||||
|
"nema-l14-50r",
|
||||||
|
"nema-l14-60r",
|
||||||
|
"nema-l15-20r",
|
||||||
|
"nema-l15-30r",
|
||||||
|
"nema-l15-50r",
|
||||||
|
"nema-l15-60r",
|
||||||
|
"nema-l21-20r",
|
||||||
|
"nema-l21-30r",
|
||||||
|
"nema-l22-30r",
|
||||||
|
"CS6360C",
|
||||||
|
"CS6364C",
|
||||||
|
"CS8164C",
|
||||||
|
"CS8264C",
|
||||||
|
"CS8364C",
|
||||||
|
"CS8464C",
|
||||||
|
"ita-e",
|
||||||
|
"ita-f",
|
||||||
|
"ita-g",
|
||||||
|
"ita-h",
|
||||||
|
"ita-i",
|
||||||
|
"ita-j",
|
||||||
|
"ita-k",
|
||||||
|
"ita-l",
|
||||||
|
"ita-m",
|
||||||
|
"ita-n",
|
||||||
|
"ita-o",
|
||||||
|
"ita-multistandard",
|
||||||
|
"usb-a",
|
||||||
|
"usb-micro-b",
|
||||||
|
"usb-c",
|
||||||
|
"dc-terminal",
|
||||||
|
"hdot-cx",
|
||||||
|
"saf-d-grid",
|
||||||
|
"neutrik-powercon-20a",
|
||||||
|
"neutrik-powercon-32a",
|
||||||
|
"neutrik-powercon-true1",
|
||||||
|
"neutrik-powercon-true1-top",
|
||||||
|
"ubiquiti-smartpower",
|
||||||
|
"hardwired",
|
||||||
|
"other"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"feed-leg": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"C"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"virtual",
|
||||||
|
"bridge",
|
||||||
|
"lag",
|
||||||
|
"100base-fx",
|
||||||
|
"100base-lfx",
|
||||||
|
"100base-tx",
|
||||||
|
"100base-t1",
|
||||||
|
"1000base-t",
|
||||||
|
"2.5gbase-t",
|
||||||
|
"5gbase-t",
|
||||||
|
"10gbase-t",
|
||||||
|
"10gbase-cx4",
|
||||||
|
"1000base-x-gbic",
|
||||||
|
"1000base-x-sfp",
|
||||||
|
"10gbase-x-sfpp",
|
||||||
|
"10gbase-x-xfp",
|
||||||
|
"10gbase-x-xenpak",
|
||||||
|
"10gbase-x-x2",
|
||||||
|
"25gbase-x-sfp28",
|
||||||
|
"50gbase-x-sfp56",
|
||||||
|
"40gbase-x-qsfpp",
|
||||||
|
"50gbase-x-sfp28",
|
||||||
|
"100gbase-x-cfp",
|
||||||
|
"100gbase-x-cfp2",
|
||||||
|
"200gbase-x-cfp2",
|
||||||
|
"100gbase-x-cfp4",
|
||||||
|
"100gbase-x-cxp",
|
||||||
|
"100gbase-x-cpak",
|
||||||
|
"100gbase-x-dsfp",
|
||||||
|
"100gbase-x-sfpdd",
|
||||||
|
"100gbase-x-qsfp28",
|
||||||
|
"100gbase-x-qsfpdd",
|
||||||
|
"200gbase-x-qsfp56",
|
||||||
|
"200gbase-x-qsfpdd",
|
||||||
|
"400gbase-x-qsfpdd",
|
||||||
|
"400gbase-x-osfp",
|
||||||
|
"400gbase-x-cdfp",
|
||||||
|
"400gbase-x-cfp8",
|
||||||
|
"800gbase-x-qsfpdd",
|
||||||
|
"800gbase-x-osfp",
|
||||||
|
"1000base-kx",
|
||||||
|
"10gbase-kr",
|
||||||
|
"10gbase-kx4",
|
||||||
|
"25gbase-kr",
|
||||||
|
"40gbase-kr4",
|
||||||
|
"50gbase-kr",
|
||||||
|
"100gbase-kp4",
|
||||||
|
"100gbase-kr2",
|
||||||
|
"100gbase-kr4",
|
||||||
|
"ieee802.11a",
|
||||||
|
"ieee802.11g",
|
||||||
|
"ieee802.11n",
|
||||||
|
"ieee802.11ac",
|
||||||
|
"ieee802.11ad",
|
||||||
|
"ieee802.11ax",
|
||||||
|
"ieee802.11ay",
|
||||||
|
"ieee802.15.1",
|
||||||
|
"other-wireless",
|
||||||
|
"gsm",
|
||||||
|
"cdma",
|
||||||
|
"lte",
|
||||||
|
"sonet-oc3",
|
||||||
|
"sonet-oc12",
|
||||||
|
"sonet-oc48",
|
||||||
|
"sonet-oc192",
|
||||||
|
"sonet-oc768",
|
||||||
|
"sonet-oc1920",
|
||||||
|
"sonet-oc3840",
|
||||||
|
"1gfc-sfp",
|
||||||
|
"2gfc-sfp",
|
||||||
|
"4gfc-sfp",
|
||||||
|
"8gfc-sfpp",
|
||||||
|
"16gfc-sfpp",
|
||||||
|
"32gfc-sfp28",
|
||||||
|
"64gfc-qsfpp",
|
||||||
|
"128gfc-qsfp28",
|
||||||
|
"infiniband-sdr",
|
||||||
|
"infiniband-ddr",
|
||||||
|
"infiniband-qdr",
|
||||||
|
"infiniband-fdr10",
|
||||||
|
"infiniband-fdr",
|
||||||
|
"infiniband-edr",
|
||||||
|
"infiniband-hdr",
|
||||||
|
"infiniband-ndr",
|
||||||
|
"infiniband-xdr",
|
||||||
|
"t1",
|
||||||
|
"e1",
|
||||||
|
"t3",
|
||||||
|
"e3",
|
||||||
|
"xdsl",
|
||||||
|
"docsis",
|
||||||
|
"gpon",
|
||||||
|
"xg-pon",
|
||||||
|
"xgs-pon",
|
||||||
|
"ng-pon2",
|
||||||
|
"epon",
|
||||||
|
"10g-epon",
|
||||||
|
"cisco-stackwise",
|
||||||
|
"cisco-stackwise-plus",
|
||||||
|
"cisco-flexstack",
|
||||||
|
"cisco-flexstack-plus",
|
||||||
|
"cisco-stackwise-80",
|
||||||
|
"cisco-stackwise-160",
|
||||||
|
"cisco-stackwise-320",
|
||||||
|
"cisco-stackwise-480",
|
||||||
|
"cisco-stackwise-1t",
|
||||||
|
"juniper-vcp",
|
||||||
|
"extreme-summitstack",
|
||||||
|
"extreme-summitstack-128",
|
||||||
|
"extreme-summitstack-256",
|
||||||
|
"extreme-summitstack-512",
|
||||||
|
"other"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"poe_mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"pd",
|
||||||
|
"pse"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"poe_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"type1-ieee802.3af",
|
||||||
|
"type2-ieee802.3at",
|
||||||
|
"type3-ieee802.3bt",
|
||||||
|
"type4-ieee802.3bt",
|
||||||
|
"passive-24v-2pair",
|
||||||
|
"passive-24v-4pair",
|
||||||
|
"passive-48v-2pair",
|
||||||
|
"passive-48v-4pair"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"front-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"8p8c",
|
||||||
|
"8p6c",
|
||||||
|
"8p4c",
|
||||||
|
"8p2c",
|
||||||
|
"6p6c",
|
||||||
|
"6p4c",
|
||||||
|
"6p2c",
|
||||||
|
"4p4c",
|
||||||
|
"4p2c",
|
||||||
|
"gg45",
|
||||||
|
"tera-4p",
|
||||||
|
"tera-2p",
|
||||||
|
"tera-1p",
|
||||||
|
"110-punch",
|
||||||
|
"bnc",
|
||||||
|
"f",
|
||||||
|
"n",
|
||||||
|
"mrj21",
|
||||||
|
"fc",
|
||||||
|
"lc",
|
||||||
|
"lc-pc",
|
||||||
|
"lc-upc",
|
||||||
|
"lc-apc",
|
||||||
|
"lsh",
|
||||||
|
"lsh-pc",
|
||||||
|
"lsh-upc",
|
||||||
|
"lsh-apc",
|
||||||
|
"lx5",
|
||||||
|
"lx5-pc",
|
||||||
|
"lx5-upc",
|
||||||
|
"lx5-apc",
|
||||||
|
"mpo",
|
||||||
|
"mtrj",
|
||||||
|
"sc",
|
||||||
|
"sc-pc",
|
||||||
|
"sc-upc",
|
||||||
|
"sc-apc",
|
||||||
|
"st",
|
||||||
|
"cs",
|
||||||
|
"sn",
|
||||||
|
"sma-905",
|
||||||
|
"sma-906",
|
||||||
|
"urm-p2",
|
||||||
|
"urm-p4",
|
||||||
|
"urm-p8",
|
||||||
|
"splice",
|
||||||
|
"other"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rear-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"8p8c",
|
||||||
|
"8p6c",
|
||||||
|
"8p4c",
|
||||||
|
"8p2c",
|
||||||
|
"6p6c",
|
||||||
|
"6p4c",
|
||||||
|
"6p2c",
|
||||||
|
"4p4c",
|
||||||
|
"4p2c",
|
||||||
|
"gg45",
|
||||||
|
"tera-4p",
|
||||||
|
"tera-2p",
|
||||||
|
"tera-1p",
|
||||||
|
"110-punch",
|
||||||
|
"bnc",
|
||||||
|
"f",
|
||||||
|
"n",
|
||||||
|
"mrj21",
|
||||||
|
"fc",
|
||||||
|
"lc",
|
||||||
|
"lc-pc",
|
||||||
|
"lc-upc",
|
||||||
|
"lc-apc",
|
||||||
|
"lsh",
|
||||||
|
"lsh-pc",
|
||||||
|
"lsh-upc",
|
||||||
|
"lsh-apc",
|
||||||
|
"lx5",
|
||||||
|
"lx5-pc",
|
||||||
|
"lx5-upc",
|
||||||
|
"lx5-apc",
|
||||||
|
"mpo",
|
||||||
|
"mtrj",
|
||||||
|
"sc",
|
||||||
|
"sc-pc",
|
||||||
|
"sc-upc",
|
||||||
|
"sc-apc",
|
||||||
|
"st",
|
||||||
|
"cs",
|
||||||
|
"sn",
|
||||||
|
"sma-905",
|
||||||
|
"sma-906",
|
||||||
|
"urm-p2",
|
||||||
|
"urm-p4",
|
||||||
|
"urm-p8",
|
||||||
|
"splice",
|
||||||
|
"other"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -43,10 +43,22 @@ Follow these instructions to perform a new installation of NetBox in a temporary
|
|||||||
|
|
||||||
Submit a pull request to merge the `feature` branch into the `develop` branch in preparation for its release. Once it has been merged, continue with the section for patch releases below.
|
Submit a pull request to merge the `feature` branch into the `develop` branch in preparation for its release. Once it has been merged, continue with the section for patch releases below.
|
||||||
|
|
||||||
|
### Rebuild Demo Data (After Release)
|
||||||
|
|
||||||
|
After the release of a new minor version, generate a new demo data snapshot compatible with the new release. See the [`netbox-demo-data`](https://github.com/netbox-community/netbox-demo-data) repository for instructions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Patch Releases
|
## Patch Releases
|
||||||
|
|
||||||
|
### Notify netbox-docker Project of Any Relevant Changes
|
||||||
|
|
||||||
|
Notify the [`netbox-docker`](https://github.com/netbox-community/netbox-docker) maintainers (in **#netbox-docker**) of any changes that may be relevant to their build process, including:
|
||||||
|
|
||||||
|
* Significant changes to `upgrade.sh`
|
||||||
|
* Increases in minimum versions for service dependencies (PostgreSQL, Redis, etc.)
|
||||||
|
* Any changes to the reference installation
|
||||||
|
|
||||||
### Update Requirements
|
### Update Requirements
|
||||||
|
|
||||||
Before each release, update each of NetBox's Python dependencies to its most recent stable version. These are defined in `requirements.txt`, which is updated from `base_requirements.txt` using `pip`. To do this:
|
Before each release, update each of NetBox's Python dependencies to its most recent stable version. These are defined in `requirements.txt`, which is updated from `base_requirements.txt` using `pip`. To do this:
|
||||||
@ -58,6 +70,16 @@ Before each release, update each of NetBox's Python dependencies to its most rec
|
|||||||
|
|
||||||
In cases where upgrading a dependency to its most recent release is breaking, it should be constrained to its current minor version in `base_requirements.txt` with an explanatory comment and revisited for the next major NetBox release (see the [Address Constrained Dependencies](#address-constrained-dependencies) section above).
|
In cases where upgrading a dependency to its most recent release is breaking, it should be constrained to its current minor version in `base_requirements.txt` with an explanatory comment and revisited for the next major NetBox release (see the [Address Constrained Dependencies](#address-constrained-dependencies) section above).
|
||||||
|
|
||||||
|
### Rebuild the Device Type Definition Schema
|
||||||
|
|
||||||
|
Run the following command to update the device type definition validation schema:
|
||||||
|
|
||||||
|
```nohighlight
|
||||||
|
./manage.py buildschema --write
|
||||||
|
```
|
||||||
|
|
||||||
|
This will automatically update the schema file at `contrib/generated_schema.json`.
|
||||||
|
|
||||||
### Update Version and Changelog
|
### Update Version and Changelog
|
||||||
|
|
||||||
* Update the `VERSION` constant in `settings.py` to the new release version.
|
* Update the `VERSION` constant in `settings.py` to the new release version.
|
||||||
|
@ -1,5 +1,32 @@
|
|||||||
# NetBox v3.5
|
# NetBox v3.5
|
||||||
|
|
||||||
|
## v3.5.8 (2023-08-15)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#10030](https://github.com/netbox-community/netbox/issues/10030) - Ship a validation schema for the device type library with each release
|
||||||
|
* [#11675](https://github.com/netbox-community/netbox/issues/11675) - Add support for specifying import/export route targets during VRF bulk import
|
||||||
|
* [#11922](https://github.com/netbox-community/netbox/issues/11922) - Automatically populate any VDC assignments from the parent when adding a child interface via the UI
|
||||||
|
* [#12889](https://github.com/netbox-community/netbox/issues/12889) - Add 400GE CFP2 interface type
|
||||||
|
* [#13033](https://github.com/netbox-community/netbox/issues/13033) - Add human-friendly speed column to interfaces table
|
||||||
|
* [#13151](https://github.com/netbox-community/netbox/issues/13151) - Add "assigned" filter for IP addresses
|
||||||
|
* [#13368](https://github.com/netbox-community/netbox/issues/13368) - List installed plugins on the server error report page
|
||||||
|
* [#13442](https://github.com/netbox-community/netbox/issues/13442) - Add 200 and 400 Gbps speeds to dropdown choices on interface form
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#11578](https://github.com/netbox-community/netbox/issues/11578) - Fix schema definition for available IP & VLAN REST API endpoints
|
||||||
|
* [#12639](https://github.com/netbox-community/netbox/issues/12639) - Raise validation error for invalid alphanumeric ranges when creating objects
|
||||||
|
* [#12665](https://github.com/netbox-community/netbox/issues/12665) - Avoid escaping semicolons when rendering custom links
|
||||||
|
* [#12750](https://github.com/netbox-community/netbox/issues/12750) - Automatically delete an AutoSyncRecord when its object is deleted
|
||||||
|
* [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view
|
||||||
|
* [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports
|
||||||
|
* [#13414](https://github.com/netbox-community/netbox/issues/13414) - Fix support for "hide-if-unset" custom fields on bulk import forms
|
||||||
|
* [#13446](https://github.com/netbox-community/netbox/issues/13446) - Don't disable bulk edit/delete buttons after deselecting "select all" checkbox
|
||||||
|
* [#13451](https://github.com/netbox-community/netbox/issues/13451) - Disable table ordering for custom link columns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v3.5.7 (2023-07-28)
|
## v3.5.7 (2023-07-28)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
@ -163,7 +163,7 @@ class ProviderNetworkView(generic.ObjectView):
|
|||||||
related_models = (
|
related_models = (
|
||||||
(
|
(
|
||||||
Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance),
|
Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance),
|
||||||
'providernetwork_id',
|
'provider_network_id',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -834,6 +834,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
TYPE_200GE_CFP2 = '200gbase-x-cfp2'
|
TYPE_200GE_CFP2 = '200gbase-x-cfp2'
|
||||||
TYPE_200GE_QSFP56 = '200gbase-x-qsfp56'
|
TYPE_200GE_QSFP56 = '200gbase-x-qsfp56'
|
||||||
TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd'
|
TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd'
|
||||||
|
TYPE_400GE_CFP2 = '400gbase-x-cfp2'
|
||||||
TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd'
|
TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd'
|
||||||
TYPE_400GE_OSFP = '400gbase-x-osfp'
|
TYPE_400GE_OSFP = '400gbase-x-osfp'
|
||||||
TYPE_400GE_CDFP = '400gbase-x-cdfp'
|
TYPE_400GE_CDFP = '400gbase-x-cdfp'
|
||||||
@ -976,6 +977,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
(TYPE_100GE_CFP, 'CFP (100GE)'),
|
(TYPE_100GE_CFP, 'CFP (100GE)'),
|
||||||
(TYPE_100GE_CFP2, 'CFP2 (100GE)'),
|
(TYPE_100GE_CFP2, 'CFP2 (100GE)'),
|
||||||
(TYPE_200GE_CFP2, 'CFP2 (200GE)'),
|
(TYPE_200GE_CFP2, 'CFP2 (200GE)'),
|
||||||
|
(TYPE_400GE_CFP2, 'CFP2 (400GE)'),
|
||||||
(TYPE_100GE_CFP4, 'CFP4 (100GE)'),
|
(TYPE_100GE_CFP4, 'CFP4 (100GE)'),
|
||||||
(TYPE_100GE_CXP, 'CXP (100GE)'),
|
(TYPE_100GE_CXP, 'CXP (100GE)'),
|
||||||
(TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'),
|
(TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'),
|
||||||
@ -1139,6 +1141,8 @@ class InterfaceSpeedChoices(ChoiceSet):
|
|||||||
(25000000, '25 Gbps'),
|
(25000000, '25 Gbps'),
|
||||||
(40000000, '40 Gbps'),
|
(40000000, '40 Gbps'),
|
||||||
(100000000, '100 Gbps'),
|
(100000000, '100 Gbps'),
|
||||||
|
(200000000, '200 Gbps'),
|
||||||
|
(400000000, '400 Gbps'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1042,6 +1042,9 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
|||||||
queryset=VirtualDeviceContext.objects.all(),
|
queryset=VirtualDeviceContext.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Virtual Device Contexts',
|
label='Virtual Device Contexts',
|
||||||
|
initial_params={
|
||||||
|
'interfaces': '$parent',
|
||||||
|
},
|
||||||
query_params={
|
query_params={
|
||||||
'device_id': '$device',
|
'device_id': '$device',
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,10 @@ class ComponentCreateForm(forms.Form):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate that all replication fields generate an equal number of values
|
# Validate that all replication fields generate an equal number of values
|
||||||
pattern_count = len(self.cleaned_data[self.replication_fields[0]])
|
if not (patterns := self.cleaned_data.get(self.replication_fields[0])):
|
||||||
|
return
|
||||||
|
|
||||||
|
pattern_count = len(patterns)
|
||||||
for field_name in self.replication_fields:
|
for field_name in self.replication_fields:
|
||||||
value_count = len(self.cleaned_data[field_name])
|
value_count = len(self.cleaned_data[field_name])
|
||||||
if self.cleaned_data[field_name] and value_count != pattern_count:
|
if self.cleaned_data[field_name] and value_count != pattern_count:
|
||||||
|
@ -53,23 +53,23 @@ class LinkPeerType(graphene.Union):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, info):
|
def resolve_type(cls, instance, info):
|
||||||
if type(instance) == CircuitTermination:
|
if type(instance) is CircuitTermination:
|
||||||
return CircuitTerminationType
|
return CircuitTerminationType
|
||||||
if type(instance) == ConsolePortType:
|
if type(instance) is ConsolePortType:
|
||||||
return ConsolePortType
|
return ConsolePortType
|
||||||
if type(instance) == ConsoleServerPort:
|
if type(instance) is ConsoleServerPort:
|
||||||
return ConsoleServerPortType
|
return ConsoleServerPortType
|
||||||
if type(instance) == FrontPort:
|
if type(instance) is FrontPort:
|
||||||
return FrontPortType
|
return FrontPortType
|
||||||
if type(instance) == Interface:
|
if type(instance) is Interface:
|
||||||
return InterfaceType
|
return InterfaceType
|
||||||
if type(instance) == PowerFeed:
|
if type(instance) is PowerFeed:
|
||||||
return PowerFeedType
|
return PowerFeedType
|
||||||
if type(instance) == PowerOutlet:
|
if type(instance) is PowerOutlet:
|
||||||
return PowerOutletType
|
return PowerOutletType
|
||||||
if type(instance) == PowerPort:
|
if type(instance) is PowerPort:
|
||||||
return PowerPortType
|
return PowerPortType
|
||||||
if type(instance) == RearPort:
|
if type(instance) is RearPort:
|
||||||
return RearPortType
|
return RearPortType
|
||||||
|
|
||||||
|
|
||||||
@ -89,23 +89,23 @@ class CableTerminationTerminationType(graphene.Union):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, info):
|
def resolve_type(cls, instance, info):
|
||||||
if type(instance) == CircuitTermination:
|
if type(instance) is CircuitTermination:
|
||||||
return CircuitTerminationType
|
return CircuitTerminationType
|
||||||
if type(instance) == ConsolePortType:
|
if type(instance) is ConsolePortType:
|
||||||
return ConsolePortType
|
return ConsolePortType
|
||||||
if type(instance) == ConsoleServerPort:
|
if type(instance) is ConsoleServerPort:
|
||||||
return ConsoleServerPortType
|
return ConsoleServerPortType
|
||||||
if type(instance) == FrontPort:
|
if type(instance) is FrontPort:
|
||||||
return FrontPortType
|
return FrontPortType
|
||||||
if type(instance) == Interface:
|
if type(instance) is Interface:
|
||||||
return InterfaceType
|
return InterfaceType
|
||||||
if type(instance) == PowerFeed:
|
if type(instance) is PowerFeed:
|
||||||
return PowerFeedType
|
return PowerFeedType
|
||||||
if type(instance) == PowerOutlet:
|
if type(instance) is PowerOutlet:
|
||||||
return PowerOutletType
|
return PowerOutletType
|
||||||
if type(instance) == PowerPort:
|
if type(instance) is PowerPort:
|
||||||
return PowerPortType
|
return PowerPortType
|
||||||
if type(instance) == RearPort:
|
if type(instance) is RearPort:
|
||||||
return RearPortType
|
return RearPortType
|
||||||
|
|
||||||
|
|
||||||
@ -123,19 +123,19 @@ class InventoryItemTemplateComponentType(graphene.Union):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, info):
|
def resolve_type(cls, instance, info):
|
||||||
if type(instance) == ConsolePortTemplate:
|
if type(instance) is ConsolePortTemplate:
|
||||||
return ConsolePortTemplateType
|
return ConsolePortTemplateType
|
||||||
if type(instance) == ConsoleServerPortTemplate:
|
if type(instance) is ConsoleServerPortTemplate:
|
||||||
return ConsoleServerPortTemplateType
|
return ConsoleServerPortTemplateType
|
||||||
if type(instance) == FrontPortTemplate:
|
if type(instance) is FrontPortTemplate:
|
||||||
return FrontPortTemplateType
|
return FrontPortTemplateType
|
||||||
if type(instance) == InterfaceTemplate:
|
if type(instance) is InterfaceTemplate:
|
||||||
return InterfaceTemplateType
|
return InterfaceTemplateType
|
||||||
if type(instance) == PowerOutletTemplate:
|
if type(instance) is PowerOutletTemplate:
|
||||||
return PowerOutletTemplateType
|
return PowerOutletTemplateType
|
||||||
if type(instance) == PowerPortTemplate:
|
if type(instance) is PowerPortTemplate:
|
||||||
return PowerPortTemplateType
|
return PowerPortTemplateType
|
||||||
if type(instance) == RearPortTemplate:
|
if type(instance) is RearPortTemplate:
|
||||||
return RearPortTemplateType
|
return RearPortTemplateType
|
||||||
|
|
||||||
|
|
||||||
@ -153,17 +153,17 @@ class InventoryItemComponentType(graphene.Union):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, info):
|
def resolve_type(cls, instance, info):
|
||||||
if type(instance) == ConsolePort:
|
if type(instance) is ConsolePort:
|
||||||
return ConsolePortType
|
return ConsolePortType
|
||||||
if type(instance) == ConsoleServerPort:
|
if type(instance) is ConsoleServerPort:
|
||||||
return ConsoleServerPortType
|
return ConsoleServerPortType
|
||||||
if type(instance) == FrontPort:
|
if type(instance) is FrontPort:
|
||||||
return FrontPortType
|
return FrontPortType
|
||||||
if type(instance) == Interface:
|
if type(instance) is Interface:
|
||||||
return InterfaceType
|
return InterfaceType
|
||||||
if type(instance) == PowerOutlet:
|
if type(instance) is PowerOutlet:
|
||||||
return PowerOutletType
|
return PowerOutletType
|
||||||
if type(instance) == PowerPort:
|
if type(instance) is PowerPort:
|
||||||
return PowerPortType
|
return PowerPortType
|
||||||
if type(instance) == RearPort:
|
if type(instance) is RearPort:
|
||||||
return RearPortType
|
return RearPortType
|
||||||
|
62
netbox/dcim/management/commands/buildschema.py
Normal file
62
netbox/dcim/management/commands/buildschema.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from jinja2 import FileSystemLoader, Environment
|
||||||
|
|
||||||
|
from dcim.choices import *
|
||||||
|
|
||||||
|
TEMPLATE_FILENAME = 'devicetype_schema.jinja2'
|
||||||
|
OUTPUT_FILENAME = 'contrib/generated_schema.json'
|
||||||
|
|
||||||
|
CHOICES_MAP = {
|
||||||
|
'airflow_choices': DeviceAirflowChoices,
|
||||||
|
'weight_unit_choices': WeightUnitChoices,
|
||||||
|
'subdevice_role_choices': SubdeviceRoleChoices,
|
||||||
|
'console_port_type_choices': ConsolePortTypeChoices,
|
||||||
|
'console_server_port_type_choices': ConsolePortTypeChoices,
|
||||||
|
'power_port_type_choices': PowerPortTypeChoices,
|
||||||
|
'power_outlet_type_choices': PowerOutletTypeChoices,
|
||||||
|
'power_outlet_feedleg_choices': PowerOutletFeedLegChoices,
|
||||||
|
'interface_type_choices': InterfaceTypeChoices,
|
||||||
|
'interface_poe_mode_choices': InterfacePoEModeChoices,
|
||||||
|
'interface_poe_type_choices': InterfacePoETypeChoices,
|
||||||
|
'front_port_type_choices': PortTypeChoices,
|
||||||
|
'rear_port_type_choices': PortTypeChoices,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Generate JSON schema for validating NetBox device type definitions"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--write',
|
||||||
|
action='store_true',
|
||||||
|
help="Write the generated schema to file"
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
# Initialize template
|
||||||
|
template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/schema/')
|
||||||
|
template_env = Environment(loader=template_loader)
|
||||||
|
template = template_env.get_template(TEMPLATE_FILENAME)
|
||||||
|
|
||||||
|
# Render template
|
||||||
|
context = {
|
||||||
|
key: json.dumps(choices.values())
|
||||||
|
for key, choices in CHOICES_MAP.items()
|
||||||
|
}
|
||||||
|
rendered = template.render(**context)
|
||||||
|
|
||||||
|
if kwargs['write']:
|
||||||
|
# $root/contrib/generated_schema.json
|
||||||
|
filename = os.path.join(os.path.split(settings.BASE_DIR)[0], OUTPUT_FILENAME)
|
||||||
|
with open(filename, mode='w', encoding='UTF-8') as f:
|
||||||
|
f.write(json.dumps(json.loads(rendered), indent=4))
|
||||||
|
f.write('\n')
|
||||||
|
f.close()
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Schema written to {filename}."))
|
||||||
|
else:
|
||||||
|
self.stdout.write(rendered)
|
@ -545,6 +545,11 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
mgmt_only = columns.BooleanColumn()
|
mgmt_only = columns.BooleanColumn()
|
||||||
|
speed_formatted = columns.TemplateColumn(
|
||||||
|
template_code='{% load helpers %}{{ value|humanize_speed }}',
|
||||||
|
accessor=Accessor('speed'),
|
||||||
|
verbose_name='Speed'
|
||||||
|
)
|
||||||
wireless_link = tables.Column(
|
wireless_link = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
@ -568,7 +573,7 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
|
|||||||
model = models.Interface
|
model = models.Interface
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu',
|
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu',
|
||||||
'speed', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', 'rf_role', 'rf_channel',
|
'speed', 'speed_formatted', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', 'rf_role', 'rf_channel',
|
||||||
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'mark_connected', 'cable',
|
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'mark_connected', 'cable',
|
||||||
'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn',
|
'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn',
|
||||||
'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated',
|
'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import traceback
|
import traceback
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -45,6 +46,15 @@ CABLE_TERMINATION_TYPES = {
|
|||||||
|
|
||||||
|
|
||||||
class DeviceComponentsView(generic.ObjectChildrenView):
|
class DeviceComponentsView(generic.ObjectChildrenView):
|
||||||
|
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename', 'bulk_disconnect')
|
||||||
|
action_perms = defaultdict(set, **{
|
||||||
|
'add': {'add'},
|
||||||
|
'import': {'add'},
|
||||||
|
'bulk_edit': {'change'},
|
||||||
|
'bulk_delete': {'delete'},
|
||||||
|
'bulk_rename': {'change'},
|
||||||
|
'bulk_disconnect': {'change'},
|
||||||
|
})
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@ -1997,6 +2007,7 @@ class DeviceModuleBaysView(DeviceComponentsView):
|
|||||||
table = tables.DeviceModuleBayTable
|
table = tables.DeviceModuleBayTable
|
||||||
filterset = filtersets.ModuleBayFilterSet
|
filterset = filtersets.ModuleBayFilterSet
|
||||||
template_name = 'dcim/device/modulebays.html'
|
template_name = 'dcim/device/modulebays.html'
|
||||||
|
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename')
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Module Bays'),
|
label=_('Module Bays'),
|
||||||
badge=lambda obj: obj.modulebays.count(),
|
badge=lambda obj: obj.modulebays.count(),
|
||||||
@ -2012,6 +2023,7 @@ class DeviceDeviceBaysView(DeviceComponentsView):
|
|||||||
table = tables.DeviceDeviceBayTable
|
table = tables.DeviceDeviceBayTable
|
||||||
filterset = filtersets.DeviceBayFilterSet
|
filterset = filtersets.DeviceBayFilterSet
|
||||||
template_name = 'dcim/device/devicebays.html'
|
template_name = 'dcim/device/devicebays.html'
|
||||||
|
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename')
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Device Bays'),
|
label=_('Device Bays'),
|
||||||
badge=lambda obj: obj.devicebays.count(),
|
badge=lambda obj: obj.devicebays.count(),
|
||||||
@ -2023,6 +2035,7 @@ class DeviceDeviceBaysView(DeviceComponentsView):
|
|||||||
|
|
||||||
@register_model_view(Device, 'inventory')
|
@register_model_view(Device, 'inventory')
|
||||||
class DeviceInventoryView(DeviceComponentsView):
|
class DeviceInventoryView(DeviceComponentsView):
|
||||||
|
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename')
|
||||||
child_model = InventoryItem
|
child_model = InventoryItem
|
||||||
table = tables.DeviceInventoryItemTable
|
table = tables.DeviceInventoryItemTable
|
||||||
filterset = filtersets.InventoryItemFilterSet
|
filterset = filtersets.InventoryItemFilterSet
|
||||||
|
@ -285,7 +285,7 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
text = clean_html(text, allowed_schemes)
|
text = clean_html(text, allowed_schemes)
|
||||||
|
|
||||||
# Sanitize link
|
# Sanitize link
|
||||||
link = urllib.parse.quote(link, safe='/:?&=%+[]@#,')
|
link = urllib.parse.quote(link, safe='/:?&=%+[]@#,;')
|
||||||
|
|
||||||
# Verify link scheme is allowed
|
# Verify link scheme is allowed
|
||||||
result = urllib.parse.urlparse(link)
|
result = urllib.parse.urlparse(link)
|
||||||
|
@ -2,7 +2,6 @@ import collections
|
|||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
from packaging import version
|
from packaging import version
|
||||||
@ -146,23 +145,3 @@ class PluginConfig(AppConfig):
|
|||||||
for setting, value in cls.default_settings.items():
|
for setting, value in cls.default_settings.items():
|
||||||
if setting not in user_config:
|
if setting not in user_config:
|
||||||
user_config[setting] = value
|
user_config[setting] = value
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Utilities
|
|
||||||
#
|
|
||||||
|
|
||||||
def get_plugin_config(plugin_name, parameter, default=None):
|
|
||||||
"""
|
|
||||||
Return the value of the specified plugin configuration parameter.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plugin_name: The name of the plugin
|
|
||||||
parameter: The name of the configuration parameter
|
|
||||||
default: The value to return if the parameter is not defined (default: None)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
plugin_config = settings.PLUGINS_CONFIG[plugin_name]
|
|
||||||
return plugin_config.get(parameter, default)
|
|
||||||
except KeyError:
|
|
||||||
raise ImproperlyConfigured(f"Plugin {plugin_name} is not registered.")
|
|
||||||
|
37
netbox/extras/plugins/utils.py
Normal file
37
netbox/extras/plugins/utils.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'get_installed_plugins',
|
||||||
|
'get_plugin_config',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_installed_plugins():
|
||||||
|
"""
|
||||||
|
Return a dictionary mapping the names of installed plugins to their versions.
|
||||||
|
"""
|
||||||
|
plugins = {}
|
||||||
|
for plugin_name in settings.PLUGINS:
|
||||||
|
plugin_name = plugin_name.rsplit('.', 1)[-1]
|
||||||
|
plugin_config = apps.get_app_config(plugin_name)
|
||||||
|
plugins[plugin_name] = getattr(plugin_config, 'version', None)
|
||||||
|
|
||||||
|
return dict(sorted(plugins.items()))
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin_config(plugin_name, parameter, default=None):
|
||||||
|
"""
|
||||||
|
Return the value of the specified plugin configuration parameter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_name: The name of the plugin
|
||||||
|
parameter: The name of the configuration parameter
|
||||||
|
default: The value to return if the parameter is not defined (default: None)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
plugin_config = settings.PLUGINS_CONFIG[plugin_name]
|
||||||
|
return plugin_config.get(parameter, default)
|
||||||
|
except KeyError:
|
||||||
|
raise ImproperlyConfigured(f"Plugin {plugin_name} is not registered.")
|
@ -214,20 +214,18 @@ class Report(object):
|
|||||||
self.active_test = method_name
|
self.active_test = method_name
|
||||||
test_method = getattr(self, method_name)
|
test_method = getattr(self, method_name)
|
||||||
test_method()
|
test_method()
|
||||||
|
job.data = self._results
|
||||||
if self.failed:
|
if self.failed:
|
||||||
self.logger.warning("Report failed")
|
self.logger.warning("Report failed")
|
||||||
job.status = JobStatusChoices.STATUS_FAILED
|
job.terminate(status=JobStatusChoices.STATUS_FAILED)
|
||||||
else:
|
else:
|
||||||
self.logger.info("Report completed successfully")
|
self.logger.info("Report completed successfully")
|
||||||
job.status = JobStatusChoices.STATUS_COMPLETED
|
job.terminate()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
stacktrace = traceback.format_exc()
|
stacktrace = traceback.format_exc()
|
||||||
self.log_failure(None, f"An exception occurred: {type(e).__name__}: {e} <pre>{stacktrace}</pre>")
|
self.log_failure(None, f"An exception occurred: {type(e).__name__}: {e} <pre>{stacktrace}</pre>")
|
||||||
logger.error(f"Exception raised during report execution: {e}")
|
logger.error(f"Exception raised during report execution: {e}")
|
||||||
job.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
job.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
||||||
finally:
|
|
||||||
job.data = self._results
|
|
||||||
job.terminate()
|
|
||||||
|
|
||||||
# Perform any post-run tasks
|
# Perform any post-run tasks
|
||||||
self.post_run()
|
self.post_run()
|
||||||
|
@ -5,8 +5,9 @@ from django.core.exceptions import ImproperlyConfigured
|
|||||||
from django.test import Client, TestCase, override_settings
|
from django.test import Client, TestCase, override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from extras.plugins import PluginMenu, get_plugin_config
|
from extras.plugins import PluginMenu
|
||||||
from extras.tests.dummy_plugin import config as dummy_config
|
from extras.tests.dummy_plugin import config as dummy_config
|
||||||
|
from extras.plugins.utils import get_plugin_config
|
||||||
from netbox.graphql.schema import Query
|
from netbox.graphql.schema import Query
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ class WebhookTest(APITestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
site_ct = ContentType.objects.get_for_model(Site)
|
site_ct = ContentType.objects.get_for_model(Site)
|
||||||
DUMMY_URL = "http://localhost/"
|
DUMMY_URL = 'http://localhost:9000/'
|
||||||
DUMMY_SECRET = "LOOKATMEIMASECRETSTRING"
|
DUMMY_SECRET = 'LOOKATMEIMASECRETSTRING'
|
||||||
|
|
||||||
webhooks = Webhook.objects.bulk_create((
|
webhooks = Webhook.objects.bulk_create((
|
||||||
Webhook(name='Webhook 1', type_create=True, payload_url=DUMMY_URL, secret=DUMMY_SECRET, additional_headers='X-Foo: Bar'),
|
Webhook(name='Webhook 1', type_create=True, payload_url=DUMMY_URL, secret=DUMMY_SECRET, additional_headers='X-Foo: Bar'),
|
||||||
@ -259,7 +259,7 @@ class WebhookTest(APITestCase):
|
|||||||
name='Conditional Webhook',
|
name='Conditional Webhook',
|
||||||
type_create=True,
|
type_create=True,
|
||||||
type_update=True,
|
type_update=True,
|
||||||
payload_url='http://localhost/',
|
payload_url='http://localhost:9000/',
|
||||||
conditions={
|
conditions={
|
||||||
'and': [
|
'and': [
|
||||||
{
|
{
|
||||||
|
@ -224,7 +224,10 @@ class AvailableASNsView(ObjectValidationMixin, APIView):
|
|||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@extend_schema(methods=["post"], responses={201: serializers.ASNSerializer(many=True)})
|
@extend_schema(methods=["post"],
|
||||||
|
responses={201: serializers.ASNSerializer(many=True)},
|
||||||
|
request=serializers.ASNSerializer(many=True),
|
||||||
|
)
|
||||||
@advisory_lock(ADVISORY_LOCK_KEYS['available-asns'])
|
@advisory_lock(ADVISORY_LOCK_KEYS['available-asns'])
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
self.queryset = self.queryset.restrict(request.user, 'add')
|
self.queryset = self.queryset.restrict(request.user, 'add')
|
||||||
@ -293,7 +296,10 @@ class AvailablePrefixesView(ObjectValidationMixin, APIView):
|
|||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@extend_schema(methods=["post"], responses={201: serializers.PrefixSerializer(many=True)})
|
@extend_schema(methods=["post"],
|
||||||
|
responses={201: serializers.PrefixSerializer(many=True)},
|
||||||
|
request=serializers.PrefixSerializer(many=True),
|
||||||
|
)
|
||||||
@advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes'])
|
@advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes'])
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
self.queryset = self.queryset.restrict(request.user, 'add')
|
self.queryset = self.queryset.restrict(request.user, 'add')
|
||||||
@ -388,7 +394,10 @@ class AvailableIPAddressesView(ObjectValidationMixin, APIView):
|
|||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@extend_schema(methods=["post"], responses={201: serializers.IPAddressSerializer(many=True)})
|
@extend_schema(methods=["post"],
|
||||||
|
responses={201: serializers.IPAddressSerializer(many=True)},
|
||||||
|
request=serializers.IPAddressSerializer(many=True),
|
||||||
|
)
|
||||||
@advisory_lock(ADVISORY_LOCK_KEYS['available-ips'])
|
@advisory_lock(ADVISORY_LOCK_KEYS['available-ips'])
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
self.queryset = self.queryset.restrict(request.user, 'add')
|
self.queryset = self.queryset.restrict(request.user, 'add')
|
||||||
@ -468,7 +477,10 @@ class AvailableVLANsView(ObjectValidationMixin, APIView):
|
|||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@extend_schema(methods=["post"], responses={201: serializers.VLANSerializer(many=True)})
|
@extend_schema(methods=["post"],
|
||||||
|
responses={201: serializers.VLANSerializer(many=True)},
|
||||||
|
request=serializers.VLANSerializer(many=True),
|
||||||
|
)
|
||||||
@advisory_lock(ADVISORY_LOCK_KEYS['available-vlans'])
|
@advisory_lock(ADVISORY_LOCK_KEYS['available-vlans'])
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
self.queryset = self.queryset.restrict(request.user, 'add')
|
self.queryset = self.queryset.restrict(request.user, 'add')
|
||||||
|
@ -591,6 +591,10 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
method='_assigned_to_interface',
|
method='_assigned_to_interface',
|
||||||
label=_('Is assigned to an interface'),
|
label=_('Is assigned to an interface'),
|
||||||
)
|
)
|
||||||
|
assigned = django_filters.BooleanFilter(
|
||||||
|
method='_assigned',
|
||||||
|
label=_('Is assigned'),
|
||||||
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=IPAddressStatusChoices,
|
choices=IPAddressStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -706,6 +710,18 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
assigned_object_id__isnull=False
|
assigned_object_id__isnull=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _assigned(self, queryset, name, value):
|
||||||
|
if value:
|
||||||
|
return queryset.exclude(
|
||||||
|
assigned_object_type__isnull=True,
|
||||||
|
assigned_object_id__isnull=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return queryset.filter(
|
||||||
|
assigned_object_type__isnull=True,
|
||||||
|
assigned_object_id__isnull=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FHRPGroupFilterSet(NetBoxModelFilterSet):
|
class FHRPGroupFilterSet(NetBoxModelFilterSet):
|
||||||
protocol = django_filters.MultipleChoiceFilter(
|
protocol = django_filters.MultipleChoiceFilter(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.models import Device, Interface, Site
|
from dcim.models import Device, Interface, Site
|
||||||
@ -10,7 +9,9 @@ from ipam.constants import *
|
|||||||
from ipam.models import *
|
from ipam.models import *
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms.fields import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
|
from utilities.forms.fields import (
|
||||||
|
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField
|
||||||
|
)
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -41,10 +42,25 @@ class VRFImportForm(NetBoxModelImportForm):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned tenant')
|
help_text=_('Assigned tenant')
|
||||||
)
|
)
|
||||||
|
import_targets = CSVModelMultipleChoiceField(
|
||||||
|
queryset=RouteTarget.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Import route targets')
|
||||||
|
)
|
||||||
|
export_targets = CSVModelMultipleChoiceField(
|
||||||
|
queryset=RouteTarget.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Export route targets')
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VRF
|
model = VRF
|
||||||
fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments', 'tags')
|
fields = (
|
||||||
|
'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'comments',
|
||||||
|
'tags',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RouteTargetImportForm(NetBoxModelImportForm):
|
class RouteTargetImportForm(NetBoxModelImportForm):
|
||||||
|
@ -253,7 +253,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = IPRange
|
model = IPRange
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
('Attriubtes', ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
|
('Attributes', ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
|
||||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
)
|
)
|
||||||
family = forms.ChoiceField(
|
family = forms.ChoiceField(
|
||||||
|
@ -24,11 +24,11 @@ class IPAddressAssignmentType(graphene.Union):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, info):
|
def resolve_type(cls, instance, info):
|
||||||
if type(instance) == Interface:
|
if type(instance) is Interface:
|
||||||
return InterfaceType
|
return InterfaceType
|
||||||
if type(instance) == FHRPGroup:
|
if type(instance) is FHRPGroup:
|
||||||
return FHRPGroupType
|
return FHRPGroupType
|
||||||
if type(instance) == VMInterface:
|
if type(instance) is VMInterface:
|
||||||
return VMInterfaceType
|
return VMInterfaceType
|
||||||
|
|
||||||
|
|
||||||
@ -42,11 +42,11 @@ class L2VPNAssignmentType(graphene.Union):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, info):
|
def resolve_type(cls, instance, info):
|
||||||
if type(instance) == Interface:
|
if type(instance) is Interface:
|
||||||
return InterfaceType
|
return InterfaceType
|
||||||
if type(instance) == VLAN:
|
if type(instance) is VLAN:
|
||||||
return VLANType
|
return VLANType
|
||||||
if type(instance) == VMInterface:
|
if type(instance) is VMInterface:
|
||||||
return VMInterfaceType
|
return VMInterfaceType
|
||||||
|
|
||||||
|
|
||||||
@ -59,9 +59,9 @@ class FHRPGroupInterfaceType(graphene.Union):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, info):
|
def resolve_type(cls, instance, info):
|
||||||
if type(instance) == Interface:
|
if type(instance) is Interface:
|
||||||
return InterfaceType
|
return InterfaceType
|
||||||
if type(instance) == VMInterface:
|
if type(instance) is VMInterface:
|
||||||
return VMInterfaceType
|
return VMInterfaceType
|
||||||
|
|
||||||
|
|
||||||
@ -79,17 +79,17 @@ class VLANGroupScopeType(graphene.Union):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, info):
|
def resolve_type(cls, instance, info):
|
||||||
if type(instance) == Cluster:
|
if type(instance) is Cluster:
|
||||||
return ClusterType
|
return ClusterType
|
||||||
if type(instance) == ClusterGroup:
|
if type(instance) is ClusterGroup:
|
||||||
return ClusterGroupType
|
return ClusterGroupType
|
||||||
if type(instance) == Location:
|
if type(instance) is Location:
|
||||||
return LocationType
|
return LocationType
|
||||||
if type(instance) == Rack:
|
if type(instance) is Rack:
|
||||||
return RackType
|
return RackType
|
||||||
if type(instance) == Region:
|
if type(instance) is Region:
|
||||||
return RegionType
|
return RegionType
|
||||||
if type(instance) == Site:
|
if type(instance) is Site:
|
||||||
return SiteType
|
return SiteType
|
||||||
if type(instance) == SiteGroup:
|
if type(instance) is SiteGroup:
|
||||||
return SiteGroupType
|
return SiteGroupType
|
||||||
|
@ -992,6 +992,12 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'fhrpgroup_id': [fhrp_groups[0].pk, fhrp_groups[1].pk]}
|
params = {'fhrpgroup_id': [fhrp_groups[0].pk, fhrp_groups[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_assigned(self):
|
||||||
|
params = {'assigned': 'true'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
params = {'assigned': 'false'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
def test_assigned_to_interface(self):
|
def test_assigned_to_interface(self):
|
||||||
params = {'assigned_to_interface': 'true'}
|
params = {'assigned_to_interface': 'true'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
@ -121,7 +121,7 @@ def add_available_vlans(vlans, vlan_group=None):
|
|||||||
})
|
})
|
||||||
|
|
||||||
vlans = list(vlans) + new_vlans
|
vlans = list(vlans) + new_vlans
|
||||||
vlans.sort(key=lambda v: v.vid if type(v) == VLAN else v['vid'])
|
vlans.sort(key=lambda v: v.vid if type(v) is VLAN else v['vid'])
|
||||||
|
|
||||||
return vlans
|
return vlans
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ class ASNRangeASNsView(generic.ObjectChildrenView):
|
|||||||
child_model = ASN
|
child_model = ASN
|
||||||
table = tables.ASNTable
|
table = tables.ASNTable
|
||||||
filterset = filtersets.ASNFilterSet
|
filterset = filtersets.ASNFilterSet
|
||||||
template_name = 'ipam/asnrange/asns.html'
|
template_name = 'generic/object_children.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('ASNs'),
|
label=_('ASNs'),
|
||||||
badge=lambda x: x.get_child_asns().count(),
|
badge=lambda x: x.get_child_asns().count(),
|
||||||
@ -816,7 +816,6 @@ class IPAddressAssignView(generic.ObjectView):
|
|||||||
table = None
|
table = None
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
||||||
addresses = self.queryset.prefetch_related('vrf', 'tenant')
|
addresses = self.queryset.prefetch_related('vrf', 'tenant')
|
||||||
# Limit to 100 results
|
# Limit to 100 results
|
||||||
addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100]
|
addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100]
|
||||||
@ -866,7 +865,7 @@ class IPAddressRelatedIPsView(generic.ObjectChildrenView):
|
|||||||
child_model = IPAddress
|
child_model = IPAddress
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
filterset = filtersets.IPAddressFilterSet
|
filterset = filtersets.IPAddressFilterSet
|
||||||
template_name = 'ipam/ipaddress/ip_addresses.html'
|
template_name = 'generic/object_children.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Related IPs'),
|
label=_('Related IPs'),
|
||||||
badge=lambda x: x.get_related_ips().count(),
|
badge=lambda x: x.get_related_ips().count(),
|
||||||
@ -963,7 +962,6 @@ class FHRPGroupView(generic.ObjectView):
|
|||||||
queryset = FHRPGroup.objects.all()
|
queryset = FHRPGroup.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
# Get assigned interfaces
|
# Get assigned interfaces
|
||||||
members_table = tables.FHRPGroupAssignmentTable(
|
members_table = tables.FHRPGroupAssignmentTable(
|
||||||
data=FHRPGroupAssignment.objects.restrict(request.user, 'view').filter(group=instance),
|
data=FHRPGroupAssignment.objects.restrict(request.user, 'view').filter(group=instance),
|
||||||
@ -1077,7 +1075,7 @@ class VLANInterfacesView(generic.ObjectChildrenView):
|
|||||||
child_model = Interface
|
child_model = Interface
|
||||||
table = tables.VLANDevicesTable
|
table = tables.VLANDevicesTable
|
||||||
filterset = InterfaceFilterSet
|
filterset = InterfaceFilterSet
|
||||||
template_name = 'ipam/vlan/interfaces.html'
|
template_name = 'generic/object_children.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Device Interfaces'),
|
label=_('Device Interfaces'),
|
||||||
badge=lambda x: x.get_interfaces().count(),
|
badge=lambda x: x.get_interfaces().count(),
|
||||||
@ -1095,7 +1093,7 @@ class VLANVMInterfacesView(generic.ObjectChildrenView):
|
|||||||
child_model = VMInterface
|
child_model = VMInterface
|
||||||
table = tables.VLANVirtualMachinesTable
|
table = tables.VLANVirtualMachinesTable
|
||||||
filterset = VMInterfaceFilterSet
|
filterset = VMInterfaceFilterSet
|
||||||
template_name = 'ipam/vlan/vminterfaces.html'
|
template_name = 'generic/object_children.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('VM Interfaces'),
|
label=_('VM Interfaces'),
|
||||||
badge=lambda x: x.get_vminterfaces().count(),
|
badge=lambda x: x.get_vminterfaces().count(),
|
||||||
|
@ -11,6 +11,7 @@ from rest_framework.reverse import reverse
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rq.worker import Worker
|
from rq.worker import Worker
|
||||||
|
|
||||||
|
from extras.plugins.utils import get_installed_plugins
|
||||||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||||
|
|
||||||
|
|
||||||
@ -61,19 +62,11 @@ class StatusView(APIView):
|
|||||||
installed_apps[app_config.name] = version
|
installed_apps[app_config.name] = version
|
||||||
installed_apps = {k: v for k, v in sorted(installed_apps.items())}
|
installed_apps = {k: v for k, v in sorted(installed_apps.items())}
|
||||||
|
|
||||||
# Gather installed plugins
|
|
||||||
plugins = {}
|
|
||||||
for plugin_name in settings.PLUGINS:
|
|
||||||
plugin_name = plugin_name.rsplit('.', 1)[-1]
|
|
||||||
plugin_config = apps.get_app_config(plugin_name)
|
|
||||||
plugins[plugin_name] = getattr(plugin_config, 'version', None)
|
|
||||||
plugins = {k: v for k, v in sorted(plugins.items())}
|
|
||||||
|
|
||||||
return Response({
|
return Response({
|
||||||
'django-version': DJANGO_VERSION,
|
'django-version': DJANGO_VERSION,
|
||||||
'installed-apps': installed_apps,
|
'installed-apps': installed_apps,
|
||||||
'netbox-version': settings.VERSION,
|
'netbox-version': settings.VERSION,
|
||||||
'plugins': plugins,
|
'plugins': get_installed_plugins(),
|
||||||
'python-version': platform.python_version(),
|
'python-version': platform.python_version(),
|
||||||
'rq-workers-running': Worker.count(get_connection('default')),
|
'rq-workers-running': Worker.count(get_connection('default')),
|
||||||
})
|
})
|
||||||
|
@ -78,7 +78,10 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
|
|||||||
|
|
||||||
def _get_custom_fields(self, content_type):
|
def _get_custom_fields(self, content_type):
|
||||||
return CustomField.objects.filter(content_types=content_type).filter(
|
return CustomField.objects.filter(content_types=content_type).filter(
|
||||||
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE
|
ui_visibility__in=[
|
||||||
|
CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
|
||||||
|
CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_form_field(self, customfield):
|
def _get_form_field(self, customfield):
|
||||||
|
@ -442,6 +442,19 @@ class SyncedDataMixin(models.Model):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
from core.models import AutoSyncRecord
|
||||||
|
|
||||||
|
# Delete AutoSyncRecord
|
||||||
|
content_type = ContentType.objects.get_for_model(self)
|
||||||
|
AutoSyncRecord.objects.filter(
|
||||||
|
datafile=self.data_file,
|
||||||
|
object_type=content_type,
|
||||||
|
object_id=self.pk
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
return super().delete(*args, **kwargs)
|
||||||
|
|
||||||
def resolve_data_file(self):
|
def resolve_data_file(self):
|
||||||
"""
|
"""
|
||||||
Determine the designated DataFile object identified by its parent DataSource and its path. Returns None if
|
Determine the designated DataFile object identified by its parent DataSource and its path. Returns None if
|
||||||
|
@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '3.5.7'
|
VERSION = '3.5.8'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
@ -461,8 +461,6 @@ LOGIN_REDIRECT_URL = f'/{BASE_PATH}'
|
|||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
TEST_RUNNER = "django_rich.test.RichRunner"
|
|
||||||
|
|
||||||
# Exclude potentially sensitive models from wildcard view exemption. These may still be exempted
|
# Exclude potentially sensitive models from wildcard view exemption. These may still be exempted
|
||||||
# by specifying the model individually in the EXEMPT_VIEW_PERMISSIONS configuration parameter.
|
# by specifying the model individually in the EXEMPT_VIEW_PERMISSIONS configuration parameter.
|
||||||
EXEMPT_EXCLUDE_MODELS = (
|
EXEMPT_EXCLUDE_MODELS = (
|
||||||
|
@ -504,9 +504,9 @@ class CustomLinkColumn(tables.Column):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, customlink, *args, **kwargs):
|
def __init__(self, customlink, *args, **kwargs):
|
||||||
self.customlink = customlink
|
self.customlink = customlink
|
||||||
kwargs['accessor'] = Accessor('pk')
|
kwargs.setdefault('accessor', Accessor('pk'))
|
||||||
if 'verbose_name' not in kwargs:
|
kwargs.setdefault('orderable', False)
|
||||||
kwargs['verbose_name'] = customlink.name
|
kwargs.setdefault('verbose_name', customlink.name)
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class BaseTable(tables.Table):
|
|||||||
# 3. Meta.fields
|
# 3. Meta.fields
|
||||||
selected_columns = None
|
selected_columns = None
|
||||||
if user is not None and not isinstance(user, AnonymousUser):
|
if user is not None and not isinstance(user, AnonymousUser):
|
||||||
selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
|
selected_columns = user.config.get(f"tables.{self.name}.columns")
|
||||||
if not selected_columns:
|
if not selected_columns:
|
||||||
selected_columns = getattr(self.Meta, 'default_columns', self.Meta.fields)
|
selected_columns = getattr(self.Meta, 'default_columns', self.Meta.fields)
|
||||||
|
|
||||||
@ -113,6 +113,10 @@ class BaseTable(tables.Table):
|
|||||||
columns.append((name, column.verbose_name))
|
columns.append((name, column.verbose_name))
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available_columns(self):
|
def available_columns(self):
|
||||||
return self._get_columns(visible=False)
|
return self._get_columns(visible=False)
|
||||||
@ -138,17 +142,16 @@ class BaseTable(tables.Table):
|
|||||||
"""
|
"""
|
||||||
# Save ordering preference
|
# Save ordering preference
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
table_name = self.__class__.__name__
|
|
||||||
if self.prefixed_order_by_field in request.GET:
|
if self.prefixed_order_by_field in request.GET:
|
||||||
if request.GET[self.prefixed_order_by_field]:
|
if request.GET[self.prefixed_order_by_field]:
|
||||||
# If an ordering has been specified as a query parameter, save it as the
|
# If an ordering has been specified as a query parameter, save it as the
|
||||||
# user's preferred ordering for this table.
|
# user's preferred ordering for this table.
|
||||||
ordering = request.GET.getlist(self.prefixed_order_by_field)
|
ordering = request.GET.getlist(self.prefixed_order_by_field)
|
||||||
request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True)
|
request.user.config.set(f'tables.{self.name}.ordering', ordering, commit=True)
|
||||||
else:
|
else:
|
||||||
# If the ordering has been set to none (empty), clear any existing preference.
|
# If the ordering has been set to none (empty), clear any existing preference.
|
||||||
request.user.config.clear(f'tables.{table_name}.ordering', commit=True)
|
request.user.config.clear(f'tables.{self.name}.ordering', commit=True)
|
||||||
elif ordering := request.user.config.get(f'tables.{table_name}.ordering'):
|
elif ordering := request.user.config.get(f'tables.{self.name}.ordering'):
|
||||||
# If no ordering has been specified, set the preferred ordering (if any).
|
# If no ordering has been specified, set the preferred ordering (if any).
|
||||||
self.order_by = ordering
|
self.order_by = ordering
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found
|
|||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from sentry_sdk import capture_message
|
from sentry_sdk import capture_message
|
||||||
|
|
||||||
|
from extras.plugins.utils import get_installed_plugins
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'handler_404',
|
'handler_404',
|
||||||
'handler_500',
|
'handler_500',
|
||||||
@ -53,4 +55,5 @@ def handler_500(request, template_name=ERROR_500_TEMPLATE_NAME):
|
|||||||
'exception': str(type_),
|
'exception': str(type_),
|
||||||
'netbox_version': settings.VERSION,
|
'netbox_version': settings.VERSION,
|
||||||
'python_version': platform.python_version(),
|
'python_version': platform.python_version(),
|
||||||
|
'plugins': get_installed_plugins(),
|
||||||
}))
|
}))
|
||||||
|
@ -143,9 +143,12 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
|
|||||||
return render(request, self.get_template_name(), {
|
return render(request, self.get_template_name(), {
|
||||||
'object': instance,
|
'object': instance,
|
||||||
'child_model': self.child_model,
|
'child_model': self.child_model,
|
||||||
|
'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html',
|
||||||
'table': table,
|
'table': table,
|
||||||
|
'table_config': f'{table.name}_config',
|
||||||
'actions': actions,
|
'actions': actions,
|
||||||
'tab': self.tab,
|
'tab': self.tab,
|
||||||
|
'return_url': request.get_full_path(),
|
||||||
**self.get_extra_context(request, instance),
|
**self.get_extra_context(request, instance),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
@ -1,4 +1,4 @@
|
|||||||
import { getElement, getElements, findFirstAdjacent } from '../util';
|
import { getElements, findFirstAdjacent } from '../util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If any PK checkbox is checked, uncheck the select all table checkbox and the select all
|
* If any PK checkbox is checked, uncheck the select all table checkbox and the select all
|
||||||
@ -63,29 +63,6 @@ function handleSelectAllToggle(event: Event): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronize the select all confirmation checkbox state with the select all confirmation button
|
|
||||||
* disabled state. If the select all confirmation checkbox is checked, the buttons should be
|
|
||||||
* enabled. If not, the buttons should be disabled.
|
|
||||||
*
|
|
||||||
* @param event Change Event
|
|
||||||
*/
|
|
||||||
function handleSelectAll(event: Event): void {
|
|
||||||
const target = event.currentTarget as HTMLInputElement;
|
|
||||||
const selectAllBox = getElement<HTMLDivElement>('select-all-box');
|
|
||||||
if (selectAllBox !== null) {
|
|
||||||
for (const button of selectAllBox.querySelectorAll<HTMLButtonElement>(
|
|
||||||
'button[type="submit"]',
|
|
||||||
)) {
|
|
||||||
if (target.checked) {
|
|
||||||
button.disabled = false;
|
|
||||||
} else {
|
|
||||||
button.disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize table select all elements.
|
* Initialize table select all elements.
|
||||||
*/
|
*/
|
||||||
@ -98,9 +75,4 @@ export function initSelectAll(): void {
|
|||||||
for (const element of getElements<HTMLInputElement>('input[type="checkbox"][name="pk"]')) {
|
for (const element of getElements<HTMLInputElement>('input[type="checkbox"][name="pk"]')) {
|
||||||
element.addEventListener('change', handlePkCheck);
|
element.addEventListener('change', handlePkCheck);
|
||||||
}
|
}
|
||||||
const selectAll = getElement<HTMLInputElement>('select-all');
|
|
||||||
|
|
||||||
if (selectAll !== null) {
|
|
||||||
selectAll.addEventListener('change', handleSelectAll);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,10 @@
|
|||||||
{{ error }}
|
{{ error }}
|
||||||
|
|
||||||
Python version: {{ python_version }}
|
Python version: {{ python_version }}
|
||||||
NetBox version: {{ netbox_version }}</pre>
|
NetBox version: {{ netbox_version }}
|
||||||
|
Plugins: {% for plugin, version in plugins.items %}
|
||||||
|
{{ plugin }}: {{ version }}{% empty %}None installed{% endfor %}
|
||||||
|
</pre>
|
||||||
<p>
|
<p>
|
||||||
If further assistance is required, please post to the <a href="https://github.com/netbox-community/netbox/discussions">NetBox discussion forum</a> on GitHub.
|
If further assistance is required, please post to the <a href="https://github.com/netbox-community/netbox/discussions">NetBox discussion forum</a> on GitHub.
|
||||||
</p>
|
</p>
|
||||||
|
15
netbox/templates/dcim/device/components_base.html
Normal file
15
netbox/templates/dcim/device/components_base.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% extends 'generic/object_children.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block bulk_edit_controls %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %}
|
||||||
|
{% if 'bulk_rename' in actions and bulk_rename_view %}
|
||||||
|
<button type="submit" name="_rename"
|
||||||
|
formaction="{% url bulk_rename_view %}?return_url={{ return_url }}"
|
||||||
|
class="btn btn-outline-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock bulk_edit_controls %}
|
@ -1,57 +1,27 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_delete_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceConsolePortTable_config" %}
|
{{ block.super }}
|
||||||
|
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
||||||
<form method="post">
|
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
||||||
{% csrf_token %}
|
<button type="submit" name="_disconnect"
|
||||||
|
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
||||||
<div class="card">
|
class="btn btn-outline-danger btn-sm">
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="btn-group" role="group">
|
{% endwith %}
|
||||||
{% if 'bulk_delete' in actions %}
|
{% endblock bulk_delete_controls %}
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_consoleport %}
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
<a href="{% url 'dcim:consoleport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-sm btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Console Port
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
{% block bulk_extra_controls %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% table_config_form table %}
|
{% if perms.dcim.add_consoleport %}
|
||||||
{% endblock modals %}
|
<div class="bulk-button-group">
|
||||||
|
<a href="{% url 'dcim:consoleport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}"
|
||||||
|
class="btn btn-primary btn-sm">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Console Ports
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock bulk_extra_controls %}
|
||||||
|
@ -1,57 +1,27 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_delete_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceConsoleServerPortTable_config" %}
|
{{ block.super }}
|
||||||
|
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
||||||
<form method="post">
|
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
||||||
{% csrf_token %}
|
<button type="submit" name="_disconnect"
|
||||||
|
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
||||||
<div class="card">
|
class="btn btn-outline-danger btn-sm">
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="btn-group" role="group">
|
{% endwith %}
|
||||||
{% if 'bulk_delete' in actions %}
|
{% endblock bulk_delete_controls %}
|
||||||
<button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
<a href="{% url 'dcim:consoleserverport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-primary btn-sm">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Console Server Ports
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
{% block bulk_extra_controls %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% table_config_form table %}
|
{% if perms.dcim.add_consoleserverport %}
|
||||||
{% endblock modals %}
|
<div class="bulk-button-group">
|
||||||
|
<a href="{% url 'dcim:consoleserverport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}"
|
||||||
|
class="btn btn-primary btn-sm">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Console Server Ports
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock bulk_extra_controls %}
|
@ -1,50 +1,13 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_extra_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceDeviceBayTable_config" %}
|
{{ block.super }}
|
||||||
|
{% if perms.dcim.add_devicebay %}
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:devicebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_devicebay %}
|
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
<a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-primary btn-sm">
|
<a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}"
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Device Bays
|
class="btn btn-primary btn-sm">
|
||||||
</a>
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Device Bays
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock bulk_extra_controls %}
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,57 +1,27 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_delete_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceFrontPortTable_config" %}
|
{{ block.super }}
|
||||||
|
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
||||||
<form method="post">
|
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
||||||
{% csrf_token %}
|
<button type="submit" name="_disconnect"
|
||||||
|
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
||||||
<div class="card">
|
class="btn btn-outline-danger btn-sm">
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="btn-group" role="group">
|
{% endwith %}
|
||||||
{% if 'bulk_delete' in actions %}
|
{% endblock bulk_delete_controls %}
|
||||||
<button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_frontport %}
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
<a href="{% url 'dcim:frontport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-primary btn-sm">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add front ports
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
{% block bulk_extra_controls %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% table_config_form table %}
|
{% if perms.dcim.add_frontport %}
|
||||||
{% endblock modals %}
|
<div class="bulk-button-group">
|
||||||
|
<a href="{% url 'dcim:frontport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}"
|
||||||
|
class="btn btn-primary btn-sm">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add front ports
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock bulk_extra_controls %}
|
@ -1,66 +1,27 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_delete_controls %}
|
||||||
{% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %}
|
{{ block.super }}
|
||||||
|
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
||||||
<form method="post">
|
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
||||||
{% csrf_token %}
|
<button type="submit" name="_disconnect"
|
||||||
|
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
||||||
<div class="card">
|
class="btn btn-outline-danger btn-sm">
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
{% include 'htmx/table.html' %}
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit"
|
|
||||||
formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
|
||||||
class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="_rename"
|
|
||||||
formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
|
||||||
class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete"
|
|
||||||
formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
|
||||||
class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'bulk_edit' in actions %}
|
{% endwith %}
|
||||||
<button type="submit" name="_disconnect"
|
{% endblock bulk_delete_controls %}
|
||||||
formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
|
||||||
class="btn btn-outline-danger btn-sm">
|
{% block bulk_extra_controls %}
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
{{ block.super }}
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
<a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
<a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
||||||
class="btn btn-primary btn-sm">
|
class="btn btn-primary btn-sm">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Interfaces
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Interfaces
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock bulk_extra_controls %}
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,50 +1,13 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_extra_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceInventoryItemTable_config" %}
|
{{ block.super }}
|
||||||
|
{% if perms.dcim.add_inventoryitem %}
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:inventoryitem_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_inventoryitem %}
|
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
<a href="{% url 'dcim:inventoryitem_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-primary btn-sm">
|
<a href="{% url 'dcim:inventoryitem_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}"
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Inventory Item
|
class="btn btn-primary btn-sm">
|
||||||
</a>
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Inventory Item
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock bulk_extra_controls %}
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,46 +1,13 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_extra_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceModuleBayTable_config" %}
|
{{ block.super }}
|
||||||
|
{% if perms.dcim.add_modulebay %}
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:modulebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_modulebay %}
|
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
<a href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-primary btn-sm">
|
<a href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}"
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Module Bays
|
class="btn btn-primary btn-sm">
|
||||||
</a>
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Module Bays
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock bulk_extra_controls %}
|
||||||
</form>
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock %}
|
|
@ -1,57 +1,27 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_delete_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DevicePowerOutletTable_config" %}
|
{{ block.super }}
|
||||||
|
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
||||||
<form method="post">
|
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
||||||
{% csrf_token %}
|
<button type="submit" name="_disconnect"
|
||||||
|
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
||||||
<div class="card">
|
class="btn btn-outline-danger btn-sm">
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="btn-group" role="group">
|
{% endwith %}
|
||||||
{% if 'bulk_delete' in actions %}
|
{% endblock bulk_delete_controls %}
|
||||||
<button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
<a href="{% url 'dcim:poweroutlet_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-primary btn-sm">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Power Outlets
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
{% block bulk_extra_controls %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% table_config_form table %}
|
{% if perms.dcim.add_poweroutlet %}
|
||||||
{% endblock modals %}
|
<div class="bulk-button-group">
|
||||||
|
<a href="{% url 'dcim:poweroutlet_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}"
|
||||||
|
class="btn btn-primary btn-sm">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Power Outlets
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock bulk_extra_controls %}
|
||||||
|
@ -1,57 +1,27 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_delete_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DevicePowerPortTable_config" %}
|
{{ block.super }}
|
||||||
|
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
||||||
<form method="post">
|
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
||||||
{% csrf_token %}
|
<button type="submit" name="_disconnect"
|
||||||
|
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
||||||
<div class="card">
|
class="btn btn-outline-danger btn-sm">
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="btn-group" role="group">
|
{% endwith %}
|
||||||
{% if 'bulk_delete' in actions %}
|
{% endblock bulk_delete_controls %}
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_powerport %}
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
<a href="{% url 'dcim:powerport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-sm btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Power Port
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
{% block bulk_extra_controls %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% table_config_form table %}
|
{% if perms.dcim.add_powerport %}
|
||||||
{% endblock modals %}
|
<div class="bulk-button-group">
|
||||||
|
<a href="{% url 'dcim:powerport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}"
|
||||||
|
class="btn btn-sm btn-primary">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Power Port
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock bulk_extra_controls %}
|
||||||
|
@ -1,57 +1,27 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/components_base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load static %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_delete_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceRearPortTable_config" %}
|
{{ block.super }}
|
||||||
|
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
||||||
<form method="post">
|
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
||||||
{% csrf_token %}
|
<button type="submit" name="_disconnect"
|
||||||
|
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
||||||
<div class="card">
|
class="btn btn-outline-danger btn-sm">
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="btn-group" role="group">
|
{% endwith %}
|
||||||
{% if 'bulk_delete' in actions %}
|
{% endblock bulk_delete_controls %}
|
||||||
<button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_rearport %}
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
<a href="{% url 'dcim:rearport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-primary btn-sm">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add rear ports
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
{% block bulk_extra_controls %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% table_config_form table %}
|
{% if perms.dcim.add_rearport %}
|
||||||
{% endblock modals %}
|
<div class="bulk-button-group">
|
||||||
|
<a href="{% url 'dcim:rearport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}"
|
||||||
|
class="btn btn-primary btn-sm">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add rear ports
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock bulk_extra_controls %}
|
@ -1,5 +1,4 @@
|
|||||||
{% extends 'dcim/rack/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% if perms.dcim.add_device %}
|
{% if perms.dcim.add_device %}
|
||||||
@ -10,42 +9,4 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit"
|
|
||||||
formaction="{% url 'dcim:device_bulk_edit' %}?return_url={% url 'dcim:rack_nonracked_devices' pk=object.pk %}"
|
|
||||||
class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit"
|
|
||||||
formaction="{% url 'dcim:device_bulk_delete' %}?return_url={% url 'dcim:rack_nonracked_devices' pk=object.pk %}"
|
|
||||||
class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,43 +1,12 @@
|
|||||||
{% extends 'dcim/rack/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block extra_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="RackReservationTable_config" %}
|
{% if perms.dcim.add_rackreservation %}
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:rackreservation_bulk_edit' %}?return_url={% url 'dcim:rack_reservations' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" formaction="{% url 'dcim:rackreservation_bulk_delete' %}?return_url={% url 'dcim:rack_reservations' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if perms.dcim.add_rackreservation %}
|
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
<a href="{% url 'dcim:rackreservation_add' %}?rack={{ object.pk }}&return_url={% url 'dcim:rack_reservations' pk=object.pk %}" class="btn btn-primary btn-sm">
|
<a href="{% url 'dcim:rackreservation_add' %}?rack={{ object.pk }}&return_url={% url 'dcim:rack_reservations' pk=object.pk %}"
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add reservation
|
class="btn btn-primary btn-sm">
|
||||||
</a>
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add reservation
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock extra_controls %}
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
4
netbox/templates/extras/imageattachment.html
Normal file
4
netbox/templates/extras/imageattachment.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
|
{% endblock %}
|
93
netbox/templates/extras/schema/devicetype_schema.jinja2
Normal file
93
netbox/templates/extras/schema/devicetype_schema.jinja2
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"definitions": {
|
||||||
|
"airflow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ airflow_choices }}
|
||||||
|
},
|
||||||
|
"weight-unit": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ weight_unit_choices }}
|
||||||
|
},
|
||||||
|
"subdevice-role": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ subdevice_role_choices }}
|
||||||
|
},
|
||||||
|
"console-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ console_port_type_choices }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"console-server-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ console_server_port_type_choices }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"power-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ power_port_type_choices }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"power-outlet": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ power_outlet_type_choices }}
|
||||||
|
},
|
||||||
|
"feed-leg": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ power_outlet_feedleg_choices }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ interface_type_choices }}
|
||||||
|
},
|
||||||
|
"poe_mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ interface_poe_mode_choices }}
|
||||||
|
},
|
||||||
|
"poe_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ interface_poe_type_choices }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"front-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ front_port_type_choices }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rear-port": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": {{ rear_port_type_choices}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
netbox/templates/generic/object_children.html
Normal file
57
netbox/templates/generic/object_children.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{% extends base_template %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include 'inc/table_controls_htmx.html' with table_modal=table_config %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body htmx-container table-responsive" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="noprint bulk-buttons">
|
||||||
|
{% block bulk_controls %}
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
{# Bulk edit buttons #}
|
||||||
|
{% block bulk_edit_controls %}
|
||||||
|
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
|
||||||
|
{% if 'bulk_edit' in actions and bulk_edit_view %}
|
||||||
|
<button type="submit" name="_edit"
|
||||||
|
formaction="{% url bulk_edit_view %}?return_url={{ return_url }}"
|
||||||
|
class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock bulk_edit_controls %}
|
||||||
|
</div>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
{# Bulk delete buttons #}
|
||||||
|
{% block bulk_delete_controls %}
|
||||||
|
{% with bulk_delete_view=child_model|validated_viewname:"bulk_delete" %}
|
||||||
|
{% if 'bulk_delete' in actions and bulk_delete_view %}
|
||||||
|
<button type="submit"
|
||||||
|
formaction="{% url bulk_delete_view %}?return_url={{ return_url }}"
|
||||||
|
class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock bulk_delete_controls %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{# Other bulk action buttons #}
|
||||||
|
{% block bulk_extra_controls %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock bulk_controls %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
@ -1,5 +1,4 @@
|
|||||||
{% extends 'ipam/aggregate/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% include 'ipam/inc/toggle_available.html' %}
|
{% include 'ipam/inc/toggle_available.html' %}
|
||||||
@ -9,38 +8,4 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="PrefixTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:aggregate_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:aggregate_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
{% extends 'ipam/asnrange/base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="ASNTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'ipam:asn_bulk_edit' %}?return_url={% url 'ipam:asnrange_asns' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'ipam:asn_bulk_delete' %}?return_url={% url 'ipam:asnrange_asns' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
@ -1,19 +0,0 @@
|
|||||||
{% extends 'ipam/ipaddress/base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %}
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
@ -1,44 +1,9 @@
|
|||||||
{% extends 'ipam/iprange/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and object.first_available_ip %}
|
{% if perms.ipam.add_ipaddress and object.first_available_ip %}
|
||||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ object.first_available_ip }}&vrf={{ object.vrf.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-sm btn-primary">
|
<a href="{% url 'ipam:ipaddress_add' %}?address={{ object.first_available_ip }}&vrf={{ object.vrf.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-sm btn-primary">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add IP Address
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add IP Address
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'ipam:ipaddress_bulk_edit' %}?return_url={% url 'ipam:iprange_ipaddresses' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'ipam:ipaddress_bulk_delete' %}?return_url={% url 'ipam:iprange_ipaddresses' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{% extends 'ipam/prefix/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% if perms.ipam.add_ipaddress and first_available_ip %}
|
{% if perms.ipam.add_ipaddress and first_available_ip %}
|
||||||
@ -7,38 +6,4 @@
|
|||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add IP Address
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add IP Address
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'ipam:ipaddress_bulk_edit' %}?return_url={% url 'ipam:prefix_ipaddresses' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'ipam:ipaddress_bulk_delete' %}?return_url={% url 'ipam:prefix_ipaddresses' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{% extends 'ipam/prefix/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% if perms.ipam.add_iprange and first_available_ip %}
|
{% if perms.ipam.add_iprange and first_available_ip %}
|
||||||
@ -7,38 +6,4 @@
|
|||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add IP Range
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add IP Range
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="IPRangeTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'ipam:iprange_bulk_edit' %}?return_url={% url 'ipam:prefix_ipranges' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'ipam:iprange_bulk_delete' %}?return_url={% url 'ipam:prefix_ipranges' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{% extends 'ipam/prefix/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% include 'ipam/inc/toggle_available.html' %}
|
{% include 'ipam/inc/toggle_available.html' %}
|
||||||
@ -8,39 +7,4 @@
|
|||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Prefix
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Prefix
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ block.super }}
|
{% endblock extra_controls %}
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="PrefixTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
{% extends 'ipam/vlan/base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="VLANDevicesTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
@ -1,20 +0,0 @@
|
|||||||
{% extends 'ipam/vlan/base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="VLANVirtualMachinesTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
@ -1,4 +1,4 @@
|
|||||||
{% extends base_template %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
@ -10,20 +10,3 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="ContactAssignmentTable_config" %}
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,30 +1,13 @@
|
|||||||
{% extends 'virtualization/cluster/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_delete_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %}
|
{{ block.super }}
|
||||||
|
{% if 'bulk_remove_devices' in actions %}
|
||||||
<form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
|
<button type="submit" name="_remove"
|
||||||
{% csrf_token %}
|
formaction="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}?return_url={{ return_url }}"
|
||||||
<div class="card">
|
class="btn btn-danger btn-sm">
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if perms.virtualization.change_cluster %}
|
|
||||||
<button type="submit" name="_remove" class="btn btn-danger btn-sm">
|
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Remove Devices
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Remove Devices
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock bulk_delete_controls %}
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
{% extends 'virtualization/cluster/base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="VirtualMachineTable_config" %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="noprint bulk-buttons">
|
|
||||||
<div class="bulk-button-group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'virtualization:virtualmachine_bulk_edit' %}?return_url={% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'virtualization:virtualmachine_bulk_delete' %}?return_url={% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
@ -1,47 +1,13 @@
|
|||||||
{% extends 'virtualization/virtualmachine/base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block content %}
|
{% block bulk_edit_controls %}
|
||||||
{% include 'inc/table_controls_htmx.html' with table_modal="VirtualMachineVMInterfaceTable_config" %}
|
{{ block.super }}
|
||||||
|
{% if 'bulk_rename' in actions %}
|
||||||
<form method="post">
|
<button type="submit" name="_rename"
|
||||||
{% csrf_token %}
|
formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={{ return_url }}"
|
||||||
|
class="btn btn-outline-warning btn-sm">
|
||||||
<div class="card">
|
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
||||||
<div class="card-body htmx-container table-responsive" id="object_list">
|
|
||||||
{% include 'htmx/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="noprint">
|
|
||||||
{% if perms.virtualization.change_vminterface %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?virtual_machine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm">
|
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.virtualization.delete_vminterface %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm">
|
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.virtualization.add_vminterface %}
|
{% endblock bulk_edit_controls %}
|
||||||
<div class="float-end">
|
|
||||||
<a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-primary btn-sm">
|
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% table_config_form table %}
|
|
||||||
{% endblock modals %}
|
|
||||||
|
@ -41,11 +41,6 @@ class ObjectContactsView(generic.ObjectChildrenView):
|
|||||||
|
|
||||||
return table
|
return table
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
|
||||||
return {
|
|
||||||
'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html',
|
|
||||||
}
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tenant groups
|
# Tenant groups
|
||||||
#
|
#
|
||||||
|
@ -123,9 +123,9 @@ class BulkImportForm(BootstrapMixin, SyncedDataMixin, forms.Form):
|
|||||||
records = []
|
records = []
|
||||||
try:
|
try:
|
||||||
for data in yaml.load_all(data, Loader=yaml.SafeLoader):
|
for data in yaml.load_all(data, Loader=yaml.SafeLoader):
|
||||||
if type(data) == list:
|
if type(data) is list:
|
||||||
records.extend(data)
|
records.extend(data)
|
||||||
elif type(data) == dict:
|
elif type(data) is dict:
|
||||||
records.append(data)
|
records.append(data)
|
||||||
else:
|
else:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
|
@ -60,6 +60,9 @@ def parse_alphanumeric_range(string):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
begin, end = dash_range, dash_range
|
begin, end = dash_range, dash_range
|
||||||
if begin.isdigit() and end.isdigit():
|
if begin.isdigit() and end.isdigit():
|
||||||
|
if int(begin) >= int(end):
|
||||||
|
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
|
||||||
|
|
||||||
for n in list(range(int(begin), int(end) + 1)):
|
for n in list(range(int(begin), int(end) + 1)):
|
||||||
values.append(n)
|
values.append(n)
|
||||||
else:
|
else:
|
||||||
@ -71,6 +74,10 @@ def parse_alphanumeric_range(string):
|
|||||||
# Not a valid range (more than a single character)
|
# Not a valid range (more than a single character)
|
||||||
if not len(begin) == len(end) == 1:
|
if not len(begin) == len(end) == 1:
|
||||||
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
|
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
|
||||||
|
|
||||||
|
if ord(begin) >= ord(end):
|
||||||
|
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
|
||||||
|
|
||||||
for n in list(range(ord(begin), ord(end) + 1)):
|
for n in list(range(ord(begin), ord(end) + 1)):
|
||||||
values.append(chr(n))
|
values.append(chr(n))
|
||||||
return values
|
return values
|
||||||
|
@ -114,7 +114,7 @@ def annotated_date(date_value):
|
|||||||
if not date_value:
|
if not date_value:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
if type(date_value) == datetime.date:
|
if type(date_value) is datetime.date:
|
||||||
long_ts = date(date_value, 'DATE_FORMAT')
|
long_ts = date(date_value, 'DATE_FORMAT')
|
||||||
short_ts = date(date_value, 'SHORT_DATE_FORMAT')
|
short_ts = date(date_value, 'SHORT_DATE_FORMAT')
|
||||||
else:
|
else:
|
||||||
|
@ -264,8 +264,9 @@ class ExpandAlphanumeric(TestCase):
|
|||||||
self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), [])
|
self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), [])
|
||||||
|
|
||||||
def test_invalid_range_bounds(self):
|
def test_invalid_range_bounds(self):
|
||||||
self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-8]a')), [])
|
with self.assertRaises(forms.ValidationError):
|
||||||
self.assertEqual(sorted(expand_alphanumeric_pattern('r[b-a]a')), [])
|
sorted(expand_alphanumeric_pattern('r[9-8]a'))
|
||||||
|
sorted(expand_alphanumeric_pattern('r[b-a]a'))
|
||||||
|
|
||||||
def test_invalid_range_len(self):
|
def test_invalid_range_len(self):
|
||||||
with self.assertRaises(forms.ValidationError):
|
with self.assertRaises(forms.ValidationError):
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Prefetch, Sum
|
from django.db.models import Prefetch, Sum
|
||||||
@ -175,7 +177,7 @@ class ClusterVirtualMachinesView(generic.ObjectChildrenView):
|
|||||||
child_model = VirtualMachine
|
child_model = VirtualMachine
|
||||||
table = tables.VirtualMachineTable
|
table = tables.VirtualMachineTable
|
||||||
filterset = filtersets.VirtualMachineFilterSet
|
filterset = filtersets.VirtualMachineFilterSet
|
||||||
template_name = 'virtualization/cluster/virtual_machines.html'
|
template_name = 'generic/object_children.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Virtual Machines'),
|
label=_('Virtual Machines'),
|
||||||
badge=lambda obj: obj.virtual_machines.count(),
|
badge=lambda obj: obj.virtual_machines.count(),
|
||||||
@ -194,6 +196,13 @@ class ClusterDevicesView(generic.ObjectChildrenView):
|
|||||||
table = DeviceTable
|
table = DeviceTable
|
||||||
filterset = DeviceFilterSet
|
filterset = DeviceFilterSet
|
||||||
template_name = 'virtualization/cluster/devices.html'
|
template_name = 'virtualization/cluster/devices.html'
|
||||||
|
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_remove_devices')
|
||||||
|
action_perms = defaultdict(set, **{
|
||||||
|
'add': {'add'},
|
||||||
|
'import': {'add'},
|
||||||
|
'bulk_edit': {'change'},
|
||||||
|
'bulk_remove_devices': {'change'},
|
||||||
|
})
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Devices'),
|
label=_('Devices'),
|
||||||
badge=lambda obj: obj.devices.count(),
|
badge=lambda obj: obj.devices.count(),
|
||||||
@ -353,6 +362,14 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView):
|
|||||||
permission='virtualization.view_vminterface',
|
permission='virtualization.view_vminterface',
|
||||||
weight=500
|
weight=500
|
||||||
)
|
)
|
||||||
|
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename')
|
||||||
|
action_perms = defaultdict(set, **{
|
||||||
|
'add': {'add'},
|
||||||
|
'import': {'add'},
|
||||||
|
'bulk_edit': {'change'},
|
||||||
|
'bulk_delete': {'delete'},
|
||||||
|
'bulk_rename': {'change'},
|
||||||
|
})
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
return parent.interfaces.restrict(request.user, 'view').prefetch_related(
|
return parent.interfaces.restrict(request.user, 'view').prefetch_related(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
bleach==6.0.0
|
bleach==6.0.0
|
||||||
boto3==1.28.14
|
boto3==1.28.26
|
||||||
Django==4.1.10
|
Django==4.1.10
|
||||||
django-cors-headers==4.2.0
|
django-cors-headers==4.2.0
|
||||||
django-debug-toolbar==4.1.0
|
django-debug-toolbar==4.2.0
|
||||||
django-filter==23.2
|
django-filter==23.2
|
||||||
django-graphiql-debug-toolbar==0.2.0
|
django-graphiql-debug-toolbar==0.2.0
|
||||||
django-mptt==0.14
|
django-mptt==0.14
|
||||||
@ -16,7 +16,7 @@ django-taggit==4.0.0
|
|||||||
django-timezone-field==5.1
|
django-timezone-field==5.1
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
drf-spectacular==0.26.4
|
drf-spectacular==0.26.4
|
||||||
drf-spectacular-sidecar==2023.7.1
|
drf-spectacular-sidecar==2023.8.1
|
||||||
dulwich==0.21.5
|
dulwich==0.21.5
|
||||||
feedparser==6.0.10
|
feedparser==6.0.10
|
||||||
graphene-django==3.0.0
|
graphene-django==3.0.0
|
||||||
@ -27,9 +27,9 @@ mkdocs-material==9.1.21
|
|||||||
mkdocstrings[python-legacy]==0.22.0
|
mkdocstrings[python-legacy]==0.22.0
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==10.0.0
|
Pillow==10.0.0
|
||||||
psycopg2-binary==2.9.6
|
psycopg2-binary==2.9.7
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
sentry-sdk==1.28.1
|
sentry-sdk==1.29.2
|
||||||
social-auth-app-django==5.2.0
|
social-auth-app-django==5.2.0
|
||||||
social-auth-core[openidconnect]==4.4.2
|
social-auth-core[openidconnect]==4.4.2
|
||||||
svgwrite==1.4.3
|
svgwrite==1.4.3
|
||||||
|
Loading…
Reference in New Issue
Block a user