Merge branch 'main' into feat/zabbixSyncIntegration

This commit is contained in:
Carlos Monastyrski
2025-07-02 09:25:19 -03:00
651 changed files with 12487 additions and 13813 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+365
View File
@@ -0,0 +1,365 @@
{
"v": "5.7.5",
"fr": 100,
"ip": 0,
"op": 100,
"w": 22,
"h": 20,
"nm": "Comp 1",
"ddd": 0,
"metadata": {},
"assets": [
{
"id": "0",
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "Path 1",
"sr": 1,
"ks": {
"p": { "a": 0, "k": [-6, -6], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"nm": "Path 1",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": false,
"v": [
[9.25, 9.25],
[12.25, 9.25]
],
"i": [
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0]
]
}
}
},
{
"ty": "st",
"c": { "a": 0, "k": [1, 1, 1], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"w": { "a": 0, "k": 1.5, "ix": 2 },
"lc": 2,
"lj": 2,
"ml": 4
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
}
],
"ip": 0,
"op": 101,
"st": 0,
"bm": 0
}
]
}
],
"layers": [
{
"ddd": 0,
"ind": 2,
"ty": 3,
"nm": "",
"sr": 1,
"ks": {
"p": { "a": 0, "k": [0.7886505126953125, 0.7357635498046875], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
},
"ao": 0,
"ip": 0,
"op": 101,
"st": 0,
"bm": 0
},
{
"ddd": 0,
"refId": "0",
"w": 10,
"h": 7,
"ind": 3,
"ty": 0,
"nm": "10 Outlines 2",
"sr": 1,
"ks": {
"p": { "a": 0, "k": [6, 6], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": {
"a": 1,
"k": [
{ "t": 0, "s": [100], "i": { "x": [0.667], "y": [1] }, "o": { "x": [1], "y": [0] } },
{ "t": 50, "s": [0], "i": { "x": [0.667], "y": [1] }, "o": { "x": [1], "y": [0] } },
{
"t": 100,
"s": [100],
"i": { "x": [0.75], "y": [0.75] },
"o": { "x": [0.25], "y": [0.25] }
}
],
"ix": 2
},
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
},
"ao": 0,
"ip": 0,
"op": 101,
"st": 0,
"bm": 0,
"parent": 2
},
{
"ddd": 0,
"ind": 4,
"ty": 4,
"nm": "10 Outlines",
"sr": 1,
"ks": {
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ty": "gr",
"it": [
{
"ty": "gr",
"nm": "Path 1",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": false,
"v": [
[4.75, 4.75],
[7.75, 7],
[4.75, 9.25]
],
"i": [
[0, 0],
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0],
[0, 0]
]
}
}
},
{
"ty": "st",
"c": { "a": 0, "k": [1, 1, 1], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"w": { "a": 0, "k": 1, "ix": 2 },
"lc": 2,
"lj": 2,
"ml": 4
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"it": [
{
"ty": "gr",
"nm": "Group",
"it": [
{
"ty": "gr",
"nm": "Path 2",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[3.25, 17.5],
[16.75, 17.5],
[18.341, 16.841],
[19, 15.25],
[19, 3.25],
[18.341, 1.659],
[16.75, 1],
[3.25, 1],
[1.659, 1.659],
[1, 3.25],
[1, 15.25],
[1.659, 16.841],
[3.25, 17.5]
],
"i": [
[0, 0],
[0, 0],
[-0.422, 0.422],
[0, 0.597],
[0, 0],
[0.422, 0.422],
[0.597, 0],
[0, 0],
[0.422, -0.422],
[0, -0.597],
[0, 0],
[-0.422, -0.422],
[-0.597, 0]
],
"o": [
[0, 0],
[0.597, 0],
[0.422, -0.422],
[0, 0],
[0, -0.597],
[-0.422, -0.422],
[0, 0],
[-0.597, 0],
[-0.422, 0.422],
[0, 0],
[0, 0.597],
[0.422, 0.422],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "st",
"c": { "a": 0, "k": [1, 1, 1], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"w": { "a": 0, "k": 1.5, "ix": 2 },
"lc": 2,
"lj": 2,
"ml": 4
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [0.7886505126953125, 0.7357635498046875], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
}
],
"ip": 0,
"op": 101,
"st": 0,
"bm": 0
}
],
"markers": []
}
+992
View File
@@ -0,0 +1,992 @@
{
"v": "5.7.5",
"fr": 100,
"ip": 0,
"op": 200,
"w": 500,
"h": 500,
"nm": "Comp 1",
"ddd": 0,
"metadata": {},
"assets": [],
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "Shape Layer 2",
"sr": 1,
"ks": {
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"nm": "Shape Layer 2",
"it": [
{
"ty": "rc",
"d": 1,
"s": { "a": 0, "k": [49.3180325191623, 68.7788286222386], "ix": 2 },
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 }
},
{
"ty": "fl",
"c": { "a": 0, "k": [1, 1, 1], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"r": 1,
"bm": 0
},
{
"ty": "tr",
"p": { "a": 0, "k": [36.65843963623047, 374.4748840332031], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
}
],
"ip": 0,
"op": 201,
"st": 0,
"bm": 0
},
{
"ddd": 0,
"ind": 2,
"ty": 4,
"nm": "Shape Layer 1",
"sr": 1,
"ks": {
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"nm": "Shape Layer 1",
"it": [
{
"ty": "rc",
"d": 1,
"s": { "a": 0, "k": [49.3180325191623, 68.7788286222386], "ix": 2 },
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 }
},
{
"ty": "fl",
"c": { "a": 0, "k": [1, 1, 1], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"r": 1,
"bm": 0
},
{
"ty": "tr",
"p": { "a": 0, "k": [36.65843963623047, 152], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
}
],
"ip": 0,
"op": 201,
"st": 0,
"bm": 0
},
{
"ddd": 0,
"ind": 3,
"ty": 4,
"nm": "bank-safe-box-svgrepo-com.svg 1",
"sr": 1,
"ks": {
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ty": "gr",
"nm": "bank-safe-box-svgrepo-com.svg 1",
"it": [
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[33.06575776130341, 49.665937523501114],
[20.000300594090934, 36.60048035628864],
[33.06575776130341, 23.530797074065426],
[46.12614359050298, 36.60048035628864],
[33.06575776130341, 49.665937523501114],
[33.06575776130341, 49.665937523501114]
],
"i": [
[0, 0],
[0, 7.20383564731219],
[-7.208906985325083, 0],
[0, -7.208061762322929],
[7.1996095323014515, 0],
[0, 0]
],
"o": [
[-7.20383564731219, 0],
[0, -7.20383564731219],
[7.1996095323014515, 0],
[0, 7.1996095323014515],
[0, 0],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [-45.124942779541016, -76.91899108886719], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 1.36079623004602e-7, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[56.59403564377824, 2.417355107683346],
[49.427568347046794, 2.417355107683346],
[38.852034318840836, 13.001059012587813],
[29.055609441248567, 10.541183410268967],
[19.84890111807693, 12.677977524964414],
[8.650971028656253, 1.4845037319247485],
[1.4845037319248013, 1.4845037319247485],
[1.4845037319248013, 8.650971028656201],
[11.935261461462396, 19.101728758193797],
[7.4351448327033705, 32.15793443849666],
[11.935261461462396, 45.210426538482],
[1.5253531154174096, 55.624791180908],
[1.5253531154174096, 62.78680218125844],
[5.108215405751383, 64.27371974038958],
[8.691077696085358, 62.78680218125844],
[19.84444482169591, 51.63343505564789],
[29.051153144867545, 53.76651559002584],
[39.33182889586317, 51.04371850122726],
[49.17653031758308, 60.89213350326467],
[52.75939260791704, 62.3745947660148],
[56.34225489825104, 60.89213350326467],
[56.34225489825104, 53.72566620653322],
[46.913474472092304, 44.296885780374474],
[50.66196244458722, 32.15347814211567],
[46.56959693469084, 19.59786308861321],
[56.58363761888923, 9.583822404414821],
[56.59403564377824, 2.417355107683346],
[56.59403564377824, 2.417355107683346]
],
"i": [
[0, 0],
[1.979338309233005, -1.9756247289154982],
[3.5251780094019862, -3.527901301634823],
[3.5427556229048607, 0],
[2.8037531397203104, -1.333175333986213],
[3.732643363140226, 3.731157931013225],
[1.9793383092330157, -1.9793383092330104],
[-1.9793383092330157, -1.9793383092330157],
[-3.483585909845862, -3.483585909845862],
[0, -4.9204939207012],
[-2.779243509624741, -3.6363378469061254],
[3.469969448681659, -3.4714548808086705],
[-1.9793383092330157, -1.979338309233005],
[-1.296782246874607, 0],
[-0.9892977965847497, 0.9900405126482554],
[-3.7177890418701867, 3.717789041870177],
[-3.308800062901711, 0],
[-3.0696454904540436, 1.6807664517051837],
[-3.281567140573295, -3.2828050006791307],
[-1.2967822468746175, 0],
[-0.9892977965847708, 0.9892977965847708],
[1.979338309233005, 1.979338309233005],
[3.142926808719575, 3.1429268087195856],
[0, 4.496403048441521],
[2.5490015299390887, 3.5472119192858735],
[-3.3380135613994684, 3.3380135613994586],
[1.979338309233005, 1.983794605614028],
[0, 0]
],
"o": [
[-1.979338309233005, -1.9756247289154982],
[-3.5251780094019756, 3.527901301634823],
[-2.9530390684842214, -1.5233106462427428],
[-3.3043437665206987, 0],
[-3.732643363140226, -3.731157931013225],
[-1.9793383092330157, -1.9793383092330104],
[-1.9793383092330157, 1.9793383092330157],
[3.483585909845862, 3.483585909845862],
[-2.779243509624741, 3.6355951308426193],
[0, 4.9204939207012],
[-3.469969448681659, 3.47145488080866],
[-1.9793383092330157, 1.9756247289154982],
[0.9900405126482554, 0.9900405126482554],
[1.296782246874607, 0],
[3.7177890418701867, -3.717789041870177],
[2.7992968433392984, 1.3287190376051898],
[3.741060811859921, 0],
[3.2815671405733053, 3.2828050006791307],
[0.9900405126482554, 0.9900405126482554],
[1.2967822468746175, 0],
[1.979338309233005, -1.979338309233005],
[-3.142926808719575, -3.142926808719575],
[2.3588662176825586, -3.46179957198314],
[0, -4.693965521333064],
[3.3380135613994684, -3.3380135613994586],
[1.9897363341220202, -1.9756247289155193],
[0, 0],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [-41.346412658691406, -72.31075286865234], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 1.36079623004602e-7, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [563.65673828125, 513.2783813476562], "ix": 2 },
"a": { "a": 0, "k": [-12.307790756225586, -40.173892974853516], "ix": 2 },
"s": { "a": 0, "k": [366.3825750350952, 366.3825750350952], "ix": 2 },
"r": {
"a": 1,
"k": [
{
"t": 0,
"s": [0],
"i": { "x": [0.75], "y": [0.75] },
"o": { "x": [0.25], "y": [0.25] }
},
{
"t": 100,
"s": [180],
"i": { "x": [0.75], "y": [0.75] },
"o": { "x": [0.25], "y": [0.25] }
}
],
"ix": 2
},
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[16.204001519577492, 10.809684651948828],
[107.78162573450534, 10.809684651948828],
[107.78162573450534, 103.16527938802837],
[16.204001519577492, 103.16527938802837],
[16.204001519577492, 10.809684651948828],
[16.204001519577492, 10.809684651948828]
],
"i": [
[0, 0],
[-30.52587473830928, 0],
[0, -30.785198245359847],
[30.52587473830929, 0],
[0, 30.785198245359847],
[0, 0]
],
"o": [
[30.52587473830928, 0],
[0, 30.785198245359833],
[-30.52587473830928, 0],
[0, -30.785198245359847],
[0, 0],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [-33.81110763549805, -68.36682891845703], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[5.187128475098463, 75.41686707022457],
[0.11734912644122932, 75.41686707022457],
[0.11734912644122932, 85.55642576753904],
[5.187128475098463, 85.55642576753904],
[5.187128475098463, 101.79962447177776],
[10.253194243805027, 106.869403820435],
[106.98749566286637, 106.869403820435],
[112.05356143157292, 101.79962447177776],
[112.05356143157292, 5.066065768706561],
[106.98749566286637, 0],
[10.2569078237557, 0],
[5.190842055049137, 5.066065768706561],
[5.190842055049137, 21.277327685369535],
[0, 21.277327685369535],
[0, 31.413172802733335],
[5.190842055049137, 31.413172802733335],
[5.190842055049137, 75.4176097862147],
[5.187128475098463, 75.4176097862147],
[5.187128475098463, 75.41686707022457]
],
"i": [
[0, 0],
[1.6899264495524133, 0],
[0, -3.3798528991048267],
[-1.6899264495524133, 0],
[0, -5.414399568079578],
[-2.795582986865779, 0],
[-32.24476713968711, 0],
[0, 2.799296566816458],
[0, 32.244519567690396],
[2.7955829868657895, 0],
[32.243529279703566, 0],
[0, -2.800039282806585],
[0, -5.40375397222099],
[1.7302806850163799, 0],
[0, -3.3786150391212706],
[-1.7302806850163799, 0],
[0, -14.66814566116046],
[0.0012378599835561636, 0],
[0, 0.0002475719967196764]
],
"o": [
[-1.6899264495524133, 0],
[0, 3.3798528991048267],
[1.6899264495524107, 0],
[0, 5.414399568079578],
[0, 2.799296566816458],
[32.24476713968713, 0],
[2.799296566816458, 0],
[0, -32.2445195676904],
[0, -2.799296566816452],
[-32.24352927970354, 0],
[-2.799296566816452, 0],
[0, 5.40375397222099],
[-1.7302806850163799, 0],
[0, 3.3786150391212706],
[1.7302806850163799, 0],
[0, 14.66814566116045],
[-0.0012378599835561636, 0],
[0, -0.0002475719967196764],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [-30.517711639404297, -64.98767852783203], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [738.9981689453125, 819], "ix": 2 },
"a": { "a": 0, "k": [75.43499755859375, 71.94499969482422], "ix": 2 },
"s": { "a": 0, "k": [366.3825750350952, 366.3825750350952], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "tr",
"p": { "a": 0, "k": [360.7227783203125, 392.2637023925781], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [454.6020030975342, 454.6020030975342], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "fl",
"c": { "a": 0, "k": [1, 1, 1], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"r": 1,
"bm": 0
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"it": [
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[33.06575776130341, 49.665937523501114],
[20.000300594090934, 36.60048035628864],
[33.06575776130341, 23.530797074065426],
[46.12614359050298, 36.60048035628864],
[33.06575776130341, 49.665937523501114],
[33.06575776130341, 49.665937523501114]
],
"i": [
[0, 0],
[0, 7.20383564731219],
[-7.208906985325083, 0],
[0, -7.208061762322929],
[7.1996095323014515, 0],
[0, 0]
],
"o": [
[-7.20383564731219, 0],
[0, -7.20383564731219],
[7.1996095323014515, 0],
[0, 7.1996095323014515],
[0, 0],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [-45.124942779541016, -76.91899108886719], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 1.36079623004602e-7, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[56.59403564377824, 2.417355107683346],
[49.427568347046794, 2.417355107683346],
[38.852034318840836, 13.001059012587813],
[29.055609441248567, 10.541183410268967],
[19.84890111807693, 12.677977524964414],
[8.650971028656253, 1.4845037319247485],
[1.4845037319248013, 1.4845037319247485],
[1.4845037319248013, 8.650971028656201],
[11.935261461462396, 19.101728758193797],
[7.4351448327033705, 32.15793443849666],
[11.935261461462396, 45.210426538482],
[1.5253531154174096, 55.624791180908],
[1.5253531154174096, 62.78680218125844],
[5.108215405751383, 64.27371974038958],
[8.691077696085358, 62.78680218125844],
[19.84444482169591, 51.63343505564789],
[29.051153144867545, 53.76651559002584],
[39.33182889586317, 51.04371850122726],
[49.17653031758308, 60.89213350326467],
[52.75939260791704, 62.3745947660148],
[56.34225489825104, 60.89213350326467],
[56.34225489825104, 53.72566620653322],
[46.913474472092304, 44.296885780374474],
[50.66196244458722, 32.15347814211567],
[46.56959693469084, 19.59786308861321],
[56.58363761888923, 9.583822404414821],
[56.59403564377824, 2.417355107683346],
[56.59403564377824, 2.417355107683346]
],
"i": [
[0, 0],
[1.979338309233005, -1.9756247289154982],
[3.5251780094019862, -3.527901301634823],
[3.5427556229048607, 0],
[2.8037531397203104, -1.333175333986213],
[3.732643363140226, 3.731157931013225],
[1.9793383092330157, -1.9793383092330104],
[-1.9793383092330157, -1.9793383092330157],
[-3.483585909845862, -3.483585909845862],
[0, -4.9204939207012],
[-2.779243509624741, -3.6363378469061254],
[3.469969448681659, -3.4714548808086705],
[-1.9793383092330157, -1.979338309233005],
[-1.296782246874607, 0],
[-0.9892977965847497, 0.9900405126482554],
[-3.7177890418701867, 3.717789041870177],
[-3.308800062901711, 0],
[-3.0696454904540436, 1.6807664517051837],
[-3.281567140573295, -3.2828050006791307],
[-1.2967822468746175, 0],
[-0.9892977965847708, 0.9892977965847708],
[1.979338309233005, 1.979338309233005],
[3.142926808719575, 3.1429268087195856],
[0, 4.496403048441521],
[2.5490015299390887, 3.5472119192858735],
[-3.3380135613994684, 3.3380135613994586],
[1.979338309233005, 1.983794605614028],
[0, 0]
],
"o": [
[-1.979338309233005, -1.9756247289154982],
[-3.5251780094019756, 3.527901301634823],
[-2.9530390684842214, -1.5233106462427428],
[-3.3043437665206987, 0],
[-3.732643363140226, -3.731157931013225],
[-1.9793383092330157, -1.9793383092330104],
[-1.9793383092330157, 1.9793383092330157],
[3.483585909845862, 3.483585909845862],
[-2.779243509624741, 3.6355951308426193],
[0, 4.9204939207012],
[-3.469969448681659, 3.47145488080866],
[-1.9793383092330157, 1.9756247289154982],
[0.9900405126482554, 0.9900405126482554],
[1.296782246874607, 0],
[3.7177890418701867, -3.717789041870177],
[2.7992968433392984, 1.3287190376051898],
[3.741060811859921, 0],
[3.2815671405733053, 3.2828050006791307],
[0.9900405126482554, 0.9900405126482554],
[1.2967822468746175, 0],
[1.979338309233005, -1.979338309233005],
[-3.142926808719575, -3.142926808719575],
[2.3588662176825586, -3.46179957198314],
[0, -4.693965521333064],
[3.3380135613994684, -3.3380135613994586],
[1.9897363341220202, -1.9756247289155193],
[0, 0],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [-41.346412658691406, -72.31075286865234], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 1.36079623004602e-7, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "fl",
"c": { "a": 0, "k": [0.00392156862745098, 0, 0.00784313725490196], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"r": 1,
"bm": 0,
"hd": true
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [563.65673828125, 513.2783813476562], "ix": 2 },
"a": { "a": 0, "k": [-12.307790756225586, -40.173892974853516], "ix": 2 },
"s": { "a": 0, "k": [366.3825750350952, 366.3825750350952], "ix": 2 },
"r": {
"a": 1,
"k": [
{
"t": 0,
"s": [0],
"i": { "x": [0.75], "y": [0.75] },
"o": { "x": [0.25], "y": [0.25] }
},
{
"t": 100,
"s": [180],
"i": { "x": [0.75], "y": [0.75] },
"o": { "x": [0.25], "y": [0.25] }
}
],
"ix": 2
},
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"it": [
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[16.204001519577492, 10.809684651948828],
[107.78162573450534, 10.809684651948828],
[107.78162573450534, 103.16527938802837],
[16.204001519577492, 103.16527938802837],
[16.204001519577492, 10.809684651948828],
[16.204001519577492, 10.809684651948828]
],
"i": [
[0, 0],
[-30.52587473830928, 0],
[0, -30.785198245359847],
[30.52587473830929, 0],
[0, 30.785198245359847],
[0, 0]
],
"o": [
[30.52587473830928, 0],
[0, 30.785198245359833],
[-30.52587473830928, 0],
[0, -30.785198245359847],
[0, 0],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [-33.81110763549805, -68.36682891845703], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "sh",
"d": 1,
"ks": {
"a": 0,
"k": {
"c": true,
"v": [
[5.187128475098463, 75.41686707022457],
[0.11734912644122932, 75.41686707022457],
[0.11734912644122932, 85.55642576753904],
[5.187128475098463, 85.55642576753904],
[5.187128475098463, 101.79962447177776],
[10.253194243805027, 106.869403820435],
[106.98749566286637, 106.869403820435],
[112.05356143157292, 101.79962447177776],
[112.05356143157292, 5.066065768706561],
[106.98749566286637, 0],
[10.2569078237557, 0],
[5.190842055049137, 5.066065768706561],
[5.190842055049137, 21.277327685369535],
[0, 21.277327685369535],
[0, 31.413172802733335],
[5.190842055049137, 31.413172802733335],
[5.190842055049137, 75.4176097862147],
[5.187128475098463, 75.4176097862147],
[5.187128475098463, 75.41686707022457]
],
"i": [
[0, 0],
[1.6899264495524133, 0],
[0, -3.3798528991048267],
[-1.6899264495524133, 0],
[0, -5.414399568079578],
[-2.795582986865779, 0],
[-32.24476713968711, 0],
[0, 2.799296566816458],
[0, 32.244519567690396],
[2.7955829868657895, 0],
[32.243529279703566, 0],
[0, -2.800039282806585],
[0, -5.40375397222099],
[1.7302806850163799, 0],
[0, -3.3786150391212706],
[-1.7302806850163799, 0],
[0, -14.66814566116046],
[0.0012378599835561636, 0],
[0, 0.0002475719967196764]
],
"o": [
[-1.6899264495524133, 0],
[0, 3.3798528991048267],
[1.6899264495524107, 0],
[0, 5.414399568079578],
[0, 2.799296566816458],
[32.24476713968713, 0],
[2.799296566816458, 0],
[0, -32.2445195676904],
[0, -2.799296566816452],
[-32.24352927970354, 0],
[-2.799296566816452, 0],
[0, 5.40375397222099],
[-1.7302806850163799, 0],
[0, 3.3786150391212706],
[1.7302806850163799, 0],
[0, 14.66814566116045],
[-0.0012378599835561636, 0],
[0, -0.0002475719967196764],
[0, 0]
]
}
}
},
{
"ty": "tr",
"p": { "a": 0, "k": [-30.517711639404297, -64.98767852783203], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "fl",
"c": { "a": 0, "k": [0.00392156862745098, 0, 0.00784313725490196], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"r": 1,
"bm": 0,
"hd": true
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [738.9981689453125, 819], "ix": 2 },
"a": { "a": 0, "k": [75.43499755859375, 71.94499969482422], "ix": 2 },
"s": { "a": 0, "k": [366.3825750350952, 366.3825750350952], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "gr",
"it": [
{
"ty": "gr",
"nm": "Path",
"it": [
{
"ty": "fl",
"c": { "a": 0, "k": [0.00392156862745098, 0, 0.00784313725490196], "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"r": 1,
"bm": 0,
"hd": true
},
{
"ty": "tr",
"p": { "a": 0, "k": [0, 0], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [100, 100], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [360.7227783203125, 392.2637023925781], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 2 },
"s": { "a": 0, "k": [454.6020030975342, 454.6020030975342], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
},
{
"ty": "tr",
"p": { "a": 0, "k": [80.75597381591797, 122.56097412109375], "ix": 2 },
"a": { "a": 0, "k": [399.99816274642944, 399.99998253828016], "ix": 2 },
"s": { "a": 0, "k": [112.70011646566, 112.70011646566], "ix": 2 },
"r": { "a": 0, "k": 0, "ix": 2 },
"o": { "a": 0, "k": 100, "ix": 2 },
"sk": { "a": 0, "k": 0, "ix": 2 },
"sa": { "a": 0, "k": 0, "ix": 2 }
}
]
}
],
"ip": 0,
"op": 201,
"st": 0,
"bm": 0
}
],
"markers": []
}
@@ -4,7 +4,6 @@ import { useNavigate } from "@tanstack/react-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";
@@ -23,7 +22,7 @@ export default function TeamInviteStep(): JSX.Element {
// Redirect user to the getting started page
const redirectToHome = async () => {
navigate({ to: `/organization/${ProjectType.SecretManager}/overview` as const });
navigate({ to: "/organization/projects" as const });
};
const inviteUsers = async ({ emails: inviteEmails }: { emails: string }) => {
@@ -71,7 +71,7 @@ export default function NavHeader({
{currentOrg?.name?.charAt(0)}
</div>
<Link
to="/organization/secret-manager/overview"
to="/organization/projects"
className="truncate pl-0.5 text-sm font-semibold text-primary/80 hover:text-primary"
>
{currentOrg?.name}
@@ -93,7 +93,7 @@ export default function NavHeader({
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
{pageName === "Secrets" ? (
<Link
to={`/${ProjectType.SecretManager}/$projectId/overview` as const}
to="/projects/$projectId/secret-manager/overview"
params={{ projectId: currentWorkspace.id }}
className="text-sm font-semibold text-primary/80 hover:text-primary"
>
@@ -129,7 +129,7 @@ export default function NavHeader({
<div className="flex items-center space-x-3">
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
<Link
to={`/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const}
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
params={{ projectId: currentWorkspace.id, envSlug: routerEnvSlug }}
className="text-sm font-semibold text-primary/80 hover:text-primary"
>
@@ -191,7 +191,7 @@ export default function NavHeader({
</div>
) : (
<Link
to={`/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const}
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
params={{
projectId: currentWorkspace.id,
envSlug: routerEnvSlug || ""
@@ -70,7 +70,7 @@ export const SecretDashboardPathBreadcrumb = ({
</div>
) : (
<Link
to={`/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const}
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
params={{
projectId,
envSlug: environmentSlug
@@ -7,7 +7,6 @@ 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";
import { GenericResourceNameSchema } from "@app/lib/schemas";
const schema = z
@@ -59,10 +58,7 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
});
navigate({
to: `/organization/${ProjectType.SecretManager}/overview` as const,
params: {
organizationId: organization.id
}
to: "/organization/projects"
});
localStorage.setItem("orgData.id", organization.id);
@@ -5,12 +5,14 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, FormControl, Input, TextArea } from "@app/components/v2";
import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useUpdateProject } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
const baseFormSchema = z.object({
name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
defaultProduct: z.nativeEnum(ProjectType).default(ProjectType.SecretManager),
description: z
.string()
.trim()
@@ -50,6 +52,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
reset({
name: currentWorkspace.name,
description: currentWorkspace.description ?? "",
defaultProduct: currentWorkspace.defaultProduct,
...(showSlugField && { slug: currentWorkspace.slug })
});
}
@@ -63,6 +66,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
projectID: currentWorkspace.id,
newProjectName: data.name,
newProjectDescription: data.description,
defaultProduct: data.defaultProduct,
...(showSlugField &&
"slug" in data && {
newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined
@@ -212,6 +216,31 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
</ProjectPermissionCan>
</div>
</div>
<Controller
control={control}
name="defaultProduct"
defaultValue={ProjectType.SecretManager}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Default Product"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full max-w-md bg-mineshaft-800"
>
<SelectItem value={ProjectType.SecretManager}>Secrets Manager</SelectItem>
<SelectItem value={ProjectType.CertificateManager}>PKI Manager</SelectItem>
<SelectItem value={ProjectType.KMS}>KMS</SelectItem>
<SelectItem value={ProjectType.SSH}>SSH</SelectItem>
<SelectItem value={ProjectType.SecretScanning}>Secret Scanning</SelectItem>
</Select>
</FormControl>
)}
/>
<div>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
@@ -35,7 +35,6 @@ import { getProjectHomePage } from "@app/helpers/project";
import { useCreateWorkspace, useGetExternalKmsList, useGetUserWorkspaces } 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"),
@@ -53,12 +52,11 @@ type TAddProjectFormData = z.infer<typeof formSchema>;
interface NewProjectModalProps {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
projectType: ProjectType;
}
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange" | "projectType">;
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange">;
const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
const navigate = useNavigate();
const { currentOrg } = useOrganization();
const { permission } = useOrgPermission();
@@ -72,7 +70,7 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
OrgPermissionSubjects.ProjectTemplates
);
const { data: projectTemplates = [] } = useListProjectTemplates(projectType, {
const { data: projectTemplates = [] } = useListProjectTemplates({
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates)
});
@@ -115,15 +113,17 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
projectName: name,
projectDescription: description,
kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined,
template,
type: projectType
template
});
await refetchWorkspaces();
createNotification({ text: "Project created", type: "success" });
reset();
onOpenChange(false);
navigate({ to: getProjectHomePage(project), params: { projectId: project.id } });
navigate({
to: getProjectHomePage(project.defaultProduct),
params: { projectId: project.id }
});
} catch (err) {
console.error(err);
createNotification({ text: "Failed to create project", type: "error" });
@@ -270,18 +270,14 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
);
};
export const NewProjectModal: FC<NewProjectModalProps> = ({
isOpen,
onOpenChange,
projectType
}) => {
export const NewProjectModal: FC<NewProjectModalProps> = ({ isOpen, onOpenChange }) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Create a new project"
subTitle="This project will contain your secrets and configurations."
>
<NewProjectForm onOpenChange={onOpenChange} projectType={projectType} />
<NewProjectForm onOpenChange={onOpenChange} />
</ModalContent>
</Modal>
);
@@ -1,30 +0,0 @@
import { useState } from "react";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectTemplatesTab } from "./components";
const tabs = [
{ name: "Project Templates", key: "project-templates", component: ProjectTemplatesTab }
];
export const ProjectSettings = () => {
const [selectedTab, setSelectedTab] = useState(tabs[0].key);
return (
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
<TabList>
{tabs.map((tab) => (
<Tab value={tab.key} key={tab.key}>
{tab.name}
</Tab>
))}
</TabList>
{tabs.map(({ key, component: Component }) => (
<TabPanel value={key} key={`tab-panel-${key}`}>
<Component />
</TabPanel>
))}
</Tabs>
);
};
@@ -1 +0,0 @@
export * from "./ProjectTemplatesTab";
@@ -1 +0,0 @@
export * from "./ProjectSettings";
@@ -4,7 +4,7 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
import { FilterableSelect, FormControl, Input, Tooltip } from "@app/components/v2";
import {
TOnePassVault,
useOnePassConnectionListVaults
@@ -32,6 +32,7 @@ export const OnePassSyncFields = () => {
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.vaultId", "");
setValue("destinationConfig.valueLabel", "");
}}
/>
@@ -69,6 +70,22 @@ export const OnePassSyncFields = () => {
</FormControl>
)}
/>
<Controller
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
isOptional
label="Value Label"
tooltipText="It's the label of the 1Password item field which will hold your secret value. For example, if you were to sync Infisical secret 'foo: bar', the 1Password item equivalent would have an item title of 'foo', and a field on that item 'value: bar'. The field label 'value' is what gets changed by this option."
>
<Input value={value} onChange={onChange} placeholder="value" />
</FormControl>
)}
control={control}
name="destinationConfig.valueLabel"
/>
</>
);
};
@@ -76,7 +76,7 @@ export const GcpSyncFields = () => {
helperText={
<Tooltip
className="max-w-md"
content="Ensure that you've enabled the Secret Manager API and Cloud Resource Manager API on your GCP project. Additionally, make sure that the service account is assigned the appropriate GCP roles."
content="Ensure that you've enabled the Secret Manager API, Cloud Resource Manager API, and Service Usage API on your GCP project. Additionally, make sure that the service account is assigned the appropriate GCP roles."
>
<div>
<span>Don&#39;t see the project you&#39;re looking for?</span>{" "}
@@ -6,7 +6,15 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
export const OnePassSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.OnePass }>();
const vaultId = watch("destinationConfig.vaultId");
const [vaultId, valueLabel] = watch([
"destinationConfig.vaultId",
"destinationConfig.valueLabel"
]);
return <GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>;
return (
<>
<GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>
<GenericFieldLabel label="Value Key">{valueLabel || "value"}</GenericFieldLabel>
</>
);
};
@@ -7,7 +7,8 @@ export const OnePassSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({
destination: z.literal(SecretSync.OnePass),
destinationConfig: z.object({
vaultId: z.string().trim().min(1, "Vault ID required")
vaultId: z.string().trim().min(1, "Vault ID required"),
valueLabel: z.string().trim().optional()
})
})
);
@@ -0,0 +1,20 @@
import { ComponentType, ReactNode } from "react";
type ShouldWrapProps<T extends Record<string, any>> = {
children: ReactNode;
wrapper: ComponentType<T & { children: ReactNode }>;
isWrapped?: boolean;
} & T;
export const ShouldWrap = <T extends Record<string, any>>({
children,
wrapper: Wrapper,
isWrapped = false,
...wrapperProps
}: ShouldWrapProps<T>) => {
if (isWrapped) {
return <Wrapper {...(wrapperProps as any)}>{children}</Wrapper>;
}
return children;
};
@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */
import React from "react";
import { faCaretDown, faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { faEllipsis, faSort } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, ReactNode } from "@tanstack/react-router";
import { LinkComponentProps } from "node_modules/@tanstack/react-router/dist/esm/link";
@@ -40,7 +40,7 @@ const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWitho
({ className, ...props }, ref) => (
<li
ref={ref}
className={twMerge("inline-flex items-center gap-1.5 font-medium", className)}
className={twMerge("inline-flex items-center gap-1.5 text-sm", className)}
{...props}
/>
)
@@ -78,13 +78,8 @@ const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWit
BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
<li
role="presentation"
aria-hidden="true"
className={twMerge("[&>svg]:h-3.5 [&>svg]:w-3.5", className)}
{...props}
>
{children ?? <FontAwesomeIcon icon={faChevronRight} />}
<li role="presentation" aria-hidden="true" className={twMerge("", className)} {...props}>
{children ?? <p className="px-2 text-lg text-mineshaft-400/70">/</p>}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
@@ -139,12 +134,12 @@ const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[]
<DropdownMenu>
<DropdownMenuTrigger>
<BreadcrumbItem>
<BreadcrumbSegment>
{el.label} <FontAwesomeIcon icon={faCaretDown} size="sm" />
<BreadcrumbSegment className="rounded-md px-2 py-1 py-2 hover:bg-mineshaft-600">
{el.label} <FontAwesomeIcon icon={faSort} size="sm" />
</BreadcrumbSegment>
</BreadcrumbItem>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuContent side="right" align="start">
{el?.dropdownTitle && <DropdownMenuLabel>{el.dropdownTitle}</DropdownMenuLabel>}
{el.links.map((i, dropIndex) => (
<Link
+10 -5
View File
@@ -2,6 +2,8 @@ import { ButtonHTMLAttributes, forwardRef, ReactNode } from "react";
import { cva, VariantProps } from "cva";
import { twMerge } from "tailwind-merge";
import { Lottie } from "../Lottie";
type Props = {
children: ReactNode;
isDisabled?: boolean;
@@ -189,11 +191,14 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
{...props}
>
{isLoading && (
<img
src="/images/loading/loading.gif"
width={36}
alt="loading animation"
className="absolute rounded-xl opacity-80"
<Lottie
icon={
variant === "solid" && colorSchema === "primary"
? "infisical_loading_bw"
: "infisical_loading"
}
isAutoPlay
className="absolute w-8 rounded-xl opacity-80 shadow-sm"
/>
)}
{leftIcon && (
@@ -5,6 +5,8 @@ import { useEffect, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { Lottie } from "../Lottie";
type Props = {
text?: string | string[];
frequency?: number;
@@ -31,14 +33,7 @@ export const ContentLoader = ({ text, frequency = 2000, className }: Props) => {
className
)}
>
<img
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
decoding="async"
loading="lazy"
/>
<Lottie icon="infisical_loading" className="h-32 w-32" />
{text && isTextArray && (
<AnimatePresence mode="wait">
<motion.div
@@ -20,6 +20,7 @@ type Props = {
children?: ReactNode;
deletionMessage?: ReactNode;
buttonColorSchema?: "danger" | "primary" | "secondary" | "gray" | null;
isDisabled?: boolean;
};
export const DeleteActionModal = ({
@@ -34,6 +35,7 @@ export const DeleteActionModal = ({
formContent,
deletionMessage,
buttonColorSchema = "danger",
isDisabled,
children
}: Props): JSX.Element => {
const [inputData, setInputData] = useState("");
@@ -70,7 +72,7 @@ export const DeleteActionModal = ({
<Button
className="mr-4"
colorSchema={buttonColorSchema}
isDisabled={!(deleteKey === inputData) || isLoading}
isDisabled={!(deleteKey === inputData) || isLoading || isDisabled}
onClick={onDelete}
isLoading={isLoading}
>
@@ -7,7 +7,7 @@ interface IProps {
export const Divider = ({ className }: IProps): JSX.Element => {
return (
<div className={twMerge("flex items-center px-2 opacity-50", className)}>
<div aria-hidden="true" className="h-5 w-full grow border border-t border-mineshaft-200" />
<div aria-hidden="true" className="h-1 w-full grow border-t border-mineshaft-300" />
</div>
);
};
@@ -1,4 +1,6 @@
import { ReactNode } from "react";
import { IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
type Props = {
@@ -7,6 +9,7 @@ type Props = {
className?: string;
labelClassName?: string;
truncate?: boolean;
icon?: IconDefinition;
};
export const GenericFieldLabel = ({
@@ -14,11 +17,15 @@ export const GenericFieldLabel = ({
children,
className,
labelClassName,
truncate
truncate,
icon
}: Props) => {
return (
<div className={twMerge("min-w-0", className)}>
<p className={twMerge("text-xs font-medium text-mineshaft-400", labelClassName)}>{label}</p>
<div className="flex items-center gap-1.5">
{icon && <FontAwesomeIcon icon={icon} className="text-mineshaft-400" size="sm" />}
<p className={twMerge("text-xs font-medium text-mineshaft-400", labelClassName)}>{label}</p>
</div>
{children ? (
<p className={twMerge("text-sm text-mineshaft-100", truncate && "truncate")}>{children}</p>
) : (
@@ -0,0 +1,37 @@
import { forwardRef, ReactNode, useRef } from "react";
import { DotLottie, DotLottieReact, Mode } from "@lottiefiles/dotlottie-react";
export type LottieProps = {
// Kudos to https://itnext.io/react-polymorphic-components-with-typescript-f7ce72ea7af2
children?: ReactNode;
icon?: string;
iconMode?: Mode;
className?: string;
isAutoPlay?: boolean;
};
export const Lottie = forwardRef<HTMLDivElement, LottieProps>(
({ children, icon, iconMode, isAutoPlay, ...props }, ref): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null);
return (
<div
onMouseEnter={() => iconRef.current?.play()}
onMouseLeave={() => iconRef.current?.stop()}
{...props}
ref={ref}
>
<DotLottieReact
dotLottieRefCallback={(el) => {
iconRef.current = el;
}}
mode={iconMode}
src={`/lotties/${icon}.json`}
loop
autoplay={isAutoPlay}
className="h-full w-full"
/>
{children}
</div>
);
}
);
@@ -0,0 +1 @@
export { Lottie } from "./Lottie";
+12 -31
View File
@@ -1,5 +1,4 @@
import { ComponentPropsWithRef, ElementType, ReactNode, Ref, useRef } from "react";
import { DotLottie, DotLottieReact, Mode } from "@lottiefiles/dotlottie-react";
import { ComponentPropsWithRef, ElementType, ReactNode, Ref } from "react";
import { twMerge } from "tailwind-merge";
export type MenuProps = {
@@ -14,9 +13,8 @@ export const Menu = ({ children, className }: MenuProps): JSX.Element => {
export type MenuItemProps<T extends ElementType> = {
// Kudos to https://itnext.io/react-polymorphic-components-with-typescript-f7ce72ea7af2
as?: T;
children: ReactNode;
icon?: string;
iconMode?: Mode;
children?: ReactNode;
leftIcon?: ReactNode;
description?: ReactNode;
isDisabled?: boolean;
isSelected?: boolean;
@@ -26,7 +24,7 @@ export type MenuItemProps<T extends ElementType> = {
export const MenuItem = <T extends ElementType = "button">({
children,
icon,
leftIcon,
iconMode,
className,
isDisabled,
@@ -37,41 +35,21 @@ export const MenuItem = <T extends ElementType = "button">({
inputRef,
...props
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null);
return (
<Item
type="button"
role="menuitem"
className={twMerge(
"duration-50 group relative mt-0.5 flex w-full cursor-pointer items-center rounded px-1 py-2 font-inter text-sm text-bunker-100 transition-all hover:bg-mineshaft-700",
"duration-50 group relative mt-0.5 flex w-full cursor-pointer items-center rounded px-2 py-2 font-inter text-sm text-bunker-100 transition-all hover:bg-mineshaft-700",
isSelected && "bg-mineshaft-600 hover:bg-mineshaft-600",
isDisabled && "cursor-not-allowed hover:bg-transparent",
className
)}
ref={inputRef}
onMouseEnter={() => iconRef.current?.play()}
onMouseLeave={() => iconRef.current?.stop()}
{...props}
>
<div
className={`${
isSelected ? "visisble" : "invisible"
} absolute -left-[0.28rem] h-5 w-[0.07rem] rounded-md bg-primary`}
/>
{icon && (
<div style={{ width: "22px", height: "22px" }} className="my-auto ml-1 mr-3">
<DotLottieReact
dotLottieRefCallback={(el) => {
iconRef.current = el;
}}
mode={iconMode}
src={`/lotties/${icon}.json`}
loop
className="h-full w-full"
/>
</div>
)}
<span className="flex-grow text-left">{children}</span>
{leftIcon}
{children && <span className="flex-grow whitespace-nowrap text-left">{children}</span>}
{description && <span className="mt-2 text-xs">{description}</span>}
</Item>
);
@@ -82,11 +60,14 @@ MenuItem.displayName = "MenuItem";
export type MenuGroupProps = {
children: ReactNode;
title: ReactNode;
className?: string;
};
export const MenuGroup = ({ children, title }: MenuGroupProps): JSX.Element => (
export const MenuGroup = ({ children, title, className }: MenuGroupProps): JSX.Element => (
<>
<li className="px-2 pt-3 text-xs uppercase text-gray-400">{title}</li>
<li className={twMerge("px-2 pt-3 text-xs font-medium uppercase text-gray-400", className)}>
{title}
</li>
{children}
</>
);
@@ -64,7 +64,7 @@ export const Pagination = ({
<FontAwesomeIcon className="text-xs" icon={faCaretDown} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-fit">
<DropdownMenuContent sideOffset={2} className="min-w-fit">
{perPageList.map((perPageOption) => (
<DropdownMenuItem
key={`pagination-per-page-options-${perPageOption}`}
+2
View File
@@ -10,6 +10,7 @@ export * from "./ConfirmActionModal";
export * from "./ContentLoader";
export * from "./DatePicker";
export * from "./DeleteActionModal";
export * from "./Divider";
export * from "./Drawer";
export * from "./Dropdown";
export * from "./Editor";
@@ -22,6 +23,7 @@ export * from "./GenericFieldLabel";
export * from "./HoverCardv2";
export * from "./IconButton";
export * from "./Input";
export * from "./Lottie";
export * from "./Menu";
export * from "./Modal";
export * from "./NoticeBanner";
+132 -132
View File
@@ -74,276 +74,276 @@ export const ROUTE_PATHS = Object.freeze({
},
SecretManager: {
ApprovalPage: setRoute(
"/secret-manager/$projectId/approval",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/approval"
"/projects/$projectId/secret-manager/approval",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/approval"
),
SecretDashboardPage: setRoute(
"/secret-manager/$projectId/secrets/$envSlug",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug"
"/projects/$projectId/secret-manager/secrets/$envSlug",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/secrets/$envSlug"
),
RollbackPreviewPage: setRoute(
"/secret-manager/$projectId/commits/$environment/$folderId/$commitId/restore",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId/restore"
"/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId/restore",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId/$commitId/restore"
),
CommitDetailsPage: setRoute(
"/secret-manager/$projectId/commits/$environment/$folderId/$commitId",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId"
"/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId/$commitId"
),
CommitsPage: setRoute(
"/secret-manager/$projectId/commits/$environment/$folderId",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/commits/$environment/$folderId"
"/projects/$projectId/secret-manager/commits/$environment/$folderId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId"
),
OverviewPage: setRoute(
"/secret-manager/$projectId/overview",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/overview"
"/projects/$projectId/secret-manager/overview",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/overview"
),
IntegrationsListPage: setRoute(
"/secret-manager/$projectId/integrations",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/"
"/projects/$projectId/secret-manager/integrations",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/"
),
IntegrationDetailsByIDPage: setRoute(
"/secret-manager/$projectId/integrations/$integrationId",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId"
"/projects/$projectId/secret-manager/integrations/$integrationId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/$integrationId"
),
SecretSyncDetailsByIDPage: setRoute(
"/secret-manager/$projectId/integrations/secret-syncs/$destination/$syncId",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/secret-syncs/$destination/$syncId"
"/projects/$projectId/secret-manager/integrations/secret-syncs/$destination/$syncId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/secret-syncs/$destination/$syncId"
),
Integratons: {
SelectIntegrationAuth: setRoute(
"/secret-manager/$projectId/integrations/select-integration-auth",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/select-integration-auth"
"/projects/$projectId/secret-manager/integrations/select-integration-auth",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/select-integration-auth"
),
HerokuOauthCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/heroku/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/heroku/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/heroku/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/heroku/oauth2/callback"
),
HerokuConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/heroku/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/heroku/create"
"/projects/$projectId/secret-manager/integrations/heroku/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/heroku/create"
),
AwsParameterStoreConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/aws-parameter-store/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/aws-parameter-store/create"
"/projects/$projectId/secret-manager/integrations/aws-parameter-store/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/aws-parameter-store/create"
),
AwsSecretManagerConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/aws-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/aws-secret-manager/create"
"/projects/$projectId/secret-manager/integrations/aws-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/aws-secret-manager/create"
),
AzureAppConfigurationsOauthCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/azure-app-configuration/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/azure-app-configuration/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback"
),
AzureAppConfigurationsConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/azure-app-configuration/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create"
"/projects/$projectId/secret-manager/integrations/azure-app-configuration/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-app-configuration/create"
),
AzureDevopsConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/azure-devops/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/create"
"/projects/$projectId/secret-manager/integrations/azure-devops/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-devops/create"
),
AzureKeyVaultAuthorizePage: setRoute(
"/secret-manager/$projectId/integrations/azure-key-vault/authorize",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize"
"/projects/$projectId/secret-manager/integrations/azure-key-vault/authorize",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/authorize"
),
AzureKeyVaultOauthCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/azure-key-vault/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/azure-key-vault/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
),
AzureKeyVaultConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/azure-key-vault/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create"
"/projects/$projectId/secret-manager/integrations/azure-key-vault/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/create"
),
BitbucketOauthCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/bitbucket/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/bitbucket/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
),
BitbucketConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/bitbucket/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/create"
"/projects/$projectId/secret-manager/integrations/bitbucket/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/bitbucket/create"
),
ChecklyConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/checkly/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/create"
"/projects/$projectId/secret-manager/integrations/checkly/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/checkly/create"
),
CircleConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/circleci/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/circleci/create"
"/projects/$projectId/secret-manager/integrations/circleci/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/circleci/create"
),
CloudflarePagesConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/cloudflare-pages/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-pages/create"
"/projects/$projectId/secret-manager/integrations/cloudflare-pages/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-pages/create"
),
DigitalOceanAppPlatformConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/digital-ocean-app-platform/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/digital-ocean-app-platform/create"
"/projects/$projectId/secret-manager/integrations/digital-ocean-app-platform/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/digital-ocean-app-platform/create"
),
CloudflareWorkersConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/cloudflare-workers/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-workers/create"
"/projects/$projectId/secret-manager/integrations/cloudflare-workers/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-workers/create"
),
CodefreshConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/codefresh/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/codefresh/create"
"/projects/$projectId/secret-manager/integrations/codefresh/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/codefresh/create"
),
GcpSecretManagerConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/gcp-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/create"
"/projects/$projectId/secret-manager/integrations/gcp-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gcp-secret-manager/create"
),
GcpSecretManagerOauthCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/gcp-secret-manager/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/gcp-secret-manager/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback"
),
GithubConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/github/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/github/create"
"/projects/$projectId/secret-manager/integrations/github/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/github/create"
),
GithubOauthCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/github/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/github/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/github/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/github/oauth2/callback"
),
GitlabConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/gitlab/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/gitlab/create"
"/projects/$projectId/secret-manager/integrations/gitlab/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gitlab/create"
),
GitlabOauthCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/gitlab/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/gitlab/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/gitlab/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gitlab/oauth2/callback"
),
VercelOauthCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/vercel/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/vercel/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/vercel/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/vercel/oauth2/callback"
),
VercelConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/vercel/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/vercel/create"
"/projects/$projectId/secret-manager/integrations/vercel/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/vercel/create"
),
FlyioConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/flyio/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/flyio/create"
"/projects/$projectId/secret-manager/integrations/flyio/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/flyio/create"
),
HashicorpVaultConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/hashicorp-vault/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/hashicorp-vault/create"
"/projects/$projectId/secret-manager/integrations/hashicorp-vault/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/hashicorp-vault/create"
),
HasuraCloudConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/hasura-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/hasura-cloud/create"
"/projects/$projectId/secret-manager/integrations/hasura-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/hasura-cloud/create"
),
LaravelForgeConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/laravel-forge/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/laravel-forge/create"
"/projects/$projectId/secret-manager/integrations/laravel-forge/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/laravel-forge/create"
),
NorthflankConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/northflank/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/northflank/create"
"/projects/$projectId/secret-manager/integrations/northflank/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/northflank/create"
),
RailwayConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/railway/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/railway/create"
"/projects/$projectId/secret-manager/integrations/railway/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/railway/create"
),
RenderConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/render/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/render/create"
"/projects/$projectId/secret-manager/integrations/render/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/render/create"
),
RundeckConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/rundeck/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/rundeck/create"
"/projects/$projectId/secret-manager/integrations/rundeck/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/rundeck/create"
),
WindmillConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/windmill/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/windmill/create"
"/projects/$projectId/secret-manager/integrations/windmill/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/windmill/create"
),
TravisCIConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/travisci/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/travisci/create"
"/projects/$projectId/secret-manager/integrations/travisci/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/travisci/create"
),
TerraformCloudConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/terraform-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/terraform-cloud/create"
"/projects/$projectId/secret-manager/integrations/terraform-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/terraform-cloud/create"
),
TeamcityConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/teamcity/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/teamcity/create"
"/projects/$projectId/secret-manager/integrations/teamcity/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/teamcity/create"
),
SupabaseConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/supabase/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/supabase/create"
"/projects/$projectId/secret-manager/integrations/supabase/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/supabase/create"
),
OctopusDeployCloudConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/octopus-deploy/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/octopus-deploy/create"
"/projects/$projectId/secret-manager/integrations/octopus-deploy/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/octopus-deploy/create"
),
DatabricksConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/databricks/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/databricks/create"
"/projects/$projectId/secret-manager/integrations/databricks/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/databricks/create"
),
QoveryConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/qovery/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/qovery/create"
"/projects/$projectId/secret-manager/integrations/qovery/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/qovery/create"
),
Cloud66ConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/cloud-66/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloud-66/create"
"/projects/$projectId/secret-manager/integrations/cloud-66/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloud-66/create"
),
NetlifyConfigurePage: setRoute(
"/secret-manager/$projectId/integrations/netlify/create",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/netlify/create"
"/projects/$projectId/secret-manager/integrations/netlify/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/netlify/create"
),
NetlifyOuathCallbackPage: setRoute(
"/secret-manager/$projectId/integrations/netlify/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/netlify/oauth2/callback"
"/projects/$projectId/secret-manager/integrations/netlify/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/netlify/oauth2/callback"
)
}
},
CertManager: {
CertAuthDetailsByIDPage: setRoute(
"/cert-manager/$projectId/ca/$caName",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caName"
"/projects/$projectId/cert-manager/ca/$caName",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/ca/$caName"
),
SubscribersPage: setRoute(
"/cert-manager/$projectId/subscribers",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"
"/projects/$projectId/cert-manager/subscribers",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers"
),
CertificatesPage: setRoute(
"/cert-manager/$projectId/certificates",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates"
"/projects/$projectId/cert-manager/certificates",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificates"
),
CertificateAuthoritiesPage: setRoute(
"/cert-manager/$projectId/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities"
"/projects/$projectId/cert-manager/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-authorities"
),
AlertingPage: setRoute(
"/cert-manager/$projectId/alerting",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting"
"/projects/$projectId/cert-manager/alerting",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/alerting"
),
PkiCollectionDetailsByIDPage: setRoute(
"/cert-manager/$projectId/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
"/projects/$projectId/cert-manager/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/pki-collections/$collectionId"
),
PkiSubscriberDetailsByIDPage: setRoute(
"/cert-manager/$projectId/subscribers/$subscriberName",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName"
"/projects/$projectId/cert-manager/subscribers/$subscriberName",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/$subscriberName"
)
},
Ssh: {
SshCaByIDPage: setRoute(
"/ssh/$projectId/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/ca/$caId"
"/projects/$projectId/ssh/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout/ca/$caId"
),
SshHostGroupDetailsByIDPage: setRoute(
"/ssh/$projectId/ssh-host-groups/$sshHostGroupId",
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId"
"/projects/$projectId/ssh/ssh-host-groups/$sshHostGroupId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout/ssh-host-groups/$sshHostGroupId"
)
},
SecretScanning: {
DataSourceByIdPage: setRoute(
"/secret-scanning/$projectId/data-sources/$type/$dataSourceId",
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/data-sources/$type/$dataSourceId"
"/projects/$projectId/secret-scanning/data-sources/$type/$dataSourceId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-scanning/_secret-scanning-layout/data-sources/$type/$dataSourceId"
),
FindingsPage: setRoute(
"/secret-scanning/$projectId/findings",
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/findings"
"/projects/$projectId/secret-scanning/findings",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-scanning/_secret-scanning-layout/findings"
)
},
Public: {
+12 -8
View File
@@ -1,6 +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";
import { ProjectType } from "@app/hooks/api/workspace/types";
const secretsToBeAdded = [
{
@@ -42,8 +42,7 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
const {
data: { project }
} = await createWorkspace({
projectName,
type: ProjectType.SecretManager
projectName
});
try {
@@ -60,14 +59,14 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
return project;
};
export const getProjectHomePage = (workspace: Workspace) => {
switch (workspace.type) {
export const getProjectHomePage = (type: ProjectType) => {
switch (type) {
case ProjectType.CertificateManager:
return `/${workspace.type}/$projectId/subscribers` as const;
return `/projects/$projectId/${type}/subscribers` as const;
case ProjectType.SecretScanning:
return `/${workspace.type}/$projectId/data-sources` as const;
return `/projects/$projectId/${type}/data-sources` as const;
default:
return `/${workspace.type}/$projectId/overview` as const;
return `/projects/$projectId/${type}/overview` as const;
}
};
@@ -81,3 +80,8 @@ export const getProjectTitle = (type: ProjectType) => {
};
return titleConvert[type];
};
export const getCurrentProductFromUrl = (location: string) => {
const type = Object.values(ProjectType).find((el) => location.includes(`/${el}`));
return type;
};
@@ -25,8 +25,8 @@ export const accessApprovalKeys = {
requestedBy?: string,
bypassReason?: string
) => [{ projectSlug, envSlug, requestedBy, bypassReason }, "access-approvals-requests"] as const,
getAccessApprovalRequestCount: (projectSlug: string) =>
[{ projectSlug }, "access-approval-request-count"] as const
getAccessApprovalRequestCount: (projectSlug: string, policyId?: string) =>
[{ projectSlug }, "access-approval-request-count", ...(policyId ? [policyId] : [])] as const
};
export const fetchPolicyApprovalCount = async ({
@@ -87,21 +87,22 @@ const fetchApprovalRequests = async ({
}));
};
const fetchAccessRequestsCount = async (projectSlug: string) => {
const fetchAccessRequestsCount = async (projectSlug: string, policyId?: string) => {
const { data } = await apiRequest.get<TAccessRequestCount>(
"/api/v1/access-approvals/requests/count",
{ params: { projectSlug } }
{ params: { projectSlug, policyId } }
);
return data;
};
export const useGetAccessRequestsCount = ({
projectSlug,
policyId,
options = {}
}: TGetAccessApprovalRequestsDTO & TReactQueryOptions) =>
useQuery({
queryKey: accessApprovalKeys.getAccessApprovalRequestCount(projectSlug),
queryFn: () => fetchAccessRequestsCount(projectSlug),
queryKey: accessApprovalKeys.getAccessApprovalRequestCount(projectSlug, policyId),
queryFn: () => fetchAccessRequestsCount(projectSlug, policyId),
...options,
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
});
@@ -147,6 +147,7 @@ export type TCreateAccessRequestDTO = {
export type TGetAccessApprovalRequestsDTO = {
projectSlug: string;
policyId?: string;
envSlug?: string;
authorUserId?: string;
};
@@ -11,5 +11,6 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
[IdentityAuthMethod.OCI_AUTH]: "OCI Auth",
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth",
[IdentityAuthMethod.LDAP_AUTH]: "LDAP Auth",
[IdentityAuthMethod.JWT_AUTH]: "JWT Auth"
[IdentityAuthMethod.JWT_AUTH]: "JWT Auth",
[IdentityAuthMethod.TLS_CERT_AUTH]: "TLS Certificate Auth"
};
+2 -1
View File
@@ -9,7 +9,8 @@ export enum IdentityAuthMethod {
OCI_AUTH = "oci-auth",
OIDC_AUTH = "oidc-auth",
LDAP_AUTH = "ldap-auth",
JWT_AUTH = "jwt-auth"
JWT_AUTH = "jwt-auth",
TLS_CERT_AUTH = "tls-cert-auth"
}
export enum IdentityJwtConfigurationType {
@@ -14,6 +14,7 @@ import {
AddIdentityLdapAuthDTO,
AddIdentityOciAuthDTO,
AddIdentityOidcAuthDTO,
AddIdentityTlsCertAuthDTO,
AddIdentityTokenAuthDTO,
AddIdentityUniversalAuthDTO,
ClientSecretData,
@@ -32,6 +33,7 @@ import {
DeleteIdentityLdapAuthDTO,
DeleteIdentityOciAuthDTO,
DeleteIdentityOidcAuthDTO,
DeleteIdentityTlsCertAuthDTO,
DeleteIdentityTokenAuthDTO,
DeleteIdentityUniversalAuthClientSecretDTO,
DeleteIdentityUniversalAuthDTO,
@@ -46,6 +48,7 @@ import {
IdentityLdapAuth,
IdentityOciAuth,
IdentityOidcAuth,
IdentityTlsCertAuth,
IdentityTokenAuth,
IdentityUniversalAuth,
RevokeTokenDTO,
@@ -60,6 +63,7 @@ import {
UpdateIdentityLdapAuthDTO,
UpdateIdentityOciAuthDTO,
UpdateIdentityOidcAuthDTO,
UpdateIdentityTlsCertAuthDTO,
UpdateIdentityTokenAuthDTO,
UpdateIdentityUniversalAuthDTO,
UpdateTokenIdentityTokenAuthDTO
@@ -655,6 +659,107 @@ export const useDeleteIdentityAliCloudAuth = () => {
});
};
export const useAddIdentityTlsCertAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityTlsCertAuth, object, AddIdentityTlsCertAuthDTO>({
mutationFn: async ({
identityId,
allowedCommonNames,
caCertificate,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const {
data: { identityTlsCertAuth }
} = await apiRequest.post<{ identityTlsCertAuth: IdentityTlsCertAuth }>(
`/api/v1/auth/tls-cert-auth/identities/${identityId}`,
{
allowedCommonNames,
caCertificate,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return identityTlsCertAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
});
}
});
};
export const useUpdateIdentityTlsCertAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityTlsCertAuth, object, UpdateIdentityTlsCertAuthDTO>({
mutationFn: async ({
identityId,
allowedCommonNames,
caCertificate,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const {
data: { identityTlsCertAuth }
} = await apiRequest.patch<{ identityTlsCertAuth: IdentityTlsCertAuth }>(
`/api/v1/auth/tls-cert-auth/identities/${identityId}`,
{
caCertificate,
allowedCommonNames,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return identityTlsCertAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
});
}
});
};
export const useDeleteIdentityTlsCertAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityTlsCertAuth, object, DeleteIdentityTlsCertAuthDTO>({
mutationFn: async ({ identityId }) => {
const {
data: { identityTlsCertAuth }
} = await apiRequest.delete(`/api/v1/auth/tls-cert-auth/identities/${identityId}`);
return identityTlsCertAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
});
}
});
};
export const useUpdateIdentityOidcAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityOidcAuth, object, UpdateIdentityOidcAuthDTO>({
+26 -1
View File
@@ -17,6 +17,7 @@ import {
IdentityMembershipOrg,
IdentityOciAuth,
IdentityOidcAuth,
IdentityTlsCertAuth,
IdentityTokenAuth,
IdentityUniversalAuth,
TSearchIdentitiesDTO
@@ -34,6 +35,8 @@ export const identitiesKeys = {
getIdentityGcpAuth: (identityId: string) => [{ identityId }, "identity-gcp-auth"] as const,
getIdentityOidcAuth: (identityId: string) => [{ identityId }, "identity-oidc-auth"] as const,
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const,
getIdentityTlsCertAuth: (identityId: string) =>
[{ identityId }, "identity-tls-cert-auth"] as const,
getIdentityAliCloudAuth: (identityId: string) =>
[{ identityId }, "identity-alicloud-auth"] as const,
getIdentityOciAuth: (identityId: string) => [{ identityId }, "identity-oci-auth"] as const,
@@ -78,7 +81,8 @@ export const useSearchIdentities = (dto: TSearchIdentitiesDTO) => {
search
});
return data;
}
},
placeholderData: (previousData) => previousData
});
};
@@ -175,6 +179,27 @@ export const useGetIdentityAwsAuth = (
});
};
export const useGetIdentityTlsCertAuth = (
identityId: string,
options?: TReactQueryOptions["options"]
) => {
return useQuery({
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId),
queryFn: async () => {
const {
data: { identityTlsCertAuth }
} = await apiRequest.get<{ identityTlsCertAuth: IdentityTlsCertAuth }>(
`/api/v1/auth/tls-cert-auth/identities/${identityId}`
);
return identityTlsCertAuth;
},
staleTime: 0,
gcTime: 0,
...options,
enabled: Boolean(identityId) && (options?.enabled ?? true)
});
};
export const useGetIdentityOciAuth = (
identityId: string,
options?: TReactQueryOptions["options"]
+42 -1
View File
@@ -51,7 +51,7 @@ export type IdentityMembershipOrg = {
export type IdentityMembership = {
id: string;
identity: Identity;
project: Pick<Workspace, "id" | "name" | "type">;
project: Pick<Workspace, "id" | "name" | "defaultProduct">;
roles: Array<
{
id: string;
@@ -485,6 +485,47 @@ export type DeleteIdentityKubernetesAuthDTO = {
identityId: string;
};
export type IdentityTlsCertAuth = {
identityId: string;
caCertificate: string;
allowedCommonNames: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: IdentityTrustedIp[];
};
export type AddIdentityTlsCertAuthDTO = {
organizationId: string;
identityId: string;
caCertificate: string;
allowedCommonNames?: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
export type UpdateIdentityTlsCertAuthDTO = {
organizationId: string;
identityId: string;
caCertificate: string;
allowedCommonNames?: string | null;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
export type DeleteIdentityTlsCertAuthDTO = {
organizationId: string;
identityId: string;
};
export type CreateIdentityUniversalAuthClientSecretDTO = {
identityId: string;
description?: string;
+2 -2
View File
@@ -154,6 +154,6 @@ export type TOrgIdentitiesList = {
};
export enum OrgIdentityOrderBy {
Name = "name"
// Role = "role"
Name = "name",
Role = "role"
}
@@ -6,17 +6,14 @@ import {
TProjectTemplate,
TProjectTemplateResponse
} from "@app/hooks/api/projectTemplates/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const projectTemplateKeys = {
all: ["project-template"] as const,
list: (projectType?: ProjectType) =>
[...projectTemplateKeys.all, "list", ...(projectType ? [projectType] : [])] as const,
list: () => [...projectTemplateKeys.all, "list"] as const,
byId: (templateId: string) => [...projectTemplateKeys.all, templateId] as const
};
export const useListProjectTemplates = (
type?: ProjectType,
options?: Omit<
UseQueryOptions<
TProjectTemplate[],
@@ -28,11 +25,9 @@ export const useListProjectTemplates = (
>
) => {
return useQuery({
queryKey: projectTemplateKeys.list(type),
queryKey: projectTemplateKeys.list(),
queryFn: async () => {
const { data } = await apiRequest.get<TListProjectTemplates>("/api/v1/project-templates", {
params: { type }
});
const { data } = await apiRequest.get<TListProjectTemplates>("/api/v1/project-templates");
return data.projectTemplates;
},
@@ -1,10 +1,8 @@
import { TProjectRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
export type TProjectTemplate = {
id: string;
name: string;
type: ProjectType;
description?: string;
roles: Pick<TProjectRole, "slug" | "name" | "permissions">[];
environments?: { name: string; slug: string; position: number }[] | null;
@@ -16,7 +14,6 @@ export type TListProjectTemplates = { projectTemplates: TProjectTemplate[] };
export type TProjectTemplateResponse = { projectTemplate: TProjectTemplate };
export type TCreateProjectTemplateDTO = {
type: ProjectType;
name: string;
description?: string;
};
@@ -34,9 +34,10 @@ export const secretApprovalRequestKeys = {
] as const,
detail: ({ id }: Omit<TGetSecretApprovalRequestDetails, "decryptKey">) =>
[{ id }, "secret-approval-request-detail"] as const,
count: ({ workspaceId }: TGetSecretApprovalRequestCount) => [
count: ({ workspaceId, policyId }: TGetSecretApprovalRequestCount) => [
{ workspaceId },
"secret-approval-request-count"
"secret-approval-request-count",
...(policyId ? [policyId] : [])
]
};
@@ -204,10 +205,13 @@ export const useGetSecretApprovalRequestDetails = ({
enabled: Boolean(id) && (options?.enabled ?? true)
});
const fetchSecretApprovalRequestCount = async ({ workspaceId }: TGetSecretApprovalRequestCount) => {
const fetchSecretApprovalRequestCount = async ({
workspaceId,
policyId
}: TGetSecretApprovalRequestCount) => {
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequestCount }>(
"/api/v1/secret-approval-requests/count",
{ params: { workspaceId } }
{ params: { workspaceId, policyId } }
);
return data.approvals;
@@ -215,6 +219,7 @@ const fetchSecretApprovalRequestCount = async ({ workspaceId }: TGetSecretApprov
export const useGetSecretApprovalRequestCount = ({
workspaceId,
policyId,
options = {}
}: TGetSecretApprovalRequestCount & {
options?: Omit<
@@ -228,8 +233,8 @@ export const useGetSecretApprovalRequestCount = ({
>;
}) =>
useQuery({
queryKey: secretApprovalRequestKeys.count({ workspaceId }),
queryKey: secretApprovalRequestKeys.count({ workspaceId, policyId }),
refetchInterval: 15000,
queryFn: () => fetchSecretApprovalRequestCount({ workspaceId }),
queryFn: () => fetchSecretApprovalRequestCount({ workspaceId, policyId }),
enabled: Boolean(workspaceId) && (options?.enabled ?? true)
});
@@ -118,6 +118,7 @@ export type TGetSecretApprovalRequestList = {
export type TGetSecretApprovalRequestCount = {
workspaceId: string;
policyId?: string;
};
export type TGetSecretApprovalRequestDetails = {
@@ -6,6 +6,7 @@ export type TOnePassSync = TRootSecretSync & {
destination: SecretSync.OnePass;
destinationConfig: {
vaultId: string;
valueLabel?: string;
};
connection: {
app: AppConnection.OnePass;
+15 -16
View File
@@ -154,16 +154,14 @@ export const useGetWorkspaceById = (
export const useGetUserWorkspaces = ({
includeRoles,
type = "all",
options = {}
}: {
includeRoles?: boolean;
type?: ProjectType | "all";
options?: { enabled?: boolean };
} = {}) =>
useQuery({
queryKey: workspaceKeys.getAllUserWorkspace(type),
queryFn: () => fetchUserWorkspaces(includeRoles, type),
queryKey: workspaceKeys.getAllUserWorkspace(),
queryFn: () => fetchUserWorkspaces(includeRoles),
...options
});
@@ -257,17 +255,16 @@ export const useCreateWorkspace = () => {
const queryClient = useQueryClient();
return useMutation<{ data: { project: Workspace } }, object, CreateWorkspaceDTO>({
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template, type }) =>
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template }) =>
createWorkspace({
projectName,
projectDescription,
kmsKeyId,
template,
type
template
}),
onSuccess: (dto) => {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: workspaceKeys.getAllUserWorkspace(dto.data.project.type)
queryKey: workspaceKeys.getAllUserWorkspace()
});
}
});
@@ -283,7 +280,8 @@ export const useUpdateProject = () => {
newProjectDescription,
newSlug,
secretSharing,
showSnapshotsLegacy
showSnapshotsLegacy,
defaultProduct
}) => {
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
`/api/v1/workspace/${projectID}`,
@@ -291,6 +289,7 @@ export const useUpdateProject = () => {
name: newProjectName,
description: newProjectDescription,
slug: newSlug,
defaultProduct,
secretSharing,
showSnapshotsLegacy
}
@@ -351,8 +350,8 @@ export const useUpdateWorkspaceVersionLimit = () => {
});
return data.workspace;
},
onSuccess: (dto) => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace(dto.type) });
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
}
});
};
@@ -370,8 +369,8 @@ export const useUpdateWorkspaceAuditLogsRetention = () => {
);
return data.workspace;
},
onSuccess: (dto) => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace(dto.type) });
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
}
});
};
@@ -384,8 +383,8 @@ export const useDeleteWorkspace = () => {
const { data } = await apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
return data.workspace;
},
onSuccess: (dto) => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace(dto.type) });
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
queryClient.invalidateQueries({
queryKey: ["org-admin-projects"]
});
@@ -12,8 +12,7 @@ export const workspaceKeys = {
getWorkspaceMemberships: (orgId: string) => [{ orgId }, "workspace-memberships"],
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
getAllUserWorkspace: (type?: string) =>
type ? ["workspaces", { type }] : (["workspaces"] as const),
getAllUserWorkspace: () => ["workspaces"] as const,
getWorkspaceAuditLogs: (workspaceId: string) =>
[{ workspaceId }, "workspace-audit-logs"] as const,
getWorkspaceUsers: (
+2 -3
View File
@@ -24,7 +24,7 @@ export type Workspace = {
__v: number;
id: string;
name: string;
type: ProjectType;
defaultProduct: ProjectType;
description?: string;
orgId: string;
version: ProjectVersion;
@@ -71,7 +71,6 @@ export type CreateWorkspaceDTO = {
projectDescription?: string;
kmsKeyId?: string;
template?: string;
type: ProjectType;
};
export type UpdateProjectDTO = {
@@ -81,6 +80,7 @@ export type UpdateProjectDTO = {
newSlug?: string;
secretSharing?: boolean;
showSnapshotsLegacy?: boolean;
defaultProduct?: ProjectType;
};
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
@@ -181,7 +181,6 @@ export enum ProjectIdentityOrderBy {
Name = "name"
}
export type TSearchProjectsDTO = {
type?: ProjectType;
name?: string;
limit?: number;
offset?: number;
+1
View File
@@ -1,5 +1,6 @@
export { useDebounce } from "./useDebounce";
export * from "./useGetProjectTypeFromRoute";
export { useLocalStorageState } from "./useLocalStorageState";
export { usePagination } from "./usePagination";
export { usePersistentState } from "./usePersistentState";
export { usePopUp } from "./usePopUp";
@@ -8,15 +8,10 @@ export const useGetProjectTypeFromRoute = () => {
return useMemo(() => {
const segments = location.pathname.split("/");
const type = segments?.[2];
if (!type) return ProjectType.SecretManager;
let type: ProjectType | undefined;
// location of project type can vary in router path, so we need to check all possible values
segments.forEach((segment) => {
if (Object.values(ProjectType).includes(segment as ProjectType))
type = segment as ProjectType;
});
return type;
// second element would be /projects/$projectId/<type>
return Object.values(ProjectType).find((el) => el === type) || ProjectType.SecretManager;
}, [location]);
};
@@ -0,0 +1,70 @@
import { useCallback, useEffect, useSyncExternalStore } from "react";
type SetStateAction<T> = T | ((prevState: T) => T);
const dispatchStorageEvent = (key: string, newValue: string | null): void => {
window.dispatchEvent(new StorageEvent("storage", { key, newValue }));
};
const setLocalStorageItem = (key: string, value: unknown): void => {
const stringifiedValue = JSON.stringify(value);
window.localStorage.setItem(key, stringifiedValue);
dispatchStorageEvent(key, stringifiedValue);
};
const removeLocalStorageItem = (key: string): void => {
window.localStorage.removeItem(key);
dispatchStorageEvent(key, null);
};
const getLocalStorageItem = (key: string): string | null => {
return window.localStorage.getItem(key);
};
const useLocalStorageSubscribe = (callback: (e: StorageEvent) => void) => {
window.addEventListener("storage", callback);
return () => window.removeEventListener("storage", callback);
};
const getLocalStorageServerSnapshot = (): never => {
throw Error("useLocalStorage is a client-only hook");
};
export const useLocalStorageState = <T>(
key: string,
initialValue: T
): [T, (value: SetStateAction<T>) => void] => {
const getSnapshot = () => getLocalStorageItem(key);
const store = useSyncExternalStore(
useLocalStorageSubscribe,
getSnapshot,
getLocalStorageServerSnapshot
);
const setState = useCallback(
(v: SetStateAction<T>): void => {
try {
const nextState =
typeof v === "function" ? (v as (prevState: T) => T)(JSON.parse(store || "null")) : v;
if (nextState === undefined || nextState === null) {
removeLocalStorageItem(key);
} else {
setLocalStorageItem(key, nextState);
}
} catch (e) {
console.warn(e);
}
},
[key, store]
);
useEffect(() => {
if (getLocalStorageItem(key) === null && typeof initialValue !== "undefined") {
setLocalStorageItem(key, initialValue);
}
}, [key, initialValue]);
return [store ? JSON.parse(store) : initialValue, setState];
};
+20 -59
View File
@@ -1,20 +1,8 @@
import { faArrowLeft, faInfo, faQuestion } from "@fortawesome/free-solid-svg-icons";
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useMatchRoute } from "@tanstack/react-router";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Menu,
MenuGroup,
MenuItem
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
const generalTabs = [
{
@@ -69,21 +57,13 @@ export const AdminSidebar = () => {
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div className="flex-grow">
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
<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
</div>
</Link>
<Menu>
<MenuGroup title="General">
{generalTabs.map((tab) => {
const isActive = matchRoute({ to: tab.link, fuzzy: false });
return (
<Link key={tab.link} to={tab.link}>
<MenuItem isSelected={Boolean(isActive)} icon={tab.icon}>
{tab.label}
</MenuItem>
<MenuItem isSelected={Boolean(isActive)}>{tab.label}</MenuItem>
</Link>
);
})}
@@ -93,48 +73,29 @@ export const AdminSidebar = () => {
const isActive = matchRoute({ to: tab.link, fuzzy: false });
return (
<Link key={tab.link} to={tab.link}>
<MenuItem isSelected={Boolean(isActive)} icon={tab.icon}>
{tab.label}
</MenuItem>
<MenuItem isSelected={Boolean(isActive)}>{tab.label}</MenuItem>
</Link>
);
})}
</MenuGroup>
</Menu>
</div>
<div className="relative mb-4 mt-10 flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
Help & Support
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
<DropdownMenuItem key={url as string}>
<a
target="_blank"
rel="noopener noreferrer"
href={String(url)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</a>
</DropdownMenuItem>
))}
{envConfig.PLATFORM_VERSION && (
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Version: {envConfig.PLATFORM_VERSION}
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<FontAwesomeIcon
className="mx-1 inline-block shrink-0"
icon={faChevronLeft}
flip="vertical"
/>
}
>
Back to organization
</MenuItem>
</Link>
</Menu>
</nav>
</aside>
);
@@ -0,0 +1,61 @@
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
export const KmsLayout = () => {
const { currentWorkspace } = useWorkspace();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">KMS</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/kms/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Overview</MenuItem>}
</Link>
<Link
to="/projects/$projectId/kms/kmip"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>KMIP</MenuItem>}
</Link>
{
// <Link
// to="/projects/$projectId/kms/settings"
// params={{
// projectId: currentWorkspace.id
// }}
// >
// {({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
// </Link>
}
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
<Outlet />
</div>
</div>
</div>
);
};
+1
View File
@@ -0,0 +1 @@
export { KmsLayout } from "./KmsLayout";
@@ -1,121 +1,48 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { linkOptions, Outlet, useLocation, useRouterState } from "@tanstack/react-router";
import { AnimatePresence, motion } from "framer-motion";
import { Outlet, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import { Banner } from "@app/components/page-frames/Banner";
import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2";
import { OrgPermissionSubjects, useOrgPermission, useServerConfig } from "@app/context";
import { OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types";
import { useServerConfig } from "@app/context";
import { usePopUp } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar";
import { SidebarHeader } from "./components/SidebarHeader";
import { DefaultSideBar, ProjectOverviewSideBar, SecretSharingSideBar } from "./ProductsSideBar";
import { Navbar } from "./components/NavBar";
import { OrgSidebar } from "./components/OrgSidebar";
export const OrganizationLayout = () => {
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const location = useLocation();
const { config } = useServerConfig();
const { permission } = useOrgPermission();
const shouldShowProductsSidebar = permission.can(
OrgPermissionSecretShareAction.ManageSettings,
OrgPermissionSubjects.SecretShare
);
const isOrganizationSpecificPage = location.pathname.startsWith("/organization");
const breadcrumbs =
isOrganizationSpecificPage && matches && "breadcrumbs" in matches
? matches.breadcrumbs
: undefined;
const projectId = useParams({
strict: false,
select: (el) => el?.projectId
});
const isInsideProject = Boolean(projectId);
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
const { t } = useTranslation();
const isSecretSharingPage = (
[
linkOptions({ to: "/organization/secret-sharing" }).to,
linkOptions({ to: "/organization/secret-sharing/settings" }).to
] as string[]
).includes(location.pathname);
const isProjectOverviewOrSettingsPage = (
[
linkOptions({ to: "/organization/secret-manager/overview" }).to,
linkOptions({ to: "/organization/secret-manager/settings" }).to,
linkOptions({ to: "/organization/cert-manager/overview" }).to,
linkOptions({ to: "/organization/cert-manager/settings" }).to,
linkOptions({ to: "/organization/kms/overview" }).to,
linkOptions({ to: "/organization/kms/settings" }).to,
linkOptions({ to: "/organization/ssh/overview" }).to,
linkOptions({ to: "/organization/ssh/settings" }).to,
linkOptions({ to: "/organization/secret-scanning/overview" }).to,
linkOptions({ to: "/organization/secret-scanning/settings" }).to
] as string[]
).includes(location.pathname);
const shouldShowOrgSidebar =
location.pathname.startsWith("/organization") &&
(!isSecretSharingPage || shouldShowProductsSidebar);
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
let SideBarComponent = <DefaultSideBar />;
if (isSecretSharingPage) {
SideBarComponent = <SecretSharingSideBar />;
} else if (isProjectOverviewOrSettingsPage) {
SideBarComponent = (
<ProjectOverviewSideBar type={location.pathname.split("/")[2] as ProjectType} />
);
}
return (
<>
<Banner />
<div
className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden bg-bunker-800 transition-all md:flex`}
>
<Navbar />
{!window.isSecureContext && <InsecureConnectionBanner />}
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<MinimizedOrgSidebar />
<AnimatePresence mode="popLayout">
{shouldShowOrgSidebar && (
<motion.div
key="menu-list-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-60 overflow-hidden border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
{!isProjectOverviewOrSettingsPage && (
<div className="p-2 pt-3">
<SidebarHeader />
</div>
)}
{SideBarComponent}
</nav>
</motion.div>
)}
</AnimatePresence>
<OrgSidebar isHidden={isInsideProject} />
<main
className={twMerge(
"flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 px-4 pb-4 dark:[color-scheme:dark]",
!isOrganizationSpecificPage && "overflow-hidden p-0"
"flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 px-4 pb-4 pt-8 dark:[color-scheme:dark]",
isInsideProject && "p-0"
)}
>
{breadcrumbs ? (
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} />
) : null}
<Outlet />
</main>
</div>
@@ -1,61 +0,0 @@
import { Link } from "@tanstack/react-router";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
export const DefaultSideBar = () => (
<Menu>
<MenuGroup title="Organization Control">
<Link to="/organization/access-management">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="groups">
Access Control
</MenuItem>
)}
</Link>
<Link to="/organization/app-connections">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="jigsaw-puzzle">
App Connections
</MenuItem>
)}
</Link>
<Link to="/organization/gateways">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="gateway" iconMode="reverse">
Gateways
</MenuItem>
)}
</Link>
<Link to="/organization/sso">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="check">
Single Sign-On (SSO)
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Other">
<Link to="/organization/audit-logs">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="moving-block">
Audit Logs
</MenuItem>
)}
</Link>
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="spinning-coin">
Usage & Billing
</MenuItem>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Organization Settings
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
);
@@ -1,99 +0,0 @@
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useMatchRoute } from "@tanstack/react-router";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { ProjectType } from "@app/hooks/api/workspace/types";
type TProjectOverviewSideBarProps = {
type: ProjectType;
};
export const ProjectOverviewSideBar = ({ type }: TProjectOverviewSideBarProps) => {
const matchRoute = useMatchRoute();
const isOverviewActive = !!matchRoute({
to: `/organization/${type}/overview`,
fuzzy: false
});
let label: string;
let icon: string;
let link: string;
switch (type) {
case ProjectType.CertificateManager:
label = "Cert Management";
icon = "note";
link = "https://infisical.com/docs/documentation/platform/pki/overview";
break;
case ProjectType.SecretManager:
label = "Secret Management";
icon = "sliding-carousel";
link = "https://infisical.com/docs/documentation/getting-started/introduction";
break;
case ProjectType.KMS:
label = "KMS";
icon = "unlock";
link = "https://infisical.com/docs/documentation/platform/kms/overview";
break;
case ProjectType.SSH:
label = "SSH";
icon = "verified";
link = "https://infisical.com/docs/documentation/platform/ssh/overview";
break;
case ProjectType.SecretScanning:
label = "Secret Scanning";
icon = "secret-scan";
link = "https://infisical.com/docs/documentation/platform/secret-scanning/overview";
break;
default:
throw new Error("Unknown project type");
}
return (
<>
<div className="p-2 pt-3">
<a
target="_blank"
rel="noopener noreferrer"
href={link}
className="flex w-full items-center rounded-md border border-mineshaft-600 p-2 pl-3 transition-all duration-150 hover:bg-mineshaft-700"
>
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-mineshaft-500 p-4">
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="h-3.5 w-3.5 text-mineshaft-300"
/>
</div>
<div className="-mt-1 flex flex-grow flex-col text-white">
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
Infisical Docs
</div>
<div className="text-xs leading-[10px] text-mineshaft-400">
{type === ProjectType.SecretManager ? "Get Started" : `${label} Overview`}
</div>
</div>
</a>
</div>
<Menu>
<MenuGroup title="Overview">
<Link to={`/organization/${type}/overview`}>
<MenuItem isSelected={isOverviewActive} icon={icon}>
{label}
</MenuItem>
</Link>
</MenuGroup>
<MenuGroup title="Other">
<Link to={`/organization/${type}/settings`}>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Settings
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</>
);
};
@@ -1,34 +0,0 @@
import { Link, useMatchRoute } from "@tanstack/react-router";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
export const SecretSharingSideBar = () => {
const matchRoute = useMatchRoute();
const isOverviewActive = !!matchRoute({
to: "/organization/secret-sharing",
fuzzy: false
});
return (
<Menu>
<MenuGroup title="Overview">
<Link to="/organization/secret-sharing">
{() => (
<MenuItem isSelected={isOverviewActive} icon="lock-closed">
Secret Sharing
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Other">
<Link to="/organization/secret-sharing/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Settings
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
);
};
@@ -1,3 +0,0 @@
export * from "./DefaultSideBar";
export * from "./ProjectOverviewSideBar";
export * from "./SecretSharingSideBar";
@@ -1,641 +0,0 @@
import { useState } from "react";
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import {
faArrowUpRightFromSquare,
faBook,
faCheck,
faCheckCircle,
faCog,
faDoorClosed,
faEnvelope,
faInfinity,
faInfo,
faInfoCircle,
faMoneyBill,
faPlug,
faSignOut,
faUser,
faUserCog,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQueryClient } from "@tanstack/react-query";
import { Link, linkOptions, useLocation, useNavigate, useRouter } from "@tanstack/react-router";
import { Mfa } from "@app/components/auth/Mfa";
import { createNotification } from "@app/components/notifications";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import SecurityClient from "@app/components/utilities/SecurityClient";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
Modal,
ModalContent,
Tooltip
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { useOrganization, useSubscription, useUser } from "@app/context";
import { isInfisicalCloud } from "@app/helpers/platform";
import { usePopUp, useToggle } from "@app/hooks";
import {
useGetOrganizations,
useGetOrgTrialUrl,
useLogoutUser,
useSelectOrganization,
workspaceKeys
} from "@app/hooks/api";
import { authKeys } from "@app/hooks/api/auth/queries";
import { MfaMethod } from "@app/hooks/api/auth/types";
import { getAuthToken } from "@app/hooks/api/reactQuery";
import { SubscriptionPlan } from "@app/hooks/api/types";
import { AuthMethod } from "@app/hooks/api/users/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
import { MenuIconButton } from "../MenuIconButton";
import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel";
const getPlan = (subscription: SubscriptionPlan) => {
if (subscription.groups) return "Enterprise Plan";
if (subscription.pitRecovery) return "Pro Plan";
return "Free Plan";
};
export const INFISICAL_SUPPORT_OPTIONS = [
[
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
"Support Forum",
"https://infisical.com/slack"
],
[
<FontAwesomeIcon key={2} className="pr-4 text-sm" icon={faBook} />,
"Read Docs",
"https://infisical.com/docs/documentation/getting-started/introduction"
],
[
<FontAwesomeIcon key={3} className="pr-4 text-sm" icon={faGithub} />,
"GitHub Issues",
"https://github.com/Infisical/infisical/issues"
],
[
<FontAwesomeIcon key={4} className="pr-4 text-sm" icon={faEnvelope} />,
"Email Support",
"mailto:[email protected]"
],
[
<FontAwesomeIcon key={5} className="pr-4 text-sm" icon={faUsers} />,
"Instance Admins",
"server-admins"
]
];
export const MinimizedOrgSidebar = () => {
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
const { subscription } = useSubscription();
const [open, setOpen] = useState(false);
const [openSupport, setOpenSupport] = useState(false);
const [openUser, setOpenUser] = useState(false);
const [openOrg, setOpenOrg] = useState(false);
const [showAdminsModal, setShowAdminsModal] = useState(false);
const { user } = useUser();
const { mutateAsync } = useGetOrgTrialUrl();
const { currentOrg } = useOrganization();
const { data: orgs } = useGetOrganizations();
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
const { mutateAsync: selectOrganization } = useSelectOrganization();
const navigate = useNavigate();
const router = useRouter();
const location = useLocation();
const queryClient = useQueryClient();
const isMoreSelected = (
[
linkOptions({ to: "/organization/access-management" }).to,
linkOptions({ to: "/organization/app-connections" }).to,
linkOptions({ to: "/organization/billing" }).to,
linkOptions({ to: "/organization/sso" }).to,
linkOptions({ to: "/organization/gateways" }).to,
linkOptions({ to: "/organization/settings" }).to,
linkOptions({ to: "/organization/audit-logs" }).to
] as string[]
).includes(location.pathname);
const handleOrgChange = async (orgId: string) => {
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
queryClient.removeQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
const { token, isMfaEnabled, mfaMethod } = await selectOrganization({
organizationId: orgId
});
if (isMfaEnabled) {
SecurityClient.setMfaToken(token);
if (mfaMethod) {
setRequiredMfaMethod(mfaMethod);
}
toggleShowMfa.on();
setMfaSuccessCallback(() => () => handleOrgChange(orgId));
return;
}
await router.invalidate();
await navigateUserToOrg(navigate, orgId);
};
const logout = useLogoutUser();
const logOutUser = async () => {
try {
console.log("Logging out...");
await logout.mutateAsync();
navigate({ to: "/login" });
} catch (error) {
console.error(error);
}
};
const handleCopyToken = async () => {
try {
await window.navigator.clipboard.writeText(getAuthToken());
createNotification({ type: "success", text: "Copied current login session token to clipboard" });
} catch (error) {
console.log(error);
createNotification({ type: "error", text: "Failed to copy user token to clipboard" });
}
};
if (shouldShowMfa) {
return (
<div className="flex max-h-screen min-h-screen flex-col items-center justify-center gap-2 overflow-y-auto bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700">
<Mfa
email={user.email as string}
method={requiredMfaMethod}
successCallback={mfaSuccessCallback}
closeMfa={() => toggleShowMfa.off()}
/>
</div>
);
}
return (
<>
<aside
className="dark z-10 border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 transition-all duration-150"
style={{ width: "72px" }}
>
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div>
<div className="flex items-center hover:bg-mineshaft-700">
<DropdownMenu open={openOrg} onOpenChange={setOpenOrg} modal={false}>
<DropdownMenuTrigger
onMouseEnter={() => setOpenOrg(true)}
onMouseLeave={() => setOpenOrg(false)}
asChild
>
<div className="flex w-full items-center justify-center rounded-md border border-none border-mineshaft-600 p-3 pb-5 pt-6 transition-all">
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-primary">
{currentOrg?.name.charAt(0)}
</div>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
onMouseEnter={() => setOpenOrg(true)}
onMouseLeave={() => setOpenOrg(false)}
align="start"
side="right"
className="mt-6 cursor-default p-1 shadow-mineshaft-600 drop-shadow-md"
style={{ minWidth: "220px" }}
>
<div className="px-0.5 py-1">
<div className="flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-gradient-to-tr from-primary-500/5 to-mineshaft-800 p-1 transition-all duration-300">
<div className="mr-2 flex h-8 w-8 items-center justify-center rounded-md bg-primary text-black">
{currentOrg?.name.charAt(0)}
</div>
<div className="flex flex-grow flex-col text-white">
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
{currentOrg?.name}
</div>
<div className="text-xs text-mineshaft-400">{getPlan(subscription)}</div>
</div>
</div>
</div>
<div className="px-2 py-1 text-xs capitalize text-mineshaft-400">
organizations
</div>
{orgs?.map((org) => {
return (
<DropdownMenuItem key={org.id}>
<Button
onClick={async () => {
if (currentOrg?.id === org.id) return;
if (org.authEnforced) {
// org has an org-level auth method enabled (e.g. SAML)
// -> logout + redirect to SAML SSO
await logout.mutateAsync();
if (org.orgAuthMethod === AuthMethod.OIDC) {
window.open(`/api/v1/sso/oidc/login?orgSlug=${org.slug}`);
} else {
window.open(`/api/v1/sso/redirect/saml2/organizations/${org.slug}`);
}
window.close();
return;
}
handleOrgChange(org?.id);
}}
variant="plain"
colorSchema="secondary"
size="xs"
className="flex w-full items-center justify-start p-0 font-normal"
leftIcon={
currentOrg?.id === org.id && (
<FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
)
}
>
<div className="flex w-full max-w-[150px] items-center justify-between truncate">
{org.name}
</div>
</Button>
</DropdownMenuItem>
);
})}
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<DropdownMenuItem
icon={<FontAwesomeIcon icon={faSignOut} />}
onClick={logOutUser}
>
Log Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="space-y-1">
{currentOrg.secretsProductEnabled && (
<Link to="/organization/secret-manager/overview">
{({ isActive }) => (
<MenuIconButton
isSelected={
isActive ||
window.location.pathname.startsWith(
`/organization/${ProjectType.SecretManager}`
)
}
icon="sliding-carousel"
>
Secrets
</MenuIconButton>
)}
</Link>
)}
{currentOrg.pkiProductEnabled && (
<Link to="/organization/cert-manager/overview">
{({ isActive }) => (
<MenuIconButton
isSelected={
isActive ||
window.location.pathname.startsWith(
`/organization/${ProjectType.CertificateManager}`
)
}
icon="note"
>
PKI
</MenuIconButton>
)}
</Link>
)}
{currentOrg.kmsProductEnabled && (
<Link to="/organization/kms/overview">
{({ isActive }) => (
<MenuIconButton
isSelected={
isActive ||
window.location.pathname.startsWith(`/organization/${ProjectType.KMS}`)
}
icon="unlock"
>
KMS
</MenuIconButton>
)}
</Link>
)}
{currentOrg.sshProductEnabled && (
<Link to="/organization/ssh/overview">
{({ isActive }) => (
<MenuIconButton
isSelected={
isActive ||
window.location.pathname.startsWith(`/organization/${ProjectType.SSH}`)
}
icon="verified"
>
SSH
</MenuIconButton>
)}
</Link>
)}
{currentOrg.scannerProductEnabled && (
<Link to="/organization/secret-scanning/overview">
{({ isActive }) => (
<MenuIconButton
isSelected={
isActive ||
window.location.pathname.startsWith(
`/organization/${ProjectType.SecretScanning}`
)
}
icon="secret-scan"
>
Scanner
</MenuIconButton>
)}
</Link>
)}
{(currentOrg.scannerProductEnabled || currentOrg.shareSecretsProductEnabled) && (
<div className="w-full bg-mineshaft-500" style={{ height: "1px" }} />
)}
{currentOrg.shareSecretsProductEnabled && (
<Link to="/organization/secret-sharing">
{({ isActive }) => (
<MenuIconButton isSelected={isActive} icon="lock-closed">
Share
</MenuIconButton>
)}
</Link>
)}
<div className="my-1 w-full bg-mineshaft-500" style={{ height: "1px" }} />
<DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
<DropdownMenuTrigger
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
asChild
>
<div className="w-full">
<MenuIconButton
lottieIconMode="reverse"
icon="settings-cog"
isSelected={isMoreSelected}
>
Admin
</MenuIconButton>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
align="start"
side="right"
className="p-1"
>
<DropdownMenuLabel>Organization Options</DropdownMenuLabel>
<Link to="/organization/access-management">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faUsers} />}>
Access Control
</DropdownMenuItem>
</Link>
<Link to="/organization/app-connections">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faPlug} />}>
App Connections
</DropdownMenuItem>
</Link>
<Link to="/organization/gateways">
<DropdownMenuItem
icon={<FontAwesomeIcon className="w-3" icon={faDoorClosed} />}
>
Gateways
</DropdownMenuItem>
</Link>
<Link to="/organization/billing">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faMoneyBill} />}>
Usage & Billing
</DropdownMenuItem>
</Link>
<Link to="/organization/audit-logs">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faBook} />}>
Audit Logs
</DropdownMenuItem>
</Link>
<Link to="/organization/sso">
<DropdownMenuItem
icon={<FontAwesomeIcon className="w-3" icon={faCheckCircle} />}
>
SSO Settings
</DropdownMenuItem>
</Link>
<Link to="/organization/settings">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faCog} />}>
Organization Settings
</DropdownMenuItem>
</Link>
<DropdownMenuLabel>Admin Panels</DropdownMenuLabel>
{user?.superAdmin && (
<Link to="/admin">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faUserCog} />}>
Server Admin Console
</DropdownMenuItem>
</Link>
)}
<Link to="/organization/admin">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faCog} />}>
Organization Admin Console
</DropdownMenuItem>
</Link>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div
className={`relative mt-10 ${
subscription && subscription.slug === "starter" && !subscription.has_used_trial
? "mb-2"
: "mb-4"
} flex w-full cursor-default flex-col items-center px-1 text-sm text-mineshaft-400`}
>
<DropdownMenu open={openSupport} onOpenChange={setOpenSupport} modal={false}>
<DropdownMenuTrigger
onMouseEnter={() => setOpenSupport(true)}
onMouseLeave={() => setOpenSupport(false)}
className="w-full"
>
<MenuIconButton>
<FontAwesomeIcon icon={faInfoCircle} className="mb-3 text-lg" />
Support
</MenuIconButton>
</DropdownMenuTrigger>
<DropdownMenuContent
onMouseEnter={() => setOpenSupport(true)}
onMouseLeave={() => setOpenSupport(false)}
align="end"
side="right"
className="p-1"
>
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => {
if (url === "server-admins" && isInfisicalCloud()) {
return null;
}
return (
<DropdownMenuItem key={url as string}>
{url === "server-admins" ? (
<button
type="button"
onClick={() => setShowAdminsModal(true)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</button>
) : (
<a
target="_blank"
rel="noopener noreferrer"
href={String(url)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</a>
)}
</DropdownMenuItem>
);
})}
{envConfig.PLATFORM_VERSION && (
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Version: {envConfig.PLATFORM_VERSION}
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
<Tooltip content="Start Free Pro Trial" side="right">
<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="mt-1.5 w-full"
>
<div className="justify-left mb-1.5 mt-1.5 flex w-full flex-col items-center rounded-md p-1 text-xs text-mineshaft-300 transition-all duration-150 hover:bg-mineshaft-500 hover:text-primary-400">
<FontAwesomeIcon icon={faInfinity} className="py-2 text-lg text-primary" />
Pro Trial
</div>
</button>
</Tooltip>
)}
<DropdownMenu open={openUser} onOpenChange={setOpenUser} modal={false}>
<DropdownMenuTrigger
onMouseEnter={() => setOpenUser(true)}
onMouseLeave={() => setOpenUser(false)}
className="w-full"
asChild
>
<div>
<MenuIconButton icon="user">User</MenuIconButton>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
onMouseEnter={() => setOpenUser(true)}
onMouseLeave={() => setOpenUser(false)}
side="right"
align="end"
className="p-1"
>
<div className="cursor-default px-1 py-1">
<div className="flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-gradient-to-tr from-primary-500/10 to-mineshaft-800 p-1 px-2 transition-all duration-150">
<div className="p-1 pr-3">
<FontAwesomeIcon icon={faUser} className="text-xl text-mineshaft-400" />
</div>
<div className="flex flex-grow flex-col text-white">
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
{user?.firstName} {user?.lastName}
</div>
<div className="text-xs text-mineshaft-300">{user.email}</div>
</div>
</div>
</div>
<Link to="/personal-settings">
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
</Link>
<a
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Documentation
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
<a
href="https://infisical.com/slack"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Join Slack Community
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<DropdownMenuItem onClick={handleCopyToken}>
Copy Token
<Tooltip
content="This token is linked to your current login session and can only access resources within the organization you're currently logged into."
className="max-w-3xl"
>
<FontAwesomeIcon icon={faInfoCircle} className="mb-[0.06rem] pl-1.5 text-xs" />
</Tooltip>
</DropdownMenuItem>
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<DropdownMenuItem onClick={logOutUser} icon={<FontAwesomeIcon icon={faSignOut} />}>
Log Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</nav>
</aside>
<Modal isOpen={showAdminsModal} onOpenChange={setShowAdminsModal}>
<ModalContent title="Server Administrators" subTitle="View all server administrators">
<div className="mb-2">
<ServerAdminsPanel />
</div>
</ModalContent>
</Modal>
<CreateOrgModal
isOpen={popUp?.createOrg?.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)}
/>
</>
);
};
@@ -1 +0,0 @@
export { MinimizedOrgSidebar } from "./MinimizedOrgSidebar";
@@ -0,0 +1,378 @@
import { useState } from "react";
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import { faCircleQuestion, faUserCircle } from "@fortawesome/free-regular-svg-icons";
import {
faArrowUpRightFromSquare,
faBook,
faCheck,
faCubes,
faEnvelope,
faInfo,
faInfoCircle,
faSignOut,
faSort,
faUser,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQueryClient } from "@tanstack/react-query";
import { Link, useNavigate, useRouter, useRouterState } from "@tanstack/react-router";
import { Mfa } from "@app/components/auth/Mfa";
import { createNotification } from "@app/components/notifications";
import SecurityClient from "@app/components/utilities/SecurityClient";
import {
BreadcrumbContainer,
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
IconButton,
Modal,
ModalContent,
TBreadcrumbFormat,
Tooltip
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { useOrganization, useSubscription, useUser } from "@app/context";
import { isInfisicalCloud } from "@app/helpers/platform";
import { useToggle } from "@app/hooks";
import { useGetOrganizations, useLogoutUser, workspaceKeys } from "@app/hooks/api";
import { authKeys, selectOrganization } from "@app/hooks/api/auth/queries";
import { MfaMethod } from "@app/hooks/api/auth/types";
import { getAuthToken } from "@app/hooks/api/reactQuery";
import { SubscriptionPlan } from "@app/hooks/api/types";
import { AuthMethod } from "@app/hooks/api/users/types";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel";
const getPlan = (subscription: SubscriptionPlan) => {
if (subscription.groups) return "Enterprise";
if (subscription.pitRecovery) return "Pro";
return "Free";
};
export const INFISICAL_SUPPORT_OPTIONS = [
[
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
"Support Forum",
"https://infisical.com/slack"
],
[
<FontAwesomeIcon key={2} className="pr-4 text-sm" icon={faBook} />,
"Read Docs",
"https://infisical.com/docs/documentation/getting-started/introduction"
],
[
<FontAwesomeIcon key={3} className="pr-4 text-sm" icon={faGithub} />,
"GitHub Issues",
"https://github.com/Infisical/infisical/issues"
],
[
<FontAwesomeIcon key={4} className="pr-4 text-sm" icon={faEnvelope} />,
"Email Support",
"mailto:[email protected]"
],
[
<FontAwesomeIcon key={5} className="pr-4 text-sm" icon={faUsers} />,
"Instance Admins",
"server-admins"
]
];
export const Navbar = () => {
const { user } = useUser();
const { subscription } = useSubscription();
const { currentOrg } = useOrganization();
const [showAdminsModal, setShowAdminsModal] = useState(false);
const { data: orgs } = useGetOrganizations();
const navigate = useNavigate();
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
const router = useRouter();
const queryClient = useQueryClient();
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
const handleOrgChange = async (orgId: string) => {
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
queryClient.removeQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
const { token, isMfaEnabled, mfaMethod } = await selectOrganization({
organizationId: orgId
});
if (isMfaEnabled) {
SecurityClient.setMfaToken(token);
if (mfaMethod) {
setRequiredMfaMethod(mfaMethod);
}
toggleShowMfa.on();
setMfaSuccessCallback(() => () => handleOrgChange(orgId));
return;
}
await router.invalidate();
await navigateUserToOrg(navigate, orgId);
};
const logout = useLogoutUser();
const logOutUser = async () => {
try {
console.log("Logging out...");
await logout.mutateAsync();
navigate({ to: "/login" });
} catch (error) {
console.error(error);
}
};
const handleCopyToken = async () => {
try {
await window.navigator.clipboard.writeText(getAuthToken());
createNotification({
type: "success",
text: "Copied current login session token to clipboard"
});
} catch (error) {
console.log(error);
createNotification({ type: "error", text: "Failed to copy user token to clipboard" });
}
};
if (shouldShowMfa) {
return (
<div className="flex max-h-screen min-h-screen flex-col items-center justify-center gap-2 overflow-y-auto bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700">
<Mfa
email={user.email as string}
method={requiredMfaMethod}
successCallback={mfaSuccessCallback}
closeMfa={() => toggleShowMfa.off()}
/>
</div>
);
}
return (
<div className="z-10 flex h-12 items-center border-b border-mineshaft-600 bg-mineshaft-800 px-4 py-2">
<div>
<Link to="/organization/projects">
<img alt="infisical logo" src="/images/logotransparent.png" className="h-4" />
</Link>
</div>
<div className="flex items-center">
<p className="pl-2 pr-3 text-lg text-mineshaft-400/70">/</p>
<DropdownMenu modal={false}>
<Link to="/organization/projects">
<div className="flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary-400">
<div>
<FontAwesomeIcon icon={faCubes} className="pr-1 text-xs" />
</div>
<div className="max-w-32 overflow-hidden text-ellipsis">{currentOrg?.name}</div>
<div className="mr-2 rounded border border-mineshaft-500 px-1 text-xs text-bunker-300">
{getPlan(subscription)}
</div>
</div>
</Link>
<DropdownMenuTrigger asChild>
<div>
<IconButton
variant="plain"
colorSchema="secondary"
ariaLabel="switch-org"
className="rounded-md px-2 py-2 hover:bg-mineshaft-600"
>
<FontAwesomeIcon icon={faSort} className="text-xs text-bunker-300" />
</IconButton>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
side="bottom"
className="mt-6 cursor-default p-1 shadow-mineshaft-600 drop-shadow-md"
style={{ minWidth: "220px" }}
>
<div className="px-2 py-1 text-xs capitalize text-mineshaft-400">organizations</div>
{orgs?.map((org) => {
return (
<DropdownMenuItem key={org.id}>
<Button
onClick={async () => {
if (currentOrg?.id === org.id) return;
if (org.authEnforced) {
// org has an org-level auth method enabled (e.g. SAML)
// -> logout + redirect to SAML SSO
await logout.mutateAsync();
if (org.orgAuthMethod === AuthMethod.OIDC) {
window.open(`/api/v1/sso/oidc/login?orgSlug=${org.slug}`);
} else {
window.open(`/api/v1/sso/redirect/saml2/organizations/${org.slug}`);
}
window.close();
return;
}
handleOrgChange(org?.id);
}}
variant="plain"
colorSchema="secondary"
size="xs"
className="flex w-full items-center justify-start p-0 font-normal"
leftIcon={
currentOrg?.id === org.id && (
<FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
)
}
>
<div className="flex w-full max-w-[150px] items-center justify-between truncate">
{org.name}
</div>
</Button>
</DropdownMenuItem>
);
})}
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<DropdownMenuItem icon={<FontAwesomeIcon icon={faSignOut} />} onClick={logOutUser}>
Log Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<p className="px-2 text-lg text-mineshaft-400/70">/</p>
</div>
<div className="pl-2">
{breadcrumbs ? (
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} />
) : null}
</div>
<div className="flex-grow" />
<DropdownMenu modal={false}>
<DropdownMenuTrigger>
<div className="rounded-l-md border border-r-0 border-mineshaft-500 px-2 py-1 hover:bg-mineshaft-600">
<FontAwesomeIcon icon={faCircleQuestion} className="text-mineshaft-200" />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="bottom" className="mt-3 p-1">
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => {
if (url === "server-admins" && isInfisicalCloud()) {
return null;
}
return (
<DropdownMenuItem key={url as string}>
{url === "server-admins" ? (
<button
type="button"
onClick={() => setShowAdminsModal(true)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</button>
) : (
<a
target="_blank"
rel="noopener noreferrer"
href={String(url)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</a>
)}
</DropdownMenuItem>
);
})}
{envConfig.PLATFORM_VERSION && (
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Version: {envConfig.PLATFORM_VERSION}
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<div className="rounded-r-md border border-mineshaft-500 px-2 py-1 hover:bg-mineshaft-600">
<FontAwesomeIcon icon={faUserCircle} className="text-mineshaft-200" />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" className="mt-3 p-1">
<div className="cursor-default px-1 py-1">
<div className="flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-gradient-to-tr from-primary-500/10 to-mineshaft-800 p-1 px-2 transition-all duration-150">
<div className="p-1 pr-3">
<FontAwesomeIcon icon={faUser} className="text-xl text-mineshaft-400" />
</div>
<div className="flex flex-grow flex-col text-white">
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
{user?.firstName} {user?.lastName}
</div>
<div className="text-xs text-mineshaft-300">{user.email}</div>
</div>
</div>
</div>
<Link to="/personal-settings">
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
</Link>
<a
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Documentation
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
<a
href="https://infisical.com/slack"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Join Slack Community
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<DropdownMenuItem onClick={handleCopyToken}>
Copy Token
<Tooltip
content="This token is linked to your current login session and can only access resources within the organization you're currently logged into."
className="max-w-3xl"
>
<FontAwesomeIcon icon={faInfoCircle} className="pl-1.5 text-xs" />
</Tooltip>
</DropdownMenuItem>
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<DropdownMenuItem onClick={logOutUser} icon={<FontAwesomeIcon icon={faSignOut} />}>
Log Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Modal isOpen={showAdminsModal} onOpenChange={setShowAdminsModal}>
<ModalContent title="Server Administrators" subTitle="View all server administrators">
<div className="mb-2">
<ServerAdminsPanel />
</div>
</ModalContent>
</Modal>
</div>
);
};
@@ -0,0 +1 @@
export { Navbar } from "./Navbar";
@@ -0,0 +1,237 @@
import {
faBook,
faCheckCircle,
faCog,
faCubes,
faDoorClosed,
faInfinity,
faMoneyBill,
faPlug,
faShare,
faUserCog,
faUsers,
faUserTie
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "@tanstack/react-router";
import { AnimatePresence, motion } from "framer-motion";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import { Menu, MenuGroup, MenuItem, Tooltip } from "@app/components/v2";
import { useOrganization, useSubscription, useUser } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useGetOrgTrialUrl } from "@app/hooks/api";
type Props = {
isHidden?: boolean;
};
export const OrgSidebar = ({ isHidden }: Props) => {
const { subscription } = useSubscription();
const { user } = useUser();
const { mutateAsync } = useGetOrgTrialUrl();
const { currentOrg } = useOrganization();
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
return (
<>
<AnimatePresence mode="popLayout">
{!isHidden && (
<motion.aside
key="org-sidebar"
transition={{ duration: 0.3 }}
initial={{ opacity: 0, translateX: -240 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -240 }}
layout
className="dark z-10 w-60 border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-800 to-mineshaft-900"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<Menu>
<MenuGroup title="Overview">
<Link to="/organization/projects">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCubes} />
</div>
Projects
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/access-management">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Access Control
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faMoneyBill} className="mr-4" />
</div>
Usage & Billing
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/audit-logs">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} className="mr-4" />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/sso">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCheckCircle} className="mr-4" />
</div>
SSO Settings
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} className="mr-4" />
</div>
Organization Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Resources">
<Link to="/organization/app-connections">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faPlug} className="mr-4" />
</div>
App Connections
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/gateways">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faDoorClosed} className="mr-4" />
</div>
Gateways
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
<div className="flex-grow" />
<Menu>
{subscription &&
subscription.slug === "starter" &&
!subscription.has_used_trial && (
<Tooltip content="Start Free Pro Trial">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<FontAwesomeIcon
className="mx-1 inline-block shrink-0"
icon={faInfinity}
/>
}
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;
}}
>
Pro Trial
</MenuItem>
</Tooltip>
)}
<Link to="/organization/secret-sharing">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faShare} />
</div>
}
>
Share Secret
</MenuItem>
</Link>
<Link to="/organization/admin">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faUserCog} />
</div>
}
>
Organization Admin
</MenuItem>
</Link>
{user.superAdmin && (
<Link to="/admin">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon
className="mx-1 inline-block shrink-0"
icon={faUserTie}
/>
</div>
}
>
Server Console
</MenuItem>
</Link>
)}
</Menu>
</nav>
</motion.aside>
)}
</AnimatePresence>
<CreateOrgModal
isOpen={popUp?.createOrg?.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)}
/>
</>
);
};
@@ -0,0 +1 @@
export { OrgSidebar } from "./OrgSidebar";
@@ -11,10 +11,9 @@ import {
DropdownMenuTrigger
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar";
import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/NavBar/Navbar";
export const PersonalSettingsLayout = () => {
const { t } = useTranslation();
@@ -27,7 +26,7 @@ export const PersonalSettingsLayout = () => {
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div className="flex-grow">
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
<Link to="/organization/projects">
<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
@@ -0,0 +1,103 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
export const PkiManagerLayout = () => {
const { currentWorkspace } = useWorkspace();
const { t } = useTranslation();
return (
<>
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
PKI Manager
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/cert-manager/subscribers"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Subscribers</MenuItem>}
</Link>
<Link
to="/projects/$projectId/cert-manager/certificate-templates"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}> Certificate Templates</MenuItem>
)}
</Link>
<Link
to="/projects/$projectId/cert-manager/certificates"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}> Certificates</MenuItem>}
</Link>
<Link
to="/projects/$projectId/cert-manager/certificate-authorities"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}> Certificates Authorities</MenuItem>
)}
</Link>
<Link
to="/projects/$projectId/cert-manager/alerting"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Alerting</MenuItem>}
</Link>
{
// <Link
// to="/projects/$projectId/cert-manager/settings"
// params={{
// projectId: currentWorkspace.id
// }}
// >
// {({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
// </Link>
}
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
<Outlet />
</div>
</div>
</div>
<div className="z-[200] flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
{` ${t("common.no-mobile")} `}
</p>
</div>
</>
);
};
@@ -0,0 +1 @@
export { PkiManagerLayout } from "./PkiManagerLayout";
@@ -0,0 +1,53 @@
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
export const ProjectGeneralLayout = () => {
const { currentWorkspace } = useWorkspace();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
Project Overview
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/access-management"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Access Control</MenuItem>}
</Link>
<Link
to="/projects/$projectId/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Project Settings</MenuItem>}
</Link>
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
<Outlet />
</div>
</div>
</div>
);
};
@@ -0,0 +1 @@
export { ProjectGeneralLayout } from "./ProjectGeneralLayout";
@@ -1,401 +1,317 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { faDotCircle, faMobile, faWindowMaximize } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet, useRouterState } from "@tanstack/react-router";
import { Link, Outlet, useLocation } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions";
import { ShouldWrap } from "@app/components/utilities/ShouldWrapComponent";
import {
Badge,
BreadcrumbContainer,
Divider,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Lottie,
Menu,
MenuGroup,
MenuItem,
TBreadcrumbFormat
Tooltip
} from "@app/components/v2";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useSubscription,
useWorkspace
} from "@app/context";
import { ProjectPermissionSecretScanningFindingActions } from "@app/context/ProjectPermissionContext/types";
import {
useGetAccessRequestsCount,
useGetSecretApprovalRequestCount,
useGetSecretRotations
} from "@app/hooks/api";
import { useGetSecretScanningUnresolvedFindingCount } from "@app/hooks/api/secretScanningV2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { getCurrentProductFromUrl } from "@app/helpers/project";
import { useLocalStorageState } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { AssumePrivilegeModeBanner } from "./components/AssumePrivilegeModeBanner";
import { ProjectSelect } from "./components/ProjectSelect";
enum SidebarStyle {
Expanded = "expanded",
Collapsed = "collapsed",
ExpandOnHover = "expand-on-hover"
}
const MIN_SIDEBAR_SIZE = "55px";
const MAX_SIDEBAR_SIZE = "220px";
// This is a generic layout shared by all types of projects.
// If the product layout differs significantly, create a new layout as needed.
export const ProjectLayout = () => {
const location = useLocation();
const { currentWorkspace } = useWorkspace();
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
const { permission } = useProjectPermission();
const [sidebarStyle, setSidebarStyle] = useLocalStorageState(
"project-sidebar-style",
SidebarStyle.ExpandOnHover
);
const { t } = useTranslation();
const { assumedPrivilegeDetails } = useProjectPermission();
const workspaceId = currentWorkspace?.id || "";
const projectSlug = currentWorkspace?.slug || "";
const { subscription } = useSubscription();
const isSecretManager = currentWorkspace?.type === ProjectType.SecretManager;
const isCertManager = currentWorkspace?.type === ProjectType.CertificateManager;
const isCmek = currentWorkspace?.type === ProjectType.KMS;
const isSSH = currentWorkspace?.type === ProjectType.SSH;
const isSecretScanning = currentWorkspace?.type === ProjectType.SecretScanning;
const minSidebarWidth =
sidebarStyle === SidebarStyle.Expanded ? MAX_SIDEBAR_SIZE : MIN_SIDEBAR_SIZE;
const maxSidebarWidth =
sidebarStyle === SidebarStyle.Collapsed ? MIN_SIDEBAR_SIZE : MAX_SIDEBAR_SIZE;
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({
workspaceId,
options: { enabled: isSecretManager }
});
const { data: accessApprovalRequestCount } = useGetAccessRequestsCount({
projectSlug,
options: { enabled: isSecretManager }
});
// we only show the secret rotations v1 tab if they have existing rotations
const { data: secretRotations } = useGetSecretRotations({
workspaceId,
options: {
enabled: isSecretManager && Boolean(subscription.secretRotation),
refetchOnMount: false
}
});
const pendingRequestsCount =
(secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
const { data: unresolvedFindings } = useGetSecretScanningUnresolvedFindingCount(workspaceId, {
enabled:
isSecretScanning &&
subscription.secretScanning &&
permission.can(
ProjectPermissionSecretScanningFindingActions.Read,
ProjectPermissionSub.SecretScanningFindings
),
refetchInterval: 30000
});
const currentProductType = getCurrentProductFromUrl(location.pathname);
const isSecretManager = currentProductType === ProjectType.SecretManager;
const isPki = currentProductType === ProjectType.CertificateManager;
const isKms = currentProductType === ProjectType.KMS;
const isSsh = currentProductType === ProjectType.SSH;
const isSecretScanning = currentProductType === ProjectType.SecretScanning;
return (
<>
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<div
className="dark relative hidden w-full flex-col overflow-x-hidden md:flex"
style={{
height: "calc(100vh - 3rem)",
paddingLeft: !(sidebarStyle === SidebarStyle.Expanded) ? MIN_SIDEBAR_SIZE : "0px"
}}
>
<div className="flex flex-grow flex-col overflow-y-auto overflow-x-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
style={{ width: minSidebarWidth, height: "calc(100vh - 3rem)" }}
whileHover={{
width: maxSidebarWidth
}}
className={twMerge(
"dark group z-10 w-full overflow-hidden border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60",
!(sidebarStyle === SidebarStyle.Expanded) && "absolute bottom-0 left-0"
)}
>
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div>
<ProjectSelect />
<div className="px-1">
<Menu>
<MenuGroup title="Main Menu">
<nav className="items-between flex h-full flex-col justify-between">
<Menu>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Secret Manager"
position="right"
>
<Link
to="/projects/$projectId/secret-manager/overview"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isSecretManager}
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="vault" />}
>
{isSecretManager && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
{t("nav.menu.secrets")}
</MenuItem>
)}
</Link>
)}
{isCertManager && (
<>
<Link
to={
`/${ProjectType.CertificateManager}/$projectId/subscribers` as const
}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="pki-subscriber">
Subscribers
</MenuItem>
)}
</Link>
<Link
to={
`/${ProjectType.CertificateManager}/$projectId/certificate-templates` as const
}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem
iconMode="reverse"
isSelected={isActive}
icon="pki-template"
>
Certificate Templates
</MenuItem>
)}
</Link>
<Link
to={
`/${ProjectType.CertificateManager}/$projectId/certificates` as const
}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="certificate">
Certificates
</MenuItem>
)}
</Link>
<Link
to={
`/${ProjectType.CertificateManager}/$projectId/certificate-authorities` as const
}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="certificate-authority">
Certificate Authorities
</MenuItem>
)}
</Link>
<Link
to={`/${ProjectType.CertificateManager}/$projectId/alerting` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="notification-bell">
Alerting
</MenuItem>
)}
</Link>
</>
)}
{isCmek && (
<Link
to={`/${ProjectType.KMS}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Overview
</MenuItem>
)}
</Link>
)}
{isCmek && (
<Link
to={`/${ProjectType.KMS}/$projectId/kmip` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="key-user" iconMode="reverse">
KMIP
</MenuItem>
)}
</Link>
)}
{isSSH && (
<>
<Link
to={`/${ProjectType.SSH}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="server">
Hosts
</MenuItem>
)}
</Link>
{/* <Link
to={`/${ProjectType.SSH}/$projectId/certificates` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="certificate" iconMode="reverse">
Certificates
</MenuItem>
)}
</Link> */}
<ProjectPermissionCan
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.SshCertificateAuthorities}
>
{(isAllowed) =>
isAllowed && (
<Link
to={`/${ProjectType.SSH}/$projectId/cas` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem
isSelected={isActive}
icon="certificate-authority"
iconMode="reverse"
>
Certificate Authorities
</MenuItem>
)}
</Link>
)
}
</ProjectPermissionCan>
</>
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
)}
Secret Manager
</MenuItem>
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="PKI Manager"
position="right"
>
<Link
to="/projects/$projectId/cert-manager/subscribers"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isPki}
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="note" />}
>
{isPki && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
PKI Manager
</MenuItem>
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="KMS"
position="right"
>
<Link
to="/projects/$projectId/kms/overview"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isKms}
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="unlock" />}
>
{isKms && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
KMS
</MenuItem>
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="SSH"
position="right"
>
<Link
to="/projects/$projectId/ssh/overview"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isSsh}
leftIcon={
<Lottie className="inline-block h-6 w-6 shrink-0" icon="terminal" />
}
>
{isSsh && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
SSH
</MenuItem>
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Secret Scanning"
position="right"
>
<Link
to="/projects/$projectId/secret-scanning/data-sources"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isSecretScanning}
leftIcon={
<Lottie className="inline-block h-6 w-6 shrink-0" icon="secret-scan" />
}
>
{isSecretScanning && (
<Link
to={`/${ProjectType.SecretScanning}/$projectId/data-sources` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="blocks">
Data Sources
</MenuItem>
)}
</Link>
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
)}
{isSecretScanning && (
<Link
to={`/${ProjectType.SecretScanning}/$projectId/findings` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="search">
<div className="flex w-full items-center justify-between">
<span>Findings</span>
{Boolean(unresolvedFindings) && (
<Badge variant="primary" className="mr-2">
{unresolvedFindings}
</Badge>
)}
</div>
</MenuItem>
)}
</Link>
)}
{isSecretManager && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/integrations` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="jigsaw-puzzle">
{t("nav.menu.integrations")}
</MenuItem>
)}
</Link>
)}
{isSecretManager && Boolean(secretRotations?.length) && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/secret-rotation` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="rotation">
Secret Rotation
</MenuItem>
)}
</Link>
)}
{isSecretManager && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/approval` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="circular-check">
Approvals
{Boolean(
secretApprovalReqCount?.open ||
accessApprovalRequestCount?.pendingCount
) && (
<Badge variant="primary" className="ml-1.5">
{pendingRequestsCount}
</Badge>
)}
</MenuItem>
)}
</Link>
)}
</MenuGroup>
<MenuGroup title="Other">
<Link
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
params={{
projectId: currentWorkspace.id
}}
Secret Scanning
</MenuItem>
</Link>
</ShouldWrap>
</Menu>
<Divider />
<Menu>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Access Control"
position="right"
>
<Link
to="/projects/$projectId/access-management"
params={{ projectId: currentWorkspace.id }}
>
{({ isActive }) => (
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isActive}
leftIcon={
<Lottie className="inline-block h-6 w-6 shrink-0" icon="groups" />
}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="groups">
Access Control
</MenuItem>
{isActive && (
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
)}
</Link>
<Link
to={`/${currentWorkspace.type}/$projectId/settings` as const}
params={{
projectId: currentWorkspace.id
}}
Access Control
</MenuItem>
)}
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Project Settings"
position="right"
>
<Link
to="/projects/$projectId/settings"
params={{ projectId: currentWorkspace.id }}
>
{({ isActive }) => (
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isActive}
leftIcon={
<Lottie className="inline-block h-6 w-6 shrink-0" icon="settings-cog" />
}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
{t("nav.menu.project-settings")}
</MenuItem>
{isActive && (
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
)}
</Link>
</MenuGroup>
</Menu>
</div>
</div>
Project Settings
</MenuItem>
)}
</Link>
</ShouldWrap>
</Menu>
<div className="flex-grow" />
<Menu>
<DropdownMenu>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Sidebar Control"
position="right"
>
<DropdownMenuTrigger className="w-full">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<FontAwesomeIcon
className="mx-1 inline-block shrink-0"
icon={faWindowMaximize}
flip="vertical"
/>
}
>
Sidebar Control
</MenuItem>
</DropdownMenuTrigger>
</ShouldWrap>
<DropdownMenuContent>
<DropdownMenuItem
iconPos="right"
icon={
sidebarStyle === SidebarStyle.Expanded && (
<FontAwesomeIcon icon={faDotCircle} size="sm" />
)
}
onClick={() => setSidebarStyle(SidebarStyle.Expanded)}
>
Expanded
</DropdownMenuItem>
<DropdownMenuItem
iconPos="right"
icon={
sidebarStyle === SidebarStyle.Collapsed && (
<FontAwesomeIcon icon={faDotCircle} size="sm" />
)
}
onClick={() => setSidebarStyle(SidebarStyle.Collapsed)}
>
Collapsed
</DropdownMenuItem>
<DropdownMenuItem
iconPos="right"
icon={
sidebarStyle === SidebarStyle.ExpandOnHover && (
<FontAwesomeIcon icon={faDotCircle} size="sm" />
)
}
onClick={() => setSidebarStyle(SidebarStyle.ExpandOnHover)}
>
Expand on hover
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Menu>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 px-4 pb-4 dark:[color-scheme:dark]">
{breadcrumbs ? (
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} />
) : null}
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800">
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet />
</div>
</div>
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
import { useRemoveAssumeProjectPrivilege } from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { ProjectType } from "@app/hooks/api/workspace/types";
@@ -36,20 +37,11 @@ export const AssumePrivilegeModeBanner = () => {
},
{
onSuccess: () => {
let page: string;
const url = getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager
);
switch (currentWorkspace.type) {
case ProjectType.SecretScanning:
page = "data-sources";
break;
case ProjectType.CertificateManager:
page = "subscribers";
break;
default:
page = "overview";
}
window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/${page}`;
window.location.href = url.replace("$projectId", currentWorkspace.id);
}
}
);
@@ -0,0 +1,121 @@
import { ComponentPropsWithRef, ElementType, useRef } from "react";
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
import { twMerge } from "tailwind-merge";
import { MenuItemProps } from "@app/components/v2";
export const MenuIconButton = <T extends ElementType = "button">({
children,
icon,
className,
isDisabled,
isSelected,
as: Item = "div",
description,
// wrapping in forward ref with generic component causes the loss of ts definitions on props
inputRef,
lottieIconMode = "forward",
...props
}: MenuItemProps<T> &
ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null);
return (
<div className="px-1">
<Item
type="button"
role="menuitem"
className={twMerge(
"group relative my-1 flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
isSelected && "bg-bunker-8g0 hover:bg-mineshaft-500",
isDisabled && "cursor-not-allowed hover:bg-transparent",
className
)}
onMouseEnter={() => iconRef.current?.play()}
onMouseLeave={() => iconRef.current?.stop()}
ref={inputRef}
{...props}
>
<div
className={`${isSelected ? "opacity-100" : "opacity-0"} absolute left-0 h-full w-0.5 rounded-bl rounded-tl bg-primary transition-all duration-150`}
/>
{icon && (
<div className="my-auto mb-2 h-6 w-6">
<DotLottieReact
dotLottieRefCallback={(el) => {
iconRef.current = el;
}}
src={`/lotties/${icon}.json`}
loop
className="h-full w-full"
mode={lottieIconMode}
/>
</div>
)}
<div
className="flex-grow justify-center break-words text-center"
style={{ fontSize: "10px" }}
>
{children}
</div>
</Item>
</div>
);
};
// export const MenuIconButton = <T extends ElementType = "button">({
// children,
// icon,
// className,
// isDisabled,
// isSelected,
// as: Item = "div",
// description,
// // wrapping in forward ref with generic component causes the loss of ts definitions on props
// inputRef,
// lottieIconMode = "forward",
// ...props
// }: MenuItemProps<T> &
// ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
// const iconRef = useRef<DotLottie | null>(null);
// return (
// <div className="px-1">
// <Item
// type="button"
// role="menuitem"
// className={twMerge(
// "group relative my-1 flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
// isSelected && "bg-mineshaft-600 hover:bg-mineshaft-700",
// isDisabled && "cursor-not-allowed hover:bg-transparent",
// className
// )}
// onMouseEnter={() => iconRef.current?.play()}
// onMouseLeave={() => iconRef.current?.stop()}
// ref={inputRef}
// {...props}
// >
// <div
// className={`${"opacity-0"} absolute left-0 h-full w-1 bg-primary transition-all duration-150`}
// />
// {icon && (
// <div className="my-auto mb-2 h-6 w-6">
// <DotLottieReact
// dotLottieRefCallback={(el) => {
// iconRef.current = el;
// }}
// src={`/lotties/${icon}.json`}
// loop
// className="h-full w-full"
// mode={lottieIconMode}
// />
// </div>
// )}
// <div
// className="flex-grow justify-center break-words text-center"
// style={{ fontSize: "10px" }}
// >
// {children}
// </div>
// </Item>
// </div>
// );
// };
@@ -0,0 +1,65 @@
import { ComponentPropsWithRef, ElementType, useRef } from "react";
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
import { twMerge } from "tailwind-merge";
import { MenuItemProps } from "@app/components/v2";
export const MenuIconButton = <T extends ElementType = "button">({
children,
icon,
className,
isDisabled,
isSelected,
as: Item = "div",
description,
// wrapping in forward ref with generic component causes the loss of ts definitions on props
inputRef,
lottieIconMode = "forward",
...props
}: MenuItemProps<T> &
ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null);
return (
<div>
<Item
type="button"
role="menuitem"
className={twMerge(
"group relative flex w-full cursor-pointer flex-col items-center justify-center p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
isSelected && "rounded-none bg-bunker-800 hover:bg-mineshaft-600",
isDisabled && "cursor-not-allowed hover:bg-transparent",
className
)}
onMouseEnter={() => iconRef.current?.play()}
onMouseLeave={() => iconRef.current?.stop()}
ref={inputRef}
{...props}
>
<div
className={`${
isSelected ? "opacity-100" : "opacity-0"
} absolute left-0 h-full w-0.5 bg-primary transition-all duration-150`}
/>
{icon && (
<div className="my-auto mb-2 h-6 w-6">
<DotLottieReact
dotLottieRefCallback={(el) => {
iconRef.current = el;
}}
src={`/lotties/${icon}.json`}
loop
className="h-full w-full"
mode={lottieIconMode}
/>
</div>
)}
<div
className="flex-grow justify-center break-words text-center"
style={{ fontSize: "10px" }}
>
{children}
</div>
</Item>
</div>
);
};
@@ -0,0 +1 @@
export { MenuIconButton } from "./MenuIconButton";
@@ -1,15 +1,29 @@
import { useMemo } from "react";
import { components, MenuProps, OptionProps } from "react-select";
import { useMemo, useState } from "react";
import { faStar } from "@fortawesome/free-regular-svg-icons";
import { faChevronRight, faPlus, faStar as faSolidStar } from "@fortawesome/free-solid-svg-icons";
import {
faCheck,
faCube,
faMagnifyingGlass,
faPlus,
faSort,
faStar as faSolidStar
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { Link, linkOptions } from "@tanstack/react-router";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { NewProjectModal } from "@app/components/projects";
import { Button, FilterableSelect } from "@app/components/v2";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
IconButton,
Input,
Tooltip
} from "@app/components/v2";
import {
OrgPermissionActions,
OrgPermissionSubjects,
@@ -17,49 +31,39 @@ import {
useSubscription,
useWorkspace
} from "@app/context";
import { getProjectTitle } from "@app/helpers/project";
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
import { usePopUp } from "@app/hooks";
import { useGetUserWorkspaces } from "@app/hooks/api";
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
import { Workspace } from "@app/hooks/api/workspace/types";
type TWorkspaceWithFaveProp = Workspace & { isFavorite: boolean };
const ProjectsMenu = ({ children, ...props }: MenuProps<TWorkspaceWithFaveProp>) => {
return (
<components.Menu {...props}>
{children}
<hr className="mb-2 h-px border-0 bg-mineshaft-500" />
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Workspace}>
{(isAllowed) => (
<Button
className="w-full bg-mineshaft-700 pt-2 text-bunker-200"
colorSchema="primary"
variant="outline_bg"
size="xs"
isDisabled={!isAllowed}
onClick={() => props.clearValue()}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Add Project
</Button>
)}
</OrgPermissionCan>
</components.Menu>
);
};
const ProjectOption = ({
isSelected,
children,
data,
...props
}: OptionProps<TWorkspaceWithFaveProp>) => {
// TODO(pta): add search to project select
export const ProjectSelect = () => {
const [searchProject, setSearchProject] = useState("");
const { currentWorkspace } = useWorkspace();
const { currentOrg } = useOrganization();
const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites();
const { data: workspaces = [] } = useGetUserWorkspaces();
const { data: projectFavorites } = useGetUserProjectFavorites(currentOrg.id);
const { subscription } = useSubscription();
const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites();
const addProjectToFavorites = async (projectId: string) => {
try {
await updateUserProjectFavorites({
orgId: currentOrg!.id,
projectFavorites: [...(projectFavorites || []), projectId]
});
} catch {
createNotification({
text: "Failed to add project to favorites.",
type: "error"
});
}
};
const removeProjectFromFavorites = async (projectId: string) => {
try {
await updateUserProjectFavorites({
@@ -74,63 +78,6 @@ const ProjectOption = ({
}
};
const addProjectToFavorites = async (projectId: string) => {
try {
await updateUserProjectFavorites({
orgId: currentOrg!.id,
projectFavorites: [...(projectFavorites || []), projectId]
});
} catch {
createNotification({
text: "Failed to add project to favorites.",
type: "error"
});
}
};
return (
<components.Option
isSelected={isSelected}
data={data}
{...props}
className={twMerge(props.className, isSelected && "bg-mineshaft-500")}
>
<div className="flex w-full items-center">
{isSelected && (
<FontAwesomeIcon className="mr-2 text-primary" icon={faChevronRight} size="xs" />
)}
<p className="truncate">{children}</p>
{data.isFavorite ? (
<FontAwesomeIcon
icon={faSolidStar}
className="ml-auto text-sm text-yellow-600 hover:text-mineshaft-400"
onClick={async (e) => {
e.stopPropagation();
await removeProjectFromFavorites(data.id);
}}
/>
) : (
<FontAwesomeIcon
icon={faStar}
className="ml-auto text-sm text-mineshaft-400 hover:text-mineshaft-300"
onClick={async (e) => {
e.stopPropagation();
await addProjectToFavorites(data.id);
}}
/>
)}
</div>
</components.Option>
);
};
export const ProjectSelect = () => {
const { currentWorkspace } = useWorkspace();
const { currentOrg } = useOrganization();
const { data: workspaces = [] } = useGetUserWorkspaces({ type: currentWorkspace.type });
const { data: projectFavorites } = useGetUserProjectFavorites(currentOrg.id!);
const { subscription } = useSubscription();
const isAddingProjectsAllowed = subscription?.workspaceLimit
? subscription.workspacesUsed < subscription.workspaceLimit
: true;
@@ -140,7 +87,7 @@ export const ProjectSelect = () => {
"upgradePlan"
] as const);
const { options, value } = useMemo(() => {
const projects = useMemo(() => {
const projectOptions = workspaces
.map((w): Workspace & { isFavorite: boolean } => ({
...w,
@@ -148,70 +95,136 @@ export const ProjectSelect = () => {
}))
.sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite));
const currentOption = projectOptions.find((option) => option.id === currentWorkspace?.id);
if (!currentOption) {
return {
options: projectOptions,
value: null
};
}
return {
options: [
currentOption,
...projectOptions.filter((option) => option.id !== currentOption.id)
],
value: currentOption
};
return projectOptions;
}, [workspaces, projectFavorites, currentWorkspace]);
return (
<div className="mt-2 w-full p-3">
<p className="mb-1 ml-1.5 text-xs font-semibold uppercase text-gray-400">
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"}
</p>
<FilterableSelect
className="text-sm"
value={value}
filterOption={(option, inputValue) =>
option.data.name.toLowerCase().includes(inputValue.toLowerCase())
}
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
onChange={(newValue) => {
// hacky use of null as indication to create project
if (!newValue) {
if (isAddingProjectsAllowed) {
handlePopUpOpen("addNewWs");
} else {
handlePopUpOpen("upgradePlan");
}
return;
}
const project = newValue as TWorkspaceWithFaveProp;
// 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.type}/${project.id}/overview`);
}}
options={options}
components={{
Option: ProjectOption,
Menu: ProjectsMenu
}}
/>
<div className="flex w-full items-center gap-2">
<DropdownMenu modal={false}>
<Link
to={getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || currentWorkspace.defaultProduct
)}
params={{
projectId: currentWorkspace.id
}}
>
<div className="flex cursor-pointer items-center gap-2 text-sm text-white duration-100 hover:text-primary-400">
<div>
<FontAwesomeIcon icon={faCube} className="text-xs" />
</div>
<Tooltip content={currentWorkspace.name}>
<div className="max-w-32 overflow-hidden text-ellipsis whitespace-nowrap">
{currentWorkspace?.name}
</div>
</Tooltip>
</div>
</Link>
<DropdownMenuTrigger asChild>
<div>
<IconButton
variant="plain"
colorSchema="secondary"
ariaLabel="switch-org"
className="px-2 py-1"
>
<FontAwesomeIcon icon={faSort} className="text-xs text-bunker-300" />
</IconButton>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
side="bottom"
className="mt-6 cursor-default p-1 shadow-mineshaft-600 drop-shadow-md"
style={{ minWidth: "220px" }}
>
<div className="px-2 py-1 text-xs capitalize text-mineshaft-400">Projects</div>
<div className="mb-1 border-b border-b-mineshaft-600 py-1 pb-1">
<Input
value={searchProject}
onChange={(evt) => setSearchProject(evt.target.value || "")}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
size="xs"
variant="plain"
placeholder="Search projects"
/>
</div>
<div className="thin-scrollbar max-h-80 overflow-auto">
{projects
?.filter((el) => el.name?.toLowerCase().includes(searchProject.toLowerCase()))
?.map((workspace) => {
return (
<DropdownMenuItem
key={workspace.id}
onClick={async () => {
// 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
const url = linkOptions({
to: getProjectHomePage(workspace.defaultProduct),
params: {
projectId: workspace.id
}
});
window.location.assign(url.to.replaceAll("$projectId", workspace.id));
}}
icon={
currentWorkspace?.id === workspace.id && (
<FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
)
}
>
<div className="flex items-center">
<div className="flex flex-1 items-center justify-between overflow-hidden">
<Tooltip content={workspace.name}>
<div className="max-w-40 overflow-hidden truncate whitespace-nowrap">
{workspace.name}
</div>
</Tooltip>
</div>
<div>
<FontAwesomeIcon
icon={workspace.isFavorite ? faSolidStar : faStar}
className="text-sm text-yellow-600 hover:text-mineshaft-400"
onClick={async (e) => {
e.stopPropagation();
await (
workspace.isFavorite
? removeProjectFromFavorites
: addProjectToFavorites
)(workspace.id);
}}
/>
</div>
</div>
</DropdownMenuItem>
);
})}
</div>
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Workspace}>
{(isAllowed) => (
<DropdownMenuItem
isDisabled={!isAllowed}
icon={<FontAwesomeIcon icon={faPlus} />}
onClick={() =>
handlePopUpOpen(isAddingProjectsAllowed ? "addNewWs" : "upgradePlan")
}
>
New Project
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You have exceeded the number of projects allowed on the free plan."
/>
<NewProjectModal
isOpen={popUp.addNewWs.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
projectType={currentWorkspace?.type || ProjectType.SecretManager}
/>
</div>
);
@@ -14,7 +14,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
import { useOrganization, useUser, useWorkspace } from "@app/context";
import { useOrganization, useUser } from "@app/context";
import { useGetOrganizations, useLogoutUser } from "@app/hooks/api";
import { AuthMethod } from "@app/hooks/api/users/types";
@@ -24,7 +24,6 @@ type Prop = {
export const SidebarHeader = ({ onChangeOrg }: Prop) => {
const { currentOrg } = useOrganization();
const { currentWorkspace } = useWorkspace();
const { user } = useUser();
const navigate = useNavigate();
const { data: orgs } = useGetOrganizations();
@@ -42,7 +41,7 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
return (
<div className="flex h-12 cursor-default items-center px-3 pt-6">
<Link to={`/organization/${currentWorkspace.type}/overview` as const}>
<Link to="/organization/projects">
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
<FontAwesomeIcon icon={faArrowLeft} />
</div>
@@ -0,0 +1,132 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Badge, Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import {
useGetAccessRequestsCount,
useGetSecretApprovalRequestCount,
useGetSecretRotations
} from "@app/hooks/api";
export const SecretManagerLayout = () => {
const { currentWorkspace } = useWorkspace();
const { t } = useTranslation();
const workspaceId = currentWorkspace?.id || "";
const projectSlug = currentWorkspace?.slug || "";
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({
workspaceId
});
const { data: accessApprovalRequestCount } = useGetAccessRequestsCount({
projectSlug
});
// we only show the secret rotations v1 tab if they have existing rotations
const { data: secretRotations } = useGetSecretRotations({
workspaceId,
options: {
refetchOnMount: false
}
});
const pendingRequestsCount =
(secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
return (
<>
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
Secrets Manager
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/secret-manager/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>{t("nav.menu.secrets")}</MenuItem>
)}
</Link>
<Link
to="/projects/$projectId/secret-manager/integrations"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>{t("nav.menu.integrations")}</MenuItem>
)}
</Link>
{Boolean(secretRotations?.length) && (
<Link
to="/projects/$projectId/secret-manager/secret-rotation"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Secret Rotation</MenuItem>}
</Link>
)}
<Link
to="/projects/$projectId/secret-manager/approval"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
Approvals
{Boolean(
secretApprovalReqCount?.open || accessApprovalRequestCount?.pendingCount
) && (
<Badge variant="primary" className="ml-1.5">
{pendingRequestsCount}
</Badge>
)}
</MenuItem>
)}
</Link>
<Link
to="/projects/$projectId/secret-manager/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
</Link>
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
<Outlet />
</div>
</div>
</div>
<div className="z-[200] flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
{` ${t("common.no-mobile")} `}
</p>
</div>
</>
);
};
@@ -0,0 +1 @@
export { SecretManagerLayout } from "./SecretManagerLayout";
@@ -0,0 +1,61 @@
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
export const SecretScanningLayout = () => {
const { currentWorkspace } = useWorkspace();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
Secret Scanning
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/secret-scanning/data-sources"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}> Data Sources</MenuItem>}
</Link>
<Link
to="/projects/$projectId/secret-scanning/findings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Findings</MenuItem>}
</Link>
<Link
to="/projects/$projectId/secret-scanning/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
</Link>
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
<Outlet />
</div>
</div>
</div>
);
};
@@ -0,0 +1 @@
export { SecretScanningLayout } from "./SecretScanningLayout";
@@ -0,0 +1,71 @@
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Menu, MenuItem } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
export const SshLayout = () => {
const { currentWorkspace } = useWorkspace();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">SSH</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/ssh/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Hosts</MenuItem>}
</Link>
<ProjectPermissionCan
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.SshCertificateAuthorities}
>
{(isAllowed) =>
isAllowed && (
<Link
to="/projects/$projectId/ssh/cas"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>Certificate Authorities</MenuItem>
)}
</Link>
)
}
</ProjectPermissionCan>
<Link
to="/projects/$projectId/ssh/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
</Link>
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
<Outlet />
</div>
</div>
</div>
);
};
+1
View File
@@ -0,0 +1 @@
export { SshLayout } from "./SshLayout";
+3 -3
View File
@@ -5,7 +5,7 @@ import lottieWasmUrl from "@lottiefiles/dotlottie-web/dist/dotlottie-player.wasm
import { createRouter, RouterProvider } from "@tanstack/react-router";
import NProgress from "nprogress";
import { ContentLoader } from "./components/v2";
import { Lottie } from "./components/v2";
import { queryClient } from "./hooks/api/reactQuery";
import { ErrorPage } from "./pages/public/ErrorPage/ErrorPage";
import { NotFoundPage } from "./pages/public/NotFoundPage/NotFoundPage";
@@ -62,8 +62,8 @@ const router = createRouter({
routeTree,
context: { serverConfig: null, queryClient },
defaultPendingComponent: () => (
<div className="bg-bunker-800">
<ContentLoader />
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
<Lottie icon="infisical_loading" className="h-32 w-32" />
</div>
),
defaultNotFoundComponent: NotFoundPage,
+1 -1
View File
@@ -7,7 +7,7 @@ export const Route = createFileRoute("/_authenticate/_inject-org-details/admin/_
beforeLoad: ({ context }) => {
if (!context.user.superAdmin) {
throw redirect({
to: "/organization/secret-manager/overview"
to: "/organization/projects"
});
}
}
@@ -4,7 +4,6 @@ import { useServerConfig } from "@app/context";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { queryClient } from "@app/hooks/api/reactQuery";
import { userKeys } from "@app/hooks/api/users";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const navigateUserToOrg = async (navigate: NavigateFn, organizationId?: string) => {
const userOrgs = await fetchOrganizations();
@@ -13,7 +12,7 @@ export const navigateUserToOrg = async (navigate: NavigateFn, organizationId?: s
if (organizationId) {
localStorage.setItem("orgData.id", organizationId);
navigate({ to: "/organization/secret-manager/overview" });
navigate({ to: "/organization/projects" });
return;
}
@@ -21,7 +20,7 @@ export const navigateUserToOrg = async (navigate: NavigateFn, organizationId?: s
// user is part of at least 1 non-auth enforced org
const userOrg = nonAuthEnforcedOrgs[0] && nonAuthEnforcedOrgs[0].id;
localStorage.setItem("orgData.id", userOrg);
navigate({ to: `/organization/${ProjectType.SecretManager}/overview` as const });
navigate({ to: "/organization/projects" });
} else {
// user is not part of any non-auth enforced orgs
localStorage.removeItem("orgData.id");
@@ -28,7 +28,6 @@ import {
import { MfaMethod } from "@app/hooks/api/auth/types";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { isLoggedIn } from "@app/hooks/api/reactQuery";
import { ProjectType } from "@app/hooks/api/workspace/types";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
@@ -204,7 +203,7 @@ export const SignupInvitePage = () => {
localStorage.setItem("orgData.id", orgId);
navigate({
to: `/organization/${ProjectType.SecretManager}/overview` as const
to: "/organization/projects"
});
};
@@ -13,7 +13,6 @@ import SecurityClient from "@app/components/utilities/SecurityClient";
import { useServerConfig } from "@app/context";
import { useVerifySignupEmailVerificationCode } from "@app/hooks/api";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const SignUpPage = () => {
const [email, setEmail] = useState("");
@@ -73,7 +72,7 @@ export const SignUpPage = () => {
if (!serverDetails?.emailConfigured && step === 4) {
navigate({
to: `/organization/${ProjectType.SecretManager}/overview` as const
to: "/organization/projects"
});
}
})();
@@ -18,7 +18,6 @@ import { useToggle } from "@app/hooks";
import { completeAccountSignup, useSelectOrganization } 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";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
@@ -207,7 +206,7 @@ export const UserInfoSSOStep = ({
localStorage.setItem("orgData.id", orgId);
navigate({
to: `/organization/${ProjectType.SecretManager}/overview` as const
to: "/organization/projects"
});
} catch (error) {
setIsLoading(false);
@@ -12,7 +12,6 @@ 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({
@@ -83,7 +82,7 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => {
});
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/pki-collections/$collectionId` as const,
to: "/projects/$projectId/cert-manager/pki-collections/$collectionId",
params: {
projectId,
collectionId
@@ -61,7 +61,7 @@ export const PkiCollectionTable = ({ handlePopUpOpen }: Props) => {
key={`pki-collection-${pkiCollection.id}`}
onClick={() =>
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/pki-collections/$collectionId` as const,
to: `/projects/$projectId/${ProjectType.CertificateManager}/pki-collections/$collectionId` as const,
params: {
projectId,
collectionId: pkiCollection.id
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { AlertingPage } from "./AlertingPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/alerting"
)({
component: AlertingPage,
beforeLoad: ({ context }) => {
@@ -71,7 +71,7 @@ const Page = () => {
handlePopUpClose("deleteCa");
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/certificate-authorities` as const,
to: `/projects/$projectId/${ProjectType.CertificateManager}/certificate-authorities` as const,
params: {
projectId
}
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caName"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/ca/$caName"
)({
component: CertAuthDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -13,11 +13,14 @@ export const Route = createFileRoute(
{
label: "Certificate Authorities",
link: linkOptions({
to: "/cert-manager/$projectId/certificate-authorities",
to: "/projects/$projectId/cert-manager/certificate-authorities",
params: {
projectId: params.projectId
}
})
},
{
label: params.caName
}
]
};
@@ -79,7 +79,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
key={`ca-${ca.id}`}
onClick={() =>
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/ca/$caName` as const,
to: `/projects/$projectId/${ProjectType.CertificateManager}/ca/$caName` as const,
params: {
projectId: currentWorkspace.id,
caName: ca.name
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { CertificateAuthoritiesPage } from "./CertificateAuthoritiesPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-authorities"
)({
component: CertificateAuthoritiesPage,
beforeLoad: ({ context }) => {
@@ -3,7 +3,17 @@ import { createFileRoute } from "@tanstack/react-router";
import { CertificatesPage } from "./CertificatesPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificates"
)({
component: CertificatesPage
component: CertificatesPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Certificates"
}
]
};
}
});
@@ -57,7 +57,7 @@ export const PkiCollectionPage = () => {
});
handlePopUpClose("deletePkiCollection");
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/certificates` as const,
to: `/projects/$projectId/${ProjectType.CertificateManager}/certificates` as const,
params: {
projectId
}
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { PkiCollectionDetailsByIDPage } from "./PkiCollectionDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/pki-collections/$collectionId"
)({
component: PkiCollectionDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -13,11 +13,14 @@ export const Route = createFileRoute(
{
label: "Certificate Collections",
link: linkOptions({
to: "/cert-manager/$projectId/certificates",
to: "/projects/$projectId/cert-manager/certificates",
params: {
projectId: params.projectId
}
})
},
{
label: "Details"
}
]
};
@@ -61,7 +61,7 @@ const Page = () => {
handlePopUpClose("deletePkiSubscriber");
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/subscribers` as const,
to: `/projects/$projectId/${ProjectType.CertificateManager}/subscribers` as const,
params: {
projectId
}
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/$subscriberName"
)({
component: PkiSubscriberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -13,11 +13,14 @@ export const Route = createFileRoute(
{
label: "Subscribers",
link: linkOptions({
to: "/cert-manager/$projectId/subscribers",
to: "/projects/$projectId/cert-manager/subscribers",
params: {
projectId: params.projectId
}
})
},
{
label: params.subscriberName
}
]
};

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