mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 20:01:45 +00:00
Compare commits
28 Commits
PSQL_Shell
...
defect1704
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6defcfb7e5 | ||
|
|
ad953ffa1d | ||
|
|
511b0bd8c0 | ||
|
|
44766e8213 | ||
|
|
60d666df1e | ||
|
|
ca9833e208 | ||
|
|
042ad24cc6 | ||
|
|
445427ecec | ||
|
|
6d19aaab43 | ||
|
|
7fbecfff1a | ||
|
|
712e0e0c1e | ||
|
|
6aa229462b | ||
|
|
990d86ffc6 | ||
|
|
9383609a22 | ||
|
|
2f32a676d0 | ||
|
|
950c8ee470 | ||
|
|
b0eaac5b84 | ||
|
|
952491a3ad | ||
|
|
5dde66b032 | ||
|
|
5b1db2778c | ||
|
|
1213788f9c | ||
|
|
1ce3adff0f | ||
|
|
00eb07da11 | ||
|
|
afe59c1589 | ||
|
|
53b5ebd39c | ||
|
|
5b365e642f | ||
|
|
333b3de587 | ||
|
|
e909ac43f4 |
BIN
images/firewallRule.png
Normal file
BIN
images/firewallRule.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -3,337 +3,337 @@
|
|||||||
@import "../Common/Constants";
|
@import "../Common/Constants";
|
||||||
|
|
||||||
.query-panel {
|
.query-panel {
|
||||||
display: table;
|
display: table;
|
||||||
display: none;
|
display: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid #DDDDDD;
|
border-top: 1px solid #dddddd;
|
||||||
/*[{environment-commandbar-toolbar-separator}]*/
|
/*[{environment-commandbar-toolbar-separator}]*/
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/*[{plugin-background-color}]*/
|
/*[{plugin-background-color}]*/
|
||||||
padding: 2px 0px 0px 2px;
|
padding: 2px 0px 0px 2px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .row {
|
.query-panel .row {
|
||||||
display: table-row;
|
display: table-row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .row .cell {
|
.query-panel .row .cell {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel.transition-in {
|
.query-panel.transition-in {
|
||||||
display: table;
|
display: table;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
-webkit-transition: top 2s linear;
|
-webkit-transition: top 2s linear;
|
||||||
-ms-transition: top 2s linear;
|
-ms-transition: top 2s linear;
|
||||||
-moz-transition: top 2s linear;
|
-moz-transition: top 2s linear;
|
||||||
-khtml-transition: top 2s linear;
|
-khtml-transition: top 2s linear;
|
||||||
-o-transition: top 2s linear;
|
-o-transition: top 2s linear;
|
||||||
transition: top 2s linear;
|
transition: top 2s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder {
|
.query-builder {
|
||||||
width:100%;
|
width: 100%;
|
||||||
padding-right: @DefaultSpace;
|
padding-right: @DefaultSpace;
|
||||||
border-bottom: 1px solid @BaseMedium;
|
border-bottom: 1px solid @BaseMedium;
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar {
|
.query-builder-toolbar {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/*[{plugin-background-color}]*/
|
/*[{plugin-background-color}]*/
|
||||||
min-width: 600px;
|
min-width: 600px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border-bottom: 1px solid #DDDDDD;
|
border-bottom: 1px solid #dddddd;
|
||||||
/*[1px solid {environment-commandbar-toolbar-separator}]*/
|
/*[1px solid {environment-commandbar-toolbar-separator}]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group {
|
.query-builder-toolbar .query-toolbar-group {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin: 2px 0px;
|
margin: 2px 0px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button {
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: solid transparent;
|
border: solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:active {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:active {
|
||||||
outline: 2px solid dodgerblue;
|
outline: 2px solid dodgerblue;
|
||||||
/*[2px solid {common-common-controls-button-border-hover}]*/
|
/*[2px solid {common-common-controls-button-border-hover}]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:hover {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:hover {
|
||||||
background-color: #CCCEDB;
|
background-color: #cccedb;
|
||||||
/*[{common-controls-button-hover-background}]*/
|
/*[{common-controls-button-hover-background}]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.active {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.active {
|
||||||
background-color: #E6E7ED;
|
background-color: #e6e7ed;
|
||||||
/*[{common-controls-inner-tab-active-background}]*/
|
/*[{common-controls-inner-tab-active-background}]*/
|
||||||
outline: none
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:disabled,
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button:disabled,
|
||||||
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.disabled {
|
.query-builder-toolbar .query-toolbar-group .query-toolbar-button.disabled {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/*[{common-controls-button-disabled-background}]*/
|
/*[{common-controls-button-disabled-background}]*/
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tableContainer {
|
.tableContainer {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.flex-display();
|
.flex-display();
|
||||||
.flex-direction();
|
.flex-direction();
|
||||||
}
|
}
|
||||||
|
|
||||||
.tablesQueryTab{
|
.tablesQueryTab {
|
||||||
padding-left: @MediumSpace;
|
padding-left: @MediumSpace;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom:@LargeSpace;
|
margin-bottom: @LargeSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-error-Img {
|
.entity-error-Img {
|
||||||
width: @WarningErrorIconSize;
|
width: @WarningErrorIconSize;
|
||||||
height: @WarningErrorIconSize;
|
height: @WarningErrorIconSize;
|
||||||
margin: @DefaultSpace 0px 0px @SmallSpace;
|
margin: @DefaultSpace 0px 0px @SmallSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-panel {
|
.query-editor-panel {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-text {
|
.query-editor-text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
border: solid 1px #A9ACB3;
|
border: solid 1px #a9acb3;
|
||||||
/*[{plugin-textbox-disabled-color}]*/
|
/*[{plugin-textbox-disabled-color}]*/
|
||||||
resize: none;
|
resize: none;
|
||||||
margin-top: -39px;
|
margin-top: -39px;
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-bar {
|
.error-bar {
|
||||||
padding: @LargeSpace 34px @MediumSpace 24px;
|
padding: @LargeSpace 34px @MediumSpace 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
background-color: @BaseLow;
|
background-color: @BaseLow;
|
||||||
padding: @DefaultSpace;
|
padding: @DefaultSpace;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-text {
|
.error-text {
|
||||||
padding-left: @MediumSpace;
|
padding-left: @MediumSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-text-invalid {
|
.query-editor-text-invalid {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
border: 1px solid #e51400;
|
border: 1px solid #e51400;
|
||||||
resize: none;
|
resize: none;
|
||||||
margin-top: -30px;
|
margin-top: -30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-panel .warning-bar {
|
.query-editor-panel .warning-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/*[{plugin-background-color}]*/
|
/*[{plugin-background-color}]*/
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -24px;
|
top: -24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-panel .warning-bar .warning-message {
|
.query-editor-panel .warning-bar .warning-message {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-editor-panel .warning-bar .warning-message .warning-text {
|
.query-editor-panel .warning-bar .warning-message .warning-text {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel{
|
.advanced-options-panel {
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-heading .advanced-title {
|
.advanced-options-panel .advanced-heading .advanced-title {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin-left: 27px;
|
margin-left: 27px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options {
|
.advanced-options-panel .advanced-options {
|
||||||
margin-left: 32px;
|
margin-left: 32px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
input::-webkit-outer-spin-button,
|
input::-webkit-outer-spin-button,
|
||||||
input::-webkit-inner-spin-button {
|
input::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .top .top-input {
|
.advanced-options-panel .advanced-options .top .top-input {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
word-spacing: normal;
|
word-spacing: normal;
|
||||||
color: #1E1E1E;
|
color: #1e1e1e;
|
||||||
/*[{common-controls-button-foreground}]*/
|
/*[{common-controls-button-foreground}]*/
|
||||||
border: 1px solid #CCCEDB;
|
border: 1px solid #cccedb;
|
||||||
/*[1px solid {plugin-textbox-border-color}]*/
|
/*[1px solid {plugin-textbox-border-color}]*/
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .top .invalid-top {
|
.advanced-options-panel .advanced-options .top .invalid-top {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .select {
|
.advanced-options-panel .advanced-options .select {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-icon {
|
.advanced-options-icon {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
vertical-align: sub;
|
vertical-align: sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .select .select-options-text {
|
.advanced-options-panel .advanced-options .select .select-options-text {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-options-panel .advanced-options .select .select-options-link {
|
.advanced-options-panel .advanced-options .select .select-options-link {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .row .column-headers .Field {
|
.query-panel .row .column-headers .Field {
|
||||||
padding-left: 95px;
|
padding-left: 95px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table {
|
.clause-table {
|
||||||
border-spacing: 0px;
|
border-spacing: 0px;
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-row {
|
.clause-table-row {
|
||||||
display: row;
|
display: row;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-cell {
|
.clause-table-cell {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-column>button,
|
.action-column > button,
|
||||||
.group-control-header>button,
|
.group-control-header > button,
|
||||||
.group-indicator-column>button {
|
.group-indicator-column > button {
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-control-header>button:disabled {
|
.group-control-header > button:disabled {
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-field {
|
.clause-table-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid #bbbbbb;
|
border: 1px solid #bbbbbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-cell button {
|
.clause-table-cell button {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-cell input[type="checkbox"] {
|
.clause-table-cell input[type="checkbox"] {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.and-or-svg {
|
.and-or-svg {
|
||||||
margin-top: -8px;
|
margin-top: -8px;
|
||||||
margin-right: -5px;
|
margin-right: -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll-box {
|
.scroll-box {
|
||||||
border-bottom: 1px transparent #DDD;
|
border-bottom: 1px transparent #ddd;
|
||||||
/*[1px solid {plugin-table-border-color}]*/
|
/*[1px solid {plugin-table-border-color}]*/
|
||||||
border-top: 1px transparent #DDD;
|
border-top: 1px transparent #ddd;
|
||||||
/*[1px solid {plugin-table-border-color}]*/
|
/*[1px solid {plugin-table-border-color}]*/
|
||||||
max-height: 20vh;
|
max-height: 20vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollable {
|
.scrollable {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.and-or-column,
|
.and-or-column,
|
||||||
.and-or-header {
|
.and-or-header {
|
||||||
min-width: 65px;
|
min-width: 65px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operator-column,
|
.operator-column,
|
||||||
.operator-header {
|
.operator-header {
|
||||||
min-width: 65px;
|
min-width: 65px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-header,
|
.field-header,
|
||||||
.field-column {
|
.field-column {
|
||||||
min-width: 125px;
|
min-width: 125px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-header,
|
.type-header,
|
||||||
.type-column {
|
.type-column {
|
||||||
min-width: 85px;
|
min-width: 85px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.and-or-column,
|
.and-or-column,
|
||||||
@@ -345,41 +345,41 @@ input::-webkit-inner-spin-button {
|
|||||||
.type-header,
|
.type-header,
|
||||||
.type-column,
|
.type-column,
|
||||||
.action-header {
|
.action-header {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value-header,
|
.value-header,
|
||||||
.value-column,
|
.value-column,
|
||||||
.time-column {
|
.time-column {
|
||||||
min-width: 230px;
|
min-width: 230px;
|
||||||
padding: 0px 4px 0px 0px;
|
padding: 0px 4px 0px 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-control-header,
|
.group-control-header,
|
||||||
.group-control-column {
|
.group-control-column {
|
||||||
min-width: 25px;
|
min-width: 25px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-indicator-table {
|
.group-indicator-table {
|
||||||
border-spacing: 0px;
|
border-spacing: 0px;
|
||||||
min-height: 24px
|
min-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-indicator-column {
|
.group-indicator-column {
|
||||||
min-width: 21px;
|
min-width: 21px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
border-style: none;
|
border-style: none;
|
||||||
height: 29px;
|
height: 29px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-cell.action-column,
|
.clause-table-cell.action-column,
|
||||||
.clause-table-cell.action-header {
|
.clause-table-cell.action-header {
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
padding-left: @SmallSpace;
|
padding-left: @SmallSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-header,
|
.action-header,
|
||||||
@@ -388,15 +388,14 @@ input::-webkit-inner-spin-button {
|
|||||||
.operator-header,
|
.operator-header,
|
||||||
.value-header,
|
.value-header,
|
||||||
.and-or-header {
|
.and-or-header {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-background {
|
.header-background {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*.type-header {
|
/*.type-header {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
@@ -410,112 +409,111 @@ input::-webkit-inner-spin-button {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
.clause-table-field[readonly] {
|
.clause-table-field[readonly] {
|
||||||
background-color: #EEEEF2;
|
background-color: #eeeef2;
|
||||||
/*[{plugin-table-header-background-color}]*/
|
/*[{plugin-table-header-background-color}]*/
|
||||||
border: 1px solid #CCCEDB;
|
border: 1px solid #cccedb;
|
||||||
/*[{plugin-table-border-color}]*/
|
/*[{plugin-table-border-color}]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.addClause-title {
|
.addClause-title {
|
||||||
/*[{common-common-controls-button-border-hover}]*/
|
/*[{common-common-controls-button-border-hover}]*/
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: -5px;
|
margin-left: -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addClause {
|
.addClause {
|
||||||
width: 125px;
|
width: 125px;
|
||||||
padding: 8px 0px 5px 5px;
|
padding: 8px 0px 5px 5px;
|
||||||
border: 1px solid #fff;
|
border: 1px solid #fff;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addClause:hover {
|
.addClause:hover {
|
||||||
.hover();
|
.hover();
|
||||||
}
|
}
|
||||||
|
|
||||||
.addClause:active {
|
.addClause:active {
|
||||||
.active();
|
.active();
|
||||||
border: 1px dashed @AccentMedium;
|
border: 1px dashed @AccentMedium;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-row {
|
.clause-table-row {
|
||||||
min-width: 550px;
|
min-width: 550px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-field field-column {
|
.clause-table-field field-column {
|
||||||
min-width: 75px;
|
min-width: 75px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clause-table-field field-input {
|
.clause-table-field field-input {
|
||||||
min-width: 54px;
|
min-width: 54px;
|
||||||
margin-left: -78px;
|
margin-left: -78px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .row .spacing {
|
.query-panel .row .spacing {
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-panel .divider.horizontal {
|
.query-panel .divider.horizontal {
|
||||||
height: 10px;
|
height: 10px;
|
||||||
width: 100%
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-div {
|
.inline-div {
|
||||||
display: inline
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.querybuilder-addpropertyImg,
|
.querybuilder-addpropertyImg,
|
||||||
.querybuilder-cancelImg {
|
.querybuilder-cancelImg {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addclauseProperty-Img {
|
.addclauseProperty-Img {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-Add-Cancel {
|
.entity-Add-Cancel {
|
||||||
padding: @DefaultSpace @SmallSpace @SmallSpace;
|
// padding: @DefaultSpace @SmallSpace @SmallSpace;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-Add-Cancel:hover {
|
.entity-Add-Cancel:hover {
|
||||||
.hover();
|
.hover();
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-Add-Cancel:active {
|
.entity-Add-Cancel:active {
|
||||||
.active();
|
.active();
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-builder-isDisabled {
|
.query-builder-isDisabled {
|
||||||
border: 1px solid #CCCEDB;
|
border: 1px solid #cccedb;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-value-text {
|
.edit-value-text {
|
||||||
padding-left: @DefaultSpace;
|
padding-left: @DefaultSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-triangle {
|
.expand-triangle {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-triangle-right {
|
.expand-triangle-right {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@media only screen and (max-width: 1200px) {
|
@media only screen and (max-width: 1200px) {
|
||||||
.clause-table {
|
.clause-table {
|
||||||
|
|||||||
@@ -73,6 +73,17 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
|||||||
|
|
||||||
const sectionStackTokens: IStackTokens = { childrenGap: 12 };
|
const sectionStackTokens: IStackTokens = { childrenGap: 12 };
|
||||||
|
|
||||||
|
const handleKeyPress = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
|
if (event.key === "Enter" || event.key === "Space") {
|
||||||
|
onEditEntity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleKeyPressdelete = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
|
if (event.key === "Enter" || event.key === "Space") {
|
||||||
|
onDeleteEntity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getEntityValueType = (): string => {
|
const getEntityValueType = (): string => {
|
||||||
const { Int, Smallint, Tinyint } = CassandraType;
|
const { Int, Smallint, Tinyint } = CassandraType;
|
||||||
const { Double, Int32, Int64 } = TableType;
|
const { Double, Int32, Int64 } = TableType;
|
||||||
@@ -126,12 +137,28 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
|||||||
/>
|
/>
|
||||||
{!isEntityValueDisable && (
|
{!isEntityValueDisable && (
|
||||||
<TooltipHost content="Edit property" id="editTooltip">
|
<TooltipHost content="Edit property" id="editTooltip">
|
||||||
<Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
|
<Image
|
||||||
|
{...imageProps}
|
||||||
|
src={EditIcon}
|
||||||
|
alt="editEntity"
|
||||||
|
id="editEntity"
|
||||||
|
onClick={onEditEntity}
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyPress={handleKeyPress}
|
||||||
|
/>
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
)}
|
)}
|
||||||
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
|
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
|
||||||
<TooltipHost content="Delete property" id="deleteTooltip">
|
<TooltipHost content="Delete property" id="deleteTooltip">
|
||||||
<Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />
|
<Image
|
||||||
|
{...imageProps}
|
||||||
|
src={DeleteIcon}
|
||||||
|
alt="delete entity"
|
||||||
|
id="deleteEntity"
|
||||||
|
onClick={onDeleteEntity}
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyPress={handleKeyPressdelete}
|
||||||
|
/>
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
|
|||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<TooltipHost content={children}>
|
<TooltipHost content={children}>
|
||||||
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationCons
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { getEntityName } from "../DocumentUtility";
|
import { getEntityName } from "../DocumentUtility";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { getPartitionKeyValue } from "./getPartitionKeyValue";
|
||||||
|
|
||||||
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
|
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
|
||||||
const entityName: string = getEntityName();
|
const entityName: string = getEntityName();
|
||||||
@@ -13,7 +14,7 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
|
|||||||
await client()
|
await client()
|
||||||
.database(collection.databaseId)
|
.database(collection.databaseId)
|
||||||
.container(collection.id())
|
.container(collection.id())
|
||||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
.item(documentId.id(), getPartitionKeyValue(documentId))
|
||||||
.delete();
|
.delete();
|
||||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
12
src/Common/dataAccess/getPartitionKeyValue.ts
Normal file
12
src/Common/dataAccess/getPartitionKeyValue.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { userContext } from "UserContext";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
|
export const getPartitionKeyValue = (documentId: DocumentId) => {
|
||||||
|
if (userContext.apiType === "Tables" && documentId.partitionKeyValue?.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (documentId.partitionKeyValue?.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return documentId.partitionKeyValue;
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import { HttpHeaders } from "../Constants";
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { getEntityName } from "../DocumentUtility";
|
import { getEntityName } from "../DocumentUtility";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { getPartitionKeyValue } from "./getPartitionKeyValue";
|
||||||
|
|
||||||
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
|
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
@@ -21,8 +22,7 @@ export const readDocument = async (collection: CollectionBase, documentId: Docum
|
|||||||
const response = await client()
|
const response = await client()
|
||||||
.database(collection.databaseId)
|
.database(collection.databaseId)
|
||||||
.container(collection.id())
|
.container(collection.id())
|
||||||
// use undefined if the partitionKeyValue is empty
|
.item(documentId.id(), getPartitionKeyValue(documentId))
|
||||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
|
||||||
.read(options);
|
.read(options);
|
||||||
|
|
||||||
return response?.resource;
|
return response?.resource;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationCons
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { getEntityName } from "../DocumentUtility";
|
import { getEntityName } from "../DocumentUtility";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { getPartitionKeyValue } from "./getPartitionKeyValue";
|
||||||
|
|
||||||
export const updateDocument = async (
|
export const updateDocument = async (
|
||||||
collection: CollectionBase,
|
collection: CollectionBase,
|
||||||
@@ -25,7 +26,7 @@ export const updateDocument = async (
|
|||||||
const response = await client()
|
const response = await client()
|
||||||
.database(collection.databaseId)
|
.database(collection.databaseId)
|
||||||
.container(collection.id())
|
.container(collection.id())
|
||||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
.item(documentId.id(), getPartitionKeyValue(documentId))
|
||||||
.replace(newDocument, options);
|
.replace(newDocument, options);
|
||||||
|
|
||||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export interface DatabaseAccountExtendedProperties {
|
|||||||
capacity?: { totalThroughputLimit: number };
|
capacity?: { totalThroughputLimit: number };
|
||||||
locations?: DatabaseAccountResponseLocation[];
|
locations?: DatabaseAccountResponseLocation[];
|
||||||
postgresqlEndpoint?: string;
|
postgresqlEndpoint?: string;
|
||||||
|
publicNetworkAccess?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountResponseLocation {
|
export interface DatabaseAccountResponseLocation {
|
||||||
@@ -567,6 +568,16 @@ export interface ContainerConnectionInfo {
|
|||||||
//need to add ram and rom info
|
//need to add ram and rom info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PostgresFirewallRule {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
startIpAddress: string;
|
||||||
|
endIpAddress: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export enum PhoenixErrorType {
|
export enum PhoenixErrorType {
|
||||||
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
|
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
|
||||||
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
|
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export enum MessageTypes {
|
|||||||
CloseTab,
|
CloseTab,
|
||||||
OpenQuickstartBlade,
|
OpenQuickstartBlade,
|
||||||
OpenPostgreSQLPasswordReset,
|
OpenPostgreSQLPasswordReset,
|
||||||
|
OpenPostgresNetworkingBlade,
|
||||||
|
OpenCosmosDBNetworkingBlade,
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Versions, ActionContracts, Diagnostics };
|
export { Versions, ActionContracts, Diagnostics };
|
||||||
|
|||||||
@@ -186,7 +186,6 @@ export interface Collection extends CollectionBase {
|
|||||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
|
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
|
||||||
|
|
||||||
getLabel(): string;
|
|
||||||
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,6 +396,7 @@ export interface DataExplorerInputsFrame {
|
|||||||
dataExplorerVersion?: string;
|
dataExplorerVersion?: string;
|
||||||
defaultCollectionThroughput?: CollectionCreationDefaults;
|
defaultCollectionThroughput?: CollectionCreationDefaults;
|
||||||
isPostgresAccount?: boolean;
|
isPostgresAccount?: boolean;
|
||||||
|
isReplica?: boolean;
|
||||||
// TODO: Update this param in the OSS extension to remove isFreeTier, isMarlinServerGroup, and make nodes a flat array instead of an nested array
|
// TODO: Update this param in the OSS extension to remove isFreeTier, isMarlinServerGroup, and make nodes a flat array instead of an nested array
|
||||||
connectionStringParams?: any;
|
connectionStringParams?: any;
|
||||||
flights?: readonly string[];
|
flights?: readonly string[];
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
||||||
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
||||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "postgresql")) {
|
} else if (StringUtils.endsWith(notebookServerEndpoint, "postgresql")) {
|
||||||
return "c.vimeng-postgre-citus.postgres.database.azure.com";
|
return this.props.databaseAccount?.properties.postgresqlEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (terminalEndpoint) {
|
if (terminalEndpoint) {
|
||||||
|
|||||||
@@ -344,13 +344,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
>
|
>
|
||||||
<StyledIconBase
|
<StyledIconBase
|
||||||
ariaLabel="Info"
|
ariaLabel="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage."
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
ariaLabel="Info"
|
ariaLabel="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage."
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
@@ -630,7 +630,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Info"
|
aria-label="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage."
|
||||||
className="panelInfoIcon root-57"
|
className="panelInfoIcon root-57"
|
||||||
data-icon-name="Info"
|
data-icon-name="Info"
|
||||||
role="img"
|
role="img"
|
||||||
@@ -1331,13 +1331,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
>
|
>
|
||||||
<StyledIconBase
|
<StyledIconBase
|
||||||
ariaLabel="Info"
|
ariaLabel="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
ariaLabel="Info"
|
ariaLabel="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
@@ -1617,7 +1617,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Info"
|
aria-label="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
|
||||||
className="panelInfoIcon root-57"
|
className="panelInfoIcon root-57"
|
||||||
data-icon-name="Info"
|
data-icon-name="Info"
|
||||||
role="img"
|
role="img"
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
jest.mock("../hooks/useFullScreenURLs");
|
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
|
|
||||||
import { OpenFullScreen } from "./OpenFullScreen";
|
import { OpenFullScreen } from "./OpenFullScreen";
|
||||||
|
|
||||||
it("renders the correct URLs", () => {
|
it("renders the correct URLs", () => {
|
||||||
(useFullScreenURLs as jest.Mock).mockReturnValue({
|
|
||||||
readWrite: "read and write url",
|
|
||||||
read: "read only url",
|
|
||||||
});
|
|
||||||
|
|
||||||
render(<OpenFullScreen />);
|
render(<OpenFullScreen />);
|
||||||
expect(screen.getByLabelText("Read and Write")).toHaveValue("https://cosmos.azure.com/?key=read and write url");
|
expect(screen.getByText("Open")).toBeDefined();
|
||||||
expect(screen.getByLabelText("Read Only")).toHaveValue("https://cosmos.azure.com/?key=read only url");
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,66 +1,26 @@
|
|||||||
import { DefaultButton, PrimaryButton, Spinner, Stack, Text, TextField } from "@fluentui/react";
|
import { PrimaryButton, Stack, Text } from "@fluentui/react";
|
||||||
import copyToClipboard from "clipboard-copy";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
|
|
||||||
|
|
||||||
export const OpenFullScreen: React.FunctionComponent = () => {
|
export const OpenFullScreen: React.FunctionComponent = () => {
|
||||||
const [isReadUrlCopy, setIsReadUrlCopy] = React.useState<boolean>(false);
|
|
||||||
const [isReadWriteUrlCopy, setIsReadWriteUrlCopy] = React.useState<boolean>(false);
|
|
||||||
const result = useFullScreenURLs();
|
|
||||||
if (!result) {
|
|
||||||
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const readWriteUrl = `https://cosmos.azure.com/?key=${result.readWrite}`;
|
|
||||||
const readUrl = `https://cosmos.azure.com/?key=${result.read}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
<div style={{ padding: "34px" }}>
|
||||||
<Text>
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
Open this database account in a new browser tab with Cosmos DB Explorer. Or copy the read-write or read only
|
<Text>
|
||||||
access urls below to share with others. For security purposes, the URLs grant time-bound access to the
|
Open this database account in a new browser tab with Cosmos DB Explorer. You can connect using your
|
||||||
account. When access expires, you can reconnect, using a valid connection string for the account.
|
Microsoft account or a connection string.
|
||||||
</Text>
|
</Text>
|
||||||
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
|
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
<PrimaryButton
|
||||||
<DefaultButton
|
onClick={() => {
|
||||||
ariaLabel={isReadWriteUrlCopy ? "Copied url" : "Copy"}
|
window.open("https://cosmos.azure.com/", "_blank");
|
||||||
onClick={() => {
|
}}
|
||||||
copyToClipboard(readWriteUrl);
|
text="Open"
|
||||||
setIsReadWriteUrlCopy(true);
|
iconProps={{ iconName: "OpenInNewWindow" }}
|
||||||
}}
|
/>
|
||||||
text={isReadWriteUrlCopy ? "Copied" : "Copy"}
|
</Stack>
|
||||||
iconProps={{ iconName: "Copy" }}
|
|
||||||
/>
|
|
||||||
<PrimaryButton
|
|
||||||
onClick={() => {
|
|
||||||
window.open(readWriteUrl, "_blank");
|
|
||||||
}}
|
|
||||||
text="Open"
|
|
||||||
iconProps={{ iconName: "OpenInNewWindow" }}
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
<TextField label="Read Only" readOnly defaultValue={readUrl} />
|
</div>
|
||||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
|
||||||
<DefaultButton
|
|
||||||
ariaLabel={isReadUrlCopy ? "Copied url" : "Copy"}
|
|
||||||
onClick={() => {
|
|
||||||
setIsReadUrlCopy(true);
|
|
||||||
copyToClipboard(readUrl);
|
|
||||||
}}
|
|
||||||
text={isReadUrlCopy ? "Copied" : "Copy"}
|
|
||||||
iconProps={{ iconName: "Copy" }}
|
|
||||||
/>
|
|
||||||
<PrimaryButton
|
|
||||||
onClick={() => {
|
|
||||||
window.open(readUrl, "_blank");
|
|
||||||
}}
|
|
||||||
text="Open"
|
|
||||||
iconProps={{ iconName: "OpenInNewWindow" }}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -242,6 +242,11 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
submitButtonText: getButtonLabel(userContext.apiType),
|
submitButtonText: getButtonLabel(userContext.apiType),
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
const handlekeypressaddentity = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
|
if (event.key === "Enter" || event.key === "Space") {
|
||||||
|
addNewEntity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
@@ -284,7 +289,13 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{userContext.apiType !== "Cassandra" && (
|
{userContext.apiType !== "Cassandra" && (
|
||||||
<Stack horizontal onClick={addNewEntity} className="addButtonEntiy">
|
<Stack
|
||||||
|
horizontal
|
||||||
|
onClick={addNewEntity}
|
||||||
|
className="addButtonEntiy"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyPress={handlekeypressaddentity}
|
||||||
|
>
|
||||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" />
|
<Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" />
|
||||||
<Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text>
|
<Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -29,10 +29,14 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
className="addButtonEntiy"
|
className="addButtonEntiy"
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
onKeyPress={[Function]}
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-Stack addButtonEntiy css-53"
|
className="ms-Stack addButtonEntiy css-53"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
onKeyPress={[Function]}
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<StyledImageBase
|
<StyledImageBase
|
||||||
alt="Add Entity"
|
alt="Add Entity"
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const getDescriptionText = (page: number): string => {
|
|||||||
case 1:
|
case 1:
|
||||||
return "Azure Cosmos DB is a fully managed NoSQL database service for modern app development. ";
|
return "Azure Cosmos DB is a fully managed NoSQL database service for modern app development. ";
|
||||||
case 2:
|
case 2:
|
||||||
return "Launch the quickstart for a tutotrial to learn how to create a database, add sample data, connect to a sample app and more.";
|
return "Launch the quickstart for a tutorial to learn how to create a database, add sample data, connect to a sample app and more.";
|
||||||
case 3:
|
case 3:
|
||||||
return "Already have an existing app? Connect your database to an app, or tooling of your choice from Data Explorer.";
|
return "Already have an existing app? Connect your database to an app, or tooling of your choice from Data Explorer.";
|
||||||
default:
|
default:
|
||||||
|
|||||||
22
src/Explorer/Quickstart/QuickstartFirewallNotification.tsx
Normal file
22
src/Explorer/Quickstart/QuickstartFirewallNotification.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Image, PrimaryButton, Stack, Text } from "@fluentui/react";
|
||||||
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
|
import React from "react";
|
||||||
|
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
||||||
|
|
||||||
|
export const QuickstartFirewallNotification: React.FC = (): JSX.Element => (
|
||||||
|
<Stack style={{ padding: "16px 20px" }}>
|
||||||
|
<Text block>
|
||||||
|
To use the PostgreSQL shell, you need to add a firewall rule to allow access from all IP addresses
|
||||||
|
(0.0.0.0-255.255.255).
|
||||||
|
</Text>
|
||||||
|
<Text block>We strongly recommend removing this rule once you finish using the PostgreSQL shell.</Text>
|
||||||
|
<Image style={{ margin: "20px 0" }} src={FirewallRuleScreenshot} />
|
||||||
|
<PrimaryButton
|
||||||
|
style={{ width: 150 }}
|
||||||
|
onClick={() => sendMessage({ type: MessageTypes.OpenPostgresNetworkingBlade })}
|
||||||
|
>
|
||||||
|
Add firewall rule
|
||||||
|
</PrimaryButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
@@ -96,12 +96,18 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
|||||||
<Stack style={{ flexGrow: 1, padding: "0 20px", overflow: "auto" }}>
|
<Stack style={{ flexGrow: 1, padding: "0 20px", overflow: "auto" }}>
|
||||||
<Text variant="xxLarge">Quick start guide</Text>
|
<Text variant="xxLarge">Quick start guide</Text>
|
||||||
{currentStep < 5 && (
|
{currentStep < 5 && (
|
||||||
<Pivot style={{ marginTop: 10, width: "100%" }} selectedKey={GuideSteps[currentStep]}>
|
<Pivot
|
||||||
|
style={{ marginTop: 10, width: "100%" }}
|
||||||
|
selectedKey={GuideSteps[currentStep]}
|
||||||
|
onLinkClick={(item?: PivotItem) => setCurrentStep(Object.values(GuideSteps).indexOf(item.props.itemKey))}
|
||||||
|
>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
headerText="Login"
|
headerText="Login"
|
||||||
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 0)}
|
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 0)}
|
||||||
itemKey={GuideSteps[0]}
|
itemKey={GuideSteps[0]}
|
||||||
onClick={() => setCurrentStep(0)}
|
onClick={() => {
|
||||||
|
setCurrentStep(0);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Stack style={{ marginTop: 20 }}>
|
<Stack style={{ marginTop: 20 }}>
|
||||||
<Text>
|
<Text>
|
||||||
@@ -109,8 +115,12 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
To begin, please enter the cluster's password in the PostgreSQL terminal.
|
To begin, please enter the cluster's password in the PostgreSQL terminal.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Note: If you navigate out of the Quick Start tab (PostgreSQL Shell), the session will be closed and
|
||||||
|
all ongoing commands might be interrupted.
|
||||||
</Text>
|
</Text>
|
||||||
<Youtube videoId="UaBDXHMQAUw" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="nT64dFSfiUo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
@@ -120,7 +130,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
|||||||
onClick={() => setCurrentStep(1)}
|
onClick={() => setCurrentStep(1)}
|
||||||
>
|
>
|
||||||
<Stack style={{ marginTop: 20 }}>
|
<Stack style={{ marginTop: 20 }}>
|
||||||
<Text>Let’s create two tables github_users and github_events in “cosmosdb_tutorial” schema.</Text>
|
<Text>Let's create two tables github_users and github_events in “cosmosdb_tutorial” schema.</Text>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
style={{ marginTop: 16, width: 150 }}
|
style={{ marginTop: 16, width: 150 }}
|
||||||
onClick={() => useTerminal.getState().sendMessage(newTableCommand)}
|
onClick={() => useTerminal.getState().sendMessage(newTableCommand)}
|
||||||
@@ -150,7 +160,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
|||||||
onClick={() => onCopyBtnClicked("#newTableCommand")}
|
onClick={() => onCopyBtnClicked("#newTableCommand")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Youtube videoId="VJqupvSQ-mw" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="il_sA6U1WcY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
@@ -195,7 +205,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
|||||||
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
|
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Youtube videoId="Q-AW7q1GLDY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="kCCDRRrN1r0" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
@@ -235,7 +245,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
|||||||
onClick={() => onCopyBtnClicked("#loadDataCommand")}
|
onClick={() => onCopyBtnClicked("#loadDataCommand")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Youtube videoId="h15fvLKXzRo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="XSMEE2tujEk" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
@@ -277,7 +287,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
|||||||
onClick={() => onCopyBtnClicked("#queryCommand")}
|
onClick={() => onCopyBtnClicked("#queryCommand")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Youtube videoId="p46nRnE4b8Y" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="k_EanjMtaPg" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
</Pivot>
|
</Pivot>
|
||||||
|
|||||||
@@ -14,10 +14,6 @@
|
|||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
max-width: 1168px;
|
max-width: 1168px;
|
||||||
|
|
||||||
> * {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .title {
|
> .title {
|
||||||
position: relative; // To attach FeaturePanelLauncher as absolute
|
position: relative; // To attach FeaturePanelLauncher as absolute
|
||||||
color: @BaseHigh;
|
color: @BaseHigh;
|
||||||
|
|||||||
@@ -186,7 +186,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
headline="Create your password"
|
headline="Create your password"
|
||||||
target={"#mainButton-quickstartDescription"}
|
target={"#mainButton-quickstartDescription"}
|
||||||
hasCloseButton
|
hasCloseButton
|
||||||
onDismiss={() => usePostgres.getState().setShowResetPasswordBubble(false)}
|
onDismiss={() => {
|
||||||
|
localStorage.setItem(userContext.databaseAccount.id, "true");
|
||||||
|
usePostgres.getState().setShowResetPasswordBubble(false);
|
||||||
|
}}
|
||||||
calloutProps={{
|
calloutProps={{
|
||||||
directionalHint: DirectionalHint.bottomRightEdge,
|
directionalHint: DirectionalHint.bottomRightEdge,
|
||||||
directionalHintFixed: true,
|
directionalHintFixed: true,
|
||||||
@@ -197,14 +200,15 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
primaryButtonProps={{
|
primaryButtonProps={{
|
||||||
text: "Create",
|
text: "Create",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
localStorage.setItem(userContext.databaseAccount.id, "true");
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: MessageTypes.OpenQuickstartBlade,
|
type: MessageTypes.OpenPostgreSQLPasswordReset,
|
||||||
});
|
});
|
||||||
usePostgres.getState().setShowResetPasswordBubble(false);
|
usePostgres.getState().setShowResetPasswordBubble(false);
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
This password will be used to connect to the database.
|
If you haven't changed your password yet, change it now.
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -300,7 +304,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
public createMainItems(): SplashScreenItem[] {
|
public createMainItems(): SplashScreenItem[] {
|
||||||
const heroes: SplashScreenItem[] = [];
|
const heroes: SplashScreenItem[] = [];
|
||||||
|
|
||||||
if (userContext.apiType === "SQL" || userContext.apiType === "Mongo" || userContext.apiType === "Postgres") {
|
if (
|
||||||
|
userContext.apiType === "SQL" ||
|
||||||
|
userContext.apiType === "Mongo" ||
|
||||||
|
(userContext.apiType === "Postgres" && !userContext.isReplica)
|
||||||
|
) {
|
||||||
const launchQuickstartBtn = {
|
const launchQuickstartBtn = {
|
||||||
id: "quickstartDescription",
|
id: "quickstartDescription",
|
||||||
iconSrc: QuickStartIcon,
|
iconSrc: QuickStartIcon,
|
||||||
@@ -347,10 +355,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
|
|
||||||
const connectBtn = {
|
const connectBtn = {
|
||||||
iconSrc: ConnectIcon,
|
iconSrc: ConnectIcon,
|
||||||
title: userContext.apiType === "Postgres" ? "Connect with PG Admin" : "Connect",
|
title: userContext.apiType === "Postgres" ? "Connect with pgAdmin" : "Connect",
|
||||||
description:
|
description:
|
||||||
userContext.apiType === "Postgres"
|
userContext.apiType === "Postgres"
|
||||||
? "Prefer PGadmin? Find your connection strings here"
|
? "Prefer pgAdmin? Find your connection strings here"
|
||||||
: "Prefer using your own choice of tooling? Find the connection string you need to connect",
|
: "Prefer using your own choice of tooling? Find the connection string you need to connect",
|
||||||
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
|
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ export default class QueryBuilderViewModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onAddNewClauseKeyDown = (event: KeyboardEvent): boolean => {
|
public onAddNewClauseKeyDown = (event: KeyboardEvent): boolean => {
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
if (event.key === "Enter" || event.key === "Space") {
|
||||||
this.addClauseIndex(this.clauseArray().length - 1);
|
this.addClauseIndex(this.clauseArray().length - 1);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ You can enable or disable public IP addresses on the worker nodes on 'Networking
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Label>Secure connections</Label>
|
<Label>Secure connections</Label>
|
||||||
<Text>
|
<Text style={{ marginBottom: 8 }}>
|
||||||
Only secure connections are supported. For production use cases, we recommend using the 'verify-full'
|
Only secure connections are supported. For production use cases, we recommend using the 'verify-full'
|
||||||
mode to enforce TLS certificate verification. You will need to download the Hyperscale (Citus) certificate, and
|
mode to enforce TLS certificate verification. You will need to download the Hyperscale (Citus) certificate, and
|
||||||
provide it when connecting to the database.{" "}
|
provide it when connecting to the database.{" "}
|
||||||
@@ -128,6 +128,18 @@ You can enable or disable public IP addresses on the worker nodes on 'Networking
|
|||||||
Learn more
|
Learn more
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<Label>Connect with pgAdmin</Label>
|
||||||
|
<Text>
|
||||||
|
Refer to our{" "}
|
||||||
|
<Link
|
||||||
|
href="https://learn.microsoft.com/en-us/azure/postgresql/hyperscale/howto-connect?tabs=pgadmin"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
guide
|
||||||
|
</Link>{" "}
|
||||||
|
to help you connect via pgAdmin.
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -70,24 +70,19 @@
|
|||||||
<tbody data-bind="template: { name: 'queryClause-template', foreach: clauseArray, as: 'clause' }"></tbody>
|
<tbody data-bind="template: { name: 'queryClause-template', foreach: clauseArray, as: 'clause' }"></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<button
|
||||||
class="addClause"
|
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }"
|
||||||
role="button"
|
style="border: none; background: none"
|
||||||
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }, attr: { title: addNewClauseLine }"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<div class="addClause-heading">
|
<div class="addClause" data-bind=" ">
|
||||||
<span class="clause-table addClause-title">
|
<div class="addClause-heading">
|
||||||
<img
|
<span class="clause-table addClause-title">
|
||||||
class="addclauseProperty-Img"
|
<img class="addclauseProperty-Img" style="margin-bottom: 5px" src="/Add-property.svg" />
|
||||||
style="margin-bottom: 5px"
|
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span>
|
||||||
src="/Add-property.svg"
|
</span>
|
||||||
alt="Add new clause"
|
</div>
|
||||||
/>
|
|
||||||
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Tables Query Tab Query Helper - End-->
|
<!-- Tables Query Tab Query Helper - End-->
|
||||||
@@ -168,22 +163,20 @@
|
|||||||
<script type="text/html" id="queryClause-template">
|
<script type="text/html" id="queryClause-template">
|
||||||
<tr class="clause-table-row">
|
<tr class="clause-table-row">
|
||||||
<td class="clause-table-cell action-column">
|
<td class="clause-table-cell action-column">
|
||||||
<span
|
<button
|
||||||
class="entity-Add-Cancel"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="click: $parent.addClauseIndex.bind($data, $index()), event: { keydown: $parent.onAddClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.insertNewFilterLine}"
|
data-bind="click: $parent.addClauseIndex.bind($data, $index()), event: { keydown: $parent.onAddClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.insertNewFilterLine}"
|
||||||
>
|
>
|
||||||
<img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" />
|
<span class="entity-Add-Cancel" role="button">
|
||||||
</span>
|
<img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" />
|
||||||
<span
|
</span>
|
||||||
class="entity-Add-Cancel"
|
</button>
|
||||||
role="button"
|
<button
|
||||||
tabindex="0"
|
|
||||||
data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}"
|
data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}"
|
||||||
>
|
>
|
||||||
<img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" />
|
<span class="entity-Add-Cancel" role="button">
|
||||||
</span>
|
<img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td class="clause-table-cell group-control-column">
|
<td class="clause-table-cell group-control-column">
|
||||||
<input type="checkbox" aria-label="And/Or" data-bind="checked: checkedForGrouping" />
|
<input type="checkbox" aria-label="And/Or" data-bind="checked: checkedForGrouping" />
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
|
import { Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
||||||
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
|
import { configContext } from "ConfigContext";
|
||||||
|
import { NotebookWorkspaceConnectionInfo, PostgresFirewallRule } from "Contracts/DataModels";
|
||||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
|
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||||
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
|
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
|
||||||
import React, { useEffect } from "react";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
|
import { armRequest } from "Utils/arm/request";
|
||||||
|
|
||||||
interface QuickstartTabProps {
|
interface QuickstartTabProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -13,30 +17,67 @@ interface QuickstartTabProps {
|
|||||||
|
|
||||||
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
|
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
|
||||||
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
|
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
|
||||||
useEffect(() => {
|
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
|
||||||
explorer.allocateContainer();
|
|
||||||
}, []);
|
|
||||||
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
||||||
authToken: notebookServerInfo.authToken,
|
authToken: notebookServerInfo.authToken,
|
||||||
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
|
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
|
||||||
forwardingId: notebookServerInfo.forwardingId,
|
forwardingId: notebookServerInfo.forwardingId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const checkFirewallRules = async (): Promise<void> => {
|
||||||
|
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const response: any = await armRequest({
|
||||||
|
host: configContext.ARM_ENDPOINT,
|
||||||
|
path: firewallRulesUri,
|
||||||
|
method: "GET",
|
||||||
|
apiVersion: "2020-10-05-privatepreview",
|
||||||
|
});
|
||||||
|
const firewallRules: PostgresFirewallRule[] = response?.data?.value || response?.value || [];
|
||||||
|
const isEnabled = firewallRules.some(
|
||||||
|
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
|
||||||
|
);
|
||||||
|
setIsAllPublicIPAddressEnabled(isEnabled);
|
||||||
|
|
||||||
|
// If the firewall rule is not added, check every 30 seconds to see if the user has added the rule
|
||||||
|
if (!isEnabled && useTabs.getState().activeReactTab === ReactTabKind.Quickstart) {
|
||||||
|
setTimeout(checkFirewallRules, 30000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkFirewallRules();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
explorer.allocateContainer();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack style={{ width: "100%" }} horizontal>
|
<Stack style={{ width: "100%" }} horizontal>
|
||||||
<Stack style={{ width: "50%" }}>
|
<Stack style={{ width: "50%" }}>
|
||||||
<QuickstartGuide />
|
<QuickstartGuide />
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
|
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
|
||||||
{notebookServerInfo?.notebookServerEndpoint && (
|
{!isAllPublicIPAddressEnabled && <QuickstartFirewallNotification />}
|
||||||
|
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
|
||||||
<NotebookTerminalComponent
|
<NotebookTerminalComponent
|
||||||
notebookServerInfo={getNotebookServerInfo()}
|
notebookServerInfo={getNotebookServerInfo()}
|
||||||
databaseAccount={userContext.databaseAccount}
|
databaseAccount={userContext.databaseAccount}
|
||||||
tabId="QuickstartPSQLShell"
|
tabId="QuickstartPSQLShell"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!notebookServerInfo?.notebookServerEndpoint && (
|
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
|
||||||
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
<Stack style={{ margin: "auto 0" }}>
|
||||||
|
<Text block style={{ margin: "auto" }}>
|
||||||
|
Connecting to the PostgreSQL shell.
|
||||||
|
</Text>
|
||||||
|
<Text block style={{ margin: "auto" }}>
|
||||||
|
If the cluster was just created, this could take up to a minute.
|
||||||
|
</Text>
|
||||||
|
<Spinner styles={{ root: { marginTop: 16 } }} size={SpinnerSize.large}></Spinner>
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
|
||||||
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import { CollectionTabKind } from "Contracts/ViewModels";
|
import { CollectionTabKind } from "Contracts/ViewModels";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
||||||
@@ -21,10 +24,24 @@ interface TabsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
||||||
const { openedTabs, openedReactTabs, activeTab, activeReactTab } = useTabs();
|
const { openedTabs, openedReactTabs, activeTab, activeReactTab, showNetworkSettingsWarning } = useTabs();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tabsManagerContainer">
|
<div className="tabsManagerContainer">
|
||||||
|
{showNetworkSettingsWarning && (
|
||||||
|
<MessageBar
|
||||||
|
messageBarType={MessageBarType.warning}
|
||||||
|
actions={
|
||||||
|
<MessageBarButton onClick={() => sendMessage({ type: MessageTypes.OpenPostgresNetworkingBlade })}>
|
||||||
|
Change network settings
|
||||||
|
</MessageBarButton>
|
||||||
|
}
|
||||||
|
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||||
|
>
|
||||||
|
The Network settings for this account are preventing access from Data Explorer. Please allow access from Azure
|
||||||
|
Portal to proceed.
|
||||||
|
</MessageBar>
|
||||||
|
)}
|
||||||
<div id="content" className="flexContainer hideOverflows">
|
<div id="content" className="flexContainer hideOverflows">
|
||||||
<div className="nav-tabs-margin">
|
<div className="nav-tabs-margin">
|
||||||
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Spinner, SpinnerSize } from "@fluentui/react";
|
import { Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
|
import { configContext } from "ConfigContext";
|
||||||
|
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { armRequest } from "Utils/arm/request";
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -26,10 +29,15 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
constructor(
|
constructor(
|
||||||
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
||||||
private getDatabaseAccount: () => DataModels.DatabaseAccount,
|
private getDatabaseAccount: () => DataModels.DatabaseAccount,
|
||||||
private getTabId: () => string
|
private getTabId: () => string,
|
||||||
|
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
|
if (!this.isAllPublicIPAddressesEnabled()) {
|
||||||
|
return <QuickstartFirewallNotification />;
|
||||||
|
}
|
||||||
|
|
||||||
return this.parameters() ? (
|
return this.parameters() ? (
|
||||||
<NotebookTerminalComponent
|
<NotebookTerminalComponent
|
||||||
notebookServerInfo={this.getNotebookServerInfo()}
|
notebookServerInfo={this.getNotebookServerInfo()}
|
||||||
@@ -46,25 +54,33 @@ export default class TerminalTab extends TabsBase {
|
|||||||
public readonly html = '<div style="height: 100%" data-bind="react:notebookTerminalComponentAdapter"></div> ';
|
public readonly html = '<div style="height: 100%" data-bind="react:notebookTerminalComponentAdapter"></div> ';
|
||||||
private container: Explorer;
|
private container: Explorer;
|
||||||
private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter;
|
private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter;
|
||||||
|
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
|
||||||
|
|
||||||
constructor(options: TerminalTabOptions) {
|
constructor(options: TerminalTabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
|
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
||||||
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
||||||
() => this.getNotebookServerInfo(options),
|
() => this.getNotebookServerInfo(options),
|
||||||
() => userContext?.databaseAccount,
|
() => userContext?.databaseAccount,
|
||||||
() => this.tabId
|
() => this.tabId,
|
||||||
|
this.isAllPublicIPAddressesEnabled
|
||||||
);
|
);
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
if (
|
if (
|
||||||
this.isTemplateReady() &&
|
this.isTemplateReady() &&
|
||||||
useNotebook.getState().isNotebookEnabled &&
|
useNotebook.getState().isNotebookEnabled &&
|
||||||
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint
|
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint &&
|
||||||
|
this.isAllPublicIPAddressesEnabled()
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
||||||
|
this.checkPostgresFirewallRules();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
@@ -110,4 +126,25 @@ export default class TerminalTab extends TabsBase {
|
|||||||
forwardingId: info.forwardingId,
|
forwardingId: info.forwardingId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async checkPostgresFirewallRules(): Promise<void> {
|
||||||
|
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const response: any = await armRequest({
|
||||||
|
host: configContext.ARM_ENDPOINT,
|
||||||
|
path: firewallRulesUri,
|
||||||
|
method: "GET",
|
||||||
|
apiVersion: "2020-10-05-privatepreview",
|
||||||
|
});
|
||||||
|
const firewallRules: DataModels.PostgresFirewallRule[] = response?.data?.value || response?.value || [];
|
||||||
|
const isEnabled = firewallRules.some(
|
||||||
|
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
|
||||||
|
);
|
||||||
|
this.isAllPublicIPAddressesEnabled(isEnabled);
|
||||||
|
|
||||||
|
// If the firewall rule is not added, check every 30 seconds to see if the user has added the rule
|
||||||
|
if (!isEnabled) {
|
||||||
|
setTimeout(() => this.checkPostgresFirewallRules(), 30000);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1160,23 +1160,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.onDocumentDBDocumentsClick();
|
this.onDocumentDBDocumentsClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get correct collection label depending on account API
|
|
||||||
*/
|
|
||||||
public getLabel(): string {
|
|
||||||
if (userContext.apiType === "Tables") {
|
|
||||||
return "Entities";
|
|
||||||
} else if (userContext.apiType === "Cassandra") {
|
|
||||||
return "Rows";
|
|
||||||
} else if (userContext.apiType === "Gremlin") {
|
|
||||||
return "Graph";
|
|
||||||
} else if (userContext.apiType === "Mongo") {
|
|
||||||
return "Documents";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Items";
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDatabase(): ViewModels.Database {
|
public getDatabase(): ViewModels.Database {
|
||||||
return useDatabases.getState().findDatabaseWithId(this.databaseId);
|
return useDatabases.getState().findDatabaseWithId(this.databaseId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { getItemName } from "Utils/APITypeUtils";
|
||||||
import shallow from "zustand/shallow";
|
import shallow from "zustand/shallow";
|
||||||
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
||||||
import DeleteIcon from "../../../images/delete.svg";
|
import DeleteIcon from "../../../images/delete.svg";
|
||||||
@@ -497,7 +498,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
const buildCollectionNode = (database: ViewModels.Database, collection: ViewModels.Collection): TreeNode => {
|
const buildCollectionNode = (database: ViewModels.Database, collection: ViewModels.Collection): TreeNode => {
|
||||||
const children: TreeNode[] = [];
|
const children: TreeNode[] = [];
|
||||||
children.push({
|
children.push({
|
||||||
label: collection.getLabel(),
|
label: getItemName(),
|
||||||
id: collection.isSampleCollection ? "sampleItems" : "",
|
id: collection.isSampleCollection ? "sampleItems" : "",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
collection.openTab();
|
collection.openTab();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { getItemName } from "Utils/APITypeUtils";
|
||||||
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
||||||
import DeleteIcon from "../../../images/delete.svg";
|
import DeleteIcon from "../../../images/delete.svg";
|
||||||
import GalleryIcon from "../../../images/GalleryIcon.svg";
|
import GalleryIcon from "../../../images/GalleryIcon.svg";
|
||||||
@@ -254,7 +255,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
private buildCollectionNode(database: ViewModels.Database, collection: ViewModels.Collection): TreeNode {
|
private buildCollectionNode(database: ViewModels.Database, collection: ViewModels.Collection): TreeNode {
|
||||||
const children: TreeNode[] = [];
|
const children: TreeNode[] = [];
|
||||||
children.push({
|
children.push({
|
||||||
label: collection.getLabel(),
|
label: getItemName(),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
collection.openTab();
|
collection.openTab();
|
||||||
// push to most recent
|
// push to most recent
|
||||||
|
|||||||
@@ -45,7 +45,11 @@ export class PhoenixClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
||||||
return this.executeContainerAssignmentOperation(provisionData, "allocate");
|
return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), {
|
||||||
|
retries: 4,
|
||||||
|
maxTimeout: 20000,
|
||||||
|
minTimeout: 20000,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resetContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
public async resetContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
||||||
@@ -80,9 +84,12 @@ export class PhoenixClient {
|
|||||||
}
|
}
|
||||||
const phoenixError = responseJson as IPhoenixError;
|
const phoenixError = responseJson as IPhoenixError;
|
||||||
if (response.status === HttpStatusCodes.Forbidden) {
|
if (response.status === HttpStatusCodes.Forbidden) {
|
||||||
throw new Error(this.ConvertToForbiddenErrorString(phoenixError));
|
if (phoenixError.message === "Sequence contains no elements") {
|
||||||
|
throw Error("Phoenix container allocation failed, please try again later.");
|
||||||
|
}
|
||||||
|
throw new AbortError(this.ConvertToForbiddenErrorString(phoenixError));
|
||||||
}
|
}
|
||||||
throw new Error(phoenixError.message);
|
throw new AbortError(phoenixError.message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.status = response?.status;
|
error.status = response?.status;
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import { configContext } from "../../ConfigContext";
|
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export default class AuthHeadersUtil {
|
|
||||||
public static async generateEncryptedToken(readOnly: boolean = false): Promise<DataModels.GenerateTokenResponse> {
|
|
||||||
const url = configContext.BACKEND_ENDPOINT + "/api/tokens/generateToken" + AuthHeadersUtil._generateResourceUrl();
|
|
||||||
const headers: any = { authorization: userContext.authorizationToken };
|
|
||||||
headers[Constants.HttpHeaders.getReadOnlyKey] = readOnly;
|
|
||||||
|
|
||||||
const response = await fetch(url, { method: "POST", headers });
|
|
||||||
const result = await response.json();
|
|
||||||
// This API has a quirk where the response must be parsed to JSON twice
|
|
||||||
return JSON.parse(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _generateResourceUrl(): string {
|
|
||||||
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
|
|
||||||
const apiKind: DataModels.ApiKind = DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.apiType);
|
|
||||||
const accountEndpoint = databaseAccount?.properties?.documentEndpoint || "";
|
|
||||||
const sid = subscriptionId || "";
|
|
||||||
const rg = resourceGroup || "";
|
|
||||||
const dba = databaseAccount?.name || "";
|
|
||||||
const resourceUrl = encodeURIComponent(accountEndpoint);
|
|
||||||
const rid = "";
|
|
||||||
const rtype = "";
|
|
||||||
return `?resourceUrl=${resourceUrl}&rid=${rid}&rtype=${rtype}&sid=${sid}&rg=${rg}&dba=${dba}&api=${apiKind}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -81,6 +81,9 @@ export class JupyterLabAppFactory {
|
|||||||
// Attach the widget to the dom.
|
// Attach the widget to the dom.
|
||||||
Widget.attach(panel, document.body);
|
Widget.attach(panel, document.body);
|
||||||
|
|
||||||
|
// Switch focus to the terminal
|
||||||
|
term.activate();
|
||||||
|
|
||||||
// Handle resize events.
|
// Handle resize events.
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener("resize", () => {
|
||||||
panel.update();
|
panel.update();
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const createServerSettings = (props: TerminalProps): ServerConnection.ISettings
|
|||||||
return ServerConnection.makeSettings(options);
|
return ServerConnection.makeSettings(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const initTerminal = async (props: TerminalProps): Promise<ITerminalConnection> => {
|
const initTerminal = async (props: TerminalProps): Promise<ITerminalConnection | undefined> => {
|
||||||
// Initialize userContext (only properties which are needed by TelemetryProcessor)
|
// Initialize userContext (only properties which are needed by TelemetryProcessor)
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
subscriptionId: props.subscriptionId,
|
subscriptionId: props.subscriptionId,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ interface UserContext {
|
|||||||
partitionKey?: string;
|
partitionKey?: string;
|
||||||
};
|
};
|
||||||
readonly postgresConnectionStrParams?: PostgresConnectionStrParams;
|
readonly postgresConnectionStrParams?: PostgresConnectionStrParams;
|
||||||
|
readonly isReplica?: boolean;
|
||||||
collectionCreationDefaults: CollectionCreationDefaults;
|
collectionCreationDefaults: CollectionCreationDefaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,16 +110,19 @@ function updateUserContext(newContext: Partial<UserContext>): void {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!localStorage.getItem(newContext.databaseAccount.id)) {
|
if (!localStorage.getItem(newContext.databaseAccount.id)) {
|
||||||
if (newContext.apiType === "Postgres") {
|
if (newContext.isTryCosmosDBSubscription || isNewAccount) {
|
||||||
|
if (newContext.apiType === "Postgres" && !newContext.isReplica) {
|
||||||
|
usePostgres.getState().setShowResetPasswordBubble(true);
|
||||||
|
usePostgres.getState().setShowPostgreTeachingBubble(true);
|
||||||
|
} else {
|
||||||
|
useCarousel.getState().setShouldOpen(true);
|
||||||
|
localStorage.setItem(newContext.databaseAccount.id, "true");
|
||||||
|
traceOpen(Action.OpenCarousel);
|
||||||
|
}
|
||||||
|
} else if (newContext.apiType === "Postgres") {
|
||||||
usePostgres.getState().setShowPostgreTeachingBubble(true);
|
usePostgres.getState().setShowPostgreTeachingBubble(true);
|
||||||
localStorage.setItem(newContext.databaseAccount.id, "true");
|
localStorage.setItem(newContext.databaseAccount.id, "true");
|
||||||
}
|
}
|
||||||
if (userContext.isTryCosmosDBSubscription || isNewAccount) {
|
|
||||||
useCarousel.getState().setShouldOpen(true);
|
|
||||||
usePostgres.getState().setShowResetPasswordBubble(true);
|
|
||||||
localStorage.setItem(newContext.databaseAccount.id, "true");
|
|
||||||
traceOpen(Action.OpenCarousel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object.assign(userContext, newContext);
|
Object.assign(userContext, newContext);
|
||||||
@@ -129,30 +133,28 @@ function apiType(account: DatabaseAccount | undefined): ApiType {
|
|||||||
return "SQL";
|
return "SQL";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Postgres";
|
const capabilities = account.properties?.capabilities;
|
||||||
|
if (capabilities) {
|
||||||
// const capabilities = account.properties?.capabilities;
|
if (capabilities.find((c) => c.name === "EnableCassandra")) {
|
||||||
// if (capabilities) {
|
return "Cassandra";
|
||||||
// if (capabilities.find((c) => c.name === "EnableCassandra")) {
|
}
|
||||||
// return "Cassandra";
|
if (capabilities.find((c) => c.name === "EnableGremlin")) {
|
||||||
// }
|
return "Gremlin";
|
||||||
// if (capabilities.find((c) => c.name === "EnableGremlin")) {
|
}
|
||||||
// return "Gremlin";
|
if (capabilities.find((c) => c.name === "EnableMongo")) {
|
||||||
// }
|
return "Mongo";
|
||||||
// if (capabilities.find((c) => c.name === "EnableMongo")) {
|
}
|
||||||
// return "Mongo";
|
if (capabilities.find((c) => c.name === "EnableTable")) {
|
||||||
// }
|
return "Tables";
|
||||||
// if (capabilities.find((c) => c.name === "EnableTable")) {
|
}
|
||||||
// return "Tables";
|
}
|
||||||
// }
|
if (account.kind === "MongoDB" || account.kind === "Parse") {
|
||||||
// }
|
return "Mongo";
|
||||||
// if (account.kind === "MongoDB" || account.kind === "Parse") {
|
}
|
||||||
// return "Mongo";
|
if (account.kind === "Postgres") {
|
||||||
// }
|
return "Postgres";
|
||||||
// if (account.kind === "Postgres") {
|
}
|
||||||
// return "Postgres";
|
return "SQL";
|
||||||
// }
|
|
||||||
// return "SQL";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { userContext, updateUserContext };
|
export { userContext, updateUserContext };
|
||||||
|
|||||||
@@ -74,3 +74,18 @@ export const getApiShortDisplayName = (): string => {
|
|||||||
return "Table API";
|
return "Table API";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getItemName = (): string => {
|
||||||
|
switch (userContext.apiType) {
|
||||||
|
case "Tables":
|
||||||
|
return "Entities";
|
||||||
|
case "Cassandra":
|
||||||
|
return "Rows";
|
||||||
|
case "Gremlin":
|
||||||
|
return "Graph";
|
||||||
|
case "Mongo":
|
||||||
|
return "Documents";
|
||||||
|
default:
|
||||||
|
return "Items";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
40
src/Utils/NetworkUtility.ts
Normal file
40
src/Utils/NetworkUtility.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { userContext } from "UserContext";
|
||||||
|
|
||||||
|
const PortalIPs: { [key: string]: string[] } = {
|
||||||
|
prod1: ["104.42.195.92", "40.76.54.131"],
|
||||||
|
prod2: ["104.42.196.69"],
|
||||||
|
mooncake: ["139.217.8.252"],
|
||||||
|
blackforest: ["51.4.229.218"],
|
||||||
|
fairfax: ["52.244.48.71"],
|
||||||
|
ussec: ["29.26.26.67", "29.26.26.66"],
|
||||||
|
usnat: ["7.28.202.68"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const doNetworkSettingsAllowDataExplorerAccess = (): boolean => {
|
||||||
|
const accountProperties = userContext.databaseAccount?.properties;
|
||||||
|
|
||||||
|
if (!accountProperties) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public network access is disabled
|
||||||
|
if (accountProperties.publicNetworkAccess !== "Enabled") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipRules = accountProperties.ipRules;
|
||||||
|
// public network access is set to "All networks"
|
||||||
|
if (ipRules.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const portalIPs = PortalIPs[userContext.portalEnv];
|
||||||
|
let numberOfMatches = 0;
|
||||||
|
ipRules.forEach((ipRule) => {
|
||||||
|
if (portalIPs.indexOf(ipRule.ipAddressOrRange) !== -1) {
|
||||||
|
numberOfMatches++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return numberOfMatches === portalIPs.length;
|
||||||
|
};
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { GenerateTokenResponse } from "../Contracts/DataModels";
|
|
||||||
import AuthHeadersUtil from "../Platform/Hosted/Authorization";
|
|
||||||
|
|
||||||
export function useFullScreenURLs(): GenerateTokenResponse | undefined {
|
|
||||||
const [state, setState] = useState<GenerateTokenResponse>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
Promise.all([AuthHeadersUtil.generateEncryptedToken(), AuthHeadersUtil.generateEncryptedToken(true)]).then(
|
|
||||||
([readWriteResponse, readOnlyResponse]) =>
|
|
||||||
setState({
|
|
||||||
readWrite: readWriteResponse.readWrite,
|
|
||||||
read: readOnlyResponse.read,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { doNetworkSettingsAllowDataExplorerAccess } from "Utils/NetworkUtility";
|
||||||
import { applyExplorerBindings } from "../applyExplorerBindings";
|
import { applyExplorerBindings } from "../applyExplorerBindings";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { AccountKind, Flights } from "../Common/Constants";
|
import { AccountKind, Flights } from "../Common/Constants";
|
||||||
@@ -364,7 +365,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (inputs.isPostgresAccount) {
|
if (inputs.isPostgresAccount) {
|
||||||
updateUserContext({ apiType: "Postgres" });
|
updateUserContext({ apiType: "Postgres", isReplica: !!inputs.isReplica });
|
||||||
|
|
||||||
if (inputs.connectionStringParams) {
|
if (inputs.connectionStringParams) {
|
||||||
// TODO: Remove after the nodes param has been updated to be a flat array in the OSS extension
|
// TODO: Remove after the nodes param has been updated to be a flat array in the OSS extension
|
||||||
@@ -381,6 +382,9 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDataExplorerAccessAllowed = doNetworkSettingsAllowDataExplorerAccess();
|
||||||
|
useTabs.getState().setShowNetworkSettingsWarning(!isDataExplorerAccessAllowed);
|
||||||
|
|
||||||
if (inputs.features) {
|
if (inputs.features) {
|
||||||
Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features)));
|
Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ interface TabsState {
|
|||||||
openedReactTabs: ReactTabKind[];
|
openedReactTabs: ReactTabKind[];
|
||||||
activeTab: TabsBase | undefined;
|
activeTab: TabsBase | undefined;
|
||||||
activeReactTab: ReactTabKind | undefined;
|
activeReactTab: ReactTabKind | undefined;
|
||||||
|
showNetworkSettingsWarning: boolean;
|
||||||
activateTab: (tab: TabsBase) => void;
|
activateTab: (tab: TabsBase) => void;
|
||||||
activateNewTab: (tab: TabsBase) => void;
|
activateNewTab: (tab: TabsBase) => void;
|
||||||
activateReactTab: (tabkind: ReactTabKind) => void;
|
activateReactTab: (tabkind: ReactTabKind) => void;
|
||||||
@@ -20,6 +21,7 @@ interface TabsState {
|
|||||||
closeAllNotebookTabs: (hardClose: boolean) => void;
|
closeAllNotebookTabs: (hardClose: boolean) => void;
|
||||||
openAndActivateReactTab: (tabKind: ReactTabKind) => void;
|
openAndActivateReactTab: (tabKind: ReactTabKind) => void;
|
||||||
closeReactTab: (tabKind: ReactTabKind) => void;
|
closeReactTab: (tabKind: ReactTabKind) => void;
|
||||||
|
setShowNetworkSettingsWarning: (showWarning: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ReactTabKind {
|
export enum ReactTabKind {
|
||||||
@@ -33,6 +35,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
|||||||
openedReactTabs: [ReactTabKind.Home],
|
openedReactTabs: [ReactTabKind.Home],
|
||||||
activeTab: undefined,
|
activeTab: undefined,
|
||||||
activeReactTab: ReactTabKind.Home,
|
activeReactTab: ReactTabKind.Home,
|
||||||
|
showNetworkSettingsWarning: false,
|
||||||
activateTab: (tab: TabsBase): void => {
|
activateTab: (tab: TabsBase): void => {
|
||||||
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
|
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
|
||||||
set({ activeTab: tab, activeReactTab: undefined });
|
set({ activeTab: tab, activeReactTab: undefined });
|
||||||
@@ -142,4 +145,5 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
|||||||
|
|
||||||
set({ openedReactTabs: updatedOpenedReactTabs });
|
set({ openedReactTabs: updatedOpenedReactTabs });
|
||||||
},
|
},
|
||||||
|
setShowNetworkSettingsWarning: (showWarning: boolean) => set({ showNetworkSettingsWarning: showWarning }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -82,7 +82,6 @@
|
|||||||
"./src/Explorer/Tree/AccessibleVerticalList.ts",
|
"./src/Explorer/Tree/AccessibleVerticalList.ts",
|
||||||
"./src/GitHub/GitHubConnector.ts",
|
"./src/GitHub/GitHubConnector.ts",
|
||||||
"./src/HostedExplorerChildFrame.ts",
|
"./src/HostedExplorerChildFrame.ts",
|
||||||
"./src/Platform/Hosted/Authorization.ts",
|
|
||||||
"./src/Platform/Hosted/Components/MeControl.test.tsx",
|
"./src/Platform/Hosted/Components/MeControl.test.tsx",
|
||||||
"./src/Platform/Hosted/Components/MeControl.tsx",
|
"./src/Platform/Hosted/Components/MeControl.tsx",
|
||||||
"./src/Platform/Hosted/Components/SignInButton.tsx",
|
"./src/Platform/Hosted/Components/SignInButton.tsx",
|
||||||
@@ -126,7 +125,6 @@
|
|||||||
"./src/Utils/WindowUtils.ts",
|
"./src/Utils/WindowUtils.ts",
|
||||||
"./src/hooks/useConfig.ts",
|
"./src/hooks/useConfig.ts",
|
||||||
"./src/hooks/useDirectories.tsx",
|
"./src/hooks/useDirectories.tsx",
|
||||||
"./src/hooks/useFullScreenURLs.tsx",
|
|
||||||
"./src/hooks/useGraphPhoto.tsx",
|
"./src/hooks/useGraphPhoto.tsx",
|
||||||
"./src/hooks/useNotebookSnapshotStore.ts",
|
"./src/hooks/useNotebookSnapshotStore.ts",
|
||||||
"./src/hooks/usePortalAccessToken.tsx",
|
"./src/hooks/usePortalAccessToken.tsx",
|
||||||
|
|||||||
Reference in New Issue
Block a user