mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 21:51:56 +00:00
Move ssh out to org level
This commit is contained in:
@@ -0,0 +1,808 @@
|
||||
{
|
||||
"v": "5.12.2",
|
||||
"fr": 29.9700012207031,
|
||||
"ip": 0,
|
||||
"op": 45.0000018328876,
|
||||
"w": 48,
|
||||
"h": 48,
|
||||
"nm": "note",
|
||||
"ddd": 1,
|
||||
"assets": [],
|
||||
"layers": [
|
||||
{
|
||||
"ddd": 1,
|
||||
"ind": 1,
|
||||
"ty": 4,
|
||||
"nm": "note-outline-bot_s1g1_s2g2_s3g1_s4g1 Outlines",
|
||||
"parent": 2,
|
||||
"sr": 1,
|
||||
"ks": {
|
||||
"o": { "a": 0, "k": 100, "ix": 11 },
|
||||
"rx": { "a": 0, "k": 0, "ix": 8 },
|
||||
"ry": { "a": 0, "k": 0, "ix": 9 },
|
||||
"rz": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "i": { "x": [0], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 0, "s": [0] },
|
||||
{ "i": { "x": [0], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 22, "s": [5] },
|
||||
{ "t": 44.0000017921567, "s": [0] }
|
||||
],
|
||||
"ix": 10
|
||||
},
|
||||
"or": { "a": 0, "k": [0, 0, 0], "ix": 7 },
|
||||
"p": { "a": 0, "k": [19.448, 27.122, 0], "ix": 2 },
|
||||
"a": { "a": 0, "k": [13.405, 11.539, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100, 100], "ix": 6 }
|
||||
},
|
||||
"ao": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.022, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 0,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[5, 18.078],
|
||||
[21.809, 18.078]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.022, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 22,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[4.311, 17.73],
|
||||
[21.654, 16.837]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"t": 44.0000017921567,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[5, 18.078],
|
||||
[21.809, 18.078]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ind": 1,
|
||||
"ty": "sh",
|
||||
"ix": 2,
|
||||
"ks": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.022, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 0,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[5, 11.281],
|
||||
[21.809, 11.281]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.022, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 22,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[5, 11.281],
|
||||
[21.735, 9.907]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"t": 44.0000017921567,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[5, 11.281],
|
||||
[21.809, 11.281]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 2",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ind": 2,
|
||||
"ty": "sh",
|
||||
"ix": 3,
|
||||
"ks": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.022, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 0,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[5, 5],
|
||||
[13.756, 5]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.022, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 22,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[5.497, 4.939],
|
||||
[14.633, 4.166]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"t": 44.0000017921567,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[5, 5],
|
||||
[13.756, 5]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 3",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "mm",
|
||||
"mm": 1,
|
||||
"nm": "Merge Paths 1",
|
||||
"mn": "ADBE Vector Filter - Merge",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 3 },
|
||||
"o": { "a": 0, "k": 100, "ix": 4 },
|
||||
"w": { "a": 0, "k": 2.5, "ix": 5 },
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"bm": 0,
|
||||
"nm": "Stroke 1",
|
||||
"mn": "ADBE Vector Graphic - Stroke",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": { "a": 0, "k": [0, 0], "ix": 2 },
|
||||
"a": { "a": 0, "k": [0, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100], "ix": 3 },
|
||||
"r": { "a": 0, "k": 0, "ix": 6 },
|
||||
"o": { "a": 0, "k": 100, "ix": 7 },
|
||||
"sk": { "a": 0, "k": 0, "ix": 4 },
|
||||
"sa": { "a": 0, "k": 0, "ix": 5 },
|
||||
"nm": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 1",
|
||||
"np": 5,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 1,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tm",
|
||||
"s": { "a": 0, "k": 0, "ix": 1 },
|
||||
"e": { "a": 0, "k": 100, "ix": 2 },
|
||||
"o": { "a": 0, "k": 0, "ix": 3 },
|
||||
"m": 1,
|
||||
"ix": 2,
|
||||
"nm": "Trim Paths 1",
|
||||
"mn": "ADBE Vector Filter - Trim",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tm",
|
||||
"s": { "a": 0, "k": 0, "ix": 1 },
|
||||
"e": { "a": 0, "k": 100, "ix": 2 },
|
||||
"o": { "a": 0, "k": 0, "ix": 3 },
|
||||
"m": 1,
|
||||
"ix": 3,
|
||||
"nm": "Trim Paths 2",
|
||||
"mn": "ADBE Vector Filter - Trim",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "tm",
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": [0.616], "y": [1] },
|
||||
"o": { "x": [0.41], "y": [0] },
|
||||
"t": 0,
|
||||
"s": [0]
|
||||
},
|
||||
{
|
||||
"i": { "x": [0.005], "y": [1] },
|
||||
"o": { "x": [0.369], "y": [0] },
|
||||
"t": 22,
|
||||
"s": [100]
|
||||
},
|
||||
{ "t": 44.0000017921567, "s": [0] }
|
||||
],
|
||||
"ix": 1
|
||||
},
|
||||
"e": { "a": 0, "k": 100, "ix": 2 },
|
||||
"o": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": [0], "y": [1] },
|
||||
"o": { "x": [0.333], "y": [0] },
|
||||
"t": 22,
|
||||
"s": [0]
|
||||
},
|
||||
{ "t": 44.0000017921567, "s": [360] }
|
||||
],
|
||||
"ix": 3
|
||||
},
|
||||
"m": 1,
|
||||
"ix": 1,
|
||||
"nm": "Trim Paths 1",
|
||||
"mn": "ADBE Vector Filter - Trim",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": { "a": 0, "k": [0, 0], "ix": 2 },
|
||||
"a": { "a": 0, "k": [0, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100], "ix": 3 },
|
||||
"r": { "a": 0, "k": 0, "ix": 6 },
|
||||
"o": { "a": 0, "k": 100, "ix": 7 },
|
||||
"sk": { "a": 0, "k": 0, "ix": 4 },
|
||||
"sa": { "a": 0, "k": 0, "ix": 5 },
|
||||
"nm": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 2",
|
||||
"np": 1,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 4,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
}
|
||||
],
|
||||
"ip": 0,
|
||||
"op": 45.0000018328876,
|
||||
"st": 0,
|
||||
"ct": 1,
|
||||
"bm": 0
|
||||
},
|
||||
{
|
||||
"ddd": 1,
|
||||
"ind": 2,
|
||||
"ty": 4,
|
||||
"nm": "note-outline-bot_s1g1_s2g1_s3g1_s4g1_background Outlines",
|
||||
"sr": 1,
|
||||
"ks": {
|
||||
"o": { "a": 0, "k": 100, "ix": 11 },
|
||||
"rx": { "a": 0, "k": 0, "ix": 8 },
|
||||
"ry": { "a": 0, "k": 0, "ix": 9 },
|
||||
"rz": { "a": 0, "k": 0, "ix": 10 },
|
||||
"or": { "a": 0, "k": [0, 0, 0], "ix": 7 },
|
||||
"p": { "a": 0, "k": [24.778, 23.858, 0], "ix": 2 },
|
||||
"a": { "a": 0, "k": [19.825, 23.313, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100, 100], "ix": 6 }
|
||||
},
|
||||
"ao": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 0,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[1.423, 1.657],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, -2.354],
|
||||
[0, 0],
|
||||
[-1.324, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-1.482, -1.727],
|
||||
[0, 0],
|
||||
[0, 1.429],
|
||||
[0, 0],
|
||||
[2.093, 0]
|
||||
],
|
||||
"v": [
|
||||
[4.581, 2.516],
|
||||
[1.876, -0.634],
|
||||
[-1.99, -5.137],
|
||||
[-6.005, -3.212],
|
||||
[-6.005, 4.277],
|
||||
[-3.606, 6.865],
|
||||
[2.822, 6.865]
|
||||
],
|
||||
"c": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 22,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0.56, 0.711],
|
||||
[0.835, 1.361],
|
||||
[0, 0],
|
||||
[-0.031, 0.137],
|
||||
[0, 0],
|
||||
[-0.118, -0.836],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[-0.835, -1.361],
|
||||
[-0.299, -0.286],
|
||||
[0, 0],
|
||||
[1.062, 0.814],
|
||||
[0, 0],
|
||||
[-0.101, 0.125]
|
||||
],
|
||||
"v": [
|
||||
[6.281, -2.819],
|
||||
[5.393, -4.55],
|
||||
[3.663, -5.916],
|
||||
[3.337, -6.047],
|
||||
[4.119, -5.661],
|
||||
[6.298, -2.792],
|
||||
[6.5, -1.722]
|
||||
],
|
||||
"c": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"t": 44.0000017921567,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[1.423, 1.657],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, -2.354],
|
||||
[0, 0],
|
||||
[-1.324, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-1.482, -1.727],
|
||||
[0, 0],
|
||||
[0, 1.429],
|
||||
[0, 0],
|
||||
[2.093, 0]
|
||||
],
|
||||
"v": [
|
||||
[4.581, 2.516],
|
||||
[1.876, -0.634],
|
||||
[-1.99, -5.137],
|
||||
[-6.005, -3.212],
|
||||
[-6.005, 4.277],
|
||||
[-3.606, 6.865],
|
||||
[2.822, 6.865]
|
||||
],
|
||||
"c": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 3 },
|
||||
"o": { "a": 0, "k": 100, "ix": 4 },
|
||||
"w": { "a": 0, "k": 2.5, "ix": 5 },
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"bm": 0,
|
||||
"nm": "Stroke 1",
|
||||
"mn": "ADBE Vector Graphic - Stroke",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 4 },
|
||||
"o": { "a": 0, "k": 0, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": { "a": 0, "k": [28.646, 11.865], "ix": 2 },
|
||||
"a": { "a": 0, "k": [0, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100], "ix": 3 },
|
||||
"r": { "a": 0, "k": 0, "ix": 6 },
|
||||
"o": { "a": 0, "k": 100, "ix": 7 },
|
||||
"sk": { "a": 0, "k": 0, "ix": 4 },
|
||||
"sa": { "a": 0, "k": 0, "ix": 5 },
|
||||
"nm": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 1",
|
||||
"np": 3,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 1,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 0,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, -0.781],
|
||||
[0, 0],
|
||||
[2.763, 0],
|
||||
[0, 0],
|
||||
[0, 2.828],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-2.763, 0],
|
||||
[0, 0],
|
||||
[-0.625, -0.733]
|
||||
],
|
||||
"o": [
|
||||
[0.507, 0.595],
|
||||
[0, 0],
|
||||
[0, 2.828],
|
||||
[0, 0],
|
||||
[-2.763, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, -2.828],
|
||||
[0, 0],
|
||||
[0.963, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[14.012, -8.411],
|
||||
[14.797, -6.279],
|
||||
[14.797, 13.05],
|
||||
[9.794, 18.171],
|
||||
[-9.794, 18.171],
|
||||
[-14.796, 13.05],
|
||||
[-14.796, 0.99],
|
||||
[-14.796, -13.051],
|
||||
[-9.794, -18.171],
|
||||
[4.174, -18.171],
|
||||
[6.676, -17.016]
|
||||
],
|
||||
"c": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 22,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0.171, -6.028],
|
||||
[0, 0],
|
||||
[2.763, 0],
|
||||
[0, 0],
|
||||
[0, 2.828],
|
||||
[-1.453, 6.385],
|
||||
[0, 0],
|
||||
[-2.763, 0],
|
||||
[0, 0],
|
||||
[-1.739, -0.297]
|
||||
],
|
||||
"o": [
|
||||
[0.051, 2.426],
|
||||
[-0.171, 6.028],
|
||||
[-1.046, 2.979],
|
||||
[0, 0],
|
||||
[-2.763, 0],
|
||||
[0, 0],
|
||||
[1.266, -5.562],
|
||||
[0, -2.828],
|
||||
[0, 0],
|
||||
[0.963, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[15.322, -13.817],
|
||||
[14.796, -0.028],
|
||||
[12.547, 13.021],
|
||||
[6.669, 18.201],
|
||||
[-12.044, 18.141],
|
||||
[-17.046, 13.021],
|
||||
[-14.421, 1.025],
|
||||
[-12.923, -12.566],
|
||||
[-7.92, -17.686],
|
||||
[3.673, -17.672],
|
||||
[12.612, -17.453]
|
||||
],
|
||||
"c": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"t": 44.0000017921567,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, -0.781],
|
||||
[0, 0],
|
||||
[2.763, 0],
|
||||
[0, 0],
|
||||
[0, 2.828],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-2.763, 0],
|
||||
[0, 0],
|
||||
[-0.625, -0.733]
|
||||
],
|
||||
"o": [
|
||||
[0.507, 0.595],
|
||||
[0, 0],
|
||||
[0, 2.828],
|
||||
[0, 0],
|
||||
[-2.763, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, -2.828],
|
||||
[0, 0],
|
||||
[0.963, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[14.012, -8.411],
|
||||
[14.797, -6.279],
|
||||
[14.797, 13.05],
|
||||
[9.794, 18.171],
|
||||
[-9.794, 18.171],
|
||||
[-14.796, 13.05],
|
||||
[-14.796, 0.99],
|
||||
[-14.796, -13.051],
|
||||
[-9.794, -18.171],
|
||||
[4.174, -18.171],
|
||||
[6.676, -17.016]
|
||||
],
|
||||
"c": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 3 },
|
||||
"o": { "a": 0, "k": 100, "ix": 4 },
|
||||
"w": { "a": 0, "k": 2.5, "ix": 5 },
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"bm": 0,
|
||||
"nm": "Stroke 1",
|
||||
"mn": "ADBE Vector Graphic - Stroke",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 4 },
|
||||
"o": { "a": 0, "k": 0, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": { "a": 0, "k": [19.797, 23.455], "ix": 2 },
|
||||
"a": { "a": 0, "k": [0, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100], "ix": 3 },
|
||||
"r": { "a": 0, "k": 0, "ix": 6 },
|
||||
"o": { "a": 0, "k": 100, "ix": 7 },
|
||||
"sk": { "a": 0, "k": 0, "ix": 4 },
|
||||
"sa": { "a": 0, "k": 0, "ix": 5 },
|
||||
"nm": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 2",
|
||||
"np": 3,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 2,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
}
|
||||
],
|
||||
"ip": 0,
|
||||
"op": 45.0000018328876,
|
||||
"st": 0,
|
||||
"ct": 1,
|
||||
"bm": 0
|
||||
}
|
||||
],
|
||||
"markers": [],
|
||||
"props": {}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,470 @@
|
||||
{
|
||||
"v": "5.12.2",
|
||||
"fr": 29.9700012207031,
|
||||
"ip": 0,
|
||||
"op": 45.0000018328876,
|
||||
"w": 48,
|
||||
"h": 48,
|
||||
"nm": "unlock",
|
||||
"ddd": 0,
|
||||
"assets": [],
|
||||
"layers": [
|
||||
{
|
||||
"ddd": 0,
|
||||
"ind": 1,
|
||||
"ty": 4,
|
||||
"nm": "unlock-outline-top_s1g1_s2g2_s3g1_s4g1_background Outlines",
|
||||
"parent": 2,
|
||||
"sr": 1,
|
||||
"ks": {
|
||||
"o": { "a": 0, "k": 100, "ix": 11 },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 12, "s": [0] },
|
||||
{
|
||||
"i": { "x": [0.667], "y": [1] },
|
||||
"o": { "x": [0.333], "y": [0] },
|
||||
"t": 28,
|
||||
"s": [-16]
|
||||
},
|
||||
{ "t": 40.0000016292334, "s": [0] }
|
||||
],
|
||||
"ix": 10
|
||||
},
|
||||
"p": { "a": 0, "k": [19, 19.473, 0], "ix": 2, "l": 2 },
|
||||
"a": { "a": 0, "k": [8.5, 11.125, 0], "ix": 1, "l": 2 },
|
||||
"s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
|
||||
},
|
||||
"ao": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[0, 1.292],
|
||||
[1.933, 0],
|
||||
[0, -1.933],
|
||||
[-1.042, -0.607],
|
||||
[0, 0],
|
||||
[-0.966, 0],
|
||||
[0, 0.966],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, -1.933],
|
||||
[-1.933, 0],
|
||||
[0, 1.292],
|
||||
[0, 0],
|
||||
[0, 0.966],
|
||||
[0.966, 0],
|
||||
[0, 0],
|
||||
[1.042, -0.607]
|
||||
],
|
||||
"v": [
|
||||
[3.5, -2.625],
|
||||
[0, -6.125],
|
||||
[-3.5, -2.625],
|
||||
[-1.75, 0.39],
|
||||
[-1.75, 4.375],
|
||||
[0, 6.125],
|
||||
[1.75, 4.375],
|
||||
[1.75, 0.39]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 3 },
|
||||
"o": { "a": 0, "k": 100, "ix": 4 },
|
||||
"w": { "a": 0, "k": 2.5, "ix": 5 },
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"bm": 0,
|
||||
"nm": "Stroke 1",
|
||||
"mn": "ADBE Vector Graphic - Stroke",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 4 },
|
||||
"o": { "a": 0, "k": 0, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": { "a": 0, "k": [8.5, 11.125], "ix": 2 },
|
||||
"a": { "a": 0, "k": [0, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100], "ix": 3 },
|
||||
"r": { "a": 0, "k": 0, "ix": 6 },
|
||||
"o": { "a": 0, "k": 100, "ix": 7 },
|
||||
"sk": { "a": 0, "k": 0, "ix": 4 },
|
||||
"sa": { "a": 0, "k": 0, "ix": 5 },
|
||||
"nm": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 1",
|
||||
"np": 3,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 1,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
}
|
||||
],
|
||||
"ip": 0,
|
||||
"op": 45.0000018328876,
|
||||
"st": 0,
|
||||
"ct": 1,
|
||||
"bm": 0
|
||||
},
|
||||
{
|
||||
"ddd": 0,
|
||||
"ind": 2,
|
||||
"ty": 4,
|
||||
"nm": "unlock-outline-bot_s1g1_s2g1_s3g1_s4g1_background Outlines",
|
||||
"sr": 1,
|
||||
"ks": {
|
||||
"o": { "a": 0, "k": 100, "ix": 11 },
|
||||
"r": { "a": 0, "k": 0, "ix": 10 },
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.667, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 0,
|
||||
"s": [24, 29.826, 0],
|
||||
"to": [0, 0.313, 0],
|
||||
"ti": [0, 0, 0]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.667, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 12,
|
||||
"s": [24, 31.701, 0],
|
||||
"to": [0, 0, 0],
|
||||
"ti": [0, 0.313, 0]
|
||||
},
|
||||
{ "t": 28.0000011404634, "s": [24, 29.826, 0] }
|
||||
],
|
||||
"ix": 2,
|
||||
"l": 2
|
||||
},
|
||||
"a": { "a": 0, "k": [19, 19.523, 0], "ix": 1, "l": 2 },
|
||||
"s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
|
||||
},
|
||||
"ao": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"i": [
|
||||
[-1.43, 0.576],
|
||||
[0, 0],
|
||||
[-3.266, -1.28],
|
||||
[0, 0],
|
||||
[0, -1.555],
|
||||
[0, 0],
|
||||
[7.732, 0],
|
||||
[0, 8.353],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[3.253, -1.31],
|
||||
[0, 0],
|
||||
[1.448, 0.567],
|
||||
[0, 0],
|
||||
[0, 8.353],
|
||||
[-7.732, 0],
|
||||
[0, 0],
|
||||
[0, -1.542]
|
||||
],
|
||||
"v": [
|
||||
[-11.633, -10.628],
|
||||
[-5.258, -13.195],
|
||||
[4.892, -13.243],
|
||||
[11.6, -10.615],
|
||||
[14, -7.097],
|
||||
[14, -0.602],
|
||||
[0, 14.523],
|
||||
[-14, -0.602],
|
||||
[-14, -7.123]
|
||||
],
|
||||
"c": true
|
||||
},
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 3 },
|
||||
"o": { "a": 0, "k": 100, "ix": 4 },
|
||||
"w": { "a": 0, "k": 2.5, "ix": 5 },
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"bm": 0,
|
||||
"nm": "Stroke 1",
|
||||
"mn": "ADBE Vector Graphic - Stroke",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 4 },
|
||||
"o": { "a": 0, "k": 0, "ix": 5 },
|
||||
"r": 1,
|
||||
"bm": 0,
|
||||
"nm": "Fill 1",
|
||||
"mn": "ADBE Vector Graphic - Fill",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": { "a": 0, "k": [19, 19.523], "ix": 2 },
|
||||
"a": { "a": 0, "k": [0, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100], "ix": 3 },
|
||||
"r": { "a": 0, "k": 0, "ix": 6 },
|
||||
"o": { "a": 0, "k": 100, "ix": 7 },
|
||||
"sk": { "a": 0, "k": 0, "ix": 4 },
|
||||
"sa": { "a": 0, "k": 0, "ix": 5 },
|
||||
"nm": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 1",
|
||||
"np": 3,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 1,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
}
|
||||
],
|
||||
"ip": 0,
|
||||
"op": 45.0000018328876,
|
||||
"st": 0,
|
||||
"ct": 1,
|
||||
"bm": 0
|
||||
},
|
||||
{
|
||||
"ddd": 0,
|
||||
"ind": 3,
|
||||
"ty": 4,
|
||||
"nm": "unlockoutline-bot_s1g1_s2g1_s3g1_s4g2 Outlines",
|
||||
"parent": 2,
|
||||
"sr": 1,
|
||||
"ks": {
|
||||
"o": { "a": 0, "k": 100, "ix": 11 },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 0, "s": [0] },
|
||||
{ "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 12, "s": [9] },
|
||||
{ "i": { "x": [0.667], "y": [1] }, "o": { "x": [0.333], "y": [0] }, "t": 28, "s": [9] },
|
||||
{ "t": 40.0000016292334, "s": [0] }
|
||||
],
|
||||
"ix": 10
|
||||
},
|
||||
"p": { "a": 0, "k": [17.628, 0.002, 0], "ix": 2, "l": 2 },
|
||||
"a": { "a": 0, "k": [15.029, 13.585, 0], "ix": 1, "l": 2 },
|
||||
"s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
|
||||
},
|
||||
"ao": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ind": 0,
|
||||
"ty": "sh",
|
||||
"ix": 1,
|
||||
"ks": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": 0.667, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 0,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[5.305, -0.847],
|
||||
[-0.847, -5.305],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[-0.847, -5.305],
|
||||
[-5.305, 0.846],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[10.029, 0.334],
|
||||
[-1.11, -7.738],
|
||||
[-9.182, 3.401],
|
||||
[-8.355, 8.585]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.667, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 12,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[5.341, -0.581],
|
||||
[-0.582, -5.341],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[-0.582, -5.341],
|
||||
[-5.341, 0.581],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[11.833, 5.021],
|
||||
[1.11, -3.596],
|
||||
[-7.507, 7.127],
|
||||
[-7.094, 9.966]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": { "x": 0.667, "y": 1 },
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"t": 28,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[5.341, -0.581],
|
||||
[-0.582, -5.341],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[-0.582, -5.341],
|
||||
[-5.341, 0.581],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[11.833, 5.021],
|
||||
[1.11, -3.596],
|
||||
[-7.507, 7.127],
|
||||
[-7.094, 9.966]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"t": 40.0000016292334,
|
||||
"s": [
|
||||
{
|
||||
"i": [
|
||||
[0, 0],
|
||||
[5.305, -0.847],
|
||||
[-0.847, -5.305],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[-0.847, -5.305],
|
||||
[-5.305, 0.846],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[10.029, 0.334],
|
||||
[-1.11, -7.738],
|
||||
[-9.182, 3.401],
|
||||
[-8.355, 8.585]
|
||||
],
|
||||
"c": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ix": 2
|
||||
},
|
||||
"nm": "Path 1",
|
||||
"mn": "ADBE Vector Shape - Group",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [1, 1, 1], "ix": 3 },
|
||||
"o": { "a": 0, "k": 100, "ix": 4 },
|
||||
"w": { "a": 0, "k": 2.5, "ix": 5 },
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"bm": 0,
|
||||
"nm": "Stroke 1",
|
||||
"mn": "ADBE Vector Graphic - Stroke",
|
||||
"hd": false
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"p": { "a": 0, "k": [15.029, 13.585], "ix": 2 },
|
||||
"a": { "a": 0, "k": [0, 0], "ix": 1 },
|
||||
"s": { "a": 0, "k": [100, 100], "ix": 3 },
|
||||
"r": { "a": 0, "k": 0, "ix": 6 },
|
||||
"o": { "a": 0, "k": 100, "ix": 7 },
|
||||
"sk": { "a": 0, "k": 0, "ix": 4 },
|
||||
"sa": { "a": 0, "k": 0, "ix": 5 },
|
||||
"nm": "Transform"
|
||||
}
|
||||
],
|
||||
"nm": "Group 1",
|
||||
"np": 2,
|
||||
"cix": 2,
|
||||
"bm": 0,
|
||||
"ix": 1,
|
||||
"mn": "ADBE Vector Group",
|
||||
"hd": false
|
||||
}
|
||||
],
|
||||
"ip": 0,
|
||||
"op": 45.0000018328876,
|
||||
"st": 0,
|
||||
"ct": 1,
|
||||
"bm": 0
|
||||
}
|
||||
],
|
||||
"markers": [],
|
||||
"props": {}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { Tag } from "public/data/frequentInterfaces";
|
||||
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
/**
|
||||
* This is the menu that is used to add more tags to a secret
|
||||
* @param {object} obj
|
||||
@@ -75,7 +77,7 @@ const AddTagsMenu = ({
|
||||
<button
|
||||
type="button"
|
||||
className="w-full rounded-sm bg-mineshaft-800 px-2 py-0.5 text-left text-bunker-200 duration-200 hover:bg-primary hover:text-black"
|
||||
onClick={() => router.push(`/project/${String(router.query.id)}/settings`)}
|
||||
onClick={() => router.push(`/${ProjectType.SecretManager}/${String(router.query.id)}/settings`)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} className="mr-2 text-xs" />
|
||||
Add more tags
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { Button } from "../v2";
|
||||
|
||||
interface IProps {
|
||||
@@ -20,7 +22,7 @@ export const NoEnvironmentsBanner = ({ projectId }: IProps) => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="my-2">
|
||||
<Button onClick={() => router.push(`/project/${projectId}/settings#environments`)}>
|
||||
<Button onClick={() => router.push(`/${ProjectType.SecretManager}/${projectId}/settings#environments`)}>
|
||||
Add environments
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
|
||||
|
||||
import { useAddUsersToOrg } from "@app/hooks/api";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { Button, EmailServiceSetupModal } from "../v2";
|
||||
@@ -22,7 +23,7 @@ export default function TeamInviteStep(): JSX.Element {
|
||||
|
||||
// Redirect user to the getting started page
|
||||
const redirectToHome = async () => {
|
||||
router.push(`/org/${localStorage.getItem("orgData.id")}/overview`);
|
||||
router.push(`/org/${localStorage.getItem("orgData.id")}/${ProjectType.SecretManager}/overview`);
|
||||
};
|
||||
|
||||
const inviteUsers = async ({ emails: inviteEmails }: { emails: string }) => {
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
useSubscription,
|
||||
useUser
|
||||
} from "@app/context";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import {
|
||||
fetchOrgUsers,
|
||||
useAddUserToWsNonE2EE,
|
||||
@@ -41,6 +42,7 @@ import {
|
||||
} from "@app/hooks/api";
|
||||
import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types";
|
||||
import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().trim().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
|
||||
@@ -59,11 +61,12 @@ type TAddProjectFormData = z.infer<typeof formSchema>;
|
||||
interface NewProjectModalProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
projectType: ProjectType;
|
||||
}
|
||||
|
||||
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange">;
|
||||
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange" | "projectType">;
|
||||
|
||||
const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
|
||||
const router = useRouter();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { permission } = useOrgPermission();
|
||||
@@ -117,15 +120,15 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
if (!user) return;
|
||||
try {
|
||||
const {
|
||||
data: {
|
||||
project: { id: newProjectId }
|
||||
}
|
||||
data: { project }
|
||||
} = await createWs.mutateAsync({
|
||||
projectName: name,
|
||||
projectDescription: description,
|
||||
kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined,
|
||||
template
|
||||
template,
|
||||
type: projectType
|
||||
});
|
||||
const { id: newProjectId } = project;
|
||||
|
||||
if (addMembers) {
|
||||
const orgUsers = await fetchOrgUsers(currentOrg.id);
|
||||
@@ -145,7 +148,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
createNotification({ text: "Project created", type: "success" });
|
||||
reset();
|
||||
onOpenChange(false);
|
||||
router.push(`/project/${newProjectId}/secrets/overview`);
|
||||
router.push(getProjectHomePage(project));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({ text: "Failed to create project", type: "error" });
|
||||
@@ -316,14 +319,18 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const NewProjectModal: FC<NewProjectModalProps> = ({ isOpen, onOpenChange }) => {
|
||||
export const NewProjectModal: FC<NewProjectModalProps> = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
projectType
|
||||
}) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
title="Create a new project"
|
||||
subTitle="This project will contain your secrets and configurations."
|
||||
>
|
||||
<NewProjectForm onOpenChange={onOpenChange} />
|
||||
<NewProjectForm onOpenChange={onOpenChange} projectType={projectType} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { Workspace } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type TWorkspaceContext = {
|
||||
workspaces: Workspace[];
|
||||
@@ -35,7 +35,7 @@ export const WorkspaceProvider = ({ children }: Props): JSX.Element => {
|
||||
const shouldTriggerNoProjectAccess =
|
||||
!value.isLoading &&
|
||||
!value.currentWorkspace &&
|
||||
router.pathname.startsWith("/project") &&
|
||||
Object.values(ProjectType).some((el) => router.pathname.startsWith(`/${el}`)) &&
|
||||
workspaceId;
|
||||
|
||||
// handle redirects for project-specific routes
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
/** Extracts the key and value from a passed in env string based on the provided delimiters. */
|
||||
export const getKeyValue = (pastedContent: string, delimiters: string[]) => {
|
||||
const foundDelimiter = delimiters.find((delimiter) => pastedContent.includes(delimiter));
|
||||
if (!pastedContent) {
|
||||
return { key: "", value: "" };
|
||||
}
|
||||
|
||||
if (!foundDelimiter) {
|
||||
let firstDelimiterIndex = -1;
|
||||
let foundDelimiter = "";
|
||||
|
||||
delimiters.forEach((delimiter) => {
|
||||
const index = pastedContent.indexOf(delimiter);
|
||||
if (index !== -1 && (firstDelimiterIndex === -1 || index < firstDelimiterIndex)) {
|
||||
firstDelimiterIndex = index;
|
||||
foundDelimiter = delimiter;
|
||||
}
|
||||
});
|
||||
|
||||
const hasValueAfterDelimiter = pastedContent.length > firstDelimiterIndex + foundDelimiter.length;
|
||||
|
||||
if (firstDelimiterIndex === -1 || !hasValueAfterDelimiter) {
|
||||
return { key: pastedContent.trim(), value: "" };
|
||||
}
|
||||
|
||||
const [key, value] = pastedContent.split(foundDelimiter);
|
||||
const key = pastedContent.substring(0, firstDelimiterIndex);
|
||||
const value = pastedContent.substring(firstDelimiterIndex + foundDelimiter.length);
|
||||
|
||||
return {
|
||||
key: key.trim(),
|
||||
value: (value ?? "").trim()
|
||||
value: value.trim()
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { createWorkspace } from "@app/hooks/api/workspace/queries";
|
||||
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
const secretsToBeAdded = [
|
||||
{
|
||||
@@ -36,12 +37,13 @@ const secretsToBeAdded = [
|
||||
* Create and initialize a new project in organization with id [organizationId]
|
||||
* Note: current user should be a member of the organization
|
||||
*/
|
||||
const initProjectHelper = async ({ projectName }: { projectName: string }) => {
|
||||
export const initProjectHelper = async ({ projectName }: { projectName: string }) => {
|
||||
// create new project
|
||||
const {
|
||||
data: { project }
|
||||
} = await createWorkspace({
|
||||
projectName
|
||||
projectName,
|
||||
type: ProjectType.SecretManager
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -59,4 +61,27 @@ const initProjectHelper = async ({ projectName }: { projectName: string }) => {
|
||||
return project;
|
||||
};
|
||||
|
||||
export { initProjectHelper };
|
||||
export const getProjectHomePage = (workspace: Workspace) => {
|
||||
if (workspace.type === ProjectType.SecretManager) {
|
||||
return `/${workspace.type}/${workspace.id}/secrets/overview`;
|
||||
}
|
||||
if (workspace.type === ProjectType.CertificateManager) {
|
||||
return `/${workspace.type}/${workspace.id}/certificates`;
|
||||
}
|
||||
|
||||
if (workspace.type === ProjectType.KMS) {
|
||||
return `/${workspace.type}/${workspace.id}/kms`;
|
||||
}
|
||||
|
||||
return `/${workspace.type}/${workspace.id}/ssh`;
|
||||
};
|
||||
|
||||
export const getProjectTitle = (type: ProjectType) => {
|
||||
const titleConvert = {
|
||||
[ProjectType.SecretManager]: "Secret Management",
|
||||
[ProjectType.KMS]: "Key Management",
|
||||
[ProjectType.CertificateManager]: "Cert Management",
|
||||
[ProjectType.SSH]: "SSH"
|
||||
};
|
||||
return titleConvert[type];
|
||||
};
|
||||
|
||||
@@ -7,5 +7,6 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
|
||||
[IdentityAuthMethod.GCP_AUTH]: "GCP Auth",
|
||||
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
|
||||
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
|
||||
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth"
|
||||
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth",
|
||||
[IdentityAuthMethod.JWT_AUTH]: "JWT Auth"
|
||||
};
|
||||
|
||||
@@ -5,5 +5,11 @@ export enum IdentityAuthMethod {
|
||||
GCP_AUTH = "gcp-auth",
|
||||
AWS_AUTH = "aws-auth",
|
||||
AZURE_AUTH = "azure-auth",
|
||||
OIDC_AUTH = "oidc-auth"
|
||||
OIDC_AUTH = "oidc-auth",
|
||||
JWT_AUTH = "jwt-auth"
|
||||
}
|
||||
|
||||
export enum IdentityJwtConfigurationType {
|
||||
JWKS = "jwks",
|
||||
STATIC = "static"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export {
|
||||
useAddIdentityAwsAuth,
|
||||
useAddIdentityAzureAuth,
|
||||
useAddIdentityGcpAuth,
|
||||
useAddIdentityJwtAuth,
|
||||
useAddIdentityKubernetesAuth,
|
||||
useAddIdentityOidcAuth,
|
||||
useAddIdentityTokenAuth,
|
||||
@@ -15,6 +16,7 @@ export {
|
||||
useDeleteIdentityAwsAuth,
|
||||
useDeleteIdentityAzureAuth,
|
||||
useDeleteIdentityGcpAuth,
|
||||
useDeleteIdentityJwtAuth,
|
||||
useDeleteIdentityKubernetesAuth,
|
||||
useDeleteIdentityOidcAuth,
|
||||
useDeleteIdentityTokenAuth,
|
||||
@@ -25,20 +27,24 @@ export {
|
||||
useUpdateIdentityAwsAuth,
|
||||
useUpdateIdentityAzureAuth,
|
||||
useUpdateIdentityGcpAuth,
|
||||
useUpdateIdentityJwtAuth,
|
||||
useUpdateIdentityKubernetesAuth,
|
||||
useUpdateIdentityOidcAuth,
|
||||
useUpdateIdentityTokenAuth,
|
||||
useUpdateIdentityTokenAuthToken,
|
||||
useUpdateIdentityUniversalAuth} from "./mutations";
|
||||
useUpdateIdentityUniversalAuth
|
||||
} from "./mutations";
|
||||
export {
|
||||
useGetIdentityAwsAuth,
|
||||
useGetIdentityAzureAuth,
|
||||
useGetIdentityById,
|
||||
useGetIdentityGcpAuth,
|
||||
useGetIdentityJwtAuth,
|
||||
useGetIdentityKubernetesAuth,
|
||||
useGetIdentityOidcAuth,
|
||||
useGetIdentityProjectMemberships,
|
||||
useGetIdentityTokenAuth,
|
||||
useGetIdentityTokensTokenAuth,
|
||||
useGetIdentityUniversalAuth,
|
||||
useGetIdentityUniversalAuthClientSecrets} from "./queries";
|
||||
useGetIdentityUniversalAuthClientSecrets
|
||||
} from "./queries";
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
AddIdentityAwsAuthDTO,
|
||||
AddIdentityAzureAuthDTO,
|
||||
AddIdentityGcpAuthDTO,
|
||||
AddIdentityJwtAuthDTO,
|
||||
AddIdentityKubernetesAuthDTO,
|
||||
AddIdentityOidcAuthDTO,
|
||||
AddIdentityTokenAuthDTO,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
DeleteIdentityAzureAuthDTO,
|
||||
DeleteIdentityDTO,
|
||||
DeleteIdentityGcpAuthDTO,
|
||||
DeleteIdentityJwtAuthDTO,
|
||||
DeleteIdentityKubernetesAuthDTO,
|
||||
DeleteIdentityOidcAuthDTO,
|
||||
DeleteIdentityTokenAuthDTO,
|
||||
@@ -32,6 +34,7 @@ import {
|
||||
IdentityAwsAuth,
|
||||
IdentityAzureAuth,
|
||||
IdentityGcpAuth,
|
||||
IdentityJwtAuth,
|
||||
IdentityKubernetesAuth,
|
||||
IdentityOidcAuth,
|
||||
IdentityTokenAuth,
|
||||
@@ -42,6 +45,7 @@ import {
|
||||
UpdateIdentityAzureAuthDTO,
|
||||
UpdateIdentityDTO,
|
||||
UpdateIdentityGcpAuthDTO,
|
||||
UpdateIdentityJwtAuthDTO,
|
||||
UpdateIdentityKubernetesAuthDTO,
|
||||
UpdateIdentityOidcAuthDTO,
|
||||
UpdateIdentityTokenAuthDTO,
|
||||
@@ -518,6 +522,118 @@ export const useDeleteIdentityOidcAuth = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
export const useUpdateIdentityJwtAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityJwtAuth, {}, UpdateIdentityJwtAuthDTO>({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
configurationType,
|
||||
jwksUrl,
|
||||
jwksCaCert,
|
||||
publicKeys,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps,
|
||||
boundIssuer,
|
||||
boundAudiences,
|
||||
boundClaims,
|
||||
boundSubject
|
||||
}) => {
|
||||
const {
|
||||
data: { identityJwtAuth }
|
||||
} = await apiRequest.patch<{ identityJwtAuth: IdentityJwtAuth }>(
|
||||
`/api/v1/auth/jwt-auth/identities/${identityId}`,
|
||||
{
|
||||
configurationType,
|
||||
jwksUrl,
|
||||
jwksCaCert,
|
||||
publicKeys,
|
||||
boundIssuer,
|
||||
boundAudiences,
|
||||
boundClaims,
|
||||
boundSubject,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}
|
||||
);
|
||||
|
||||
return identityJwtAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
|
||||
queryClient.invalidateQueries(identitiesKeys.getIdentityById(identityId));
|
||||
queryClient.invalidateQueries(identitiesKeys.getIdentityJwtAuth(identityId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddIdentityJwtAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityJwtAuth, {}, AddIdentityJwtAuthDTO>({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
configurationType,
|
||||
jwksUrl,
|
||||
jwksCaCert,
|
||||
publicKeys,
|
||||
boundIssuer,
|
||||
boundAudiences,
|
||||
boundClaims,
|
||||
boundSubject,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}) => {
|
||||
const {
|
||||
data: { identityJwtAuth }
|
||||
} = await apiRequest.post<{ identityJwtAuth: IdentityJwtAuth }>(
|
||||
`/api/v1/auth/jwt-auth/identities/${identityId}`,
|
||||
{
|
||||
configurationType,
|
||||
jwksUrl,
|
||||
jwksCaCert,
|
||||
publicKeys,
|
||||
boundIssuer,
|
||||
boundAudiences,
|
||||
boundClaims,
|
||||
boundSubject,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}
|
||||
);
|
||||
|
||||
return identityJwtAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
|
||||
queryClient.invalidateQueries(identitiesKeys.getIdentityById(identityId));
|
||||
queryClient.invalidateQueries(identitiesKeys.getIdentityJwtAuth(identityId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIdentityJwtAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityTokenAuth, {}, DeleteIdentityJwtAuthDTO>({
|
||||
mutationFn: async ({ identityId }) => {
|
||||
const {
|
||||
data: { identityJwtAuth }
|
||||
} = await apiRequest.delete(`/api/v1/auth/jwt-auth/identities/${identityId}`);
|
||||
return identityJwtAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
|
||||
queryClient.invalidateQueries(identitiesKeys.getIdentityById(identityId));
|
||||
queryClient.invalidateQueries(identitiesKeys.getIdentityJwtAuth(identityId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddIdentityAzureAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
IdentityAwsAuth,
|
||||
IdentityAzureAuth,
|
||||
IdentityGcpAuth,
|
||||
IdentityJwtAuth,
|
||||
IdentityKubernetesAuth,
|
||||
IdentityMembership,
|
||||
IdentityMembershipOrg,
|
||||
@@ -29,6 +30,7 @@ export const identitiesKeys = {
|
||||
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const,
|
||||
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const,
|
||||
getIdentityTokenAuth: (identityId: string) => [{ identityId }, "identity-token-auth"] as const,
|
||||
getIdentityJwtAuth: (identityId: string) => [{ identityId }, "identity-jwt-auth"] as const,
|
||||
getIdentityTokensTokenAuth: (identityId: string) =>
|
||||
[{ identityId }, "identity-tokens-token-auth"] as const,
|
||||
getIdentityProjectMemberships: (identityId: string) =>
|
||||
@@ -276,3 +278,30 @@ export const useGetIdentityOidcAuth = (
|
||||
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIdentityJwtAuth = (
|
||||
identityId: string,
|
||||
options?: UseQueryOptions<
|
||||
IdentityJwtAuth,
|
||||
unknown,
|
||||
IdentityJwtAuth,
|
||||
ReturnType<typeof identitiesKeys.getIdentityJwtAuth>
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: identitiesKeys.getIdentityJwtAuth(identityId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { identityJwtAuth }
|
||||
} = await apiRequest.get<{ identityJwtAuth: IdentityJwtAuth }>(
|
||||
`/api/v1/auth/jwt-auth/identities/${identityId}`
|
||||
);
|
||||
|
||||
return identityJwtAuth;
|
||||
},
|
||||
staleTime: 0,
|
||||
cacheTime: 0,
|
||||
...options,
|
||||
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TOrgRole } from "../roles/types";
|
||||
import { ProjectUserMembershipTemporaryMode, Workspace } from "../workspace/types";
|
||||
import { IdentityAuthMethod } from "./enums";
|
||||
import { IdentityAuthMethod, IdentityJwtConfigurationType } from "./enums";
|
||||
|
||||
export type IdentityTrustedIp = {
|
||||
id: string;
|
||||
@@ -47,7 +47,7 @@ export type IdentityMembershipOrg = {
|
||||
export type IdentityMembership = {
|
||||
id: string;
|
||||
identity: Identity;
|
||||
project: Pick<Workspace, "id" | "name">;
|
||||
project: Pick<Workspace, "id" | "name" | "type">;
|
||||
roles: Array<
|
||||
{
|
||||
id: string;
|
||||
@@ -446,6 +446,65 @@ export type DeleteIdentityTokenAuthDTO = {
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type IdentityJwtAuth = {
|
||||
identityId: string;
|
||||
configurationType: IdentityJwtConfigurationType;
|
||||
jwksUrl: string;
|
||||
jwksCaCert: string;
|
||||
publicKeys: string[];
|
||||
boundIssuer: string;
|
||||
boundAudiences: string;
|
||||
boundClaims: Record<string, string>;
|
||||
boundSubject: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||
};
|
||||
|
||||
export type AddIdentityJwtAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
configurationType: string;
|
||||
jwksUrl?: string;
|
||||
jwksCaCert: string;
|
||||
publicKeys?: string[];
|
||||
boundIssuer: string;
|
||||
boundAudiences: string;
|
||||
boundClaims: Record<string, string>;
|
||||
boundSubject: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type UpdateIdentityJwtAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
configurationType?: string;
|
||||
jwksUrl?: string;
|
||||
jwksCaCert?: string;
|
||||
publicKeys?: string[];
|
||||
boundIssuer?: string;
|
||||
boundAudiences?: string;
|
||||
boundClaims?: Record<string, string>;
|
||||
boundSubject?: string;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
accessTokenNumUsesLimit?: number;
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type DeleteIdentityJwtAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type CreateTokenIdentityTokenAuthDTO = {
|
||||
identityId: string;
|
||||
name: string;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { ProjectType } from "../workspace/types";
|
||||
|
||||
export const useImportEnvKey = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -31,7 +32,7 @@ export const useImportEnvKey = () => {
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MfaMethod } from "../auth/types";
|
||||
import { UserWsKeyPair } from "../keys/types";
|
||||
import { ProjectUserMembershipTemporaryMode } from "../workspace/types";
|
||||
import { ProjectType, ProjectUserMembershipTemporaryMode } from "../workspace/types";
|
||||
|
||||
export enum AuthMethod {
|
||||
EMAIL = "email",
|
||||
@@ -95,6 +95,7 @@ export type TWorkspaceUser = {
|
||||
project: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: ProjectType;
|
||||
};
|
||||
inviteEmail: string;
|
||||
organization: string;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { userKeys } from "../users/query-keys";
|
||||
import { workspaceKeys } from "./query-keys";
|
||||
import { TUpdateWorkspaceGroupRoleDTO } from "./types";
|
||||
import { ProjectType, TUpdateWorkspaceGroupRoleDTO } from "./types";
|
||||
|
||||
export const useAddGroupToWorkspace = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -83,7 +83,7 @@ export const useLeaveProject = () => {
|
||||
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/leave`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace());
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -95,7 +95,7 @@ export const useMigrateProjectToV3 = () => {
|
||||
return apiRequest.post(`/api/v1/workspace/${workspaceId}/migrate-v3`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
DeleteWorkspaceDTO,
|
||||
NameWorkspaceSecretsDTO,
|
||||
ProjectIdentityOrderBy,
|
||||
ProjectType,
|
||||
TGetUpgradeProjectStatusDTO,
|
||||
TListProjectIdentitiesDTO,
|
||||
ToggleAutoCapitalizationDTO,
|
||||
@@ -84,7 +85,7 @@ export const useUpgradeProject = () => {
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -104,10 +105,11 @@ export const useGetUpgradeProjectStatus = ({
|
||||
});
|
||||
};
|
||||
|
||||
const fetchUserWorkspaces = async (includeRoles?: boolean) => {
|
||||
const fetchUserWorkspaces = async (includeRoles?: boolean, type?: ProjectType | "all") => {
|
||||
const { data } = await apiRequest.get<{ workspaces: Workspace[] }>("/api/v1/workspace", {
|
||||
params: {
|
||||
includeRoles
|
||||
includeRoles,
|
||||
type
|
||||
}
|
||||
});
|
||||
return data.workspaces;
|
||||
@@ -141,8 +143,16 @@ export const useGetWorkspaceById = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetUserWorkspaces = (includeRoles?: boolean) =>
|
||||
useQuery(workspaceKeys.getAllUserWorkspace, () => fetchUserWorkspaces(includeRoles));
|
||||
export const useGetUserWorkspaces = ({
|
||||
includeRoles,
|
||||
type = "all"
|
||||
}: {
|
||||
includeRoles?: boolean;
|
||||
type?: ProjectType | "all";
|
||||
} = {}) =>
|
||||
useQuery(workspaceKeys.getAllUserWorkspace(type || ""), () =>
|
||||
fetchUserWorkspaces(includeRoles, type)
|
||||
);
|
||||
|
||||
const fetchUserWorkspaceMemberships = async (orgId: string) => {
|
||||
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
|
||||
@@ -208,33 +218,26 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
|
||||
refetchInterval: 4000
|
||||
});
|
||||
|
||||
export const createWorkspace = ({
|
||||
projectName,
|
||||
projectDescription,
|
||||
kmsKeyId,
|
||||
template
|
||||
}: CreateWorkspaceDTO): Promise<{ data: { project: Workspace } }> => {
|
||||
return apiRequest.post("/api/v2/workspace", {
|
||||
projectName,
|
||||
projectDescription,
|
||||
kmsKeyId,
|
||||
template
|
||||
});
|
||||
export const createWorkspace = (
|
||||
dto: CreateWorkspaceDTO
|
||||
): Promise<{ data: { project: Workspace } }> => {
|
||||
return apiRequest.post("/api/v2/workspace", dto);
|
||||
};
|
||||
|
||||
export const useCreateWorkspace = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({
|
||||
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template }) =>
|
||||
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template, type }) =>
|
||||
createWorkspace({
|
||||
projectName,
|
||||
projectDescription,
|
||||
kmsKeyId,
|
||||
template
|
||||
template,
|
||||
type
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
onSuccess: (dto) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.data.project.type));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -242,15 +245,19 @@ export const useCreateWorkspace = () => {
|
||||
export const useUpdateProject = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, UpdateProjectDTO>({
|
||||
mutationFn: ({ projectID, newProjectName, newProjectDescription }) => {
|
||||
return apiRequest.patch(`/api/v1/workspace/${projectID}`, {
|
||||
name: newProjectName,
|
||||
description: newProjectDescription
|
||||
});
|
||||
return useMutation<Workspace, {}, UpdateProjectDTO>({
|
||||
mutationFn: async ({ projectID, newProjectName, newProjectDescription }) => {
|
||||
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
|
||||
`/api/v1/workspace/${projectID}`,
|
||||
{
|
||||
name: newProjectName,
|
||||
description: newProjectDescription
|
||||
}
|
||||
);
|
||||
return data.workspace;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
onSuccess: (dto) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -258,13 +265,18 @@ export const useUpdateProject = () => {
|
||||
export const useToggleAutoCapitalization = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, ToggleAutoCapitalizationDTO>({
|
||||
mutationFn: ({ workspaceID, state }) =>
|
||||
apiRequest.post(`/api/v1/workspace/${workspaceID}/auto-capitalization`, {
|
||||
autoCapitalization: state
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
return useMutation<Workspace, {}, ToggleAutoCapitalizationDTO>({
|
||||
mutationFn: async ({ workspaceID, state }) => {
|
||||
const { data } = await apiRequest.post<{ workspace: Workspace }>(
|
||||
`/api/v1/workspace/${workspaceID}/auto-capitalization`,
|
||||
{
|
||||
autoCapitalization: state
|
||||
}
|
||||
);
|
||||
return data.workspace;
|
||||
},
|
||||
onSuccess: (dto) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -272,14 +284,15 @@ export const useToggleAutoCapitalization = () => {
|
||||
export const useUpdateWorkspaceVersionLimit = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, UpdatePitVersionLimitDTO>({
|
||||
mutationFn: ({ projectSlug, pitVersionLimit }) => {
|
||||
return apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
|
||||
return useMutation<Workspace, {}, UpdatePitVersionLimitDTO>({
|
||||
mutationFn: async ({ projectSlug, pitVersionLimit }) => {
|
||||
const { data } = await apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
|
||||
pitVersionLimit
|
||||
});
|
||||
return data.workspace;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
onSuccess: (dto) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -287,14 +300,18 @@ export const useUpdateWorkspaceVersionLimit = () => {
|
||||
export const useUpdateWorkspaceAuditLogsRetention = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, UpdateAuditLogsRetentionDTO>({
|
||||
mutationFn: ({ projectSlug, auditLogsRetentionDays }) => {
|
||||
return apiRequest.put(`/api/v1/workspace/${projectSlug}/audit-logs-retention`, {
|
||||
auditLogsRetentionDays
|
||||
});
|
||||
return useMutation<Workspace, {}, UpdateAuditLogsRetentionDTO>({
|
||||
mutationFn: async ({ projectSlug, auditLogsRetentionDays }) => {
|
||||
const { data } = await apiRequest.put(
|
||||
`/api/v1/workspace/${projectSlug}/audit-logs-retention`,
|
||||
{
|
||||
auditLogsRetentionDays
|
||||
}
|
||||
);
|
||||
return data.workspace;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
onSuccess: (dto) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -302,12 +319,13 @@ export const useUpdateWorkspaceAuditLogsRetention = () => {
|
||||
export const useDeleteWorkspace = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, DeleteWorkspaceDTO>({
|
||||
mutationFn: ({ workspaceID }) => {
|
||||
return apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
|
||||
return useMutation<Workspace, {}, DeleteWorkspaceDTO>({
|
||||
mutationFn: async ({ workspaceID }) => {
|
||||
const { data } = await apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
|
||||
return data.workspace;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
onSuccess: (dto) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||
queryClient.invalidateQueries(["org-admin-projects"]);
|
||||
}
|
||||
});
|
||||
@@ -324,7 +342,7 @@ export const useCreateWsEnvironment = () => {
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -341,7 +359,7 @@ export const useUpdateWsEnvironment = () => {
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -354,7 +372,7 @@ export const useDeleteWsEnvironment = () => {
|
||||
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/environments/${id}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,7 +11,8 @@ export const workspaceKeys = {
|
||||
getWorkspaceMemberships: (orgId: string) => [{ orgId }, "workspace-memberships"],
|
||||
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
|
||||
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
|
||||
getAllUserWorkspace: ["workspaces"] as const,
|
||||
getAllUserWorkspace: (type?: string) =>
|
||||
type ? ["workspaces", { type }] : (["workspace"] as const),
|
||||
getWorkspaceAuditLogs: (workspaceId: string) =>
|
||||
[{ workspaceId }, "workspace-audit-logs"] as const,
|
||||
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
||||
|
||||
@@ -8,6 +8,13 @@ export enum ProjectVersion {
|
||||
V3 = 3
|
||||
}
|
||||
|
||||
export enum ProjectType {
|
||||
SecretManager = "secret-manager",
|
||||
CertificateManager = "cert-manager",
|
||||
KMS = "kms",
|
||||
SSH = "ssh"
|
||||
}
|
||||
|
||||
export enum ProjectUserMembershipTemporaryMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
@@ -16,6 +23,7 @@ export type Workspace = {
|
||||
__v: number;
|
||||
id: string;
|
||||
name: string;
|
||||
type: ProjectType;
|
||||
description?: string;
|
||||
orgId: string;
|
||||
version: ProjectVersion;
|
||||
@@ -59,6 +67,7 @@ export type CreateWorkspaceDTO = {
|
||||
projectDescription?: string;
|
||||
kmsKeyId?: string;
|
||||
template?: string;
|
||||
type: ProjectType;
|
||||
};
|
||||
|
||||
export type UpdateProjectDTO = {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/* eslint-disable no-var */
|
||||
/* eslint-disable func-names */
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -38,21 +38,17 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization, useSubscription, useUser, useWorkspace } from "@app/context";
|
||||
import { usePopUp, useToggle } from "@app/hooks";
|
||||
import {
|
||||
useGetAccessRequestsCount,
|
||||
useGetOrgTrialUrl,
|
||||
useGetSecretApprovalRequestCount,
|
||||
useLogoutUser,
|
||||
useSelectOrganization
|
||||
} from "@app/hooks/api";
|
||||
import { useGetOrgTrialUrl, useLogoutUser, useSelectOrganization } from "@app/hooks/api";
|
||||
import { MfaMethod } from "@app/hooks/api/auth/types";
|
||||
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { InsecureConnectionBanner } from "@app/layouts/AppLayout/components/InsecureConnectionBanner";
|
||||
import { ProjectSelect } from "@app/layouts/AppLayout/components/ProjectSelect";
|
||||
import { navigateUserToOrg } from "@app/views/Login/Login.utils";
|
||||
import { Mfa } from "@app/views/Login/Mfa";
|
||||
import { CreateOrgModal } from "@app/views/Org/components";
|
||||
|
||||
import { ProjectSidebarItem } from "./components/ProjectSidebarItems";
|
||||
import { WishForm } from "./components/WishForm/WishForm";
|
||||
|
||||
interface LayoutProps {
|
||||
@@ -87,7 +83,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
|
||||
const { mutateAsync } = useGetOrgTrialUrl();
|
||||
|
||||
const { workspaces, currentWorkspace } = useWorkspace();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { orgs, currentOrg } = useOrganization();
|
||||
|
||||
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
|
||||
@@ -96,15 +92,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
|
||||
const { user } = useUser();
|
||||
const { subscription } = useSubscription();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
|
||||
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
|
||||
const { data: accessApprovalRequestCount } = useGetAccessRequestsCount({ projectSlug });
|
||||
|
||||
const pendingRequestsCount = useMemo(() => {
|
||||
return (secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
|
||||
}, [secretApprovalReqCount, accessApprovalRequestCount]);
|
||||
|
||||
const infisicalPlatformVersion = process.env.NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION;
|
||||
|
||||
@@ -152,41 +139,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
if (tempLocalStorage("orgData.id") === "" && orgs?.[0]?.id) {
|
||||
localStorage.setItem("orgData.id", orgs?.[0]?.id);
|
||||
}
|
||||
|
||||
if (
|
||||
currentOrg &&
|
||||
((workspaces?.length === 0 && router.asPath.includes("project")) ||
|
||||
router.asPath.includes("/project/undefined") ||
|
||||
(!orgs?.map((org) => org.id)?.includes(router.query.id as string) &&
|
||||
!router.asPath.includes("project") &&
|
||||
!router.asPath.includes("personal") &&
|
||||
!router.asPath.includes("secret-scanning") &&
|
||||
!router.asPath.includes("integration")))
|
||||
) {
|
||||
router.push(`/org/${currentOrg?.id}/overview`);
|
||||
}
|
||||
// else if (!router.asPath.includes("org") && !router.asPath.includes("project") && !router.asPath.includes("integrations") && !router.asPath.includes("personal-settings")) {
|
||||
|
||||
// const pathSegments = router.asPath.split("/").filter((segment) => segment.length > 0);
|
||||
|
||||
// let intendedWorkspaceId;
|
||||
// if (pathSegments.length >= 2 && pathSegments[0] === "dashboard") {
|
||||
// [, intendedWorkspaceId] = pathSegments;
|
||||
// } else if (pathSegments.length >= 3 && pathSegments[0] === "settings") {
|
||||
// [, , intendedWorkspaceId] = pathSegments;
|
||||
// } else {
|
||||
// const lastPathSegments = router.asPath.split("/").pop();
|
||||
// if (lastPathSegments !== undefined) {
|
||||
// [intendedWorkspaceId] = lastPathSegments.split("?");
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!intendedWorkspaceId) return;
|
||||
|
||||
// if (!["callback", "create", "authorize"].includes(intendedWorkspaceId)) {
|
||||
// localStorage.setItem("projectData.id", intendedWorkspaceId);
|
||||
// }
|
||||
// }
|
||||
};
|
||||
putUserInOrg();
|
||||
}, [router.query.id]);
|
||||
@@ -214,9 +166,8 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
<div>
|
||||
{!router.asPath.includes("personal") && (
|
||||
<div className="flex h-12 cursor-default items-center px-3 pt-6">
|
||||
{(router.asPath.includes("project") ||
|
||||
router.asPath.includes("integrations")) && (
|
||||
<Link href={`/org/${currentOrg?.id}/overview`}>
|
||||
{(currentWorkspace || router.asPath.includes("integrations")) && (
|
||||
<Link href={`/org/${currentOrg?.id}/${currentWorkspace?.type}/overview`}>
|
||||
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
</div>
|
||||
@@ -379,7 +330,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
(!router.asPath.includes("personal") && currentWorkspace ? (
|
||||
<ProjectSelect />
|
||||
) : (
|
||||
<Link href={`/org/${currentOrg?.id}/overview`}>
|
||||
<Link href={`/org/${currentOrg?.id}/${currentWorkspace?.type}/overview`}>
|
||||
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
|
||||
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
|
||||
Back to organization
|
||||
@@ -387,132 +338,61 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
</Link>
|
||||
))}
|
||||
<div className={`px-1 ${!router.asPath.includes("personal") ? "block" : "hidden"}`}>
|
||||
{(router.asPath.includes("project") || router.asPath.includes("integrations")) &&
|
||||
currentWorkspace ? (
|
||||
<Menu>
|
||||
<Link href={`/project/${currentWorkspace?.id}/secrets/overview`} passHref>
|
||||
<ProjectSidebarItem />
|
||||
{router.pathname.startsWith("/org") && (
|
||||
<Menu className="mt-4">
|
||||
<Link
|
||||
href={`/org/${currentOrg?.id}/${ProjectType.SecretManager}/overview`}
|
||||
passHref
|
||||
>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.includes(
|
||||
`/project/${currentWorkspace?.id}/secrets`
|
||||
`/${ProjectType.SecretManager}/overview`
|
||||
)}
|
||||
icon="system-outline-90-lock-closed"
|
||||
icon="system-outline-165-view-carousel"
|
||||
>
|
||||
{t("nav.menu.secrets")}
|
||||
Secret Management
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?.id}/certificates`} passHref>
|
||||
<Link
|
||||
href={`/org/${currentOrg?.id}/${ProjectType.CertificateManager}/overview`}
|
||||
passHref
|
||||
>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/project/${currentWorkspace?.id}/certificates`
|
||||
}
|
||||
icon="system-outline-90-lock-closed"
|
||||
isSelected={router.asPath.includes(
|
||||
`/${ProjectType.CertificateManager}/overview`
|
||||
)}
|
||||
icon="note"
|
||||
>
|
||||
Internal PKI
|
||||
Cert Management
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?.id}/kms`} passHref>
|
||||
<Link href={`/org/${currentOrg?.id}/${ProjectType.KMS}/overview`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath === `/project/${currentWorkspace?.id}/kms`}
|
||||
icon="system-outline-90-lock-closed"
|
||||
isSelected={router.asPath.includes(`/${ProjectType.KMS}/overview`)}
|
||||
icon="unlock"
|
||||
>
|
||||
Key Management
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?.id}/ssh`} passHref>
|
||||
<Link href={`/org/${currentOrg?.id}/ssh/overview`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath === `/project/${currentWorkspace?.id}/ssh`}
|
||||
icon="system-outline-90-lock-closed"
|
||||
isSelected={
|
||||
router.asPath === `/org/${currentWorkspace?.id}/ssh/overview`
|
||||
}
|
||||
icon="system-regular-126-verified-hover-verified"
|
||||
>
|
||||
SSH
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?.id}/members`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/project/${currentWorkspace?.id}/members`
|
||||
}
|
||||
icon="system-outline-96-groups"
|
||||
>
|
||||
Access Control
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/integrations/${currentWorkspace?.id}`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.includes("/integrations")}
|
||||
icon="system-outline-82-extension"
|
||||
>
|
||||
{t("nav.menu.integrations")}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?.id}/secret-rotation`} passHref>
|
||||
<a className="relative">
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/project/${currentWorkspace?.id}/secret-rotation`
|
||||
}
|
||||
icon="rotation"
|
||||
>
|
||||
Secret Rotation
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?.id}/approval`} passHref>
|
||||
<a className="relative">
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/project/${currentWorkspace?.id}/approval`
|
||||
}
|
||||
icon="system-outline-189-domain-verification"
|
||||
>
|
||||
Approvals
|
||||
{Boolean(
|
||||
secretApprovalReqCount?.open ||
|
||||
accessApprovalRequestCount?.pendingCount
|
||||
) && (
|
||||
<span className="ml-2 rounded border border-primary-400 bg-primary-600 py-0.5 px-1 text-xs font-semibold text-black">
|
||||
{pendingRequestsCount}
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?.id}/settings`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/project/${currentWorkspace?.id}/settings`
|
||||
}
|
||||
icon="system-outline-109-slider-toggle-settings"
|
||||
>
|
||||
{t("nav.menu.project-settings")}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
</Menu>
|
||||
) : (
|
||||
<Menu className="mt-4">
|
||||
<Link href={`/org/${currentOrg?.id}/overview`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.includes("/overview")}
|
||||
icon="system-outline-165-view-carousel"
|
||||
>
|
||||
Overview
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/org/${currentOrg?.id}/members`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
|
||||
@@ -5,6 +5,7 @@ import { faBugs, faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Button } from "@app/components/v2";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
@@ -62,7 +63,7 @@ const ErrorPage = ({ error }: { error: Error | null }) => {
|
||||
size="xs"
|
||||
onClick={() =>
|
||||
// we need to go to /org/${orgId}/overview, but we need to do a full page reload to ensure that the error the user is facing is properly reset.
|
||||
window.location.assign(`/org/${orgId}/overview`)
|
||||
window.location.assign(`/org/${orgId}/${ProjectType.SecretManager}/overview`)
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faHome} className="mr-2" />
|
||||
|
||||
@@ -1,343 +0,0 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
/* eslint-disable react/jsx-key */
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
|
||||
import {
|
||||
faAngleDown,
|
||||
faBook,
|
||||
faCoins,
|
||||
faEnvelope,
|
||||
faGear,
|
||||
faPlus,
|
||||
faRightFromBracket
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { TFunction } from "i18next";
|
||||
|
||||
import guidGenerator from "@app/components/utilities/randomId";
|
||||
import { useOrganization, useSubscription, useUser } from "@app/context";
|
||||
import { useGetOrgTrialUrl, useLogoutUser } from "@app/hooks/api";
|
||||
|
||||
const supportOptions = (t: TFunction) => [
|
||||
[
|
||||
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faSlack} />,
|
||||
t("nav.support.slack"),
|
||||
"https://infisical.com/slack"
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faBook} />,
|
||||
t("nav.support.docs"),
|
||||
"https://infisical.com/docs/documentation/getting-started/introduction"
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faGithub} />,
|
||||
t("nav.support.issue"),
|
||||
"https://github.com/Infisical/infisical-cli/issues"
|
||||
],
|
||||
[
|
||||
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faEnvelope} />,
|
||||
t("nav.support.email"),
|
||||
"mailto:[email protected]"
|
||||
]
|
||||
];
|
||||
|
||||
export interface ICurrentOrg {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the navigation bar in the main app.
|
||||
* It has two main components: support options and user menu (inlcudes billing, logout, org/user settings)
|
||||
* @returns NavBar
|
||||
*/
|
||||
export const Navbar = () => {
|
||||
const router = useRouter();
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { currentOrg, orgs } = useOrganization();
|
||||
const { mutateAsync } = useGetOrgTrialUrl();
|
||||
const { user } = useUser();
|
||||
|
||||
const logout = useLogoutUser();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// remove this memo
|
||||
const supportOptionsList = useMemo(() => supportOptions(t), [t]);
|
||||
|
||||
const closeApp = async () => {
|
||||
try {
|
||||
console.log("Logging out...");
|
||||
await logout.mutateAsync();
|
||||
localStorage.removeItem("protectedKey");
|
||||
localStorage.removeItem("protectedKeyIV");
|
||||
localStorage.removeItem("protectedKeyTag");
|
||||
localStorage.removeItem("publicKey");
|
||||
localStorage.removeItem("encryptedPrivateKey");
|
||||
localStorage.removeItem("iv");
|
||||
localStorage.removeItem("tag");
|
||||
localStorage.removeItem("PRIVATE_KEY");
|
||||
localStorage.removeItem("orgData.id");
|
||||
localStorage.removeItem("projectData.id");
|
||||
router.push("/login");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="z-[70] border-b border-mineshaft-500 bg-mineshaft-900 text-white">
|
||||
<div className="flex w-full justify-between px-4">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex justify-center py-4">
|
||||
<Image src="/images/logotransparent.png" height={23} width={57} alt="logo" />
|
||||
</div>
|
||||
<a href="#" className="mx-2 text-2xl font-semibold text-white">
|
||||
Infisical
|
||||
</a>
|
||||
</div>
|
||||
<div className="relative z-40 mx-2 flex items-center justify-start">
|
||||
<a
|
||||
href="https://infisical.com/docs/documentation/getting-started/introduction"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mr-4 flex items-center rounded-md px-3 py-2 text-sm text-gray-200 duration-200 hover:bg-white/10"
|
||||
>
|
||||
<FontAwesomeIcon icon={faBook} className="mr-2 text-xl" />
|
||||
Docs
|
||||
</a>
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<div className="mr-4">
|
||||
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||
<FontAwesomeIcon className="text-xl" icon={faCircleQuestion} />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-20 mt-0.5 w-64 origin-top-right rounded-md border border-mineshaft-700 bg-bunker px-2 py-1.5 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
{supportOptionsList.map(([icon, text, url]) => (
|
||||
<a
|
||||
key={guidGenerator()}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={String(url)}
|
||||
className="flex w-full items-center rounded-md py-0.5 font-normal text-gray-300 duration-200"
|
||||
>
|
||||
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md py-2 px-2 text-gray-400 duration-200 hover:bg-white/10 hover:text-gray-200">
|
||||
{icon}
|
||||
<div className="text-sm">{text}</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
<Menu as="div" className="relative mr-4 inline-block text-left">
|
||||
<div>
|
||||
<Menu.Button className="inline-flex w-full justify-center rounded-md py-2 pr-2 pl-2 text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||
{user?.firstName} {user?.lastName}
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleDown}
|
||||
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="w-68 absolute right-0 z-[125] mt-0.5 origin-top-right divide-y divide-mineshaft-700 rounded-md border border-mineshaft-700 bg-mineshaft-900 shadow-lg ring-1 ring-black ring-opacity-5 drop-shadow-2xl focus:outline-none">
|
||||
<div className="px-1 py-1">
|
||||
<div className="ml-2 mt-2 self-start text-xs font-semibold tracking-wide text-gray-400">
|
||||
{t("nav.user.signed-in-as")}
|
||||
</div>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => router.push("/personal-settings")}
|
||||
className="mx-1 my-1 flex cursor-pointer flex-row items-center rounded-md px-1 hover:bg-white/5"
|
||||
>
|
||||
<div className="flex h-8 w-9 items-center justify-center rounded-full bg-white/10 text-gray-300">
|
||||
{user?.firstName?.charAt(0)}
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div>
|
||||
<p className="px-2 pt-1 text-sm text-gray-300">
|
||||
{" "}
|
||||
{user?.firstName} {user?.lastName}
|
||||
</p>
|
||||
<p className="px-2 pb-1 text-xs text-gray-400">{user?.email}</p>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faGear}
|
||||
className="mr-1 cursor-pointer rounded-md p-2 text-lg text-gray-400 hover:bg-white/10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2 pt-2">
|
||||
<div className="ml-2 mt-2 self-start text-xs font-semibold tracking-wide text-gray-400">
|
||||
{t("nav.user.current-organization")}
|
||||
</div>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => router.push(`/settings/org/${router.query.id}`)}
|
||||
className="mt-2 flex cursor-pointer flex-row items-center rounded-md px-2 py-1 hover:bg-white/5"
|
||||
>
|
||||
<div className="flex h-7 w-8 items-center justify-center rounded-md bg-white/10 text-gray-300">
|
||||
{currentOrg?.name?.charAt(0)}
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<p className="px-2 text-sm text-gray-300">{currentOrg?.name}</p>
|
||||
<FontAwesomeIcon
|
||||
icon={faGear}
|
||||
className="cursor-pointer rounded-md p-2 text-lg text-gray-400 hover:bg-white/10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{subscription && subscription.slug !== null && (
|
||||
<button
|
||||
// onClick={buttonAction}
|
||||
type="button"
|
||||
className="w-full cursor-pointer"
|
||||
>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => router.push(`/settings/billing/${router.query.id}`)}
|
||||
className="relative mt-1 flex cursor-pointer select-none justify-start rounded-md py-2 px-2 text-gray-400 duration-200 hover:bg-white/5 hover:text-gray-200"
|
||||
>
|
||||
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faCoins} />
|
||||
<div className="text-sm">{t("nav.user.usage-billing")}</div>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
// onClick={buttonAction}
|
||||
className="mb-2 w-full cursor-pointer"
|
||||
>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => router.push(`/settings/org/${router.query.id}?invite`)}
|
||||
className="relative mt-1 flex cursor-pointer select-none justify-start rounded-md py-2 pl-10 pr-4 text-gray-400 duration-200 hover:bg-primary/100 hover:font-semibold hover:text-black"
|
||||
>
|
||||
<span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 pr-4">
|
||||
<FontAwesomeIcon icon={faPlus} className="ml-1" />
|
||||
</span>
|
||||
<div className="ml-1 text-sm">{t("nav.user.invite")}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{orgs && orgs?.length > 1 && (
|
||||
<div className="px-1 pt-1">
|
||||
<div className="ml-2 mt-2 self-start text-xs font-semibold tracking-wide text-gray-400">
|
||||
{t("nav.user.other-organizations")}
|
||||
</div>
|
||||
<div className="mt-3 mb-2 flex flex-col items-start px-1">
|
||||
{orgs
|
||||
?.filter((org: { id: string }) => org.id !== currentOrg?.id)
|
||||
.map((org: { id: string; name: string }) => (
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
key={guidGenerator()}
|
||||
onClick={() => {
|
||||
localStorage.setItem("orgData.id", org.id);
|
||||
router.reload();
|
||||
}}
|
||||
className="flex w-full cursor-pointer flex-row items-center justify-start rounded-md p-1.5 hover:bg-white/5"
|
||||
>
|
||||
<div className="flex h-7 w-8 items-center justify-center rounded-md bg-white/10 text-gray-300">
|
||||
{org.name.charAt(0)}
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<p className="px-2 text-sm text-gray-300">{org.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="px-1 py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeApp}
|
||||
className={`${
|
||||
active ? "bg-red font-semibold text-white" : "text-gray-400"
|
||||
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
||||
>
|
||||
<div className="relative flex cursor-pointer select-none items-center justify-start">
|
||||
<FontAwesomeIcon
|
||||
className="ml-1.5 mr-3 text-lg"
|
||||
icon={faRightFromBracket}
|
||||
/>
|
||||
{t("common.logout")}
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
|
||||
<div className="mx-auto w-full border-t border-mineshaft-500 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (!subscription || !currentOrg) return;
|
||||
|
||||
// direct user to start pro trial
|
||||
const url = await mutateAsync({
|
||||
orgId: currentOrg.id,
|
||||
success_url: window.location.href
|
||||
});
|
||||
|
||||
window.location.href = url;
|
||||
}}
|
||||
className="mx-auto py-4 text-center text-sm"
|
||||
>
|
||||
You are currently on the <span className="font-semibold">Starter</span> plan. Unlock the
|
||||
full power of Infisical on the{" "}
|
||||
<span className="font-semibold">Pro Free Trial →</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -16,10 +16,11 @@ import {
|
||||
useSubscription,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { getProjectHomePage, getProjectTitle } from "@app/helpers/project";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
|
||||
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
|
||||
import { Workspace } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type TWorkspaceWithFaveProp = Workspace & { isFavorite: boolean };
|
||||
|
||||
@@ -138,6 +139,7 @@ export const ProjectSelect = () => {
|
||||
|
||||
const { options, value } = useMemo(() => {
|
||||
const projectOptions = workspaces
|
||||
.filter((el) => el.type === currentWorkspace?.type)
|
||||
.map((w): Workspace & { isFavorite: boolean } => ({
|
||||
...w,
|
||||
isFavorite: Boolean(projectFavorites?.includes(w.id))
|
||||
@@ -164,7 +166,9 @@ export const ProjectSelect = () => {
|
||||
|
||||
return (
|
||||
<div className="mt-5 mb-4 w-full p-3">
|
||||
<p className="ml-1.5 mb-1 text-xs font-semibold uppercase text-gray-400">Project</p>
|
||||
<p className="ml-1.5 mb-1 text-xs font-semibold uppercase text-gray-400">
|
||||
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"}
|
||||
</p>
|
||||
<FilterableSelect
|
||||
className="text-sm"
|
||||
value={value}
|
||||
@@ -189,7 +193,7 @@ export const ProjectSelect = () => {
|
||||
// todo(akhi): this is not using react query because react query in overview is throwing error when envs are not exact same count
|
||||
// to reproduce change this back to router.push and switch between two projects with different env count
|
||||
// look into this on dashboard revamp
|
||||
window.location.assign(`/project/${project.id}/secrets/overview`);
|
||||
window.location.assign(getProjectHomePage(project));
|
||||
}}
|
||||
options={options}
|
||||
components={{
|
||||
@@ -206,6 +210,7 @@ export const ProjectSelect = () => {
|
||||
<NewProjectModal
|
||||
isOpen={popUp.addNewWs.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
|
||||
projectType={currentWorkspace?.type || ProjectType.SecretManager}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { Menu, MenuItem } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export const ProjectSidebarItem = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
|
||||
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
|
||||
const { data: accessApprovalRequestCount } = useGetAccessRequestsCount({ projectSlug });
|
||||
|
||||
const pendingRequestsCount =
|
||||
(secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
|
||||
|
||||
if (
|
||||
!currentWorkspace ||
|
||||
router.asPath.startsWith("personal") ||
|
||||
router.asPath.startsWith("integrations")
|
||||
) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const isSecretManager = currentWorkspace?.type === ProjectType.SecretManager;
|
||||
const isCertManager = currentWorkspace?.type === ProjectType.CertificateManager;
|
||||
const isCmek = currentWorkspace?.type === ProjectType.KMS;
|
||||
const isSsh = currentWorkspace?.type === ProjectType.SSH;
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
{isSecretManager && (
|
||||
<Link
|
||||
href={`/${ProjectType.SecretManager}/${currentWorkspace?.id}/secrets/overview`}
|
||||
passHref
|
||||
>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.includes(
|
||||
`/${ProjectType.SecretManager}/${currentWorkspace?.id}/secrets`
|
||||
)}
|
||||
icon="system-outline-90-lock-closed"
|
||||
>
|
||||
{t("nav.menu.secrets")}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
{isCertManager && (
|
||||
<Link
|
||||
href={`/${ProjectType.CertificateManager}/${currentWorkspace?.id}/certificates`}
|
||||
passHref
|
||||
>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath ===
|
||||
`/${ProjectType.CertificateManager}/${currentWorkspace?.id}/certificates`
|
||||
}
|
||||
icon="system-outline-90-lock-closed"
|
||||
>
|
||||
Overview
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
{isCmek && (
|
||||
<Link href={`/${ProjectType.KMS}/${currentWorkspace?.id}/kms`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath === `/${ProjectType.KMS}/${currentWorkspace?.id}/kms`}
|
||||
icon="system-outline-90-lock-closed"
|
||||
>
|
||||
Overview
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
{isSsh && (
|
||||
<Link href={`/${ProjectType.SSH}/${currentWorkspace?.id}/ssh`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath === `/${ProjectType.SSH}/${currentWorkspace?.id}/ssh`}
|
||||
icon="system-outline-90-lock-closed"
|
||||
>
|
||||
Overview
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
<Link href={`/${currentWorkspace.type}/${currentWorkspace?.id}/members`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.endsWith(`/${currentWorkspace?.id}/members`)}
|
||||
icon="system-outline-96-groups"
|
||||
>
|
||||
Access Control
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
{isSecretManager && (
|
||||
<Link href={`/integrations/${currentWorkspace?.id}`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.includes("/integrations")}
|
||||
icon="system-outline-82-extension"
|
||||
>
|
||||
{t("nav.menu.integrations")}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
{isSecretManager && (
|
||||
<Link
|
||||
href={`/${ProjectType.SecretManager}/${currentWorkspace?.id}/secret-rotation`}
|
||||
passHref
|
||||
>
|
||||
<a className="relative">
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath ===
|
||||
`/${ProjectType.SecretManager}/${currentWorkspace?.id}/secret-rotation`
|
||||
}
|
||||
icon="rotation"
|
||||
>
|
||||
Secret Rotation
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
{isSecretManager && (
|
||||
<Link href={`/secret-manager/${currentWorkspace?.id}/approval`} passHref>
|
||||
<a className="relative">
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/${ProjectType.SecretManager}/${currentWorkspace?.id}/approval`
|
||||
}
|
||||
icon="system-outline-189-domain-verification"
|
||||
>
|
||||
Approvals
|
||||
{Boolean(
|
||||
secretApprovalReqCount?.open || accessApprovalRequestCount?.pendingCount
|
||||
) && (
|
||||
<span className="ml-2 rounded border border-primary-400 bg-primary-600 py-0.5 px-1 text-xs font-semibold text-black">
|
||||
{pendingRequestsCount}
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
<Link href={`/${currentWorkspace.type}/${currentWorkspace?.id}/settings`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.endsWith(`/${currentWorkspace?.id}/settings`)}
|
||||
icon="system-outline-109-slider-toggle-settings"
|
||||
>
|
||||
{t("nav.menu.project-settings")}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { ProjectSidebarItem } from "./ProjectSidebarItems";
|
||||
@@ -65,7 +65,7 @@ export const WishForm = () => {
|
||||
<PopoverTrigger asChild>
|
||||
<div className="text-md mb-3 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||
<FontAwesomeIcon icon={faRocketchat} className="mr-2" />
|
||||
Make a wish
|
||||
Request a feature
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { IPAllowlistPage } from "@app/views/Project/IPAllowListPage";
|
||||
|
||||
const ProjectAllowlist = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<IPAllowlistPage />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectAllowlist;
|
||||
|
||||
ProjectAllowlist.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { IdentityDetailsPage } from "@app/views/Project/IdentityDetailsPage";
|
||||
|
||||
export default function ProjectIdentityDetailsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<IdentityDetailsPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ProjectIdentityDetailsPage.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { MemberDetailsPage } from "@app/views/Project/MemberDetailsPage";
|
||||
|
||||
export default function Page() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<MemberDetailsPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Page.requireAuth = true;
|
||||
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { MembersPage } from "@app/views/Project/MembersPage";
|
||||
|
||||
export default function WorkspaceMemberSettings() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<MembersPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
WorkspaceMemberSettings.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { RolePage } from "@app/views/Project/RolePage";
|
||||
|
||||
export default function Role() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: "Project Settings" })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<RolePage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Role.requireAuth = true;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { ProjectSettingsPage } from "@app/views/Settings/ProjectSettingsPage";
|
||||
|
||||
const ProjectSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<ProjectSettingsPage />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectSettings;
|
||||
|
||||
ProjectSettings.requireAuth = true;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { ProductOverview } from "../secret-manager/overview";
|
||||
|
||||
const CertManagerOverviewPage = () => <ProductOverview type={ProjectType.CertificateManager} />;
|
||||
|
||||
Object.assign(CertManagerOverviewPage, { requireAuth: true });
|
||||
|
||||
export default CertManagerOverviewPage;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { ProductOverview } from "../secret-manager/overview";
|
||||
|
||||
const CmekManagerOverviewPage = () => <ProductOverview type={ProjectType.KMS} />;
|
||||
|
||||
Object.assign(CmekManagerOverviewPage, { requireAuth: true });
|
||||
|
||||
export default CmekManagerOverviewPage;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,530 @@
|
||||
// REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex
|
||||
|
||||
import { ReactNode, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faFolderOpen, faStar } from "@fortawesome/free-regular-svg-icons";
|
||||
import {
|
||||
faArrowDownAZ,
|
||||
faArrowRight,
|
||||
faArrowUpZA,
|
||||
faBorderAll,
|
||||
faExclamationCircle,
|
||||
faList,
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faSearch,
|
||||
faStar as faSolidStar
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Input,
|
||||
Pagination,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { NewProjectModal } from "@app/components/v2/projects";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
// import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { Workspace } from "@app/hooks/api/types";
|
||||
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
|
||||
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
enum ProjectsViewMode {
|
||||
GRID = "grid",
|
||||
LIST = "list"
|
||||
}
|
||||
|
||||
enum ProjectOrderBy {
|
||||
Name = "name"
|
||||
}
|
||||
|
||||
const formatTitle = (type: ProjectType) => {
|
||||
if (type === ProjectType.SecretManager) return "Secret Management";
|
||||
if (type === ProjectType.CertificateManager) return "Cert Management";
|
||||
if (type === ProjectType.KMS) return "Key Management";
|
||||
return "SSH";
|
||||
};
|
||||
|
||||
const formatDescription = (type: ProjectType) => {
|
||||
if (type === ProjectType.SecretManager)
|
||||
return "Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.";
|
||||
if (type === ProjectType.CertificateManager)
|
||||
return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
|
||||
if (type === ProjectType.KMS)
|
||||
return "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
|
||||
return "Generate SSH credentials to provide secure and centralized SSH access control for your infrastructure.";
|
||||
};
|
||||
|
||||
type Props = {
|
||||
type: ProjectType;
|
||||
};
|
||||
|
||||
// #TODO: Update all the workspaceIds
|
||||
export const ProductOverview = ({ type }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { data: workspaces, isLoading: isWorkspaceLoading } = useGetUserWorkspaces({ type });
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgWorkspaces = workspaces || [];
|
||||
const { data: projectFavorites, isLoading: isProjectFavoritesLoading } =
|
||||
useGetUserProjectFavorites(currentOrg?.id!);
|
||||
const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites();
|
||||
|
||||
const isProjectViewLoading = isWorkspaceLoading || isProjectFavoritesLoading;
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"addNewWs",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const [searchFilter, setSearchFilter] = useState("");
|
||||
const { data: serverDetails } = useFetchServerStatus();
|
||||
const [projectsViewMode, setProjectsViewMode] = useState<ProjectsViewMode>(
|
||||
(localStorage.getItem("projectsViewMode") as ProjectsViewMode) || ProjectsViewMode.GRID
|
||||
);
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||
? subscription.workspacesUsed < subscription.workspaceLimit
|
||||
: true;
|
||||
|
||||
const isWorkspaceEmpty = !isProjectViewLoading && orgWorkspaces?.length === 0;
|
||||
|
||||
const {
|
||||
setPage,
|
||||
perPage,
|
||||
setPerPage,
|
||||
page,
|
||||
offset,
|
||||
limit,
|
||||
toggleOrderDirection,
|
||||
orderDirection
|
||||
} = usePagination(ProjectOrderBy.Name, { initPerPage: 24 });
|
||||
|
||||
const filteredWorkspaces = useMemo(
|
||||
() =>
|
||||
orgWorkspaces
|
||||
.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()))
|
||||
.sort((a, b) =>
|
||||
orderDirection === OrderByDirection.ASC
|
||||
? a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||
: b.name.toLowerCase().localeCompare(a.name.toLowerCase())
|
||||
),
|
||||
[searchFilter, orderDirection, orgWorkspaces]
|
||||
);
|
||||
|
||||
useResetPageHelper({
|
||||
setPage,
|
||||
offset,
|
||||
totalCount: filteredWorkspaces.length
|
||||
});
|
||||
|
||||
const { workspacesWithFaveProp } = useMemo(() => {
|
||||
const workspacesWithFav = filteredWorkspaces
|
||||
.map((w): Workspace & { isFavorite: boolean } => ({
|
||||
...w,
|
||||
isFavorite: Boolean(projectFavorites?.includes(w.id))
|
||||
}))
|
||||
.sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite))
|
||||
.slice(offset, limit * page);
|
||||
|
||||
return {
|
||||
workspacesWithFaveProp: workspacesWithFav
|
||||
};
|
||||
}, [filteredWorkspaces, projectFavorites]);
|
||||
|
||||
const addProjectToFavorites = async (projectId: string) => {
|
||||
try {
|
||||
if (currentOrg?.id) {
|
||||
await updateUserProjectFavorites({
|
||||
orgId: currentOrg?.id,
|
||||
projectFavorites: [...(projectFavorites || []), projectId]
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: "Failed to add project to favorites.",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const removeProjectFromFavorites = async (projectId: string) => {
|
||||
try {
|
||||
if (currentOrg?.id) {
|
||||
await updateUserProjectFavorites({
|
||||
orgId: currentOrg?.id,
|
||||
projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)]
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: "Failed to remove project from favorites.",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderProjectGridItem = (workspace: Workspace, isFavorite: boolean) => (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push(getProjectHomePage(workspace));
|
||||
localStorage.setItem("projectData.id", workspace.id);
|
||||
}}
|
||||
key={workspace.id}
|
||||
className="min-w-72 flex h-40 cursor-pointer flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
|
||||
{isFavorite ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSolidStar}
|
||||
className="text-sm text-yellow-600 hover:text-mineshaft-400"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeProjectFromFavorites(workspace.id);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faStar}
|
||||
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
addProjectToFavorites(workspace.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="mt-1 mb-2.5 grow text-sm text-mineshaft-300"
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 2
|
||||
}}
|
||||
>
|
||||
{workspace.description}
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-row items-end justify-between place-self-end">
|
||||
{type === ProjectType.SecretManager && (
|
||||
<div className="mt-0 text-xs text-mineshaft-400">
|
||||
{workspace.environments?.length || 0} environments
|
||||
</div>
|
||||
)}
|
||||
<button type="button">
|
||||
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200">
|
||||
Explore{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="pl-1.5 pr-0.5 duration-200 hover:pl-2 hover:pr-0"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderProjectListItem = (workspace: Workspace, isFavorite: boolean, index: number) => (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push(getProjectHomePage(workspace));
|
||||
localStorage.setItem("projectData.id", workspace.id);
|
||||
}}
|
||||
key={workspace.id}
|
||||
className={`min-w-72 group grid h-14 cursor-pointer grid-cols-6 border-t border-l border-r border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
|
||||
index === 0 && "rounded-t-md"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center sm:col-span-3 lg:col-span-4">
|
||||
<div className="truncate text-sm text-mineshaft-100">{workspace.name}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
|
||||
<div className="text-center text-sm text-mineshaft-300">
|
||||
{workspace.environments?.length || 0} environments
|
||||
</div>
|
||||
{isFavorite ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSolidStar}
|
||||
className="ml-6 text-sm text-yellow-600 hover:text-mineshaft-400"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeProjectFromFavorites(workspace.id);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faStar}
|
||||
className="ml-6 text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
addProjectToFavorites(workspace.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
let projectsComponents: ReactNode;
|
||||
|
||||
if (filteredWorkspaces.length || isProjectViewLoading) {
|
||||
switch (projectsViewMode) {
|
||||
case ProjectsViewMode.GRID:
|
||||
projectsComponents = (
|
||||
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
{isProjectViewLoading &&
|
||||
Array.apply(0, Array(3)).map((_x, i) => (
|
||||
<div
|
||||
key={`workspace-cards-loading-${i + 1}`}
|
||||
className="min-w-72 flex h-40 flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
>
|
||||
<div className="mt-0 text-lg text-mineshaft-100">
|
||||
<Skeleton className="w-3/4 bg-mineshaft-600" />
|
||||
</div>
|
||||
<div className="mt-0 pb-6 text-sm text-mineshaft-300">
|
||||
<Skeleton className="w-1/2 bg-mineshaft-600" />
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Skeleton className="w-1/2 bg-mineshaft-600" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{!isProjectViewLoading && (
|
||||
<>
|
||||
{workspacesWithFaveProp.map((workspace) =>
|
||||
renderProjectGridItem(workspace, workspace.isFavorite)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
break;
|
||||
case ProjectsViewMode.LIST:
|
||||
default:
|
||||
projectsComponents = (
|
||||
<div className="mt-4 w-full rounded-md">
|
||||
{isProjectViewLoading &&
|
||||
Array.apply(0, Array(3)).map((_x, i) => (
|
||||
<div
|
||||
key={`workspace-cards-loading-${i + 1}`}
|
||||
className={`min-w-72 group flex h-12 cursor-pointer flex-row items-center justify-between border border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
|
||||
i === 0 && "rounded-t-md"
|
||||
} ${i === 2 && "rounded-b-md border-b"}`}
|
||||
>
|
||||
<Skeleton className="w-full bg-mineshaft-600" />
|
||||
</div>
|
||||
))}
|
||||
{!isProjectViewLoading &&
|
||||
workspacesWithFaveProp.map((workspace, ind) =>
|
||||
renderProjectListItem(workspace, workspace.isFavorite, ind)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else if (orgWorkspaces.length && searchFilter) {
|
||||
projectsComponents = (
|
||||
<div className="mt-4 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300">
|
||||
<FontAwesomeIcon
|
||||
icon={faSearch}
|
||||
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
|
||||
/>
|
||||
<div className="text-center font-light">No projects match search...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
{!serverDetails?.redisConfigured && (
|
||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
||||
<p className="mr-4 mb-4 font-semibold text-white">Announcements</p>
|
||||
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationCircle}
|
||||
className="mr-4 p-4 text-2xl text-mineshaft-50"
|
||||
/>
|
||||
Attention: Updated versions of Infisical now require Redis for full functionality. Learn
|
||||
how to configure it
|
||||
<Link
|
||||
href="https://infisical.com/docs/self-hosting/configuration/redis"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="cursor-pointer pl-1 text-white underline underline-offset-2 duration-100 hover:text-blue-200 hover:decoration-blue-400">
|
||||
here
|
||||
</span>
|
||||
</Link>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0">
|
||||
<div className="flex w-full justify-between">
|
||||
<p className="mr-4 text-3xl font-semibold text-white">{formatTitle(type)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mr-4 mt-2 text-gray-400">{formatDescription(type)}</p>
|
||||
</div>
|
||||
<div className="mt-6 flex w-full flex-row">
|
||||
<Input
|
||||
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||
placeholder="Search by project name..."
|
||||
value={searchFilter}
|
||||
onChange={(e) => setSearchFilter(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
/>
|
||||
<div className="ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
|
||||
<Tooltip content="Toggle Sort Direction">
|
||||
<IconButton
|
||||
className="min-w-[2.4rem] border-none hover:bg-mineshaft-600"
|
||||
ariaLabel={`Sort ${
|
||||
orderDirection === OrderByDirection.ASC ? "descending" : "ascending"
|
||||
}`}
|
||||
variant="plain"
|
||||
size="xs"
|
||||
colorSchema="secondary"
|
||||
onClick={toggleOrderDirection}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={orderDirection === OrderByDirection.ASC ? faArrowDownAZ : faArrowUpZA}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
localStorage.setItem("projectsViewMode", ProjectsViewMode.GRID);
|
||||
setProjectsViewMode(ProjectsViewMode.GRID);
|
||||
}}
|
||||
ariaLabel="grid"
|
||||
size="xs"
|
||||
className={`${
|
||||
projectsViewMode === ProjectsViewMode.GRID ? "bg-mineshaft-500" : "bg-transparent"
|
||||
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBorderAll} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
localStorage.setItem("projectsViewMode", ProjectsViewMode.LIST);
|
||||
setProjectsViewMode(ProjectsViewMode.LIST);
|
||||
}}
|
||||
ariaLabel="list"
|
||||
size="xs"
|
||||
className={`${
|
||||
projectsViewMode === ProjectsViewMode.LIST ? "bg-mineshaft-500" : "bg-transparent"
|
||||
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faList} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Workspace}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
colorSchema="primary"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
if (isAddingProjectsAllowed) {
|
||||
handlePopUpOpen("addNewWs");
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}}
|
||||
className="ml-2"
|
||||
>
|
||||
Add New Project
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
{projectsComponents}
|
||||
{!isProjectViewLoading && Boolean(filteredWorkspaces.length) && (
|
||||
<Pagination
|
||||
className={
|
||||
projectsViewMode === ProjectsViewMode.GRID
|
||||
? "col-span-full !justify-start border-transparent bg-transparent pl-2"
|
||||
: "rounded-b-md border border-mineshaft-600"
|
||||
}
|
||||
perPage={perPage}
|
||||
perPageList={[12, 24, 48, 96]}
|
||||
count={filteredWorkspaces.length}
|
||||
page={page}
|
||||
onChangePage={setPage}
|
||||
onChangePerPage={setPerPage}
|
||||
/>
|
||||
)}
|
||||
{isWorkspaceEmpty && (
|
||||
<div className="mt-4 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300">
|
||||
<FontAwesomeIcon
|
||||
icon={faFolderOpen}
|
||||
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
|
||||
/>
|
||||
<div className="text-center font-light">
|
||||
You are not part of any projects in this organization yet. When you are, they will
|
||||
appear here.
|
||||
</div>
|
||||
<div className="mt-0.5 text-center font-light">
|
||||
Create a new project, or ask other organization members to give you necessary
|
||||
permissions.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<NewProjectModal
|
||||
isOpen={popUp.addNewWs.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
|
||||
projectType={type}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You have exceeded the number of projects allowed on the free plan."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SecretManagerOverviewPage = () => <ProductOverview type={ProjectType.SecretManager} />;
|
||||
|
||||
Object.assign(SecretManagerOverviewPage, { requireAuth: true });
|
||||
|
||||
export default SecretManagerOverviewPage;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { ProductOverview } from "../secret-manager/overview";
|
||||
|
||||
const SshManagerOverviewPage = () => <ProductOverview type={ProjectType.SSH} />;
|
||||
|
||||
Object.assign(SshManagerOverviewPage, { requireAuth: true });
|
||||
|
||||
export default SshManagerOverviewPage;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { IPAllowlistPage } from "@app/views/Project/IPAllowListPage";
|
||||
|
||||
const ProjectAllowlist = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<IPAllowlistPage />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectAllowlist;
|
||||
|
||||
ProjectAllowlist.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { IdentityDetailsPage } from "@app/views/Project/IdentityDetailsPage";
|
||||
|
||||
export default function ProjectIdentityDetailsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<IdentityDetailsPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ProjectIdentityDetailsPage.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { MemberDetailsPage } from "@app/views/Project/MemberDetailsPage";
|
||||
|
||||
export default function Page() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<MemberDetailsPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Page.requireAuth = true;
|
||||
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { MembersPage } from "@app/views/Project/MembersPage";
|
||||
|
||||
export default function WorkspaceMemberSettings() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<MembersPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
WorkspaceMemberSettings.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { RolePage } from "@app/views/Project/RolePage";
|
||||
|
||||
export default function Role() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: "Project Settings" })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<RolePage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Role.requireAuth = true;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { ProjectSettingsPage } from "@app/views/Settings/ProjectSettingsPage";
|
||||
|
||||
const ProjectSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<ProjectSettingsPage />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectSettings;
|
||||
|
||||
ProjectSettings.requireAuth = true;
|
||||
@@ -16,6 +16,7 @@ import { useServerConfig } from "@app/context";
|
||||
import { useVerifySignupEmailVerificationCode } from "@app/hooks/api";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
/**
|
||||
* @returns the signup page
|
||||
@@ -47,7 +48,7 @@ export default function SignUp() {
|
||||
const tryAuth = async () => {
|
||||
try {
|
||||
const userOrgs = await fetchOrganizations();
|
||||
router.push(`/org/${userOrgs[0].id}/overview`);
|
||||
router.push(`/org/${userOrgs[0].id}/${ProjectType.SecretManager}/overview`);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
@@ -90,7 +91,7 @@ export default function SignUp() {
|
||||
|
||||
if (!serverDetails?.emailConfigured && step === 5) {
|
||||
const userOrgs = await fetchOrganizations();
|
||||
router.push(`/org/${userOrgs[0].id}/overview`);
|
||||
router.push(`/org/${userOrgs[0].id}/${ProjectType.SecretManager}/overview`);
|
||||
}
|
||||
})();
|
||||
}, [step]);
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from "@app/hooks/api/auth/queries";
|
||||
import { MfaMethod } from "@app/hooks/api/auth/types";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { navigateUserToOrg } from "@app/views/Login/Login.utils";
|
||||
import { Mfa } from "@app/views/Login/Mfa";
|
||||
|
||||
@@ -386,7 +387,7 @@ export default function SignupInvite() {
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued
|
||||
});
|
||||
router.push(`/org/${organizationId}/overview`);
|
||||
router.push(`/org/${organizationId}/${ProjectType.SecretManager}/overview`);
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { IPAllowlistPage } from "@app/views/Project/IPAllowListPage";
|
||||
|
||||
const ProjectAllowlist = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<IPAllowlistPage />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectAllowlist;
|
||||
|
||||
ProjectAllowlist.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { IdentityDetailsPage } from "@app/views/Project/IdentityDetailsPage";
|
||||
|
||||
export default function ProjectIdentityDetailsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<IdentityDetailsPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ProjectIdentityDetailsPage.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { MemberDetailsPage } from "@app/views/Project/MemberDetailsPage";
|
||||
|
||||
export default function Page() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<MemberDetailsPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Page.requireAuth = true;
|
||||
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { MembersPage } from "@app/views/Project/MembersPage";
|
||||
|
||||
export default function WorkspaceMemberSettings() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<MembersPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
WorkspaceMemberSettings.requireAuth = true;
|
||||
@@ -0,0 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { RolePage } from "@app/views/Project/RolePage";
|
||||
|
||||
export default function Role() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: "Project Settings" })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<RolePage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Role.requireAuth = true;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { ProjectSettingsPage } from "@app/views/Settings/ProjectSettingsPage";
|
||||
|
||||
const ProjectSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<ProjectSettingsPage />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectSettings;
|
||||
|
||||
ProjectSettings.requireAuth = true;
|
||||
@@ -3,6 +3,7 @@ import { NextRouter, useRouter } from "next/router";
|
||||
import { useServerConfig } from "@app/context";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { userKeys } from "@app/hooks/api/users";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { queryClient } from "@app/reactQuery";
|
||||
|
||||
export const navigateUserToOrg = async (router: NextRouter, organizationId?: string) => {
|
||||
@@ -12,7 +13,7 @@ export const navigateUserToOrg = async (router: NextRouter, organizationId?: str
|
||||
|
||||
if (organizationId) {
|
||||
localStorage.setItem("orgData.id", organizationId);
|
||||
router.push(`/org/${organizationId}/overview`);
|
||||
router.push(`/org/${organizationId}/${ProjectType.SecretManager}/overview`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,7 +21,7 @@ export const navigateUserToOrg = async (router: NextRouter, organizationId?: str
|
||||
// user is part of at least 1 non-auth enforced org
|
||||
const userOrg = nonAuthEnforcedOrgs[0] && nonAuthEnforcedOrgs[0].id;
|
||||
localStorage.setItem("orgData.id", userOrg);
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
router.push(`/org/${userOrg}/${ProjectType.SecretManager}/overview`);
|
||||
} else {
|
||||
// user is not part of any non-auth enforced orgs
|
||||
localStorage.removeItem("orgData.id");
|
||||
|
||||
+7
-2
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { IdentityMembership } from "@app/hooks/api/identities/types";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
@@ -52,7 +52,9 @@ export const IdentityProjectRow = ({
|
||||
key={`identity-project-membership-${id}`}
|
||||
onClick={() => {
|
||||
if (isAccessible) {
|
||||
router.push(`/project/${project.id}/members?selectedTab=${TabSections.Identities}`);
|
||||
router.push(
|
||||
`/${project?.type}/${project.id}/members?selectedTab=${TabSections.Identities}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,6 +65,9 @@ export const IdentityProjectRow = ({
|
||||
}}
|
||||
>
|
||||
<Td className="max-w-0 truncate">{project.name}</Td>
|
||||
<Td>
|
||||
<Tag size="xs">{project.type}</Tag>
|
||||
</Td>
|
||||
<Td>{`${formatRoleName(roles[0].role, roles[0].customRoleName)}${
|
||||
roles.length > 1 ? ` (+${roles.length - 1})` : ""
|
||||
}`}</Td>
|
||||
|
||||
+1
@@ -106,6 +106,7 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) =>
|
||||
</div>
|
||||
</Th>
|
||||
|
||||
<Th>Type</Th>
|
||||
<Th>Role</Th>
|
||||
<Th>Added On</Th>
|
||||
<Th className="w-5" />
|
||||
|
||||
+23
-2
@@ -23,12 +23,17 @@ import {
|
||||
useDeleteIdentityTokenAuth,
|
||||
useDeleteIdentityUniversalAuth
|
||||
} from "@app/hooks/api";
|
||||
import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities";
|
||||
import {
|
||||
IdentityAuthMethod,
|
||||
identityAuthToNameMap,
|
||||
useDeleteIdentityJwtAuth
|
||||
} from "@app/hooks/api/identities";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { IdentityAwsAuthForm } from "./IdentityAwsAuthForm";
|
||||
import { IdentityAzureAuthForm } from "./IdentityAzureAuthForm";
|
||||
import { IdentityGcpAuthForm } from "./IdentityGcpAuthForm";
|
||||
import { IdentityJwtAuthForm } from "./IdentityJwtAuthForm";
|
||||
import { IdentityKubernetesAuthForm } from "./IdentityKubernetesAuthForm";
|
||||
import { IdentityOidcAuthForm } from "./IdentityOidcAuthForm";
|
||||
import { IdentityTokenAuthForm } from "./IdentityTokenAuthForm";
|
||||
@@ -68,7 +73,11 @@ const identityAuthMethods = [
|
||||
{ label: "GCP Auth", value: IdentityAuthMethod.GCP_AUTH },
|
||||
{ label: "AWS Auth", value: IdentityAuthMethod.AWS_AUTH },
|
||||
{ label: "Azure Auth", value: IdentityAuthMethod.AZURE_AUTH },
|
||||
{ label: "OIDC Auth", value: IdentityAuthMethod.OIDC_AUTH }
|
||||
{ label: "OIDC Auth", value: IdentityAuthMethod.OIDC_AUTH },
|
||||
{
|
||||
label: "JWT Auth",
|
||||
value: IdentityAuthMethod.JWT_AUTH
|
||||
}
|
||||
];
|
||||
|
||||
const schema = yup
|
||||
@@ -100,6 +109,7 @@ export const IdentityAuthMethodModalContent = ({
|
||||
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
|
||||
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
|
||||
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
|
||||
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
|
||||
|
||||
const { control, watch } = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
@@ -216,6 +226,17 @@ export const IdentityAuthMethodModalContent = ({
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
||||
[IdentityAuthMethod.JWT_AUTH]: {
|
||||
revokeMethod: revokeJwtAuth,
|
||||
render: () => (
|
||||
<IdentityJwtAuthForm
|
||||
identityAuthMethodData={identityAuthMethodData}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+688
@@ -0,0 +1,688 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
TextArea,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization, useSubscription } from "@app/context";
|
||||
import { useAddIdentityJwtAuth, useUpdateIdentityJwtAuth } from "@app/hooks/api";
|
||||
import { IdentityAuthMethod } from "@app/hooks/api/identities";
|
||||
import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums";
|
||||
import { useGetIdentityJwtAuth } from "@app/hooks/api/identities/queries";
|
||||
import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const commonSchema = z.object({
|
||||
accessTokenTrustedIps: z
|
||||
.array(
|
||||
z.object({
|
||||
ipAddress: z.string().max(50)
|
||||
})
|
||||
)
|
||||
.min(1),
|
||||
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token Max TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenNumUsesLimit: z.string(),
|
||||
boundIssuer: z.string().trim().default(""),
|
||||
boundAudiences: z.string().optional().default(""),
|
||||
boundClaims: z.array(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
value: z.string()
|
||||
})
|
||||
),
|
||||
boundSubject: z.string().optional().default("")
|
||||
});
|
||||
|
||||
const schema = z.discriminatedUnion("configurationType", [
|
||||
z
|
||||
.object({
|
||||
configurationType: z.literal(IdentityJwtConfigurationType.JWKS),
|
||||
jwksUrl: z.string().trim().url(),
|
||||
jwksCaCert: z.string().trim().default(""),
|
||||
publicKeys: z
|
||||
.object({
|
||||
value: z.string()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
})
|
||||
.merge(commonSchema),
|
||||
z
|
||||
.object({
|
||||
configurationType: z.literal(IdentityJwtConfigurationType.STATIC),
|
||||
jwksUrl: z.string().trim().optional(),
|
||||
jwksCaCert: z.string().trim().optional().default(""),
|
||||
publicKeys: z
|
||||
.object({
|
||||
value: z.string().min(1)
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
})
|
||||
.merge(commonSchema)
|
||||
]);
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
|
||||
type Props = {
|
||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
identityAuthMethodData: {
|
||||
identityId: string;
|
||||
name: string;
|
||||
configuredAuthMethods?: IdentityAuthMethod[];
|
||||
authMethod?: IdentityAuthMethod;
|
||||
};
|
||||
};
|
||||
|
||||
export const IdentityJwtAuthForm = ({
|
||||
handlePopUpOpen,
|
||||
handlePopUpToggle,
|
||||
identityAuthMethodData
|
||||
}: Props) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityJwtAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityJwtAuth();
|
||||
|
||||
const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes(
|
||||
identityAuthMethodData.authMethod! || ""
|
||||
);
|
||||
const { data } = useGetIdentityJwtAuth(identityAuthMethodData?.identityId ?? "", {
|
||||
enabled: isUpdate
|
||||
});
|
||||
|
||||
const {
|
||||
watch,
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setValue,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
accessTokenTTL: "2592000",
|
||||
accessTokenMaxTTL: "2592000",
|
||||
accessTokenNumUsesLimit: "0",
|
||||
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }],
|
||||
configurationType: IdentityJwtConfigurationType.JWKS
|
||||
}
|
||||
});
|
||||
|
||||
const selectedConfigurationType = watch("configurationType") as IdentityJwtConfigurationType;
|
||||
|
||||
const {
|
||||
fields: publicKeyFields,
|
||||
append: appendPublicKeyFields,
|
||||
remove: removePublicKeyFields
|
||||
} = useFieldArray({
|
||||
control,
|
||||
name: "publicKeys"
|
||||
});
|
||||
|
||||
const {
|
||||
fields: boundClaimsFields,
|
||||
append: appendBoundClaimField,
|
||||
remove: removeBoundClaimField
|
||||
} = useFieldArray({
|
||||
control,
|
||||
name: "boundClaims"
|
||||
});
|
||||
|
||||
const {
|
||||
fields: accessTokenTrustedIpsFields,
|
||||
append: appendAccessTokenTrustedIp,
|
||||
remove: removeAccessTokenTrustedIp
|
||||
} = useFieldArray({ control, name: "accessTokenTrustedIps" });
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset({
|
||||
configurationType: data.configurationType,
|
||||
jwksUrl: data.jwksUrl,
|
||||
jwksCaCert: data.jwksCaCert,
|
||||
publicKeys: data.publicKeys.map((pk) => ({
|
||||
value: pk
|
||||
})),
|
||||
boundIssuer: data.boundIssuer,
|
||||
boundAudiences: data.boundAudiences,
|
||||
boundClaims: Object.entries(data.boundClaims).map(([key, value]) => ({
|
||||
key,
|
||||
value
|
||||
})),
|
||||
boundSubject: data.boundSubject,
|
||||
accessTokenTTL: String(data.accessTokenTTL),
|
||||
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
|
||||
accessTokenTrustedIps: data.accessTokenTrustedIps.map(
|
||||
({ ipAddress, prefix }: IdentityTrustedIp) => {
|
||||
return {
|
||||
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
|
||||
};
|
||||
}
|
||||
)
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
configurationType: IdentityJwtConfigurationType.JWKS,
|
||||
jwksUrl: "",
|
||||
jwksCaCert: "",
|
||||
boundIssuer: "",
|
||||
boundAudiences: "",
|
||||
boundClaims: [],
|
||||
boundSubject: "",
|
||||
accessTokenTTL: "2592000",
|
||||
accessTokenMaxTTL: "2592000",
|
||||
accessTokenNumUsesLimit: "0",
|
||||
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const onFormSubmit = async ({
|
||||
accessTokenTrustedIps,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
configurationType,
|
||||
jwksUrl,
|
||||
jwksCaCert,
|
||||
publicKeys,
|
||||
boundIssuer,
|
||||
boundAudiences,
|
||||
boundClaims,
|
||||
boundSubject
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (!identityAuthMethodData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
identityId: identityAuthMethodData.identityId,
|
||||
organizationId: orgId,
|
||||
configurationType,
|
||||
jwksUrl,
|
||||
jwksCaCert,
|
||||
publicKeys: publicKeys?.map((field) => field.value).filter(Boolean),
|
||||
boundIssuer,
|
||||
boundAudiences,
|
||||
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
|
||||
boundSubject,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||
accessTokenTrustedIps
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
identityId: identityAuthMethodData.identityId,
|
||||
configurationType,
|
||||
jwksUrl,
|
||||
jwksCaCert,
|
||||
publicKeys: publicKeys?.map((field) => field.value).filter(Boolean),
|
||||
boundIssuer,
|
||||
boundAudiences,
|
||||
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
|
||||
boundSubject,
|
||||
organizationId: orgId,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||
accessTokenTrustedIps
|
||||
});
|
||||
}
|
||||
|
||||
handlePopUpToggle("identityAuthMethod", false);
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: `Failed to ${isUpdate ? "update" : "configure"} identity`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="configurationType"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Configuration Type"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => {
|
||||
if (e === IdentityJwtConfigurationType.JWKS) {
|
||||
setValue("publicKeys", []);
|
||||
} else {
|
||||
setValue("publicKeys", [
|
||||
{
|
||||
value: ""
|
||||
}
|
||||
]);
|
||||
setValue("jwksUrl", "");
|
||||
setValue("jwksCaCert", "");
|
||||
}
|
||||
onChange(e);
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<SelectItem value={IdentityJwtConfigurationType.JWKS} key="jwks">
|
||||
JWKS
|
||||
</SelectItem>
|
||||
<SelectItem value={IdentityJwtConfigurationType.STATIC} key="static">
|
||||
Static
|
||||
</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{selectedConfigurationType === IdentityJwtConfigurationType.JWKS && (
|
||||
<>
|
||||
<Controller
|
||||
control={control}
|
||||
name="jwksUrl"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isRequired
|
||||
label="JWKS URL"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} type="text" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="jwksCaCert"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="JWKS CA Certificate"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedConfigurationType === IdentityJwtConfigurationType.STATIC && (
|
||||
<>
|
||||
{publicKeyFields.map(({ id }, index) => (
|
||||
<div key={id} className="flex gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name={`publicKeys.${index}.value`}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="flex-grow"
|
||||
label={`Public Key ${index + 1}`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
icon={
|
||||
<Tooltip
|
||||
className="text-center"
|
||||
content={<span>This field only accepts PEM-formatted public keys</span>}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<TextArea {...field} placeholder="-----BEGIN PUBLIC KEY----- ..." />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
if (publicKeyFields.length === 1) {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "A public key is required for static configurations"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
removePublicKeyFields(index);
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="p-3"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
<div className="my-4 ml-1">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() =>
|
||||
appendPublicKeyFields({
|
||||
value: ""
|
||||
})
|
||||
}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
size="xs"
|
||||
>
|
||||
Add Public Key
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="boundIssuer"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Issuer" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input {...field} type="text" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="boundSubject"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Subject"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
icon={
|
||||
<Tooltip
|
||||
className="text-center"
|
||||
content={<span>This field supports glob patterns</span>}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<Input {...field} type="text" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="boundAudiences"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Audiences"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
icon={
|
||||
<Tooltip
|
||||
className="text-center"
|
||||
content={<span>This field supports glob patterns</span>}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<Input {...field} type="text" placeholder="service1, service2" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{boundClaimsFields.map(({ id }, index) => (
|
||||
<div className="mb-3 flex items-end space-x-2" key={id}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`boundClaims.${index}.key`}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className="mb-0 flex-grow"
|
||||
label={index === 0 ? "Claims" : undefined}
|
||||
icon={
|
||||
index === 0 ? (
|
||||
<Tooltip
|
||||
className="text-center"
|
||||
content={<span>This field supports glob patterns</span>}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
|
||||
</Tooltip>
|
||||
) : undefined
|
||||
}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
value={field.value}
|
||||
onChange={(e) => field.onChange(e)}
|
||||
placeholder="property"
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`boundClaims.${index}.value`}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className="mb-0 flex-grow"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
value={field.value}
|
||||
onChange={(e) => field.onChange(e)}
|
||||
placeholder="value1, value2"
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
onClick={() => removeBoundClaimField(index)}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="p-3"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
<div className="my-4 ml-1">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() =>
|
||||
appendBoundClaimField({
|
||||
key: "",
|
||||
value: ""
|
||||
})
|
||||
}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
size="xs"
|
||||
>
|
||||
Add Claims
|
||||
</Button>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue="2592000"
|
||||
name="accessTokenTTL"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Access Token TTL (seconds)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue="2592000"
|
||||
name="accessTokenMaxTTL"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Access Token Max TTL (seconds)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue="0"
|
||||
name="accessTokenNumUsesLimit"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Access Token Max Number of Uses"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="0" type="number" min="0" step="1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{accessTokenTrustedIpsFields.map(({ id }, index) => (
|
||||
<div className="mb-3 flex items-end space-x-2" key={id}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`accessTokenTrustedIps.${index}.ipAddress`}
|
||||
defaultValue="0.0.0.0/0"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className="mb-0 flex-grow"
|
||||
label={index === 0 ? "Access Token Trusted IPs" : undefined}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
field.onChange(e);
|
||||
return;
|
||||
}
|
||||
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}}
|
||||
placeholder="123.456.789.0"
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
removeAccessTokenTrustedIp(index);
|
||||
return;
|
||||
}
|
||||
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="p-3"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
<div className="my-4 ml-1">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
appendAccessTokenTrustedIp({
|
||||
ipAddress: "0.0.0.0/0"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
size="xs"
|
||||
>
|
||||
Add IP Address
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{isUpdate ? "Update" : "Create"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
{isUpdate && (
|
||||
<Button
|
||||
size="sm"
|
||||
colorSchema="danger"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
onClick={() => handlePopUpToggle("revokeAuthMethod", true)}
|
||||
>
|
||||
Remove Auth Method
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
+3
-1
@@ -71,7 +71,9 @@ export const AddOrgMemberModal = ({
|
||||
const { data: organizationRoles } = useGetOrgRoles(currentOrg?.id ?? "");
|
||||
const { data: serverDetails } = useFetchServerStatus();
|
||||
const { mutateAsync: addUsersMutateAsync } = useAddUsersToOrg();
|
||||
const { data: projects, isLoading: isProjectsLoading } = useGetUserWorkspaces(true);
|
||||
const { data: projects, isLoading: isProjectsLoading } = useGetUserWorkspaces({
|
||||
includeRoles: true
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||
@@ -48,7 +48,7 @@ export const UserProjectRow = ({
|
||||
key={`user-project-membership-${id}`}
|
||||
onClick={() => {
|
||||
if (isAccessible) {
|
||||
router.push(`/project/${project.id}/members?selectedTab=${TabSections.Member}`);
|
||||
router.push(`/${project.type}/${project.id}/members?selectedTab=${TabSections.Member}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,9 @@ export const UserProjectRow = ({
|
||||
}}
|
||||
>
|
||||
<Td className="max-w-0 truncate">{project.name}</Td>
|
||||
<Td>
|
||||
<Tag size="xs">{project.type}</Tag>
|
||||
</Td>
|
||||
<Td>{`${formatRoleName(roles[0].role, roles[0].customRoleName)}${
|
||||
roles.length > 1 ? ` (+${roles.length - 1})` : ""
|
||||
}`}</Td>
|
||||
|
||||
@@ -108,6 +108,7 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => {
|
||||
</IconButton>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>Type</Th>
|
||||
<Th>Role</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
|
||||
@@ -7,6 +7,7 @@ import z from "zod";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
||||
import { useCreateOrg, useSelectOrganization } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
@@ -57,8 +58,8 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
|
||||
type: "success"
|
||||
});
|
||||
|
||||
if (router.isReady) router.push(`/org/${organization.id}/overview`);
|
||||
else window.location.href = `/org/${organization.id}/overview`;
|
||||
if (router.isReady) router.push(`/org/${organization.id}/${ProjectType.SecretManager}/overview`);
|
||||
else window.location.href = `/org/${organization.id}/${ProjectType.SecretManager}/overview`;
|
||||
|
||||
localStorage.setItem("orgData.id", organization.id);
|
||||
|
||||
|
||||
@@ -52,16 +52,13 @@ export const OrgAdminProjects = withPermission(
|
||||
const projectCount = data?.count || 0;
|
||||
const isEmpty = !isProjectsLoading && projects.length === 0;
|
||||
|
||||
const handleAccessProject = async (projectId: string) => {
|
||||
const handleAccessProject = async (type:string,projectId: string) => {
|
||||
try {
|
||||
await orgAdminAccessProject.mutateAsync({
|
||||
projectId
|
||||
});
|
||||
await router.push({
|
||||
pathname: "/project/[projectId]/secrets/overview",
|
||||
query: {
|
||||
projectId
|
||||
}
|
||||
pathname: `/${type}/${projectId}/secrets/overview`,
|
||||
});
|
||||
} catch {
|
||||
createNotification({
|
||||
@@ -103,7 +100,7 @@ export const OrgAdminProjects = withPermission(
|
||||
<TBody>
|
||||
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
|
||||
{!isProjectsLoading &&
|
||||
projects?.map(({ name, slug, createdAt, id }) => (
|
||||
projects?.map(({ name, slug, createdAt,type, id }) => (
|
||||
<Tr key={`project-${id}`} className="group w-full">
|
||||
<Td>{name}</Td>
|
||||
<Td>{slug}</Td>
|
||||
@@ -124,7 +121,7 @@ export const OrgAdminProjects = withPermission(
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleAccessProject(id);
|
||||
handleAccessProject(type,id);
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faSignIn} />}
|
||||
disabled={
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { useDeleteCa, useGetCaById } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { CaModal } from "@app/views/Project/CertificatesPage/components/CaTab/components/CaModal";
|
||||
|
||||
@@ -60,7 +61,7 @@ export const CaPage = withProjectPermission(
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteCa");
|
||||
router.push(`/project/${projectId}/certificates`);
|
||||
router.push(`/${ProjectType.CertificateManager}/${projectId}/certificates`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
@@ -78,7 +79,7 @@ export const CaPage = withProjectPermission(
|
||||
variant="link"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => router.push(`/project/${projectId}/certificates`)}
|
||||
onClick={() => router.push(`/${ProjectType.CertificateManager}/${projectId}/certificates`)}
|
||||
className="mb-4"
|
||||
>
|
||||
Certificate Authorities
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
caTypeToNameMap,
|
||||
getCaStatusBadgeVariant
|
||||
} from "@app/hooks/api/ca/constants";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -75,7 +76,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
||||
<Tr
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
key={`ca-${ca.id}`}
|
||||
onClick={() => router.push(`/project/${currentWorkspace?.id}/ca/${ca.id}`)}
|
||||
onClick={() => router.push(`/${ProjectType.CertificateManager}/${currentWorkspace?.id}/ca/${ca.id}`)}
|
||||
>
|
||||
<Td>{ca.friendlyName}</Td>
|
||||
<Td>
|
||||
|
||||
+2
-1
@@ -12,6 +12,7 @@ import {
|
||||
useGetPkiCollectionById,
|
||||
useUpdatePkiCollection
|
||||
} from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = z.object({
|
||||
@@ -81,7 +82,7 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
projectId
|
||||
});
|
||||
|
||||
router.push(`/project/${projectId}/pki-collections/${createdId}`);
|
||||
router.push(`/${ProjectType.CertificateManager}/${projectId}/pki-collections/${createdId}`);
|
||||
}
|
||||
|
||||
handlePopUpToggle("pkiCollection", false);
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { useListWorkspacePkiCollections } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -59,7 +60,7 @@ export const PkiCollectionTable = ({ handlePopUpOpen }: Props) => {
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
key={`pki-collection-${pkiCollection.id}`}
|
||||
onClick={() =>
|
||||
router.push(`/project/${projectId}/pki-collections/${pkiCollection.id}`)
|
||||
router.push(`/${ProjectType.CertificateManager}/${projectId}/pki-collections/${pkiCollection.id}`)
|
||||
}
|
||||
>
|
||||
<Td>{pkiCollection.name}</Td>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal, EmptyState, Spinner } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { getProjectTitle } from "@app/helpers/project";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
@@ -48,7 +49,7 @@ export const IdentityDetailsPage = withProjectPermission(
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpClose("deleteIdentity");
|
||||
router.push(`/project/${workspaceId}/members?selectedTab=identities`);
|
||||
router.push(`/${currentWorkspace?.type}/${workspaceId}/members?selectedTab=identities`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
@@ -77,11 +78,14 @@ export const IdentityDetailsPage = withProjectPermission(
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
router.push(`/project/${workspaceId}/members?selectedTab=identities`);
|
||||
router.push(
|
||||
`/${currentWorkspace?.type}/${workspaceId}/members?selectedTab=identities`
|
||||
);
|
||||
}}
|
||||
className="mb-4"
|
||||
>
|
||||
Project Access Control
|
||||
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access
|
||||
Control
|
||||
</Button>
|
||||
</div>
|
||||
{identityMembershipDetails ? (
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
useOrganization,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { getProjectTitle } from "@app/helpers/project";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteUserFromWorkspace, useGetWorkspaceUserDetails } from "@app/hooks/api";
|
||||
@@ -58,7 +59,7 @@ export const MemberDetailsPage = withProjectPermission(
|
||||
text: "Successfully removed user from project",
|
||||
type: "success"
|
||||
});
|
||||
router.push(`/project/${currentWorkspace?.id}/members`);
|
||||
router.push(`/${currentWorkspace.type}/${currentWorkspace?.id}/members`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
createNotification({
|
||||
@@ -85,11 +86,12 @@ export const MemberDetailsPage = withProjectPermission(
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
router.push(`/project/${workspaceId}/members`);
|
||||
router.push(`/${currentWorkspace?.type}/${workspaceId}/members`);
|
||||
}}
|
||||
className="mb-4"
|
||||
>
|
||||
Project Access Control
|
||||
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access
|
||||
Control
|
||||
</Button>
|
||||
</div>
|
||||
{membershipDetails ? (
|
||||
|
||||
@@ -3,8 +3,10 @@ import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { getProjectTitle } from "@app/helpers/project";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { isTabSection, TabSections } from "../Types";
|
||||
import {
|
||||
@@ -18,6 +20,7 @@ import {
|
||||
export const MembersPage = withProjectPermission(
|
||||
() => {
|
||||
const router = useRouter();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { query } = router;
|
||||
const selectedTab = query.selectedTab as string;
|
||||
const [activeTab, setActiveTab] = useState<TabSections>(TabSections.Member);
|
||||
@@ -38,7 +41,10 @@ export const MembersPage = withProjectPermission(
|
||||
return (
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
|
||||
<p className="mr-4 mb-4 text-3xl font-semibold text-white">Project Access Control</p>
|
||||
<p className="mr-4 mb-4 text-3xl font-semibold text-white">
|
||||
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access
|
||||
Control
|
||||
</p>
|
||||
<Tabs value={activeTab} onValueChange={updateSelectedTab}>
|
||||
<TabList>
|
||||
<Tab value={TabSections.Member}>Users</Tab>
|
||||
@@ -48,7 +54,9 @@ export const MembersPage = withProjectPermission(
|
||||
<p>Machine Identities</p>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value={TabSections.ServiceTokens}>Service Tokens</Tab>
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<Tab value={TabSections.ServiceTokens}>Service Tokens</Tab>
|
||||
)}
|
||||
<Tab value={TabSections.Roles}>Project Roles</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Member}>
|
||||
@@ -60,9 +68,11 @@ export const MembersPage = withProjectPermission(
|
||||
<TabPanel value={TabSections.Identities}>
|
||||
<IdentityTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.ServiceTokens}>
|
||||
<ServiceTokenTab />
|
||||
</TabPanel>
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<TabPanel value={TabSections.ServiceTokens}>
|
||||
<ServiceTokenTab />
|
||||
</TabPanel>
|
||||
)}
|
||||
<TabPanel value={TabSections.Roles}>
|
||||
<ProjectRoleListTab />
|
||||
</TabPanel>
|
||||
|
||||
@@ -244,10 +244,10 @@ export const IdentityTab = withProjectPermission(
|
||||
tabIndex={0}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
router.push(`/project/${workspaceId}/identities/${id}`);
|
||||
router.push(`/${currentWorkspace?.type}/${workspaceId}/identities/${id}`);
|
||||
}
|
||||
}}
|
||||
onClick={() => router.push(`/project/${workspaceId}/identities/${id}`)}
|
||||
onClick={() => router.push(`/${currentWorkspace?.type}/${workspaceId}/identities/${id}`)}
|
||||
>
|
||||
<Td>{name}</Td>
|
||||
|
||||
|
||||
+2
-2
@@ -212,10 +212,10 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
|
||||
tabIndex={0}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
router.push(`/project/${workspaceId}/members/${membershipId}`);
|
||||
router.push(`/${currentWorkspace?.type}/${workspaceId}/members/${membershipId}`);
|
||||
}
|
||||
}}
|
||||
onClick={() => router.push(`/project/${workspaceId}/members/${membershipId}`)}
|
||||
onClick={() => router.push(`/${currentWorkspace?.type}/${workspaceId}/members/${membershipId}`)}
|
||||
>
|
||||
<Td>{name}</Td>
|
||||
<Td>{email}</Td>
|
||||
|
||||
+2
-2
@@ -92,7 +92,7 @@ export const ProjectRoleList = () => {
|
||||
<Tr
|
||||
key={`role-list-${id}`}
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => router.push(`/project/${projectId}/roles/${slug}`)}
|
||||
onClick={() => router.push(`/${currentWorkspace?.type}/${projectId}/roles/${slug}`)}
|
||||
>
|
||||
<Td>{name}</Td>
|
||||
<Td>{slug}</Td>
|
||||
@@ -115,7 +115,7 @@ export const ProjectRoleList = () => {
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
router.push(`/project/${projectId}/roles/${slug}`);
|
||||
router.push(`/${currentWorkspace?.type}/${projectId}/roles/${slug}`);
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@a
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { useDeletePkiCollection, useGetPkiCollectionById } from "@app/hooks/api";
|
||||
import { PkiItemType } from "@app/hooks/api/pkiCollections/constants";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { PkiCollectionModal } from "../CertificatesPage/components/PkiAlertsTab/components/PkiCollectionModal";
|
||||
@@ -53,7 +54,7 @@ export const PkiCollectionPage = withProjectPermission(
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpClose("deletePkiCollection");
|
||||
router.push(`/project/${projectId}/certificates`);
|
||||
router.push(`/${ProjectType.CertificateManager}/${projectId}/certificates`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@@ -68,7 +69,7 @@ export const PkiCollectionPage = withProjectPermission(
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
router.push(`/project/${projectId}/certificates`);
|
||||
router.push(`/${ProjectType.CertificateManager}/${projectId}/certificates`);
|
||||
}}
|
||||
className="mb-4"
|
||||
>
|
||||
|
||||
@@ -53,7 +53,7 @@ export const RolePage = withProjectPermission(
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpClose("deleteRole");
|
||||
router.push(`/project/${projectId}/members?selectedTab=${TabSections.Roles}`);
|
||||
router.push(`/${currentWorkspace?.type}/${projectId}/members?selectedTab=${TabSections.Roles}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
@@ -77,7 +77,7 @@ export const RolePage = withProjectPermission(
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() =>
|
||||
router.push(`/project/${projectId}/members?selectedTab=${TabSections.Roles}`)
|
||||
router.push(`/${currentWorkspace?.type}/${projectId}/members?selectedTab=${TabSections.Roles}`)
|
||||
}
|
||||
className="mb-4"
|
||||
>
|
||||
|
||||
@@ -99,7 +99,7 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
permissions: []
|
||||
});
|
||||
|
||||
router.push(`/project/${currentWorkspace?.id}/roles/${newRole.slug}`);
|
||||
router.push(`/${currentWorkspace?.type}/${currentWorkspace?.id}/roles/${newRole.slug}`);
|
||||
handlePopUpToggle("role", false);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub,useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { SshCaStatus, useListWorkspaceSshCas } from "@app/hooks/api";
|
||||
import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -59,7 +60,9 @@ export const SshCaTable = ({ handlePopUpOpen }: Props) => {
|
||||
<Tr
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
key={`ca-${ca.id}`}
|
||||
onClick={() => router.push(`/project/${currentWorkspace?.id}/ssh/ca/${ca.id}`)}
|
||||
onClick={() =>
|
||||
router.push(`/${ProjectType.SSH}/${currentWorkspace?.id}/ca/${ca.id}`)
|
||||
}
|
||||
>
|
||||
<Td>{ca.friendlyName}</Td>
|
||||
<Td>
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import { useGetProjectSecretsDetails } from "@app/hooks/api/dashboard";
|
||||
import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { DynamicSecretListView } from "@app/views/SecretMainPage/components/DynamicSecretListView";
|
||||
import { FolderListView } from "@app/views/SecretMainPage/components/FolderListView";
|
||||
import { SecretImportListView } from "@app/views/SecretMainPage/components/SecretImportListView";
|
||||
@@ -142,7 +143,7 @@ const SecretMainPageContent = () => {
|
||||
!currentWorkspace?.environments.find((env) => env.slug === environment) &&
|
||||
router.isReady
|
||||
) {
|
||||
router.push(`/project/${workspaceId}/secrets/overview`);
|
||||
router.push(`/${ProjectType.SecretManager}/${workspaceId}/secrets/overview`);
|
||||
createNotification({
|
||||
text: "No environment found with given slug",
|
||||
type: "error"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ClipboardEvent } from "react";
|
||||
import { ClipboardEvent, useRef } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -46,6 +46,7 @@ export const CreateSecretForm = ({
|
||||
control,
|
||||
reset,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm<TFormSchema>({ resolver: zodResolver(typeSchema) });
|
||||
const { closePopUp } = usePopUpAction();
|
||||
@@ -59,6 +60,11 @@ export const CreateSecretForm = ({
|
||||
canReadTags ? workspaceId : ""
|
||||
);
|
||||
|
||||
const secretKeyInputRef = useRef<HTMLInputElement>(null);
|
||||
const { ref: setSecretKeyHookRef, ...secretKeyRegisterRest } = register("key");
|
||||
|
||||
const secretKey = watch("key");
|
||||
|
||||
const slugSchema = z.string().trim().toLowerCase().min(1);
|
||||
const createNewTag = async (slug: string) => {
|
||||
// TODO: Replace with slugSchema generic
|
||||
@@ -108,13 +114,23 @@ export const CreateSecretForm = ({
|
||||
};
|
||||
|
||||
const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault();
|
||||
const delimitters = [":", "="];
|
||||
const pastedContent = e.clipboardData.getData("text");
|
||||
const { key, value } = getKeyValue(pastedContent, delimitters);
|
||||
|
||||
setValue("key", key);
|
||||
setValue("value", value);
|
||||
const isWholeKeyHighlighted =
|
||||
secretKeyInputRef.current &&
|
||||
secretKeyInputRef.current.selectionStart === 0 &&
|
||||
secretKeyInputRef.current.selectionEnd === secretKeyInputRef.current.value.length;
|
||||
|
||||
if (!secretKey || isWholeKeyHighlighted) {
|
||||
e.preventDefault();
|
||||
|
||||
setValue("key", key);
|
||||
if (value) {
|
||||
setValue("value", value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -126,7 +142,12 @@ export const CreateSecretForm = ({
|
||||
errorText={errors?.key?.message}
|
||||
>
|
||||
<Input
|
||||
{...register("key")}
|
||||
{...secretKeyRegisterRest}
|
||||
ref={(e) => {
|
||||
setSecretKeyHookRef(e);
|
||||
// @ts-expect-error this is for multiple ref single component
|
||||
secretKeyInputRef.current = e;
|
||||
}}
|
||||
placeholder="Type your secret name"
|
||||
onPaste={handlePaste}
|
||||
autoCapitalization={autoCapitalize}
|
||||
|
||||
@@ -68,7 +68,7 @@ import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
|
||||
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
|
||||
import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types";
|
||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils";
|
||||
import { SecretOverviewDynamicSecretRow } from "@app/views/SecretOverviewPage/components/SecretOverviewDynamicSecretRow";
|
||||
import {
|
||||
@@ -170,7 +170,7 @@ export const SecretOverviewPage = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWorkspaceLoading && !workspaceId && router.isReady) {
|
||||
router.push(`/org/${currentOrg?.id}/overview`);
|
||||
router.push(`/org/${currentOrg?.id}/${ProjectType.SecretManager}/overview`);
|
||||
}
|
||||
}, [isWorkspaceLoading, workspaceId, router.isReady]);
|
||||
|
||||
@@ -508,7 +508,7 @@ export const SecretOverviewPage = () => {
|
||||
const envIndex = visibleEnvs.findIndex((el) => slug === el.slug);
|
||||
if (envIndex !== -1) {
|
||||
router.push({
|
||||
pathname: "/project/[id]/secrets/[env]",
|
||||
pathname: `/${ProjectType.SecretManager}/[id]/secrets/[env]`,
|
||||
query
|
||||
});
|
||||
}
|
||||
|
||||
+26
-5
@@ -1,4 +1,4 @@
|
||||
import { ClipboardEvent } from "react";
|
||||
import { ClipboardEvent, useRef } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { subject } from "@casl/ability";
|
||||
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -46,6 +46,7 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => {
|
||||
control,
|
||||
reset,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { isSubmitting, errors }
|
||||
} = useForm<TFormSchema>({ resolver: zodResolver(typeSchema) });
|
||||
|
||||
@@ -61,6 +62,11 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => {
|
||||
canReadTags ? workspaceId : ""
|
||||
);
|
||||
|
||||
const secretKeyInputRef = useRef<HTMLInputElement>(null);
|
||||
const { ref: setSecretKeyHookRef, ...secretKeyRegisterRest } = register("key");
|
||||
|
||||
const secretKey = watch("key");
|
||||
|
||||
const handleFormSubmit = async ({ key, value, environments: selectedEnv, tags }: TFormSchema) => {
|
||||
const promises = selectedEnv.map(async (env) => {
|
||||
const environment = env.slug;
|
||||
@@ -152,13 +158,23 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => {
|
||||
};
|
||||
|
||||
const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault();
|
||||
const delimitters = [":", "="];
|
||||
const pastedContent = e.clipboardData.getData("text");
|
||||
const { key, value } = getKeyValue(pastedContent, delimitters);
|
||||
|
||||
setValue("key", key);
|
||||
setValue("value", value);
|
||||
const isWholeKeyHighlighted =
|
||||
secretKeyInputRef.current &&
|
||||
secretKeyInputRef.current.selectionStart === 0 &&
|
||||
secretKeyInputRef.current.selectionEnd === secretKeyInputRef.current.value.length;
|
||||
|
||||
if (!secretKey || isWholeKeyHighlighted) {
|
||||
e.preventDefault();
|
||||
|
||||
setValue("key", key);
|
||||
if (value) {
|
||||
setValue("value", value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const createWsTag = useCreateWsTag();
|
||||
@@ -189,7 +205,12 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => {
|
||||
errorText={errors?.key?.message}
|
||||
>
|
||||
<Input
|
||||
{...register("key")}
|
||||
{...secretKeyRegisterRest}
|
||||
ref={(e) => {
|
||||
setSecretKeyHookRef(e);
|
||||
// @ts-expect-error this is for multiple ref single component
|
||||
secretKeyInputRef.current = e;
|
||||
}}
|
||||
placeholder="Type your secret name"
|
||||
onPaste={handlePaste}
|
||||
autoCapitalization={currentWorkspace?.autoCapitalization}
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useGetWorkspaceById, useMigrateProjectToV3, workspaceKeys } from "@app/hooks/api";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
|
||||
enum ProjectUpgradeStatus {
|
||||
InProgress = "IN_PROGRESS",
|
||||
@@ -53,7 +53,7 @@ export const SecretV2MigrationSection = () => {
|
||||
if (isProjectUpgraded && migrateProjectToV3.data) {
|
||||
createNotification({ type: "success", text: "Project upgrade completed successfully" });
|
||||
migrateProjectToV3.reset();
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||
}
|
||||
}, [isProjectUpgraded, Boolean(migrateProjectToV3.data)]);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Tab } from "@headlessui/react";
|
||||
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { EncryptionTab } from "./components/EncryptionTab";
|
||||
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||
@@ -18,10 +18,20 @@ export const ProjectSettingsPage = () => {
|
||||
{
|
||||
name: "Encryption",
|
||||
key: "tab-project-encryption",
|
||||
isHidden: currentWorkspace?.version !== ProjectVersion.V3
|
||||
isHidden:
|
||||
currentWorkspace?.version !== ProjectVersion.V3 ||
|
||||
currentWorkspace?.type !== ProjectType.SecretManager
|
||||
},
|
||||
{ name: "Workflow Integrations", key: "tab-workflow-integrations" },
|
||||
{ name: "Webhooks", key: "tab-project-webhooks" }
|
||||
{
|
||||
name: "Workflow Integrations",
|
||||
key: "tab-workflow-integrations",
|
||||
isHidden: currentWorkspace?.type !== ProjectType.SecretManager
|
||||
},
|
||||
{
|
||||
name: "Webhooks",
|
||||
key: "tab-project-webhooks",
|
||||
isHidden: currentWorkspace?.type !== ProjectType.SecretManager
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -53,17 +63,23 @@ export const ProjectSettingsPage = () => {
|
||||
<Tab.Panel>
|
||||
<ProjectGeneralTab />
|
||||
</Tab.Panel>
|
||||
{currentWorkspace?.version === ProjectVersion.V3 && (
|
||||
{currentWorkspace?.version === ProjectVersion.V3 &&
|
||||
currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<Tab.Panel>
|
||||
<EncryptionTab />
|
||||
</Tab.Panel>
|
||||
)}
|
||||
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<Tab.Panel>
|
||||
<EncryptionTab />
|
||||
<WorkflowIntegrationTab />
|
||||
</Tab.Panel>
|
||||
)}
|
||||
{currentWorkspace?.type === ProjectType.SecretManager && (
|
||||
<Tab.Panel>
|
||||
<WebhooksTab />
|
||||
</Tab.Panel>
|
||||
)}
|
||||
<Tab.Panel>
|
||||
<WorkflowIntegrationTab />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<WebhooksTab />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user