mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 21:51:56 +00:00
Merge branch 'main' into feat/zabbixSyncIntegration
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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": []
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
+18
-1
@@ -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"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -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't see the project you're looking for?</span>{" "}
|
||||
|
||||
+10
-2
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
@@ -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";
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
|
||||
@@ -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>({
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: (
|
||||
|
||||
@@ -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,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];
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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";
|
||||
-641
@@ -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>
|
||||
|
||||
+5
-13
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { SshLayout } from "./SshLayout";
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
+1
-2
@@ -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"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
+1
-1
@@ -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"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user